diff --git a/indigo-plugin/indigo-plugin/src/indigoplugin/generators/ConfigGen.scala b/indigo-plugin/indigo-plugin/src/indigoplugin/generators/ConfigGen.scala index 42c113c17..dc9c60eb1 100644 --- a/indigo-plugin/indigo-plugin/src/indigoplugin/generators/ConfigGen.scala +++ b/indigo-plugin/indigo-plugin/src/indigoplugin/generators/ConfigGen.scala @@ -33,14 +33,9 @@ object ConfigGen { | magnification = 1, | transparentBackground = false, | resizePolicy = ResizePolicy.Resize, - | advanced = AdvancedGameConfig( - | renderingTechnology = RenderingTechnology.WebGL2WithFallback, - | antiAliasing = ${indigoOptions.metadata.antiAliasing.toString}, - | premultipliedAlpha = true, - | batchSize = 256, - | autoLoadStandardShaders = true, - | disableContextMenu = true - | ) + | advanced = AdvancedGameConfig + | .default + | .withAntiAliasing(${indigoOptions.metadata.antiAliasing.toString}) | ) |""".stripMargin diff --git a/indigo/indigo-extras/src/main/scala/indigoextras/ui/Button.scala b/indigo/indigo-extras/src/main/scala/indigoextras/ui/Button.scala index d0cfe6374..c7dc2cc98 100644 --- a/indigo/indigo-extras/src/main/scala/indigoextras/ui/Button.scala +++ b/indigo/indigo-extras/src/main/scala/indigoextras/ui/Button.scala @@ -7,7 +7,7 @@ import indigo.shared.datatypes.Point import indigo.shared.datatypes.Rectangle import indigo.shared.datatypes.Size 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 @@ -51,52 +51,52 @@ final case class Button( def withDepth(newDepth: Depth): Button = this.copy(depth = newDepth) - def update(mouse: Mouse): Outcome[Button] = { - val mouseInBounds = bounds.isPointWithin(mouse.position) + def update(pointer: PointerState): Outcome[Button] = { + val pointerInBounds = pointer.positions.exists(p => bounds.isPointWithin(p)) val upEvents: Batch[GlobalEvent] = - if mouseInBounds && mouse.mouseReleased then onUp() + if pointerInBounds && pointer.released then onUp() else Batch.empty val clickEvents: Batch[GlobalEvent] = - if mouseInBounds && mouse.mouseClicked then onClick() + if pointerInBounds && pointer.isClicked then onClick() else Batch.empty val downEvents: Batch[GlobalEvent] = - if mouseInBounds && mouse.mousePressed then onDown() + if pointerInBounds && pointer.pressed then onDown() else Batch.empty - val mouseButtonEvents: Batch[GlobalEvent] = + val pointerButtonEvents: Batch[GlobalEvent] = downEvents ++ upEvents ++ clickEvents state match // Stay in Down state - case ButtonState.Down if mouseInBounds && mouse.isLeftDown => - Outcome(this).addGlobalEvents(onHoldDown() ++ mouseButtonEvents) + case ButtonState.Down if pointerInBounds && pointer.isLeftDown => + Outcome(this).addGlobalEvents(onHoldDown() ++ pointerButtonEvents) // Move to Down state - case ButtonState.Up if mouseInBounds && mouse.mousePressed => - Outcome(toDownState).addGlobalEvents(onHoverOver() ++ mouseButtonEvents) + case ButtonState.Up if pointerInBounds && pointer.pressed => + Outcome(toDownState).addGlobalEvents(onHoverOver() ++ pointerButtonEvents) - case ButtonState.Over if mouseInBounds && mouse.mousePressed => - Outcome(toDownState).addGlobalEvents(mouseButtonEvents) + case ButtonState.Over if pointerInBounds && pointer.pressed => + Outcome(toDownState).addGlobalEvents(pointerButtonEvents) // Out of Down state - case ButtonState.Down if mouseInBounds && !mouse.isLeftDown => - Outcome(toOverState).addGlobalEvents(onHoverOver() ++ mouseButtonEvents) + case ButtonState.Down if pointerInBounds && !pointer.isLeftDown => + Outcome(toOverState).addGlobalEvents(onHoverOver() ++ pointerButtonEvents) - case ButtonState.Down if !mouseInBounds && !mouse.isLeftDown => - Outcome(toUpState).addGlobalEvents(onHoverOut() ++ mouseButtonEvents) + case ButtonState.Down if !pointerInBounds && !pointer.isLeftDown => + Outcome(toUpState).addGlobalEvents(onHoverOut() ++ pointerButtonEvents) // - case ButtonState.Up if mouseInBounds => - Outcome(toOverState).addGlobalEvents(onHoverOver() ++ mouseButtonEvents) + case ButtonState.Up if pointerInBounds => + Outcome(toOverState).addGlobalEvents(onHoverOver() ++ pointerButtonEvents) - case ButtonState.Over if !mouseInBounds => - Outcome(toUpState).addGlobalEvents(onHoverOut() ++ mouseButtonEvents) + case ButtonState.Over if !pointerInBounds => + Outcome(toUpState).addGlobalEvents(onHoverOut() ++ pointerButtonEvents) case _ => - Outcome(this).addGlobalEvents(mouseButtonEvents) + Outcome(this).addGlobalEvents(pointerButtonEvents) } private def applyPositionAndDepth(sceneNode: SceneNode, pt: Point, d: Depth): SceneNode = diff --git a/indigo/indigo-extras/src/main/scala/indigoextras/ui/HitArea.scala b/indigo/indigo-extras/src/main/scala/indigoextras/ui/HitArea.scala index f341a1f9d..041835293 100644 --- a/indigo/indigo-extras/src/main/scala/indigoextras/ui/HitArea.scala +++ b/indigo/indigo-extras/src/main/scala/indigoextras/ui/HitArea.scala @@ -7,7 +7,7 @@ import indigo.shared.datatypes.Rectangle import indigo.shared.events.GlobalEvent import indigo.shared.geometry.Polygon import indigo.shared.geometry.Vertex -import indigo.shared.input.Mouse +import indigo.shared.input.PointerState final case class HitArea( area: Polygon.Closed, @@ -20,42 +20,42 @@ final case class HitArea( onHoldDown: () => Batch[GlobalEvent] ) derives CanEqual: - def update(mouse: Mouse): Outcome[HitArea] = { - val mouseInBounds = area.contains(Vertex.fromPoint(mouse.position)) + def update(pointer: PointerState): Outcome[HitArea] = { + val pointerInBounds = pointer.positions.exists(p => area.contains(Vertex.fromPoint(p))) val upEvents: Batch[GlobalEvent] = - if mouseInBounds && mouse.mouseReleased then onUp() + if pointerInBounds && pointer.released then onUp() else Batch.empty val clickEvents: Batch[GlobalEvent] = - if mouseInBounds && mouse.mouseClicked then onClick() + if pointerInBounds && pointer.isClicked then onClick() else Batch.empty val downEvents: Batch[GlobalEvent] = - if mouseInBounds && mouse.mousePressed then onDown() + if pointerInBounds && pointer.pressed then onDown() else Batch.empty - val mouseButtonEvents: Batch[GlobalEvent] = + val pointerButtonEvents: Batch[GlobalEvent] = downEvents ++ upEvents ++ clickEvents state match - case ButtonState.Down if mouseInBounds && mouse.isLeftDown => - Outcome(this).addGlobalEvents(onHoldDown() ++ mouseButtonEvents) + case ButtonState.Down if pointerInBounds && pointer.isLeftDown => + Outcome(this).addGlobalEvents(onHoldDown() ++ pointerButtonEvents) - case ButtonState.Up if mouseInBounds => - Outcome(toOverState).addGlobalEvents(onHoverOver() ++ mouseButtonEvents) + case ButtonState.Up if pointerInBounds => + Outcome(toOverState).addGlobalEvents(onHoverOver() ++ pointerButtonEvents) - case ButtonState.Over if mouseInBounds && mouse.mousePressed => - Outcome(toDownState).addGlobalEvents(mouseButtonEvents) + case ButtonState.Over if pointerInBounds && pointer.pressed => + Outcome(toDownState).addGlobalEvents(pointerButtonEvents) - case ButtonState.Down if mouseInBounds && !mouse.isLeftDown => - Outcome(toOverState).addGlobalEvents(onHoverOver() ++ mouseButtonEvents) + case ButtonState.Down if pointerInBounds && !pointer.isLeftDown => + Outcome(toOverState).addGlobalEvents(onHoverOver() ++ pointerButtonEvents) - case ButtonState.Over if !mouseInBounds => - Outcome(toUpState).addGlobalEvents(onHoverOut() ++ mouseButtonEvents) + case ButtonState.Over if !pointerInBounds => + Outcome(toUpState).addGlobalEvents(onHoverOut() ++ pointerButtonEvents) case _ => - Outcome(this).addGlobalEvents(mouseButtonEvents) + Outcome(this).addGlobalEvents(pointerButtonEvents) } def withUpActions(actions: GlobalEvent*): HitArea = 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/ButtonTests.scala b/indigo/indigo-extras/src/test/scala/indigoextras/ui/ButtonTests.scala index e9b38531e..75ffe4c0b 100644 --- a/indigo/indigo-extras/src/test/scala/indigoextras/ui/ButtonTests.scala +++ b/indigo/indigo-extras/src/test/scala/indigoextras/ui/ButtonTests.scala @@ -1,5 +1,6 @@ package indigoextras.ui +import indigo.MouseButton import indigo.shared.assets.AssetName import indigo.shared.collections.Batch import indigo.shared.datatypes.Depth @@ -7,7 +8,13 @@ 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.PointerEvent.PointerDown +import indigo.shared.events.PointerEvent.PointerId +import indigo.shared.events.PointerEvent.PointerUp +import indigo.shared.events.PointerType +import indigo.shared.input.Pointer +import indigo.shared.input.PointerState +import indigo.shared.input.Pointers import indigo.shared.materials.Material import indigo.shared.scenegraph.Graphic @@ -39,9 +46,13 @@ class ButtonTests extends munit.FunSuite { test("Transition from Up -> Over when mouse over.") { assertEquals(button.state.isUp, true) - - val mouse = - new Mouse(Batch.empty, Point(20, 20), false) + val mouse = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, Point(20, 20))), + Batch.empty + ) + val pointerType = Some(PointerType.Mouse) + } val actual = button.update(mouse) @@ -50,8 +61,13 @@ class ButtonTests extends munit.FunSuite { test("Transition from Up -> Over when mouse over.Within the button: On mouse over, the over action is performed") { - val mouse = - new Mouse(Batch.empty, Point(20, 20), false) + val mouse = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, Point(20, 20))), + Batch.empty + ) + val pointerType = Some(PointerType.Mouse) + } val actual = button.update(mouse) assert(actual.unsafeGlobalEvents.length == 1) @@ -59,8 +75,13 @@ class ButtonTests extends munit.FunSuite { } test("Transition from Over -> Up when mouse out.") { - val mouse = - new Mouse(Batch.empty, Point(0, 0), false) + val mouse = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, Point(0, 0))), + Batch.empty + ) + val pointerType = Some(PointerType.Mouse) + } val actual = button.toOverState.update(mouse) @@ -70,8 +91,13 @@ class ButtonTests extends munit.FunSuite { test( "Transition from Over -> Up when mouse out.Starting within the button: On mouse out, the out action is performed" ) { - val mouse = - new Mouse(Batch.empty, Point(0, 0), false) + val mouse = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, Point(0, 0))), + Batch.empty + ) + val pointerType = Some(PointerType.Mouse) + } val actual = button.toOverState.update(mouse) assert(actual.unsafeGlobalEvents.length == 1) @@ -79,8 +105,13 @@ class ButtonTests extends munit.FunSuite { } test("Transition from Over -> Down on mouse press.") { - val mouse = - new Mouse(Batch(MouseEvent.MouseDown(20, 20)), Point(20, 20), false) + val mouse = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, Point(20, 20))), + Batch(PointerDown(20, 20, PointerType.Mouse)) + ) + val pointerType = Some(PointerType.Mouse) + } val actual = button.toOverState.update(mouse) @@ -89,8 +120,13 @@ class ButtonTests extends munit.FunSuite { test("Transition from Over -> Down on mouse press.Within the button: On mouse down, the down action is performed") { - val mouse = - new Mouse(Batch(MouseEvent.MouseDown(20, 20)), Point(20, 20), false) + val mouse = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, Point(20, 20))), + Batch(PointerDown(20, 20, PointerType.Mouse)) + ) + val pointerType = Some(PointerType.Mouse) + } val actual = button.toOverState.update(mouse) @@ -99,17 +135,26 @@ class ButtonTests extends munit.FunSuite { } test("Transition from Up -> Down on mouse press.") { - val mouse = - new Mouse(Batch(MouseEvent.MouseDown(20, 20)), Point(20, 20), false) - + val mouse = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, Point(20, 20))), + Batch(PointerDown(20, 20, PointerType.Mouse)) + ) + val pointerType = Some(PointerType.Mouse) + } val actual = button.toUpState.update(mouse) assertEquals(actual.unsafeGet.state.isDown, true) } test("Transition from Up -> Down on mouse press.Within the button: On mouse down, the down action is performed") { - val mouse = - new Mouse(Batch(MouseEvent.MouseDown(20, 20)), Point(20, 20), false) + val mouse = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, Point(20, 20))), + Batch(PointerDown(20, 20, PointerType.Mouse)) + ) + val pointerType = Some(PointerType.Mouse) + } val actual = button.toUpState.update(mouse) @@ -121,8 +166,13 @@ class ButtonTests extends munit.FunSuite { } test("Transition from Down -> Over on mouse release.") { - val mouse = - new Mouse(Batch(MouseEvent.MouseUp(20, 20)), Point(20, 20), false) + val mouse = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, Point(20, 20))), + Batch(PointerUp(20, 20, PointerType.Mouse)) + ) + val pointerType = Some(PointerType.Mouse) + } val actual = button.toDownState.update(mouse) @@ -132,8 +182,13 @@ class ButtonTests extends munit.FunSuite { test( "Transition from Down -> Over on mouse release.Within the button: On mouse release, the up action is performed" ) { - val mouse = - new Mouse(Batch(MouseEvent.MouseUp(20, 20)), Point(20, 20), false) + val mouse = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, Point(20, 20))), + Batch(PointerUp(20, 20, PointerType.Mouse)) + ) + val pointerType = Some(PointerType.Mouse) + } val actual = button.toDownState.update(mouse) @@ -143,10 +198,31 @@ class ButtonTests extends munit.FunSuite { } test("If the button is down, and the mouse moves out, the button stays down until release.") { + val mouse1 = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, Point(20, 20))), + Batch(PointerDown(20, 20, PointerType.Mouse)) + ) + val pointerType = Some(PointerType.Mouse) + } + val mouse2 = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch(MouseButton.LeftMouseButton), Point(200, 200))), + Batch.empty + ) + val pointerType = Some(PointerType.Mouse) + } + val mouse3 = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, Point(200, 200))), + Batch(PointerUp(200, 200, PointerType.Mouse)) + ) + val pointerType = Some(PointerType.Mouse) + } val actual = for { - buttonPressed <- button.update(new Mouse(Batch(MouseEvent.MouseDown(20, 20)), Point(20, 20), false)) - mouseOut <- buttonPressed.update(new Mouse(Batch.empty, Point(200, 200), true)) - mouseReleased <- mouseOut.update(new Mouse(Batch(MouseEvent.MouseUp(200, 200)), Point(200, 200), false)) + buttonPressed <- button.update(mouse1) + mouseOut <- buttonPressed.update(mouse2) + mouseReleased <- mouseOut.update(mouse3) } yield (buttonPressed.state, mouseOut.state, mouseReleased.state) assert(clue(actual.unsafeGet._1.isDown)) @@ -157,11 +233,31 @@ class ButtonTests extends munit.FunSuite { test( "If the button is down, and the mouse moves out, the button stays down until release.If the mouse is moved onto and pressed down on the button, dragged out and released, only the down action is performed." ) { - + val mouse1 = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, Point(20, 20))), + Batch(PointerDown(20, 20, PointerType.Mouse)) + ) + val pointerType = Some(PointerType.Mouse) + } + val mouse2 = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, Point(200, 200))), + Batch.empty + ) + val pointerType = Some(PointerType.Mouse) + } + val mouse3 = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, Point(200, 200))), + Batch(PointerUp(200, 200, PointerType.Mouse)) + ) + val pointerType = Some(PointerType.Mouse) + } val actual = for { - buttonPressed <- button.update(new Mouse(Batch(MouseEvent.MouseDown(20, 20)), Point(20, 20), false)) - mouseOut <- buttonPressed.update(new Mouse(Batch.empty, Point(200, 200), false)) - mouseReleased <- mouseOut.update(new Mouse(Batch(MouseEvent.MouseUp(200, 200)), Point(200, 200), false)) + buttonPressed <- button.update(mouse1) + mouseOut <- buttonPressed.update(mouse2) + mouseReleased <- mouseOut.update(mouse3) } yield (buttonPressed.state, mouseOut.state, mouseReleased.state) assert(clue(actual.unsafeGlobalEvents.length) == clue(3)) @@ -171,7 +267,13 @@ class ButtonTests extends munit.FunSuite { } test("If the button is down and we keep the mouse pressed, hold down actions are performed.") { - val mouse = new Mouse(Batch.empty, button.bounds.position, true) + val mouse = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch(MouseButton.LeftMouseButton), button.bounds.position)), + Batch.empty + ) + val pointerType = Some(PointerType.Mouse) + } val actual = for { holdDown <- button.toDownState.update(mouse) holdDownLonger <- holdDown.update(mouse) diff --git a/indigo/indigo-extras/src/test/scala/indigoextras/ui/HitAreaTests.scala b/indigo/indigo-extras/src/test/scala/indigoextras/ui/HitAreaTests.scala index 82392d5f8..df08bf545 100644 --- a/indigo/indigo-extras/src/test/scala/indigoextras/ui/HitAreaTests.scala +++ b/indigo/indigo-extras/src/test/scala/indigoextras/ui/HitAreaTests.scala @@ -1,5 +1,7 @@ package indigoextras.ui +import indigo.MouseButton +import indigo.Radians import indigo.shared.assets.AssetName import indigo.shared.collections.Batch import indigo.shared.datatypes.Depth @@ -7,7 +9,12 @@ 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.PointerEvent.PointerDown +import indigo.shared.events.PointerEvent.PointerId +import indigo.shared.events.PointerType +import indigo.shared.input.Pointer +import indigo.shared.input.PointerState +import indigo.shared.input.Pointers import indigo.shared.materials.Material import indigo.shared.scenegraph.Graphic @@ -17,7 +24,14 @@ class HitAreaTests extends munit.FunSuite { val hitArea = HitArea(bounds).withHoldDownActions(holdDownEvent) test("If the hit area is down and we keep the mouse pressed, hold down actions are performed.") { - val mouse = new Mouse(Batch.empty, bounds.position, true) + val mouse = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch(MouseButton.LeftMouseButton), bounds.position)), + Batch.empty + ) + val pointerType = Some(PointerType.Mouse) + } + val actual = for { holdDown <- hitArea.toDownState.update(mouse) holdDownLonger <- holdDown.update(mouse) @@ -29,13 +43,28 @@ class HitAreaTests extends munit.FunSuite { } test("If the hit area is down and we release the mouse, the state is set to over.") { - val mouse = new Mouse(Batch.empty, bounds.position, false) + val mouse = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch.empty, bounds.position)), + Batch.empty + ) + val pointerType = Some(PointerType.Mouse) + } + val actual = hitArea.toDownState.update(mouse).unsafeGet assert(actual.state == ButtonState.Over) } test("If the hit area is hovered and we release the mouse, the state is set to down.") { - val mouse = new Mouse(Batch(MouseEvent.MouseDown(bounds.x, bounds.y)), bounds.position, true) + val mouse = new PointerState { + val pointers = new Pointers( + Batch(Pointer(PointerId(1), PointerType.Mouse, Batch(MouseButton.LeftMouseButton), bounds.position)), + Batch( + PointerDown(bounds.position, MouseButton.LeftMouseButton, PointerType.Mouse) + ) + ) + val pointerType = Some(PointerType.Mouse) + } val actual = hitArea.toOverState.update(mouse).unsafeGet assert(actual.state == ButtonState.Down) } 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/platform/Platform.scala b/indigo/indigo/src/main/scala/indigo/platform/Platform.scala index 7fb7ac81f..033a4874d 100644 --- a/indigo/indigo/src/main/scala/indigo/platform/Platform.scala +++ b/indigo/indigo/src/main/scala/indigo/platform/Platform.scala @@ -139,7 +139,8 @@ class Platform( gameConfig.resizePolicy, gameConfig.magnification, gameConfig.advanced.disableContextMenu, - globalEventStream + globalEventStream, + gameConfig.advanced.clickTime.toLong ) GamepadInputCaptureImpl.init() else IndigoLogger.info("Re-using existing world events") diff --git a/indigo/indigo/src/main/scala/indigo/platform/events/WorldEvents.scala b/indigo/indigo/src/main/scala/indigo/platform/events/WorldEvents.scala index 45202f003..b5e0a2158 100644 --- a/indigo/indigo/src/main/scala/indigo/platform/events/WorldEvents.scala +++ b/indigo/indigo/src/main/scala/indigo/platform/events/WorldEvents.scala @@ -25,7 +25,11 @@ import org.scalajs.dom.document import org.scalajs.dom.html import org.scalajs.dom.window +import scala.scalajs.js.Date + final class WorldEvents: + @SuppressWarnings(Array("scalafix:DisableSyntax.var")) + private var pointerButtons: Map[Double, Batch[(Int, Date)]] = Map.empty def absoluteCoordsX(relativeX: Double): Int = { val offset: Double = @@ -61,11 +65,13 @@ final class WorldEvents: onPointerUp: dom.PointerEvent => Unit, onPointerMove: dom.PointerEvent => Unit, onPointerCancel: dom.PointerEvent => Unit, + onPointerOut: dom.PointerEvent => Unit, onBlur: dom.FocusEvent => Unit, onFocus: dom.FocusEvent => Unit, onOnline: dom.Event => Unit, onOffline: dom.Event => Unit, - resizeObserver: dom.ResizeObserver + resizeObserver: dom.ResizeObserver, + clickTimeMs: Long ) { canvas.addEventListener("click", onClick) canvas.addEventListener("wheel", onWheel) @@ -75,6 +81,7 @@ final class WorldEvents: canvas.addEventListener("pointerup", onPointerUp) canvas.addEventListener("pointermove", onPointerMove) canvas.addEventListener("pointercancel", onPointerCancel) + canvas.addEventListener("pointerout", onPointerOut) canvas.addEventListener("focus", onFocus) canvas.addEventListener("blur", onBlur) window.addEventListener("focus", onFocus) @@ -95,6 +102,7 @@ final class WorldEvents: canvas.removeEventListener("pointerup", onPointerUp) canvas.removeEventListener("pointermove", onPointerMove) canvas.removeEventListener("pointercancel", onPointerCancel) + canvas.removeEventListener("pointerout", onPointerOut) canvas.removeEventListener("focus", onFocus) canvas.removeEventListener("blur", onBlur) window.removeEventListener("focus", onFocus) @@ -105,6 +113,7 @@ final class WorldEvents: window.removeEventListener("online", onOnline) window.removeEventListener("offline", onOffline) resizeObserver.disconnect() + pointerButtons = Map.empty } } @@ -114,7 +123,8 @@ final class WorldEvents: resizePolicy: ResizePolicy, magnification: Int, disableContextMenu: Boolean, - globalEventStream: GlobalEventStream + globalEventStream: GlobalEventStream, + clickTimeMs: Long ): Handlers = Handlers( canvas = canvas, resizePolicy, @@ -140,7 +150,7 @@ final class WorldEvents: }, /* Follows the most conventional, basic definition of wheel. - To be fair, the wheel event doesn't necessarily means that the device is a mouse, or even that the + To be fair, the wheel event doesn't necessarily mean that the device is a mouse, or even that the deltaY represents the direction of the vertical scrolling (usually negative is upwards and positive downwards). For the sake of simplicity, we're assuming a common mouse with a simple wheel. @@ -289,6 +299,13 @@ final class WorldEvents: val movementPosition = e.movementPosition(magnification) val pointerType = e.toPointerType + // Add the button to the list of buttons that are down, to check later when the button is released + pointerButtons = pointerButtons.updated( + e.pointerId, + pointerButtons + .getOrElse(e.pointerId, Batch.empty) :+ (e.button -> new Date(Date.now())) + ) + globalEventStream.pushGlobalEvent( PointerDown( position, @@ -336,6 +353,42 @@ final class WorldEvents: val movementPosition = e.movementPosition(magnification) val pointerType = e.toPointerType + // Check to see if this button is up within the clickTimeMs, and if so fire a click event + pointerButtons.getOrElse(e.pointerId, Batch.empty).find(_._1 == e.button) match { + case Some((btn, downTime)) if btn == e.button && Date.now() - downTime.getTime() <= clickTimeMs => + globalEventStream.pushGlobalEvent( + PointerClick( + position, + buttons, + e.altKey, + e.ctrlKey, + e.metaKey, + e.shiftKey, + movementPosition, + PointerId(e.pointerId), + e.width(magnification), + e.height(magnification), + e.pressure, + e.tangentialPressure, + Radians.fromDegrees(e.tiltX), + Radians.fromDegrees(e.tiltY), + Radians.fromDegrees(e.twist), + pointerType, + e.isPrimary, + MouseButton.fromOrdinalOpt(e.button) + ) + ) + case _ => () + } + + // Remove the button from the list of buttons that are down + pointerButtons = pointerButtons.updated( + e.pointerId, + pointerButtons + .getOrElse(e.pointerId, Batch.empty) + .filterNot(_._1 == e.button) + ) + globalEventStream.pushGlobalEvent( PointerUp( position, @@ -449,6 +502,35 @@ final class WorldEvents: ) e.preventDefault() }, + onPointerOut = { e => + val position = e.position(magnification, canvas) + val buttons = e.indigoButtons + val movementPosition = e.movementPosition(magnification) + val pointerType = e.toPointerType + + globalEventStream.pushGlobalEvent( + PointerOut( + position, + buttons, + e.altKey, + e.ctrlKey, + e.metaKey, + e.shiftKey, + movementPosition, + PointerId(e.pointerId), + e.width(magnification), + e.height(magnification), + e.pressure, + e.tangentialPressure, + Radians.fromDegrees(e.tiltX), + Radians.fromDegrees(e.tiltY), + Radians.fromDegrees(e.twist), + pointerType, + e.isPrimary + ) + ) + e.preventDefault() + }, onFocus = { e => globalEventStream.pushGlobalEvent( if e.isWindowTarget then ApplicationGainedFocus @@ -520,7 +602,8 @@ final class WorldEvents: } } } - ) + ), + clickTimeMs ) } @@ -532,10 +615,13 @@ final class WorldEvents: resizePolicy: ResizePolicy, magnification: Int, disableContextMenu: Boolean, - globalEventStream: GlobalEventStream + globalEventStream: GlobalEventStream, + clickTimeMs: Long ): Unit = if (_handlers.isEmpty) - _handlers = Some(Handlers(canvas, resizePolicy, magnification, disableContextMenu, globalEventStream)) + _handlers = Some( + Handlers(canvas, resizePolicy, magnification, disableContextMenu, globalEventStream, clickTimeMs) + ) def kill(): Unit = _handlers.foreach { x => x.unbind() diff --git a/indigo/indigo/src/main/scala/indigo/scenes/SceneContext.scala b/indigo/indigo/src/main/scala/indigo/scenes/SceneContext.scala index 3f2b53b5a..d85c1b979 100644 --- a/indigo/indigo/src/main/scala/indigo/scenes/SceneContext.scala +++ b/indigo/indigo/src/main/scala/indigo/scenes/SceneContext.scala @@ -37,6 +37,7 @@ final class SceneContext[StartUpData]( export frameContext.inputState.mouse export frameContext.inputState.keyboard export frameContext.inputState.gamepad + export frameContext.inputState.pointers export frameContext.findBounds export frameContext.bounds export frameContext.captureScreen diff --git a/indigo/indigo/src/main/scala/indigo/shared/FrameContext.scala b/indigo/indigo/src/main/scala/indigo/shared/FrameContext.scala index f401dd469..7520b892a 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/FrameContext.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/FrameContext.scala @@ -43,6 +43,7 @@ final class FrameContext[StartUpData]( export inputState.mouse export inputState.keyboard export inputState.gamepad + export inputState.pointers export boundaryLocator.findBounds export boundaryLocator.bounds diff --git a/indigo/indigo/src/main/scala/indigo/shared/config/AdvancedGameConfig.scala b/indigo/indigo/src/main/scala/indigo/shared/config/AdvancedGameConfig.scala index ad85118e4..12ca90ee5 100644 --- a/indigo/indigo/src/main/scala/indigo/shared/config/AdvancedGameConfig.scala +++ b/indigo/indigo/src/main/scala/indigo/shared/config/AdvancedGameConfig.scala @@ -1,5 +1,6 @@ package indigo.shared.config +import indigo.Millis import indigo.shared.config.RenderingTechnology.WebGL1 import indigo.shared.config.RenderingTechnology.WebGL2 import indigo.shared.config.RenderingTechnology.WebGL2WithFallback @@ -27,7 +28,8 @@ final case class AdvancedGameConfig( batchSize: Int, premultipliedAlpha: Boolean, autoLoadStandardShaders: Boolean, - disableContextMenu: Boolean + disableContextMenu: Boolean, + clickTime: Millis ) derives CanEqual { def withRenderingTechnology(tech: RenderingTechnology): AdvancedGameConfig = @@ -64,12 +66,19 @@ final case class AdvancedGameConfig( def noContextMenu: AdvancedGameConfig = this.copy(disableContextMenu = true) + def withClickTime(millis: Millis): AdvancedGameConfig = + this.copy(clickTime = millis) + val asString: String = s""" |Advanced settings |- Rendering technology: ${renderingTechnology.name} |- AntiAliasing enabled: ${antiAliasing.toString} |- Render batch size: ${batchSize.toString} + |- Pre-Multiplied Alpha: ${premultipliedAlpha.toString} + |- Auto-Load Shaders: ${autoLoadStandardShaders.toString} + |- Disable Context Menu: ${disableContextMenu.toString} + |- Click Time (ms): ${clickTime.toString} |""".stripMargin } @@ -81,7 +90,8 @@ object AdvancedGameConfig { premultipliedAlpha = true, batchSize = 256, autoLoadStandardShaders = true, - disableContextMenu = true + disableContextMenu = true, + clickTime = Millis(200) ) } 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 82eb2cbe4..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,6 +570,42 @@ 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(button), + 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: PointerDown): Option[Point] = Option(e.position) @@ -596,9 +632,105 @@ 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 = + PointerUp( + 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: PointerUp): Option[Point] = Option(e.position) + /** Pointing device button has been clicked */ + final case class PointerClick( + position: Point, + buttons: Batch[MouseButton], + isAltKeyDown: Boolean, + isCtrlKeyDown: Boolean, + isMetaKeyDown: Boolean, + isShiftKeyDown: Boolean, + movementPosition: Point, + pointerId: PointerId, + width: Int, + height: Int, + pressure: Double, + tangentialPressure: Double, + tiltX: Radians, + tiltY: Radians, + twist: Radians, + pointerType: PointerType, + isPrimary: Boolean, + 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) + /** Pointing device changed coordinates. */ final case class PointerMove( @@ -621,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) @@ -653,6 +812,35 @@ object PointerEvent: def unapply(e: PointerCancel): Option[Point] = Option(e.position) + /** The pointer is no longer sending events because: + * - the pointing device is moved out of the hit boundaries + * - the PointerUp event was fired on a device that doesn't support hover + * - after firing the PointerCancel event + * - when a pen stylus leaves the hover range detectable by the digitizer. + */ + final case class PointerOut( + position: Point, + buttons: Batch[MouseButton], + isAltKeyDown: Boolean, + isCtrlKeyDown: Boolean, + isMetaKeyDown: Boolean, + isShiftKeyDown: Boolean, + movementPosition: Point, + pointerId: PointerId, + width: Int, + height: Int, + pressure: Double, + tangentialPressure: Double, + tiltX: Radians, + tiltY: Radians, + twist: Radians, + pointerType: PointerType, + isPrimary: Boolean + ) extends PointerEvent + object PointerOut: + def unapply(e: PointerOut): Option[Point] = + Option(e.position) + /** Represents all keyboard events */ sealed trait KeyboardEvent extends InputEvent { 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 new file mode 100644 index 000000000..64e4191bb --- /dev/null +++ b/indigo/indigo/src/main/scala/indigo/shared/input/PointerState.scala @@ -0,0 +1,347 @@ +package indigo.shared.input + +import indigo.shared.collections.Batch +import indigo.shared.datatypes.Point +import indigo.shared.datatypes.Rectangle +import indigo.shared.events.MouseButton +import indigo.shared.events.PointerType + +trait PointerState: + val pointers: Pointers + val pointerType: Option[PointerType] + + /** Whether the specified button is down on any pointer + * + * @param button + * The button to check + * @return + */ + def isButtonDown(button: MouseButton): Boolean = pointers.isButtonDown(button, pointerType) + + /** Whether the left button is down on any pointer + */ + lazy val isLeftDown: Boolean = isButtonDown(MouseButton.LeftMouseButton) + + /** Whether the right button is down on any pointer + */ + lazy val isRightDown: Boolean = isButtonDown(MouseButton.RightMouseButton) + + /** Whether the middle button is down on any pointer + */ + lazy val isMiddleDown: Boolean = isButtonDown(MouseButton.MiddleMouseButton) + + /** Whether the left button was pressed in this frame + */ + lazy val isPressed: Boolean = pressed(MouseButton.LeftMouseButton) + + /** Whether the left button was released in this frame + */ + 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) + + /** 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) + + /** 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 + * + * @return + */ + def pressed: Boolean = pressed(MouseButton.LeftMouseButton) + + /** Whether the specified button was pressed in this frame + * + * @param button + * The button to check + * @return + */ + def pressed(button: MouseButton): Boolean = pointers.pressed(button, pointerType) + + /** Whether the left button was released in this frame + * + * @return + */ + def released: Boolean = released(MouseButton.LeftMouseButton) + + /** Whether the specified button was released in this frame + * + * @param button + * The button to check + * @return + */ + 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 + * @return + */ + def wasClickedAt(position: Point): Boolean = isClickedAt.contains(position) + + /** Was any pointer clicked at this position in this frame + * + * @param x + * @param y + * @return + */ + def wasClickedAt(x: Int, y: Int): Boolean = wasClickedAt(Point(x, y)) + + /** Whether the pointer button was up at the specified position in this frame + * + * @param position + */ + def wasUpAt(position: Point): Boolean = pointers.pointersUpAt(pointerType).contains(position) + + /** Whether the pointer button was up at the specified position in this frame + * + * @param x + * @param y + * @return + */ + def wasUpAt(x: Int, y: Int): Boolean = wasUpAt(Point(x, y)) + + /** Whether the specified button was up at the specified position in this frame + * + * @param position + * @param button + */ + def wasUpAt(position: Point, button: MouseButton): Boolean = + pointers.upPositionsWith(Some(button), pointerType).contains(position) + + /** Whether the specified button was up at the specified position in this frame + * + * @param x + * @param y + * @param button + * @return + */ + def wasUpAt(x: Int, y: Int, button: MouseButton): Boolean = wasUpAt(Point(x, y), button) + + /** Whether the pointer button was down at the specified position in this frame + * + * @param position + * @return + */ + def wasDownAt(position: Point): Boolean = pointers.pointersDownAt(pointerType).contains(position) + + /** Whether the pointer button was down at the specified position in this frame + * + * @param x + * @param y + * @return + */ + def wasDownAt(x: Int, y: Int): Boolean = wasDownAt(Point(x, y)) + + /** Whether the specified button was down at the specified position in this frame + * + * @param position + * @param button + * @return + */ + def wasDownAt(position: Point, button: MouseButton): Boolean = + pointers.downPositionsWith(Some(button), pointerType).contains(position) + + /** Whether the specified button was down at the specified position in this frame + * + * @param x + * @param y + * @param button + * @return + */ + def wasDownAt(x: Int, y: Int, button: MouseButton): Boolean = wasDownAt(Point(x, y), button) + + /** Whether the pointer position was at the specified target in this frame + * + * @param target + * @return + */ + def wasPositionAt(target: Point): Boolean = pointers.wasPointerPositionAt(target, pointerType) + + /** Whether the pointer position was at the specified target in this frame + * + * @param x + * @param y + * @return + */ + def wasPositionAt(x: Int, y: Int): Boolean = wasPositionAt(Point(x, y)) + + /** Whether the pointer was within the specified bounds in this frame + * + * @param bounds + * @return + */ + def isWithin(bounds: Rectangle) = pointers.getPointerWithin(bounds, pointerType).nonEmpty + + /** Whether the pointer was up within the specified bounds in this frame + * + * @param bounds + * @param button + * @return + */ + def wasUpWithin(bounds: Rectangle, button: MouseButton): Boolean = + pointers.wasPointerUpWithin(bounds, button, pointerType) + + /** Whether the pointer was up within the specified bounds in this frame + * + * @param x + * @param y + * @param width + * @param height + * @param button + * @return + */ + def wasUpWithin(x: Int, y: Int, width: Int, height: Int, button: MouseButton): Boolean = + wasUpWithin(Rectangle(x, y, width, height), button) + + /** Whether the pointer was down within the specified bounds in this frame + * + * @param bounds + * @return + */ + def wasDownWithin(bounds: Rectangle): Boolean = pointers.wasPointerDownWithin(bounds, pointerType) + + /** Whether the pointer was down within the specified bounds in this frame + * + * @param x + * @param y + * @param width + * @param height + * @return + */ + def wasDownWithin(x: Int, y: Int, width: Int, height: Int): Boolean = wasDownWithin( + Rectangle(x, y, width, height) + ) + + /** Whether the pointer was down within the specified bounds in this frame + * + * @param bounds + * @param button + * @return + */ + def wasDownWithin(bounds: Rectangle, button: MouseButton): Boolean = pointers.wasPointerDownWithin( + bounds, + button, + pointerType + ) + + /** Whether the pointer was down within the specified bounds in this frame + * + * @param x + * @param y + * @param width + * @param height + * @param button + * @return + */ + def wasDownWithin(x: Int, y: Int, width: Int, height: Int, button: MouseButton): Boolean = + pointers.wasPointerDownWithin( + Rectangle(x, y, width, height), + button, + 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 + * @return + */ + def wasWithin(bounds: Rectangle): Boolean = pointers.wasPointerPositionWithin(bounds, pointerType) + + /** Whether the pointer position was within the specified bounds in this frame + * + * @param x + * @param y + * @param width + * @param height + * @return + */ + def wasWithin(x: Int, y: Int, width: Int, height: Int): Boolean = + wasWithin(Rectangle(x, y, width, height)) 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 929005d46..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,24 +1,371 @@ 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 import indigo.shared.events.PointerEvent +import indigo.shared.events.PointerEvent.PointerId +import indigo.shared.events.PointerType final class Pointers( - val pointerEvents: Batch[PointerEvent], - val position: Point -) + private val pointerBatch: Batch[Pointer], + val pointerEvents: Batch[PointerEvent] +) extends PointerState { + val pointers: Pointers = this + val pointerType: Option[PointerType] = None + + def pointerPositions(pointerType: Option[PointerType]): Batch[Point] = + pointersOfType(pointerType).map(_.position) + + /** Whether the specified button is down on any pointer + * + * @param button + * The button to check + * @return + */ + def isButtonDown(button: MouseButton, pointerType: Option[PointerType]): Boolean = + pointersOfType(pointerType).flatMap(_.buttonsDown).contains(button) + + /** Whether the left button is down on any pointer + */ + def isLeftDown(pointerType: Option[PointerType]): Boolean = isButtonDown(MouseButton.LeftMouseButton, pointerType) + + /** Whether the right button is down on any pointer + */ + def isRightDown(pointerType: Option[PointerType]): Boolean = isButtonDown(MouseButton.RightMouseButton, pointerType) + + /** Whether the middle button is down on any pointer + */ + def isMiddleDown(pointerType: Option[PointerType]): Boolean = isButtonDown(MouseButton.MiddleMouseButton, pointerType) + + /** Whether the left button was pressed in this frame + */ + def pointerPressed(pointerType: Option[PointerType]): Boolean = pressed(MouseButton.LeftMouseButton, pointerType) + + /** Whether the left button was released in this frame + */ + def pointerReleased(pointerType: Option[PointerType]): Boolean = released(MouseButton.LeftMouseButton, pointerType) + + def pointerClicked(pointerType: Option[PointerType]): Boolean = pointerEventsOfType(pointerType).exists { + case _: PointerEvent.PointerClick => true + case _ => false + } + + def pointersClickedAt(pointerType: Option[PointerType]): Batch[Point] = + pointerEventsOfType(pointerType) + .filter(_ match { + case _: PointerEvent.PointerClick => true + case _ => false + }) + .map(_.position) + + /** All the positions where the pointers were up in this frame + */ + def pointersUpAt(pointerType: Option[PointerType]): Batch[Point] = upPositionsWith(None, pointerType) + + /** All the positions where the pointers were down in this frame + */ + def pointersDownAt(pointerType: Option[PointerType]): Batch[Point] = downPositionsWith(None, pointerType) + + /** Whether the left button was pressed in this frame + * + * @return + */ + def pressed(pointerType: Option[PointerType]): Boolean = pressed(MouseButton.LeftMouseButton, pointerType) + + /** Whether the specified button was pressed in this frame + * + * @param button + * The button to check + * @return + */ + def pressed(button: MouseButton, pointerType: Option[PointerType]): Boolean = + pointerEventsOfType(pointerType) + .exists { + case md: PointerEvent.PointerDown if md.button == Some(button) => true + case _ => false + } + + /** Whether the specified button was released in this frame + * + * @param button + * The button to check + * @return + */ + def released(button: MouseButton, pointerType: Option[PointerType]): Boolean = + pointerEventsOfType(pointerType) + .exists { + case mu: PointerEvent.PointerUp if mu.button == Some(button) => true + case _ => false + } + + /** Was any pointer clicked at this position in this frame + * + * @param position + * @return + */ + def wasPointerClickedAt(position: Point, pointerType: Option[PointerType]): Boolean = + pointersClickedAt(pointerType).contains(position) + + /** Was any pointer clicked at this position in this frame + * + * @param x + * @param y + * @return + */ + def wasPointerClickedAt(x: Int, y: Int, pointerType: Option[PointerType]): Boolean = + wasPointerClickedAt(Point(x, y), pointerType) + + /** All the positions where the specified button was up in this frame + * + * @param button + */ + def upPositionsWith(button: Option[MouseButton], pointerType: Option[PointerType]): Batch[Point] = + pointerEventsOfType(pointerType).collect { + case m: PointerEvent.PointerUp if button == None || m.button == button => m.position + } + + /** All the positions where the specified button was down in this frame + * + * @param button + */ + def downPositionsWith(button: Option[MouseButton], pointerType: Option[PointerType]): Batch[Point] = + pointerEventsOfType(pointerType).collect { + 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 + * @param button + */ + def wasUpAt(position: Point, button: MouseButton, pointerType: Option[PointerType]): Boolean = + upPositionsWith(Some(button), pointerType).contains(position) + + /** Whether the specified button was up at the specified position in this frame + * + * @param x + * @param y + * @param button + * @return + */ + def wasUpAt(x: Int, y: Int, button: MouseButton, pointerType: Option[PointerType]): Boolean = + wasUpAt(Point(x, y), button, pointerType) + + /** Whether the specified button was down at the specified position in this frame + * + * @param position + * @param button + * @return + */ + def wasDownAt(position: Point, button: MouseButton, pointerType: Option[PointerType]): Boolean = + downPositionsWith(Some(button), pointerType).contains(position) + + /** Whether the specified button was down at the specified position in this frame + * + * @param x + * @param y + * @param button + * @return + */ + def wasDownAt(x: Int, y: Int, button: MouseButton, pointerType: Option[PointerType]): Boolean = + wasDownAt(Point(x, y), button, pointerType) + + /** Whether the pointer position was at the specified target in this frame + * + * @param target + * @return + */ + def wasPointerPositionAt(target: Point, pointerType: Option[PointerType]): Boolean = + pointersOfType(pointerType).map(_.position).contains(target) + + /** Whether the pointer position was at the specified target in this frame + * + * @param x + * @param y + * @return + */ + def wasPointerPositionAt(x: Int, y: Int, pointerType: Option[PointerType]): Boolean = + wasPointerPositionAt(Point(x, y), pointerType) + + // Within + private def wasPointerWithin(bounds: Rectangle, pt: Point): Boolean = + bounds.isPointWithin(pt) + + /** Whether the pointer was within the specified bounds in this frame + * + * @param bounds + * @return + */ + def isPointerWithin(bounds: Rectangle, pointerType: Option[PointerType]) = + getPointerWithin(bounds, pointerType).nonEmpty + + /** All the pointers that were within the specified bounds in this frame + * + * @param bounds + * @return + */ + def getPointerWithin(bounds: Rectangle, pointerType: Option[PointerType]): Option[Point] = + pointersOfType(pointerType).map(_.position).find(bounds.isPointWithin) + + /** Whether the pointer was up within the specified bounds in this frame + * + * @param bounds + * @param button + * @return + */ + def wasPointerUpWithin(bounds: Rectangle, button: MouseButton, pointerType: Option[PointerType]): Boolean = + upPositionsWith(Some(button), pointerType).exists(wasPointerWithin(bounds, _)) + + /** Whether the pointer was up within the specified bounds in this frame + * + * @param x + * @param y + * @param width + * @param height + * @param button + * @return + */ + def wasPointerUpWithin( + x: Int, + y: Int, + width: Int, + height: Int, + button: MouseButton, + pointerType: Option[PointerType] + ): Boolean = + wasPointerUpWithin(Rectangle(x, y, width, height), button, pointerType) + + /** Whether the pointer was down within the specified bounds in this frame + * + * @param bounds + * @return + */ + def wasPointerDownWithin(bounds: Rectangle, pointerType: Option[PointerType]): Boolean = + downPositionsWith(None, pointerType).exists(wasPointerWithin(bounds, _)) + + /** Whether the pointer was down within the specified bounds in this frame + * + * @param x + * @param y + * @param width + * @param height + * @return + */ + def wasPointerDownWithin(x: Int, y: Int, width: Int, height: Int, pointerType: Option[PointerType]): Boolean = + wasPointerDownWithin( + Rectangle(x, y, width, height), + pointerType + ) + + /** Whether the pointer was down within the specified bounds in this frame + * + * @param bounds + * @param button + * @return + */ + def wasPointerDownWithin(bounds: Rectangle, button: MouseButton, pointerType: Option[PointerType]): Boolean = + downPositionsWith(Some(button), pointerType).exists(wasPointerWithin(bounds, _)) + + /** Whether the pointer was down within the specified bounds in this frame + * + * @param x + * @param y + * @param width + * @param height + * @param button + * @return + */ + def wasPointerDownWithin( + x: Int, + y: Int, + width: Int, + height: Int, + button: MouseButton, + pointerType: Option[PointerType] + ): Boolean = + wasPointerDownWithin(Rectangle(x, y, width, height), button, pointerType) + + /** Whether the pointer position was within the specified bounds in this frame + * + * @param bounds + * @return + */ + def wasPointerPositionWithin(bounds: Rectangle, pointerType: Option[PointerType]): Boolean = + pointersOfType(pointerType).map(_.position).exists(bounds.isPointWithin(_)) + + /** Whether the pointer position was within the specified bounds in this frame + * + * @param x + * @param y + * @param width + * @param height + * @return + */ + def wasPointerPositionWithin(x: Int, y: Int, width: Int, height: Int, pointerType: Option[PointerType]): Boolean = + wasPointerPositionWithin(Rectangle(x, y, width, height), pointerType) + + private def pointersOfType(pointerType: Option[PointerType]): Batch[Pointer] = + pointerType match { + case Some(t) => + pointerBatch.filter(_.pointerType == t) + case None => pointerBatch + } + + private def pointerEventsOfType(pointerType: Option[PointerType]): Batch[PointerEvent] = + pointerType match { + case Some(t) => + pointerEvents.filter(_.pointerType == t) + case None => pointerEvents + } +} object Pointers: val default: Pointers = - Pointers(Batch.empty, Point.zero) + Pointers(Batch.empty, Batch.empty) def calculateNext(previous: Pointers, events: Batch[PointerEvent]): Pointers = - Pointers( - pointerEvents = events, - position = lastPointerPosition(previous.position, events) + Pointers(updatePointers(events, previous), events) + + private def updatePointers(events: Batch[PointerEvent], previous: Pointers): Batch[Pointer] = + val pointersToRemove = events + .filter(_ match { + case _: PointerEvent.PointerOut => true + case _ => false + }) + .map(_.pointerId) + + 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 ) - private def lastPointerPosition(previous: Point, events: Batch[PointerEvent]): Point = - events.collect { case mp: PointerEvent.PointerMove => mp.position }.lastOption.fold(previous)(identity) + 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/perf/src/main/scala/com/example/perf/PerfGame.scala b/indigo/perf/src/main/scala/com/example/perf/PerfGame.scala index d2a8e2472..e107a2121 100644 --- a/indigo/perf/src/main/scala/com/example/perf/PerfGame.scala +++ b/indigo/perf/src/main/scala/com/example/perf/PerfGame.scala @@ -42,14 +42,11 @@ object PerfGame extends IndigoDemo[Unit, Dude, DudeModel, Unit] { magnification = magnificationLevel, resizePolicy = ResizePolicy.NoResize, transparentBackground = false, - advanced = AdvancedGameConfig( - renderingTechnology = RenderingTechnology.WebGL2, - antiAliasing = false, - premultipliedAlpha = true, - batchSize = 512, - autoLoadStandardShaders = false, - disableContextMenu = true - ) + advanced = AdvancedGameConfig.default + .withRenderingTechnology(RenderingTechnology.WebGL2) + .withBatchSize(512) + .withAutoLoadStandardShaders(false) + .withContextMenu ) ) .withAssets(PerfAssets.assets) 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 => () } diff --git a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/UiScene.scala b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/UiScene.scala index 4367cc4d3..208311a80 100644 --- a/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/UiScene.scala +++ b/indigo/sandbox/src/main/scala/com/example/sandbox/scenes/UiScene.scala @@ -93,10 +93,10 @@ final case class UiSceneViewModel( ): def update(mouse: Mouse, pointers: Pointers): Outcome[UiSceneViewModel] = for { - ha <- hitArea.update(mouse) - bn1 <- button1.update(mouse) - bn2 <- button2.update(mouse) - bn3 <- button3.updateFromPointers(pointers) + ha <- hitArea.update(pointers) + bn1 <- button1.update(pointers) + bn2 <- button2.update(pointers) + bn3 <- button3.update(pointers) } yield this.copy(hitArea = ha, button1 = bn1, button2 = bn2, button3 = bn3) object UiSceneViewModel: @@ -155,57 +155,3 @@ object UiSceneViewModel: .withHoldDownActions(Log("Hold down! 3")) .moveTo(80, 16) ) - -/** This is a workaround to show a way to make buttons support simple pointer events. It is a simplified version of the - * standard Button update function. - */ -extension (b: Button) - def updateFromPointers(p: Pointers): Outcome[Button] = - val inBounds = b.bounds.isPointWithin(p.position) - - val upEvents: Batch[GlobalEvent] = - if inBounds && p.released then b.onUp() - else Batch.empty - - val downEvents: Batch[GlobalEvent] = - if inBounds && p.pressed then b.onDown() - else Batch.empty - - val pointerEvents: Batch[GlobalEvent] = - downEvents ++ upEvents - - b.state match - // Stay in Down state - case ButtonState.Down if inBounds && p.pressed => - Outcome(b).addGlobalEvents(b.onHoldDown() ++ pointerEvents) - - // Move to Down state - case ButtonState.Up if inBounds && p.pressed => - Outcome(b.toDownState).addGlobalEvents(b.onHoverOver() ++ pointerEvents) - - // Out of Down state - case ButtonState.Down if !inBounds && (p.pressed || p.released) => - Outcome(b.toUpState).addGlobalEvents(b.onHoverOut() ++ pointerEvents) - - case ButtonState.Down if inBounds && p.released => - Outcome(b.toUpState).addGlobalEvents(pointerEvents) - - // Unaccounted for states. - case _ => - Outcome(b).addGlobalEvents(pointerEvents) - -/** This is a workaround to make up for Pointer not exposing any convenience methods. - */ -extension (p: Pointers) - - def pressed: Boolean = - p.pointerEvents.exists { - case _: PointerEvent.PointerDown => true - case _ => false - } - - def released: Boolean = - p.pointerEvents.exists { - case _: PointerEvent.PointerUp => true - case _ => false - }