diff --git a/src/main/scala/ocelot/desktop/inventory/Inventory.scala b/src/main/scala/ocelot/desktop/inventory/Inventory.scala index 374b563..babcf1b 100644 --- a/src/main/scala/ocelot/desktop/inventory/Inventory.scala +++ b/src/main/scala/ocelot/desktop/inventory/Inventory.scala @@ -1,7 +1,7 @@ package ocelot.desktop.inventory import ocelot.desktop.inventory.Inventory.SlotObserver -import ocelot.desktop.ui.event.{BrainEvent, Event, EventAware} +import ocelot.desktop.ui.event.{BrainEvent, Dispatchable, EventAware} import ocelot.desktop.util.Disposable import totoro.ocelot.brain.event.NodeEvent @@ -78,7 +78,7 @@ trait Inventory extends EventAware with Disposable { super.shouldReceiveEventsFor(address) || inventoryIterator.flatMap(_.get).exists(_.shouldReceiveEventsFor(address)) - override def handleEvent(event: Event): Unit = { + override def handleEvent(event: Dispatchable): Unit = { super.handleEvent(event) for (slot <- inventoryIterator; item <- slot.get) { diff --git a/src/main/scala/ocelot/desktop/ui/UiHandler.scala b/src/main/scala/ocelot/desktop/ui/UiHandler.scala index 31a3918..df9d718 100644 --- a/src/main/scala/ocelot/desktop/ui/UiHandler.scala +++ b/src/main/scala/ocelot/desktop/ui/UiHandler.scala @@ -6,7 +6,7 @@ import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D} import ocelot.desktop.graphics.Graphics import ocelot.desktop.ui.event.handlers.HoverHandler import ocelot.desktop.ui.event.sources.{BrainEvents, KeyEvents, MouseEvents, ScrollEvents} -import ocelot.desktop.ui.event.{Event, MouseEvent} +import ocelot.desktop.ui.event.{Capturing, CapturingEvent, Dispatchable, MouseEvent} import ocelot.desktop.ui.widget.window.Window import ocelot.desktop.ui.widget.{RootWidget, Widget} import ocelot.desktop.util._ @@ -427,16 +427,18 @@ object UiHandler extends Logging { Audio.destroy() } - private def dispatchEvent(iter: => IterableOnce[Widget] = hierarchy.reverseIterator)(event: Event): Unit = { - for (widget <- iter) { - if (event.consumed) { - return - } - + private def dispatchEvent(iter: => IterableOnce[Widget] = hierarchy.reverseIterator)(event: Dispatchable): Unit = { + for (widget <- iter if !event.consumed) { widget.handleEvent(event) } } + private def dispatchCapturing(target: Widget)(event: CapturingEvent): Unit = { + val ancestors = target.ancestors.toSeq + dispatchEvent(ancestors.reverseIterator ++ Some(target))(Capturing(event)) + dispatchEvent(Some(target))(event) + } + private def dispatchBrainEvents(): Unit = { import totoro.ocelot.brain @@ -476,7 +478,10 @@ object UiHandler extends Logging { } // TODO: dispatch to the focused widget instead of broadcasting to the entire hierarchy. - KeyEvents.events.foreach(dispatchEvent()) + for (event <- KeyEvents.events) { + dispatchEvent(hierarchy)(Capturing(event)) + dispatchEvent()(event) + } MouseEvents.events .foreach(dispatchEvent(hierarchy.reverseIterator.filter(w => w.enabled && w.receiveAllMouseEvents))) @@ -487,17 +492,22 @@ object UiHandler extends Logging { val mouseTarget = hierarchy.reverseIterator .find(w => w.enabled && w.receiveMouseEvents && w.clippedBounds.contains(mousePos)) - ScrollEvents.events.foreach(dispatchEvent(scrollTarget)) + for (scrollTarget <- scrollTarget) { + ScrollEvents.events.foreach(dispatchCapturing(scrollTarget)) + } for (event <- MouseEvents.events) { if (event.state == MouseEvent.State.Pressed) { - dispatchEvent(mouseTarget)(event) + for (mouseTarget <- mouseTarget) { + dispatchCapturing(mouseTarget)(event) + } // TODO: this should be done in the event capturing phase in [[Window]] itself. for (mouseTarget <- mouseTarget if !mouseTarget.isInstanceOf[Window]) { focusParentWindow(mouseTarget.parent) } } else { + dispatchEvent(hierarchy)(Capturing(event)) dispatchEvent(hierarchy.reverseIterator)(event) } } diff --git a/src/main/scala/ocelot/desktop/ui/event/CapturingEvent.scala b/src/main/scala/ocelot/desktop/ui/event/CapturingEvent.scala new file mode 100644 index 0000000..448d6bd --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/event/CapturingEvent.scala @@ -0,0 +1,3 @@ +package ocelot.desktop.ui.event + +trait CapturingEvent extends Event diff --git a/src/main/scala/ocelot/desktop/ui/event/Dispatchable.scala b/src/main/scala/ocelot/desktop/ui/event/Dispatchable.scala new file mode 100644 index 0000000..397083c --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/event/Dispatchable.scala @@ -0,0 +1,28 @@ +package ocelot.desktop.ui.event + +sealed trait Dispatchable { + def consume(): Unit + + def consumed: Boolean +} + +trait Event extends Dispatchable { + private var _consumed: Boolean = false + + def consume(): Unit = { + _consumed = true + } + + def consumed: Boolean = _consumed +} + +/** + * A wrapper for an event that is dispatched during the capture phase. + */ +case class Capturing[A <: CapturingEvent](event: A) extends Dispatchable { + override def consume(): Unit = { + event.consume() + } + + override def consumed: Boolean = event.consumed +} diff --git a/src/main/scala/ocelot/desktop/ui/event/Event.scala b/src/main/scala/ocelot/desktop/ui/event/Event.scala deleted file mode 100644 index a547eec..0000000 --- a/src/main/scala/ocelot/desktop/ui/event/Event.scala +++ /dev/null @@ -1,11 +0,0 @@ -package ocelot.desktop.ui.event - -trait Event extends AnyRef { - private var _consumed: Boolean = false - - def consume(): Unit = { - _consumed = true - } - - def consumed: Boolean = _consumed -} diff --git a/src/main/scala/ocelot/desktop/ui/event/EventAware.scala b/src/main/scala/ocelot/desktop/ui/event/EventAware.scala index 3459153..97f86e3 100644 --- a/src/main/scala/ocelot/desktop/ui/event/EventAware.scala +++ b/src/main/scala/ocelot/desktop/ui/event/EventAware.scala @@ -7,7 +7,7 @@ trait EventAware { def shouldReceiveEventsFor(address: String): Boolean = false - def handleEvent(event: Event): Unit = { + def handleEvent(event: Dispatchable): Unit = { eventHandlers(event) } } diff --git a/src/main/scala/ocelot/desktop/ui/event/KeyEvent.scala b/src/main/scala/ocelot/desktop/ui/event/KeyEvent.scala index c35d148..ed6865e 100644 --- a/src/main/scala/ocelot/desktop/ui/event/KeyEvent.scala +++ b/src/main/scala/ocelot/desktop/ui/event/KeyEvent.scala @@ -6,4 +6,4 @@ object KeyEvent { } } -case class KeyEvent(state: KeyEvent.State.Value, code: Int, char: Char) extends Event +case class KeyEvent(state: KeyEvent.State.Value, code: Int, char: Char) extends CapturingEvent diff --git a/src/main/scala/ocelot/desktop/ui/event/MouseEvent.scala b/src/main/scala/ocelot/desktop/ui/event/MouseEvent.scala index 58c4c07..2ac0bda 100644 --- a/src/main/scala/ocelot/desktop/ui/event/MouseEvent.scala +++ b/src/main/scala/ocelot/desktop/ui/event/MouseEvent.scala @@ -12,4 +12,4 @@ object MouseEvent { } } -case class MouseEvent(state: MouseEvent.State.Value, button: MouseEvent.Button.Value) extends Event +case class MouseEvent(state: MouseEvent.State.Value, button: MouseEvent.Button.Value) extends CapturingEvent diff --git a/src/main/scala/ocelot/desktop/ui/event/ScrollEvent.scala b/src/main/scala/ocelot/desktop/ui/event/ScrollEvent.scala index 5cfa4b2..291546e 100644 --- a/src/main/scala/ocelot/desktop/ui/event/ScrollEvent.scala +++ b/src/main/scala/ocelot/desktop/ui/event/ScrollEvent.scala @@ -1,3 +1,3 @@ package ocelot.desktop.ui.event -case class ScrollEvent(offset: Int) extends Event +case class ScrollEvent(offset: Int) extends CapturingEvent diff --git a/src/main/scala/ocelot/desktop/ui/widget/EventHandlers.scala b/src/main/scala/ocelot/desktop/ui/widget/EventHandlers.scala index 1288216..7b0901b 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/EventHandlers.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/EventHandlers.scala @@ -1,27 +1,27 @@ package ocelot.desktop.ui.widget -import ocelot.desktop.ui.event.Event +import ocelot.desktop.ui.event.{Dispatchable, Event} import scala.collection.mutable.ArrayBuffer -class EventHandlers extends PartialFunction[Event, Unit] { +class EventHandlers extends PartialFunction[Dispatchable, Unit] { private val handlers = ArrayBuffer.empty[EventHandler] def +=(handler: EventHandler): Unit = handlers += handler def -=(handler: EventHandler): Unit = handlers -= handler - override def isDefinedAt(event: Event): Boolean = handlers.exists(_.isDefinedAt(event)) + override def isDefinedAt(event: Dispatchable): Boolean = handlers.exists(_.isDefinedAt(event)) - override def apply(event: Event): Unit = { + override def apply(event: Dispatchable): Unit = { for (handler <- handlers) { if (event.consumed) { return } - handler.applyOrElse(event, (_: Event) => ()) + handler.applyOrElse(event, (_: Dispatchable) => ()) } } - type EventHandler = PartialFunction[Event, Unit] + type EventHandler = PartialFunction[Dispatchable, Unit] } diff --git a/src/main/scala/ocelot/desktop/ui/widget/Widget.scala b/src/main/scala/ocelot/desktop/ui/widget/Widget.scala index 5adc139..d26ca2e 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/Widget.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/Widget.scala @@ -59,6 +59,10 @@ class Widget extends EventAware with Updatable with Disposable { final def root: Option[RootWidget] = _root + final def ancestors: Iterator[Widget] = { + Iterator.unfold(this)(_.parent.map(w => (w, w))) + } + def enabled: Boolean = true def minimumSize: Size2D = layout.minimumSize