From c2da3297d4f8413b2edbb2f3da26b8624b94526f Mon Sep 17 00:00:00 2001 From: David North Date: Wed, 6 Nov 2024 13:28:04 +0000 Subject: [PATCH] Updates Mouse to be InputState This deprecates a number of methods in favour of standardisation. It fulfils some of #775 and finishes the work on #476 --- .../scala/indigoextras/ui/InputField.scala | 6 +- .../indigoextras/ui/RadioButtonGroup.scala | 36 +-- .../ui/RadioButtonGroupTests.scala | 32 ++- .../indigo/shared/events/GlobalEvent.scala | 80 ++++++- .../indigo/shared/events/InputMapping.scala | 8 +- .../indigo/shared/events/InputState.scala | 7 +- .../scala/indigo/shared/input/Mouse.scala | 214 ++++++------------ .../indigo/shared/input/PointerState.scala | 92 +++++++- .../scala/indigo/shared/input/Pointers.scala | 33 ++- .../shared/events/InputStateTests.scala | 146 ++++++------ .../com/example/sandbox/SandboxView.scala | 2 +- 11 files changed, 405 insertions(+), 251 deletions(-) diff --git a/indigo/indigo-extras/src/main/scala/indigoextras/ui/InputField.scala b/indigo/indigo-extras/src/main/scala/indigoextras/ui/InputField.scala index c13059f5b..79b771598 100644 --- a/indigo/indigo-extras/src/main/scala/indigoextras/ui/InputField.scala +++ b/indigo/indigo-extras/src/main/scala/indigoextras/ui/InputField.scala @@ -7,6 +7,7 @@ import indigo.shared.collections.Batch import indigo.shared.constants.Key import indigo.shared.datatypes._ import indigo.shared.events.GlobalEvent +import indigo.shared.events.MouseButton import indigo.shared.scenegraph.Graphic import indigo.shared.scenegraph.SceneNode import indigo.shared.scenegraph.Text @@ -217,10 +218,11 @@ final case class InputField( rec(frameContext.inputState.keyboard.keysReleased.toList, this, false, None) else Outcome(this) - if (frameContext.inputState.mouse.mouseReleased) + if (frameContext.inputState.pointers.isReleased) bounds(frameContext.boundaryLocator) match case Some(bounds) => - if frameContext.inputState.mouse.wasMouseUpWithin(bounds) then updated.flatMap(_.giveFocus) + if frameContext.inputState.pointers.wasUpWithin(bounds, MouseButton.LeftMouseButton) then + updated.flatMap(_.giveFocus) else updated.flatMap(_.loseFocus) case _ => updated diff --git a/indigo/indigo-extras/src/main/scala/indigoextras/ui/RadioButtonGroup.scala b/indigo/indigo-extras/src/main/scala/indigoextras/ui/RadioButtonGroup.scala index 17930b19f..b5131fdc7 100644 --- a/indigo/indigo-extras/src/main/scala/indigoextras/ui/RadioButtonGroup.scala +++ b/indigo/indigo-extras/src/main/scala/indigoextras/ui/RadioButtonGroup.scala @@ -6,7 +6,7 @@ import indigo.shared.datatypes.Depth import indigo.shared.datatypes.Point import indigo.shared.datatypes.Rectangle import indigo.shared.events.GlobalEvent -import indigo.shared.input.Mouse +import indigo.shared.input.PointerState import indigo.shared.scenegraph.EntityNode import indigo.shared.scenegraph.Graphic import indigo.shared.scenegraph.Group @@ -318,25 +318,27 @@ final case class RadioButtonGroup( rec(radioButtons, false, Nil) } - /** Update all the option buttons according to the newest state of mouse input. + /** Update all the option buttons according to the newest state of pointer input. * - * @param mouse - * The current mouse state + * @param pointer + * The current pointer state * @return * An Outcome[RadioButtonGroup] with this radio button's new state */ - def update(mouse: Mouse): Outcome[RadioButtonGroup] = { + def update(pointer: PointerState): Outcome[RadioButtonGroup] = { val indexedOptions = options.zipWithIndex val selected: Option[Int] = - indexedOptions.flatMap { - case (o, i) - if mouse.isLeftDown && o.hitArea.getOrElse(hitArea).moveBy(o.position).isPointWithin(mouse.position) => - Batch(i) + pointer.maybePosition.flatMap(pointerPos => + indexedOptions.flatMap { + case (o, i) + if pointer.isLeftDown && o.hitArea.getOrElse(hitArea).moveBy(o.position).isPointWithin(pointerPos) => + Batch(i) - case _ => - Batch.empty - }.headOption + case _ => + Batch.empty + }.headOption + ) val updatedOptions: Batch[Outcome[RadioButton]] = indexedOptions.map { @@ -344,7 +346,7 @@ final case class RadioButtonGroup( case (o, _) if o.inSelectedState && selected.isEmpty => Outcome(o) - // Selected already after some mouse selection + // Selected already after some pointer selection case (o, i) if o.inSelectedState && selected.isDefined && selected.contains(i) => Outcome(o) @@ -356,15 +358,15 @@ final case class RadioButtonGroup( case (o, i) if o.inSelectedState && selected.isDefined && !selected.contains(i) => Outcome(o.copy(state = RadioButtonState.Normal), o.onUnselected()) - // Not selected, no mouse click, mouse within, should be in hover state. + // Not selected, no pointer click, pointer within, should be in hover state. case (o, _) - if !o.inSelectedState && !mouse.isLeftDown && o.hitArea + if pointer.maybePosition != None && !o.inSelectedState && !pointer.isLeftDown && o.hitArea .getOrElse(hitArea) .moveBy(o.position) - .isPointWithin(mouse.position) => + .isPointWithin(pointer.position) => Outcome(o.copy(state = RadioButtonState.Hover), o.onHoverOver()) - // Hovered, but mouse outside so revert to normal + // Hovered, but pointer outside so revert to normal case (o, _) if o.inHoverState => Outcome(o.copy(state = RadioButtonState.Normal), o.onHoverOut()) diff --git a/indigo/indigo-extras/src/test/scala/indigoextras/ui/RadioButtonGroupTests.scala b/indigo/indigo-extras/src/test/scala/indigoextras/ui/RadioButtonGroupTests.scala index 5e146aeed..d364f8377 100644 --- a/indigo/indigo-extras/src/test/scala/indigoextras/ui/RadioButtonGroupTests.scala +++ b/indigo/indigo-extras/src/test/scala/indigoextras/ui/RadioButtonGroupTests.scala @@ -5,8 +5,11 @@ import indigo.shared.collections.Batch import indigo.shared.datatypes.Point import indigo.shared.datatypes.Rectangle import indigo.shared.events.GlobalEvent -import indigo.shared.events.MouseEvent -import indigo.shared.input.Mouse +import indigo.shared.events.MouseButton +import indigo.shared.events.PointerEvent +import indigo.shared.events.PointerType +import indigo.shared.input.Pointer +import indigo.shared.input.Pointers import indigo.shared.materials.Material import indigo.shared.scenegraph.Graphic @@ -47,7 +50,10 @@ class RadioButtonGroupTests extends munit.FunSuite { test("No mouse interaction") { val mouse = - new Mouse(Batch.empty, Point(-10, -10), false) + new Pointers( + Batch(Pointer(Point(-10, -10), PointerType.Mouse)), + Batch.empty + ) val actual = radioButtons.update(mouse) @@ -61,7 +67,10 @@ class RadioButtonGroupTests extends munit.FunSuite { test("hover over unselected button") { val mouse = - new Mouse(Batch.empty, Point(5, 25), false) + new Pointers( + Batch(Pointer(Point(5, 25), PointerType.Mouse)), + Batch.empty + ) val actual = radioButtons.update(mouse) @@ -81,7 +90,10 @@ class RadioButtonGroupTests extends munit.FunSuite { test("hover out unselected button") { val mouse = - new Mouse(Batch.empty, Point(-5, 25), false) + new Pointers( + Batch(Pointer(Point(-5, 25), PointerType.Mouse)), + Batch.empty + ) val actual = radioButtons @@ -110,7 +122,10 @@ class RadioButtonGroupTests extends munit.FunSuite { test("selecting a hovered button") { val mouse = - new Mouse(Batch(MouseEvent.Click(5, 25)), Point(5, 25), true) + new Pointers( + Batch(Pointer(Point(5, 25), PointerType.Mouse, MouseButton.LeftMouseButton)), + Batch(PointerEvent.PointerClick(5, 25, PointerType.Mouse)) + ) val actual = radioButtons @@ -138,7 +153,10 @@ class RadioButtonGroupTests extends munit.FunSuite { test("selecting a hovered button, existing selected is de-selected") { val mouse = - new Mouse(Batch(MouseEvent.Click(5, 25)), Point(5, 25), true) + new Pointers( + Batch(Pointer(Point(5, 25), PointerType.Mouse, MouseButton.LeftMouseButton)), + Batch(PointerEvent.PointerClick(5, 25, PointerType.Mouse)) + ) val actual = radioButtons diff --git a/indigo/indigo/src/main/scala/indigo/shared/events/GlobalEvent.scala b/indigo/indigo/src/main/scala/indigo/shared/events/GlobalEvent.scala index d34997d8d..091e30b00 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/events/GlobalEvent.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/events/GlobalEvent.scala @@ -570,16 +570,24 @@ object PointerEvent: button: Option[MouseButton] ) extends PointerEvent object PointerDown: + def apply(position: Point): PointerDown = + PointerDown(position, MouseButton.LeftMouseButton, PointerType.Mouse) + def apply(x: Int, y: Int): PointerDown = + PointerDown(Point(x, y), MouseButton.LeftMouseButton, PointerType.Mouse) def apply(position: Point, pointerType: PointerType): PointerDown = PointerDown(position, MouseButton.LeftMouseButton, pointerType) def apply(x: Int, y: Int, pointerType: PointerType): PointerDown = PointerDown(Point(x, y), MouseButton.LeftMouseButton, pointerType) + def apply(position: Point, button: MouseButton): PointerDown = + PointerDown(position, button, PointerType.Mouse) + def apply(x: Int, y: Int, button: MouseButton): PointerDown = + PointerDown(Point(x, y), button, PointerType.Mouse) def apply(x: Int, y: Int, button: MouseButton, pointerType: PointerType): PointerDown = PointerDown(Point(x, y), button, pointerType) def apply(position: Point, button: MouseButton, pointerType: PointerType): PointerDown = PointerDown( position = position, - buttons = Batch.empty, + buttons = Batch(button), isAltKeyDown = false, isCtrlKeyDown = false, isMetaKeyDown = false, @@ -624,10 +632,18 @@ object PointerEvent: button: Option[MouseButton] ) extends PointerEvent object PointerUp: + def apply(position: Point): PointerUp = + PointerUp(position, MouseButton.LeftMouseButton, PointerType.Mouse) + def apply(x: Int, y: Int): PointerUp = + PointerUp(Point(x, y), MouseButton.LeftMouseButton, PointerType.Mouse) def apply(position: Point, pointerType: PointerType): PointerUp = PointerUp(position, MouseButton.LeftMouseButton, pointerType) def apply(x: Int, y: Int, pointerType: PointerType): PointerUp = PointerUp(Point(x, y), MouseButton.LeftMouseButton, pointerType) + def apply(position: Point, button: MouseButton): PointerUp = + PointerUp(position, button, PointerType.Mouse) + def apply(x: Int, y: Int, button: MouseButton): PointerUp = + PointerUp(Point(x, y), button, PointerType.Mouse) def apply(x: Int, y: Int, button: MouseButton, pointerType: PointerType): PointerUp = PointerUp(Point(x, y), button, pointerType) def apply(position: Point, button: MouseButton, pointerType: PointerType): PointerUp = @@ -677,6 +693,41 @@ object PointerEvent: button: Option[MouseButton] ) extends PointerEvent object PointerClick: + def apply(position: Point): PointerClick = + PointerClick(position, MouseButton.LeftMouseButton, PointerType.Mouse) + def apply(x: Int, y: Int): PointerClick = + PointerClick(Point(x, y), MouseButton.LeftMouseButton, PointerType.Mouse) + def apply(position: Point, pointerType: PointerType): PointerClick = + PointerClick(position, MouseButton.LeftMouseButton, pointerType) + def apply(x: Int, y: Int, pointerType: PointerType): PointerClick = + PointerClick(Point(x, y), MouseButton.LeftMouseButton, pointerType) + def apply(position: Point, button: MouseButton): PointerClick = + PointerClick(position, button, PointerType.Mouse) + def apply(x: Int, y: Int, button: MouseButton): PointerClick = + PointerClick(Point(x, y), button, PointerType.Mouse) + def apply(x: Int, y: Int, button: MouseButton, pointerType: PointerType): PointerClick = + PointerClick(Point(x, y), button, pointerType) + def apply(position: Point, button: MouseButton, pointerType: PointerType): PointerClick = + PointerClick( + position = position, + buttons = Batch.empty, + isAltKeyDown = false, + isCtrlKeyDown = false, + isMetaKeyDown = false, + isShiftKeyDown = false, + movementPosition = Point.zero, + button = Some(button), + pointerId = 0, + width = 0, + height = 0, + pressure = 0, + tangentialPressure = 0, + tiltX = Radians.zero, + tiltY = Radians.zero, + twist = Radians.zero, + pointerType = pointerType, + isPrimary = true + ) def unapply(e: PointerClick): Option[Point] = Option(e.position) @@ -702,6 +753,33 @@ object PointerEvent: isPrimary: Boolean ) extends PointerEvent object PointerMove: + def apply(position: Point): PointerMove = + PointerMove(position, PointerType.Mouse) + def apply(x: Int, y: Int): PointerMove = + PointerMove(Point(x, y), PointerType.Mouse) + def apply(x: Int, y: Int, pointerType: PointerType): PointerMove = + PointerMove(Point(x, y), pointerType) + def apply(position: Point, pointerType: PointerType): PointerMove = + PointerMove( + position = position, + buttons = Batch.empty, + isAltKeyDown = false, + isCtrlKeyDown = false, + isMetaKeyDown = false, + isShiftKeyDown = false, + movementPosition = Point.zero, + pointerId = 0, + width = 0, + height = 0, + pressure = 0, + tangentialPressure = 0, + tiltX = Radians.zero, + tiltY = Radians.zero, + twist = Radians.zero, + pointerType = pointerType, + isPrimary = true + ) + def unapply(e: PointerMove): Option[Point] = Option(e.position) diff --git a/indigo/indigo/src/main/scala/indigo/shared/events/InputMapping.scala b/indigo/indigo/src/main/scala/indigo/shared/events/InputMapping.scala index fe1e0fd13..855956121 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/events/InputMapping.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/events/InputMapping.scala @@ -20,10 +20,10 @@ final case class InputMapping[A](oneOf: List[(Combo, A)]) { oneOf .find { c => c._1.mouseInputs.forall { - case MouseInput.MouseUp => mouse.mouseReleased - case MouseInput.MouseDown => mouse.mousePressed - case MouseInput.MouseClick => mouse.mouseClicked - case MouseInput.MouseAt(pt) => mouse.position == pt + case MouseInput.MouseUp => mouse.isReleased + case MouseInput.MouseDown => mouse.isPressed + case MouseInput.MouseClick => mouse.isClicked + case MouseInput.MouseAt(pt) => mouse.maybePosition == Some(pt) case MouseInput.MouseButtonUp(button) => mouse.released(button) case MouseInput.MouseButtonDown(button) => mouse.pressed(button) case MouseInput.MouseWheelDown => mouse.scrolled.contains(MouseWheel.ScrollDown) diff --git a/indigo/indigo/src/main/scala/indigo/shared/events/InputState.scala b/indigo/indigo/src/main/scala/indigo/shared/events/InputState.scala index 0c48ce1ea..83a574c88 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/events/InputState.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/events/InputState.scala @@ -1,6 +1,7 @@ package indigo.shared.events import indigo.shared.collections.Batch +import indigo.shared.events.MouseEvent import indigo.shared.input.Gamepad import indigo.shared.input.Keyboard import indigo.shared.input.Mouse @@ -38,10 +39,12 @@ object InputState { events: Batch[InputEvent], gamepadState: Gamepad ): InputState = + val pointers = Pointers.calculateNext(previous.pointers, events.collect { case e: PointerEvent => e }); + InputState( - Mouse.calculateNext(previous.mouse, events.collect { case e: MouseEvent => e }), + Mouse(pointers, events.collect { case e: MouseEvent.Wheel => e }), Keyboard.calculateNext(previous.keyboard, events.collect { case e: KeyboardEvent => e }), gamepadState, - Pointers.calculateNext(previous.pointers, events.collect { case e: PointerEvent => e }) + pointers ) } diff --git a/indigo/indigo/src/main/scala/indigo/shared/input/Mouse.scala b/indigo/indigo/src/main/scala/indigo/shared/input/Mouse.scala index 486271737..7b7190688 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/input/Mouse.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/input/Mouse.scala @@ -6,163 +6,99 @@ import indigo.shared.datatypes.Rectangle import indigo.shared.events.MouseButton import indigo.shared.events.MouseEvent import indigo.shared.events.MouseWheel +import indigo.shared.events.PointerEvent +import indigo.shared.events.PointerEvent.PointerId +import indigo.shared.events.PointerType import scala.annotation.tailrec -final class Mouse( - mouseEvents: Batch[MouseEvent], - val position: Point, - @deprecated("use `isButtonDown` function instead of this value", "0.12.0") val leftMouseIsDown: Boolean, - val buttonsDown: Set[MouseButton] -) { - - def this(mouseEvents: Batch[MouseEvent], position: Point, leftMouseIsDown: Boolean) = - this( - mouseEvents, - position, - leftMouseIsDown, - buttonsDown = if (leftMouseIsDown) Set(MouseButton.LeftMouseButton) else Set.empty - ) - - def isButtonDown(button: MouseButton): Boolean = buttonsDown.contains(button) - - lazy val isLeftDown: Boolean = isButtonDown(MouseButton.LeftMouseButton) - lazy val isRightDown: Boolean = isButtonDown(MouseButton.RightMouseButton) - - lazy val mouseClicked: Boolean = mouseEvents.exists { - case _: MouseEvent.Click => true - case _ => false - } - lazy val mousePressed: Boolean = pressed(MouseButton.LeftMouseButton) - lazy val mouseReleased: Boolean = released(MouseButton.LeftMouseButton) +final class Mouse(val pointers: Pointers, val wheelEvents: Batch[MouseEvent.Wheel]) extends PointerState { + val pointerType: Option[PointerType] = Some(PointerType.Mouse) - def pressed(button: MouseButton): Boolean = - mouseEvents.exists { - case md: MouseEvent.MouseDown if md.button == button => true - case _ => false - } + @deprecated("Use `isClicked` instead.", "0.18.0") + lazy val mouseClicked: Boolean = isClicked - def released(button: MouseButton): Boolean = - mouseEvents.exists { - case mu: MouseEvent.MouseUp if mu.button == button => true - case _ => false - } + @deprecated("Use `isPressed` instead.", "0.18.0") + lazy val mousePressed: Boolean = pressed(MouseButton.LeftMouseButton) + + @deprecated("Use `isReleased` instead.", "0.18.0") + lazy val mouseReleased: Boolean = released(MouseButton.LeftMouseButton) lazy val scrolled: Option[MouseWheel] = - val amount = mouseEvents.foldLeft(0d) { - case (acc, MouseEvent.Wheel(_, deltaY)) => acc + deltaY - case (acc, _) => acc + val amount = wheelEvents.foldLeft(0d) { case (acc, e) => + acc + e.deltaY } if amount == 0 then Option.empty[MouseWheel] else if amount < 0 then Some(MouseWheel.ScrollUp) else Some(MouseWheel.ScrollDown) - lazy val mouseClickAt: Option[Point] = mouseEvents.collectFirst { case m: MouseEvent.Click => - m.position - } - lazy val mouseUpAt: Option[Point] = maybeUpAtPositionWith(MouseButton.LeftMouseButton) - lazy val mouseDownAt: Option[Point] = maybeDownAtPositionWith(MouseButton.LeftMouseButton) - - def maybeUpAtPositionWith(button: MouseButton): Option[Point] = mouseEvents.collectFirst { - case m: MouseEvent.MouseUp if m.button == button => m.position - } - def maybeDownAtPositionWith(button: MouseButton): Option[Point] = mouseEvents.collectFirst { - case m: MouseEvent.MouseDown if m.button == button => m.position - } - - private def wasMouseAt(position: Point, maybePosition: Option[Point]): Boolean = - maybePosition match - case Some(pt) => position == pt - case None => false - - def wasMouseClickedAt(position: Point): Boolean = wasMouseAt(position, mouseClickAt) - def wasMouseClickedAt(x: Int, y: Int): Boolean = wasMouseClickedAt(Point(x, y)) - - def wasMouseUpAt(position: Point): Boolean = wasMouseAt(position, mouseUpAt) - def wasMouseUpAt(x: Int, y: Int): Boolean = wasMouseUpAt(Point(x, y)) - def wasUpAt(position: Point, button: MouseButton): Boolean = - wasMouseAt(position, maybeUpAtPositionWith(button)) - def wasUpAt(x: Int, y: Int, button: MouseButton): Boolean = - wasUpAt(Point(x, y), button) - - def wasMouseDownAt(position: Point): Boolean = wasMouseAt(position, mouseDownAt) - def wasMouseDownAt(x: Int, y: Int): Boolean = wasMouseDownAt(Point(x, y)) - def wasDownAt(position: Point, button: MouseButton): Boolean = - wasMouseAt(position, maybeDownAtPositionWith(button)) - def wasDownAt(x: Int, y: Int, button: MouseButton): Boolean = - wasDownAt(Point(x, y), button) - - def wasMousePositionAt(target: Point): Boolean = target == position - def wasMousePositionAt(x: Int, y: Int): Boolean = wasMousePositionAt(Point(x, y)) - - // Within - private def wasMouseWithin(bounds: Rectangle, maybePosition: Option[Point]): Boolean = - maybePosition match { - case Some(pt) => bounds.isPointWithin(pt) - case None => false - } + @deprecated("Use `isClickedAt` instead.", "0.18.0") + lazy val mouseClickAt: Option[Point] = isClickedAt.headOption + + @deprecated("Use `isUpAt` instead.", "0.18.0") + lazy val mouseUpAt: Option[Point] = isUpAt.headOption + + @deprecated("Use `isDownAt` instead.", "0.18.0") + lazy val mouseDownAt: Option[Point] = isDownAt.headOption + + @deprecated("Use `wasClickedAt` instead.", "0.18.0") + def wasMouseClickedAt(position: Point): Boolean = wasClickedAt(position) + + @deprecated("Use `wasClickedAt` instead.", "0.18.0") + def wasMouseClickedAt(x: Int, y: Int): Boolean = wasClickedAt(Point(x, y)) + + @deprecated("Use `wasUpAt` instead.", "0.18.0") + def wasMouseUpAt(position: Point): Boolean = wasUpAt(position, MouseButton.LeftMouseButton) + + @deprecated("Use `wasUpAt` instead.", "0.18.0") + def wasMouseUpAt(x: Int, y: Int): Boolean = wasUpAt(Point(x, y), MouseButton.LeftMouseButton) - def wasMouseClickedWithin(bounds: Rectangle): Boolean = wasMouseWithin(bounds, mouseClickAt) - def wasMouseClickedWithin(x: Int, y: Int, width: Int, height: Int): Boolean = - wasMouseClickedWithin(Rectangle(x, y, width, height)) + @deprecated("Use `wasDownAt` instead.", "0.18.0") + def wasMouseDownAt(position: Point): Boolean = wasDownAt(position, MouseButton.LeftMouseButton) - def wasMouseUpWithin(bounds: Rectangle): Boolean = wasMouseWithin(bounds, mouseUpAt) - def wasMouseUpWithin(x: Int, y: Int, width: Int, height: Int): Boolean = wasMouseUpWithin( - Rectangle(x, y, width, height) + @deprecated("Use `wasDownAt` instead.", "0.18.0") + def wasMouseDownAt(x: Int, y: Int): Boolean = wasDownAt(Point(x, y), MouseButton.LeftMouseButton) + + @deprecated("Use `wasPositionAt` instead.", "0.18.0") + def wasMousePositionAt(target: Point): Boolean = wasPositionAt(target) + + @deprecated("Use `wasPositionAt` instead.", "0.18.0") + def wasMousePositionAt(x: Int, y: Int): Boolean = wasPositionAt(Point(x, y)) + + @deprecated("Use `wasClickedWithin` instead.", "0.18.0") + def wasMouseClickedWithin(bounds: Rectangle): Boolean = wasClickedWithin(bounds, MouseButton.LeftMouseButton) + + @deprecated("Use `wasClickedWithin` instead.", "0.18.0") + def wasMouseClickedWithin(x: Int, y: Int, width: Int, height: Int): Boolean = wasClickedWithin( + Rectangle(x, y, width, height), + MouseButton.LeftMouseButton ) - def wasUpWithin(bounds: Rectangle, button: MouseButton): Boolean = - wasMouseWithin(bounds, maybeUpAtPositionWith(button)) - def wasUpWithin(x: Int, y: Int, width: Int, height: Int, button: MouseButton): Boolean = - wasUpWithin(Rectangle(x, y, width, height), button) - - def wasMouseDownWithin(bounds: Rectangle): Boolean = wasMouseWithin(bounds, mouseDownAt) - def wasMouseDownWithin(x: Int, y: Int, width: Int, height: Int): Boolean = wasMouseDownWithin( - Rectangle(x, y, width, height) + + @deprecated("Use `wasUpWithin` instead.", "0.18.0") + def wasMouseUpWithin(bounds: Rectangle): Boolean = wasUpWithin(bounds, MouseButton.LeftMouseButton) + + @deprecated("Use `wasUpWithin` instead.", "0.18.0") + def wasMouseUpWithin(x: Int, y: Int, width: Int, height: Int): Boolean = wasUpWithin( + Rectangle(x, y, width, height), + MouseButton.LeftMouseButton ) - def wasDownWithin(bounds: Rectangle, button: MouseButton): Boolean = - wasMouseWithin(bounds, maybeDownAtPositionWith(button)) - def wasDownWithin(x: Int, y: Int, width: Int, height: Int, button: MouseButton): Boolean = - wasDownWithin(Rectangle(x, y, width, height), button) - def wasMousePositionWithin(bounds: Rectangle): Boolean = bounds.isPointWithin(position) - def wasMousePositionWithin(x: Int, y: Int, width: Int, height: Int): Boolean = - wasMousePositionWithin(Rectangle(x, y, width, height)) + @deprecated("Use `wasDownWithin` instead.", "0.18.0") + def wasMouseDownWithin(bounds: Rectangle): Boolean = wasDownWithin(bounds, MouseButton.LeftMouseButton) + + @deprecated("Use `wasDownWithin` instead.", "0.18.0") + def wasMouseDownWithin(x: Int, y: Int, width: Int, height: Int): Boolean = wasDownWithin( + Rectangle(x, y, width, height), + MouseButton.LeftMouseButton + ) + @deprecated("Use `wasWithin` instead.", "0.18.0") + def wasMousePositionWithin(bounds: Rectangle): Boolean = wasWithin(bounds) + + @deprecated("Use `wasWithin` instead.", "0.18.0") + def wasMousePositionWithin(x: Int, y: Int, width: Int, height: Int): Boolean = + wasWithin(Rectangle(x, y, width, height)) } object Mouse: - val default: Mouse = - Mouse(Batch.empty, Point.zero, false) - - def calculateNext(previous: Mouse, events: Batch[MouseEvent]): Mouse = - val newButtonsDown = calculateButtonsDown(events, previous.buttonsDown) - Mouse( - events, - lastMousePosition(previous.position, events), - newButtonsDown.contains(MouseButton.LeftMouseButton), - newButtonsDown - ) - - private def lastMousePosition(previous: Point, events: Batch[MouseEvent]): Point = - events.collect { case mp: MouseEvent.Move => mp.position }.lastOption.fold(previous)(identity) - - private given CanEqual[Batch[MouseEvent], Batch[MouseEvent]] = CanEqual.derived - - // Similar strategy as followed for `Keyboard`, this one uses Set (no button order preserved) - private def calculateButtonsDown( - mouseEvents: Batch[MouseEvent], - previousButtonsDown: Set[MouseButton] - ): Set[MouseButton] = - @tailrec - def rec(remaining: List[MouseEvent], buttonsDownAcc: Set[MouseButton]): Set[MouseButton] = - remaining match - case Nil => - buttonsDownAcc - case (e: MouseEvent.MouseDown) :: moreEvents => - rec(moreEvents, buttonsDownAcc + e.button) - case (e: MouseEvent.MouseUp) :: moreEvents => - rec(moreEvents, buttonsDownAcc - e.button) - case _ :: moreEvents => - rec(moreEvents, buttonsDownAcc) - - rec(mouseEvents.toList, previousButtonsDown) + val default: Mouse = Mouse(Pointers.default, Batch.empty) diff --git a/indigo/indigo/src/main/scala/indigo/shared/input/PointerState.scala b/indigo/indigo/src/main/scala/indigo/shared/input/PointerState.scala index d5bad3afe..64e4191bb 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/input/PointerState.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/input/PointerState.scala @@ -38,14 +38,33 @@ trait PointerState: */ lazy val isReleased: Boolean = released(MouseButton.LeftMouseButton) + /** Thw positions that the pointer was clicked at in this frame + */ lazy val isClickedAt: Batch[Point] = pointers.pointersClickedAt(pointerType) - lazy val isUpAt: Boolean = pointers.upPositionsWith(None, pointerType).nonEmpty - lazy val isDownAt: Boolean = pointers.downPositionsWith(None, pointerType).nonEmpty + /** The positions that the pointer was up at in this frame + */ + lazy val isUpAt: Batch[Point] = pointers.upPositionsWith(Some(MouseButton.LeftMouseButton), pointerType) + + /** The positions that the pointer was down at in this frame + */ + lazy val isDownAt: Batch[Point] = pointers.downPositionsWith(Some(MouseButton.LeftMouseButton), pointerType) + /** The positions of the pointer in this frame + */ lazy val positions: Batch[Point] = pointers.pointerPositions(pointerType) - lazy val position: Option[Point] = positions.headOption - lazy val isClicked: Boolean = pointers.pointerClicked(pointerType) + + /** The first position of the pointer in this frame, defaulting to 0,0 + */ + lazy val position: Point = maybePosition.getOrElse(Point.zero) + + /** The first position of the pointer in this frame + */ + lazy val maybePosition: Option[Point] = positions.headOption + + /** Whether the pointer was clicked this frame + */ + lazy val isClicked: Boolean = pointers.pointerClicked(pointerType) /** Whether the left button was pressed in this frame * @@ -75,6 +94,22 @@ trait PointerState: */ def released(button: MouseButton): Boolean = pointers.released(button, pointerType) + /** The first position where the specified button was up on this frame + * + * @param button + * @return + */ + def maybeUpAtPositionWith(button: MouseButton): Option[Point] = + pointers.upPositionsWith(Some(button), pointerType).headOption + + /** The first position where the specified button was down on this frame + * + * @param button + * @return + */ + def maybeDownAtPositionWith(button: MouseButton): Option[Point] = + pointers.downPositionsWith(Some(button), pointerType).headOption + /** Was any pointer clicked at this position in this frame * * @param position @@ -244,6 +279,55 @@ trait PointerState: pointerType ) + /** Whether the pointer was clicked within the specified bounds in this frame + * + * @param bounds + * @return + */ + def wasClickedWithin(bounds: Rectangle): Boolean = wasClickedWithin(bounds, None) + + /** Whether the pointer button was clicked within the specified bounds in this frame + * + * @param bounds + * @param button + * @return + */ + def wasClickedWithin(bounds: Rectangle, button: MouseButton): Boolean = wasClickedWithin(bounds, Some(button)) + + /** Whether the pointer was clicked within the specified bounds in this frame + * + * @param x + * @param y + * @param width + * @param height + * @param button + * @return + */ + def wasClickedWithin(x: Int, y: Int, width: Int, height: Int): Boolean = wasClickedWithin( + Rectangle(x, y, width, height) + ) + + /** Whether the pointer button was clicked within the specified bounds in this frame + * + * @param x + * @param y + * @param width + * @param height + * @param button + * @return + */ + def wasClickedWithin(x: Int, y: Int, width: Int, height: Int, button: MouseButton): Boolean = + wasClickedWithin(Rectangle(x, y, width, height), Some(button)) + + /** Whether the pointer was clicked within the specified bounds in this frame + * + * @param bounds + * @param button + * @return + */ + def wasClickedWithin(bounds: Rectangle, button: Option[MouseButton]): Boolean = + pointers.clickedPositionsWith(button, pointerType).exists(bounds.isPointWithin) + /** Whether the pointer position was within the specified bounds in this frame * * @param bounds diff --git a/indigo/indigo/src/main/scala/indigo/shared/input/Pointers.scala b/indigo/indigo/src/main/scala/indigo/shared/input/Pointers.scala index c519fbd32..35e08121f 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/input/Pointers.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/input/Pointers.scala @@ -1,6 +1,7 @@ package indigo.shared.input import indigo.MouseButton +import indigo.shared.IndigoLogger import indigo.shared.collections.Batch import indigo.shared.datatypes.Point import indigo.shared.datatypes.Rectangle @@ -135,6 +136,15 @@ final class Pointers( case m: PointerEvent.PointerDown if button == None || m.button == button => m.position } + /** All the positions where the specified button was clicked in this frame + * + * @param button + */ + def clickedPositionsWith(button: Option[MouseButton], pointerType: Option[PointerType]): Batch[Point] = + pointerEventsOfType(pointerType).collect { + case m: PointerEvent.PointerClick if button == None || m.button == button => m.position + } + /** Whether the specified button was up at the specified position in this frame * * @param position @@ -336,15 +346,26 @@ object Pointers: }) .map(_.pointerId) - val pointersToAdd = events - .filter(_ match { - case _: (PointerEvent.PointerCancel | PointerEvent.PointerOut) => false - case e => true - }) - .map(e => Pointer(e.pointerId, e.pointerType, e.buttons, e.position)) + val pointersToAdd = Batch.fromArray( + events + .filter(_ match { + case _: (PointerEvent.PointerCancel | PointerEvent.PointerOut) => false + case e => true + }) + .foldLeft(Map.empty[PointerId, Pointer])((acc, e) => + acc + (e.pointerId -> Pointer(e.pointerId, e.pointerType, e.buttons, e.position)) + ) + .values + .toArray + ) previous.pointerBatch .filterNot(p => pointersToRemove.contains(p.id) || pointersToAdd.exists(_.id == p.id)) ++ pointersToAdd final case class Pointer(id: PointerId, pointerType: PointerType, buttonsDown: Batch[MouseButton], position: Point) +object Pointer: + def apply(position: Point, pointerType: PointerType): Pointer = + Pointer(PointerId(0), pointerType, Batch.empty, position) + def apply(position: Point, pointerType: PointerType, button: MouseButton): Pointer = + Pointer(PointerId(0), pointerType, Batch(button), position) diff --git a/indigo/indigo/src/test/scala/indigo/shared/events/InputStateTests.scala b/indigo/indigo/src/test/scala/indigo/shared/events/InputStateTests.scala index 151572fb1..85bdf820a 100644 --- a/indigo/indigo/src/test/scala/indigo/shared/events/InputStateTests.scala +++ b/indigo/indigo/src/test/scala/indigo/shared/events/InputStateTests.scala @@ -1,5 +1,6 @@ package indigo.shared.events +import indigo.MouseButton import indigo.shared.collections.Batch import indigo.shared.constants.Key import indigo.shared.datatypes.Point @@ -23,17 +24,18 @@ class InputStateTests extends munit.FunSuite { test("The default state object does the expected thing") { assertEquals(inputState.mouse.isLeftDown, false) + assertEquals(inputState.mouse.maybePosition, None) assertEquals(inputState.mouse.position, Point.zero) - assertEquals(inputState.mouse.wasMouseClickedWithin(bounds), false) + assertEquals(inputState.mouse.wasClickedWithin(bounds), false) } - val events1: Batch[MouseEvent] = + val events1: Batch[PointerEvent] = Batch( - MouseEvent.Move(10, 10), - MouseEvent.MouseDown(10, 10), - MouseEvent.MouseUp(10, 10), - MouseEvent.Click(10, 10) + PointerEvent.PointerMove(10, 10), + PointerEvent.PointerDown(10, 10), + PointerEvent.PointerUp(10, 10), + PointerEvent.PointerClick(10, 10) ) val state = InputState.calculateNext(inputState, events1, gamepadState1) @@ -42,108 +44,112 @@ class InputStateTests extends munit.FunSuite { assertEquals(state.mouse.position == Point(10, 10), true) } - test("Mouse state.mousePressed") { - assertEquals(state.mouse.mousePressed, true) + test("Mouse state.maybePosition") { + assertEquals(state.mouse.maybePosition == Some(Point(10, 10)), true) } - test("Mouse state.mouseReleased") { - assertEquals(state.mouse.mouseReleased, true) + test("Mouse state.isPressed") { + assertEquals(state.mouse.isPressed, true) } - test("Mouse state.mouseClicked") { - assertEquals(state.mouse.mouseClicked, true) + test("Mouse state.isReleased") { + assertEquals(state.mouse.isReleased, true) + } + + test("Mouse state.isClicked") { + assertEquals(state.mouse.isClicked, true) assertEquals( - InputState.calculateNext(inputState, Batch(MouseEvent.MouseDown(0, 0)), gamepadState1).mouse.mouseClicked, + InputState.calculateNext(inputState, Batch(PointerEvent.PointerDown(0, 0)), gamepadState1).mouse.isClicked, false ) } - test("Mouse state.mouseClickAt") { - assertEquals(state.mouse.mouseClickAt, Some(Point(10, 10))) + test("Mouse state.isClickedAt") { + assertEquals(state.mouse.isClickedAt, Batch(Point(10, 10))) assertEquals( - InputState.calculateNext(inputState, Batch(MouseEvent.MouseDown(0, 0)), gamepadState1).mouse.mouseClickAt, - None + InputState.calculateNext(inputState, Batch(PointerEvent.PointerDown(0, 0)), gamepadState1).mouse.isClickedAt, + Batch.empty ) } - test("Mouse state.mouseUpAt") { - assertEquals(state.mouse.mouseUpAt, Some(Point(10, 10))) + test("Mouse state.isUpAt") { + assertEquals(state.mouse.isUpAt, Batch(Point(10, 10))) assertEquals( - InputState.calculateNext(inputState, Batch(MouseEvent.MouseDown(0, 0)), gamepadState1).mouse.mouseUpAt, - None + InputState.calculateNext(inputState, Batch(PointerEvent.PointerDown(0, 0)), gamepadState1).mouse.isUpAt, + Batch.empty ) } - test("Mouse state.mouseDownAt") { - assertEquals(state.mouse.mouseDownAt, Some(Point(10, 10))) + test("Mouse state.isDownAt") { + assertEquals(state.mouse.isDownAt, Batch(Point(10, 10))) assertEquals( - InputState.calculateNext(inputState, Batch(MouseEvent.MouseUp(0, 0)), gamepadState1).mouse.mouseDownAt, - None + InputState.calculateNext(inputState, Batch(PointerEvent.PointerUp(0, 0)), gamepadState1).mouse.isDownAt, + Batch.empty ) } - test("Mouse state.wasMouseClickedAt") { - assertEquals(state.mouse.wasMouseClickedAt(10, 10), true) - assertEquals(state.mouse.wasMouseClickedAt(20, 10), false) + test("Mouse state.wasClickedAt") { + assertEquals(state.mouse.wasClickedAt(10, 10), true) + assertEquals(state.mouse.wasClickedAt(20, 10), false) } - test("Mouse state.wasMouseUpAt") { - assertEquals(state.mouse.wasMouseUpAt(10, 10), true) - assertEquals(state.mouse.wasMouseUpAt(20, 10), false) + test("Mouse state.wasUpAt") { + assertEquals(state.mouse.wasUpAt(10, 10), true) + assertEquals(state.mouse.wasUpAt(20, 10), false) } - test("Mouse state.wasMouseDownAt") { - assertEquals(state.mouse.wasMouseDownAt(10, 10), true) - assertEquals(state.mouse.wasMouseDownAt(20, 10), false) + test("Mouse state.wasDownAt") { + assertEquals(state.mouse.wasDownAt(10, 10), true) + assertEquals(state.mouse.wasDownAt(20, 10), false) } - test("Mouse state.wasMousePositionAt") { - assertEquals(state.mouse.wasMousePositionAt(Point.zero), false) - assertEquals(state.mouse.wasMousePositionAt(Point(10, 10)), true) + test("Mouse state.wasPositionAt") { + assertEquals(state.mouse.wasPositionAt(Point.zero), false) + assertEquals(state.mouse.wasPositionAt(Point(10, 10)), true) } - test("Mouse state.wasMouseClickedWithin") { - assertEquals(state.mouse.wasMouseClickedWithin(Rectangle(0, 0, 5, 5)), false) - assertEquals(state.mouse.wasMouseClickedWithin(Rectangle(50, 50, 5, 5)), false) - assertEquals(state.mouse.wasMouseClickedWithin(Rectangle(5, 5, 10, 10)), true) + test("Mouse state.wasClickedWithin") { + assertEquals(state.mouse.wasClickedWithin(Rectangle(0, 0, 5, 5)), false) + assertEquals(state.mouse.wasClickedWithin(Rectangle(50, 50, 5, 5)), false) + assertEquals(state.mouse.wasClickedWithin(Rectangle(5, 5, 10, 10)), true) } - test("Mouse state.wasMouseUpWithin") { - assertEquals(state.mouse.wasMouseUpWithin(Rectangle(0, 0, 5, 5)), false) - assertEquals(state.mouse.wasMouseUpWithin(Rectangle(50, 50, 5, 5)), false) - assertEquals(state.mouse.wasMouseUpWithin(Rectangle(5, 5, 10, 10)), true) + test("Mouse state.wasUpWithin") { + assertEquals(state.mouse.wasUpWithin(Rectangle(0, 0, 5, 5), MouseButton.LeftMouseButton), false) + assertEquals(state.mouse.wasUpWithin(Rectangle(50, 50, 5, 5), MouseButton.LeftMouseButton), false) + assertEquals(state.mouse.wasUpWithin(Rectangle(5, 5, 10, 10), MouseButton.LeftMouseButton), true) } - test("Mouse state.wasMouseDownWithin") { - assertEquals(state.mouse.wasMouseDownWithin(Rectangle(0, 0, 5, 5)), false) - assertEquals(state.mouse.wasMouseDownWithin(Rectangle(50, 50, 5, 5)), false) - assertEquals(state.mouse.wasMouseDownWithin(Rectangle(5, 5, 10, 10)), true) + test("Mouse state.wasDownWithin") { + assertEquals(state.mouse.wasDownWithin(Rectangle(0, 0, 5, 5), MouseButton.LeftMouseButton), false) + assertEquals(state.mouse.wasDownWithin(Rectangle(50, 50, 5, 5), MouseButton.LeftMouseButton), false) + assertEquals(state.mouse.wasDownWithin(Rectangle(5, 5, 10, 10), MouseButton.LeftMouseButton), true) } - test("Mouse state.wasMousePositionWithin") { - assertEquals(state.mouse.wasMousePositionWithin(Rectangle(0, 0, 5, 5)), false) - assertEquals(state.mouse.wasMousePositionWithin(Rectangle(50, 50, 5, 5)), false) - assertEquals(state.mouse.wasMousePositionWithin(Rectangle(5, 5, 10, 10)), true) + test("Mouse state.wasWithin") { + assertEquals(state.mouse.wasWithin(Rectangle(0, 0, 5, 5)), false) + assertEquals(state.mouse.wasWithin(Rectangle(50, 50, 5, 5)), false) + assertEquals(state.mouse.wasWithin(Rectangle(5, 5, 10, 10)), true) } test("Mouse state.isLeftDown") { - val state2 = InputState.calculateNext(state, Batch(MouseEvent.MouseDown(0, 0)), gamepadState1) // true - val state3 = InputState.calculateNext(state2, Batch.empty, gamepadState1) // still true - val state4 = InputState.calculateNext(state3, Batch(MouseEvent.MouseDown(20, 20)), gamepadState1) // still true + val state2 = InputState.calculateNext(state, Batch(PointerEvent.PointerDown(0, 0)), gamepadState1) // true + val state3 = InputState.calculateNext(state2, Batch.empty, gamepadState1) // still true + val state4 = InputState.calculateNext(state3, Batch(PointerEvent.PointerDown(20, 20)), gamepadState1) // still true val state5 = InputState.calculateNext( // Still true state4, - Batch(MouseEvent.MouseUp(20, 20), MouseEvent.MouseDown(20, 20)), + Batch(PointerEvent.PointerUp(20, 20), PointerEvent.PointerDown(20, 20)), gamepadState1 ) - val state6 = InputState.calculateNext(state5, Batch(MouseEvent.MouseUp(20, 20)), gamepadState1) // false + val state6 = InputState.calculateNext(state5, Batch(PointerEvent.PointerUp(20, 20)), gamepadState1) // false val state7 = InputState.calculateNext( // Still false state6, - Batch(MouseEvent.MouseDown(20, 20), MouseEvent.MouseUp(20, 20)), + Batch(PointerEvent.PointerDown(20, 20), PointerEvent.PointerUp(20, 20)), gamepadState1 ) @@ -160,23 +166,23 @@ class InputStateTests extends munit.FunSuite { import MouseButton._ val state2 = - InputState.calculateNext(state, Batch(MouseEvent.MouseDown(0, 0, RightMouseButton)), gamepadState1) // true + InputState.calculateNext(state, Batch(PointerEvent.PointerDown(0, 0, RightMouseButton)), gamepadState1) // true val state3 = InputState.calculateNext(state2, Batch.empty, gamepadState1) // still true val state4 = InputState.calculateNext( // still true state3, - Batch(MouseEvent.MouseDown(20, 20, RightMouseButton)), + Batch(PointerEvent.PointerDown(20, 20, RightMouseButton)), gamepadState1 ) val state5 = InputState.calculateNext( // Still true state4, - Batch(MouseEvent.MouseUp(20, 20, RightMouseButton), MouseEvent.MouseDown(20, 20, RightMouseButton)), + Batch(PointerEvent.PointerUp(20, 20, RightMouseButton), PointerEvent.PointerDown(20, 20, RightMouseButton)), gamepadState1 ) val state6 = // false - InputState.calculateNext(state5, Batch(MouseEvent.MouseUp(20, 20, RightMouseButton)), gamepadState1) + InputState.calculateNext(state5, Batch(PointerEvent.PointerUp(20, 20, RightMouseButton)), gamepadState1) val state7 = InputState.calculateNext( // Still false state6, - Batch(MouseEvent.MouseDown(20, 20, RightMouseButton), MouseEvent.MouseUp(20, 20, RightMouseButton)), + Batch(PointerEvent.PointerDown(20, 20, RightMouseButton), PointerEvent.PointerUp(20, 20, RightMouseButton)), gamepadState1 ) @@ -196,9 +202,13 @@ class InputStateTests extends munit.FunSuite { Batch(MouseEvent.Wheel(0, 0, -5), MouseEvent.Wheel(0, 0, 10)), gamepadState1 ) - val state3 = InputState.calculateNext(state2, Batch.empty[MouseEvent], gamepadState1) + val state3 = InputState.calculateNext(state2, Batch.empty[PointerEvent], gamepadState1) val state4 = - InputState.calculateNext(state3, Batch(MouseEvent.Wheel(0, 0, -10), MouseEvent.Wheel(0, 0, 10)), gamepadState1) + InputState.calculateNext( + state3, + Batch(MouseEvent.Wheel(0, 0, -10), MouseEvent.Wheel(0, 0, 10)), + gamepadState1 + ) assertEquals(initialState.mouse.scrolled, Some(MouseWheel.ScrollUp)) assertEquals(state2.mouse.scrolled, Some(MouseWheel.ScrollDown)) @@ -331,8 +341,8 @@ class InputStateTests extends munit.FunSuite { KeyboardEvent.KeyDown(Key.KEY_B), KeyboardEvent.KeyDown(Key.KEY_C), KeyboardEvent.KeyDown(Key.KEY_D), - MouseEvent.Move(10, 10), - MouseEvent.MouseDown(10, 10), + PointerEvent.PointerMove(10, 10), + PointerEvent.PointerDown(10, 10), MouseEvent.Wheel(10, 10, -15) ) diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/SandboxView.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/SandboxView.scala index cb6923beb..aca0484ec 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/SandboxView.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/SandboxView.scala @@ -12,7 +12,7 @@ object SandboxView: mouse: Mouse, bl: BoundaryLocator ): SceneUpdateFragment = { - mouse.mouseClickAt match { + mouse.isClickedAt.headOption match { case Some(position) => println("Mouse clicked at: " + position.toString()) case None => () }