From 46bd385675ae781c4614d997e4792f53fc43271d Mon Sep 17 00:00:00 2001 From: Luan Nico Date: Mon, 6 Oct 2025 08:11:31 -0700 Subject: [PATCH] feat!: Support secondary taps (right click) on new callbacks system (#3741) Support secondary taps (right click) on new callbacks system. In order to follow through with our [event system migration](https://docs.google.com/document/d/1nBUup9QCPioVwWL1zs79z1hWF882tAYfNywFMdye2Qc), we need to make sure the new system is equipped to support all use cases; also changes the existing TapCallbacks to be primary-only. I noticed that we don't support "secondary taps" (i.e. right clicks), so I am adding this. I honestly really dislike the fact that this is considered a completely different event from the left click, instead of just a property on the click event. But I kept this structure to replicate what Flutter does, so this is more familiar for users. I think that is worth the slight verbosity of having yet another detector. Also, it plays well this way with Flutter because that underlying events are a bit different (for example, the secondary ones don't support `pointId`). Note: this is a slight breaking change because the existing detector works for BOTH left and right click, but there is NO WAY of distinguishing them because the `buttons` property is not propagated in the Flutter end (massive oversight I believe - might put a PR later). Since this provides the secondary as a solution, it also removes secondary clicks from triggering the primary. I think this is more versatile than having tap detector=`(primary OR secondary)` and secondary=`(secondary only)`. I don't think this should affect basically any users because (1) desktop only and (2) this acceptance of right clicks was probably a bug anyway (for example, on the example it would rotate the square and also open the context menu, which is jarring). However I am happy to add an option or pursue a different approach, I believe this is the best path forward, IMO. --- doc/flame/inputs/tap_events.md | 28 ++ examples/lib/stories/input/input.dart | 7 + .../secondary_tap_callbacks_example.dart | 63 ++++ packages/flame/lib/events.dart | 10 + .../secondary_tap_callbacks.dart | 30 ++ .../multi_tap_dispatcher.dart | 4 +- .../secondary_tap_dispatcher.dart | 69 +++++ .../messages/secondary_tap_cancel_event.dart | 22 ++ .../messages/secondary_tap_down_event.dart | 28 ++ .../messages/secondary_tap_up_event.dart | 29 ++ .../secondary_tap_callbacks_test.dart | 272 ++++++++++++++++++ .../lib/src/mock_tap_drag_events.dart | 32 +++ 12 files changed, 593 insertions(+), 1 deletion(-) create mode 100644 examples/lib/stories/input/secondary_tap_callbacks_example.dart create mode 100644 packages/flame/lib/src/events/component_mixins/secondary_tap_callbacks.dart create mode 100644 packages/flame/lib/src/events/flame_game_mixins/secondary_tap_dispatcher.dart create mode 100644 packages/flame/lib/src/events/messages/secondary_tap_cancel_event.dart create mode 100644 packages/flame/lib/src/events/messages/secondary_tap_down_event.dart create mode 100644 packages/flame/lib/src/events/messages/secondary_tap_up_event.dart create mode 100644 packages/flame/test/events/component_mixins/secondary_tap_callbacks_test.dart diff --git a/doc/flame/inputs/tap_events.md b/doc/flame/inputs/tap_events.md index 07e298f5c..8474d1fc3 100644 --- a/doc/flame/inputs/tap_events.md +++ b/doc/flame/inputs/tap_events.md @@ -170,6 +170,34 @@ class MyComponent extends Component with TapCallbacks { ``` +### SecondaryTapCallbacks + +In addition to the primary tap events (i.e. left mouse button on desktop), Flame also supports +secondary tap events (i.e. right mouse button on desktop). To receive these events, add the +`SecondaryTapCallbacks` mixin to your `PositionComponent`. + +```dart +class MyComponent extends PositionComponent with SecondaryTapCallbacks { + @override + void onSecondaryTapUp(SecondaryTapUpEvent event) { + /// Do something + } + + @override + void onSecondaryTapCancel(SecondaryTapCancelEvent event) { + /// Do something + } + + @override + void onSecondaryTapDown(SecondaryTapDownEvent event) { + /// Do something + } +``` + +You can extend both `TapCallbacks` and `SecondaryTapCallbacks` in the same component to +receive both primary and secondary tap events. + + ### DoubleTapCallbacks Flame also offers a mixin named `DoubleTapCallbacks` to receive a double-tap event from the diff --git a/examples/lib/stories/input/input.dart b/examples/lib/stories/input/input.dart index 40e5b1016..d2dae7a1a 100644 --- a/examples/lib/stories/input/input.dart +++ b/examples/lib/stories/input/input.dart @@ -16,6 +16,7 @@ import 'package:examples/stories/input/multitap_advanced_example.dart'; import 'package:examples/stories/input/multitap_example.dart'; import 'package:examples/stories/input/overlapping_tap_callbacks_example.dart'; import 'package:examples/stories/input/scroll_example.dart'; +import 'package:examples/stories/input/secondary_tap_callbacks_example.dart'; import 'package:examples/stories/input/tap_callbacks_example.dart'; import 'package:flame/game.dart'; import 'package:flutter/material.dart'; @@ -28,6 +29,12 @@ void addInputStories(Dashbook dashbook) { codeLink: baseLink('input/tap_callbacks_example.dart'), info: TapCallbacksExample.description, ) + ..add( + 'SecondaryTapCallbacks', + (_) => GameWidget(game: SecondaryTapCallbacksExample()), + codeLink: baseLink('input/secondary_tap_callbacks_example.dart'), + info: SecondaryTapCallbacksExample.description, + ) ..add( 'DragCallbacks', (context) { diff --git a/examples/lib/stories/input/secondary_tap_callbacks_example.dart b/examples/lib/stories/input/secondary_tap_callbacks_example.dart new file mode 100644 index 000000000..f8172172e --- /dev/null +++ b/examples/lib/stories/input/secondary_tap_callbacks_example.dart @@ -0,0 +1,63 @@ +import 'package:flame/components.dart'; +import 'package:flame/events.dart'; +import 'package:flame/extensions.dart'; +import 'package:flame/game.dart'; +import 'package:flame/palette.dart'; +import 'package:flutter/material.dart'; + +class SecondaryTapCallbacksExample extends FlameGame { + static const String description = ''' + In this example we show how to listen to both primary (left) and + secondary (right) tap events using the `TapCallbacks` and + the `SecondaryTapCallbacks` mixin to any `PositionComponent`.\n\n + The squares will change color depending on which button was used to tap them. + '''; + + @override + Future onLoad() async { + world.add(TappableSquare()..anchor = Anchor.center); + world.add(TappableSquare()..y = 350); + } +} + +class TappableSquare extends PositionComponent + with TapCallbacks, SecondaryTapCallbacks { + static final Paint _red = BasicPalette.red.paint(); + static final Paint _blue = BasicPalette.blue.paint(); + static final TextPaint _text = TextPaint( + style: TextStyle(color: BasicPalette.white.color, fontSize: 24), + ); + + int counter = 0; + Paint _paint = _red; + + TappableSquare({Vector2? position}) + : super( + position: position ?? Vector2.all(100), + size: Vector2.all(100), + anchor: Anchor.center, + ); + + @override + void render(Canvas canvas) { + canvas.drawRect(size.toRect(), _paint); + _text.render( + canvas, + '$counter', + size / 2, + anchor: Anchor.center, + ); + } + + @override + void onTapDown(_) { + _paint = _red; + counter++; + } + + @override + void onSecondaryTapDown(_) { + _paint = _blue; + counter++; + } +} diff --git a/packages/flame/lib/events.dart b/packages/flame/lib/events.dart index 917bc07ee..1d7ccf21e 100644 --- a/packages/flame/lib/events.dart +++ b/packages/flame/lib/events.dart @@ -4,6 +4,8 @@ export 'src/events/component_mixins/drag_callbacks.dart' show DragCallbacks; export 'src/events/component_mixins/hover_callbacks.dart' show HoverCallbacks; export 'src/events/component_mixins/pointer_move_callbacks.dart' show PointerMoveCallbacks; +export 'src/events/component_mixins/secondary_tap_callbacks.dart' + show SecondaryTapCallbacks; export 'src/events/component_mixins/tap_callbacks.dart' show TapCallbacks; export 'src/events/flame_game_mixins/double_tap_dispatcher.dart' show DoubleTapDispatcher, DoubleTapDispatcherKey; @@ -13,6 +15,8 @@ export 'src/events/flame_game_mixins/multi_tap_dispatcher.dart' show MultiTapDispatcher, MultiTapDispatcherKey; export 'src/events/flame_game_mixins/pointer_move_dispatcher.dart' show PointerMoveDispatcher, MouseMoveDispatcherKey; +export 'src/events/flame_game_mixins/secondary_tap_dispatcher.dart' + show SecondaryTapDispatcher, SecondaryTapDispatcherKey; export 'src/events/game_mixins/multi_touch_drag_detector.dart' show MultiTouchDragDetector; export 'src/events/game_mixins/multi_touch_tap_detector.dart' @@ -30,6 +34,12 @@ export 'src/events/messages/drag_end_event.dart' show DragEndEvent; export 'src/events/messages/drag_start_event.dart' show DragStartEvent; export 'src/events/messages/drag_update_event.dart' show DragUpdateEvent; export 'src/events/messages/pointer_move_event.dart' show PointerMoveEvent; +export 'src/events/messages/secondary_tap_cancel_event.dart' + show SecondaryTapCancelEvent; +export 'src/events/messages/secondary_tap_down_event.dart' + show SecondaryTapDownEvent; +export 'src/events/messages/secondary_tap_up_event.dart' + show SecondaryTapUpEvent; export 'src/events/messages/tap_cancel_event.dart' show TapCancelEvent; export 'src/events/messages/tap_down_event.dart' show TapDownEvent; export 'src/events/messages/tap_up_event.dart' show TapUpEvent; diff --git a/packages/flame/lib/src/events/component_mixins/secondary_tap_callbacks.dart b/packages/flame/lib/src/events/component_mixins/secondary_tap_callbacks.dart new file mode 100644 index 000000000..1132d4e31 --- /dev/null +++ b/packages/flame/lib/src/events/component_mixins/secondary_tap_callbacks.dart @@ -0,0 +1,30 @@ +import 'package:flame/components.dart'; +import 'package:flame/events.dart'; +import 'package:meta/meta.dart'; + +/// This mixin can be added to a [Component] allowing it to receive secondary +/// tap events (i.e. right mouse clicks). +/// +/// In addition to adding this mixin, the component must also implement the +/// [containsLocalPoint] method -- the component will only be considered +/// "tapped" if the point where the tap has occurred is inside the component. +/// +/// Note that FlameGame _is_ a [Component] and does implement +/// [containsLocalPoint]; so this can be used at the game level. +mixin SecondaryTapCallbacks on Component { + void onSecondaryTapDown(SecondaryTapDownEvent event) {} + void onSecondaryTapUp(SecondaryTapUpEvent event) {} + void onSecondaryTapCancel(SecondaryTapCancelEvent event) {} + + @override + @mustCallSuper + void onMount() { + super.onMount(); + final game = findRootGame()!; + if (game.findByKey(const SecondaryTapDispatcherKey()) == null) { + final dispatcher = SecondaryTapDispatcher(); + game.registerKey(const SecondaryTapDispatcherKey(), dispatcher); + game.add(dispatcher); + } + } +} diff --git a/packages/flame/lib/src/events/flame_game_mixins/multi_tap_dispatcher.dart b/packages/flame/lib/src/events/flame_game_mixins/multi_tap_dispatcher.dart index 8171d36fb..face8426c 100644 --- a/packages/flame/lib/src/events/flame_game_mixins/multi_tap_dispatcher.dart +++ b/packages/flame/lib/src/events/flame_game_mixins/multi_tap_dispatcher.dart @@ -154,7 +154,9 @@ class MultiTapDispatcher extends Component implements MultiTapListener { @override void onMount() { game.gestureDetectors.add( - MultiTapGestureRecognizer.new, + () => MultiTapGestureRecognizer( + allowedButtonsFilter: (buttons) => buttons == kPrimaryButton, + ), (MultiTapGestureRecognizer instance) { instance.longTapDelay = Duration( milliseconds: (longTapDelay * 1000).toInt(), diff --git a/packages/flame/lib/src/events/flame_game_mixins/secondary_tap_dispatcher.dart b/packages/flame/lib/src/events/flame_game_mixins/secondary_tap_dispatcher.dart new file mode 100644 index 000000000..8698e4b23 --- /dev/null +++ b/packages/flame/lib/src/events/flame_game_mixins/secondary_tap_dispatcher.dart @@ -0,0 +1,69 @@ +import 'package:flame/components.dart'; +import 'package:flame/events.dart'; +import 'package:flame/game.dart'; +import 'package:flutter/gestures.dart'; + +class SecondaryTapDispatcherKey implements ComponentKey { + const SecondaryTapDispatcherKey(); + + @override + int get hashCode => 'SecondaryTapDispatcherKey'.hashCode; + + @override + bool operator ==(Object other) => + other is SecondaryTapDispatcherKey && other.hashCode == hashCode; +} + +/// [SecondaryTapDispatcher] propagates secondary-tap events (i.e. right mouse +/// clicks) to every components in the component tree that is mixed with +/// [SecondaryTapCallbacks]. This will be attached to the [FlameGame] instance +/// automatically whenever any [SecondaryTapCallbacks] are mounted into the +/// component tree. +class SecondaryTapDispatcher extends Component + with HasGameReference { + final _components = {}; + + void _onSecondaryTapDown(SecondaryTapDownEvent event) { + event.deliverAtPoint( + rootComponent: game, + eventHandler: (SecondaryTapCallbacks component) { + _components.add(component..onSecondaryTapDown(event)); + }, + ); + } + + void _onSecondaryTapUp(SecondaryTapUpEvent event) { + for (final component in _components) { + component.onSecondaryTapUp(event); + } + _components.clear(); + } + + void _onSecondaryTapCancel(SecondaryTapCancelEvent event) { + for (final component in _components) { + component.onSecondaryTapCancel(event); + } + _components.clear(); + } + + @override + void onMount() { + game.gestureDetectors.add( + TapGestureRecognizer.new, + (TapGestureRecognizer instance) { + instance.onSecondaryTapDown = (details) => + _onSecondaryTapDown(SecondaryTapDownEvent(game, details)); + instance.onSecondaryTapCancel = () => + _onSecondaryTapCancel(SecondaryTapCancelEvent()); + instance.onSecondaryTapUp = (details) => + _onSecondaryTapUp(SecondaryTapUpEvent(game, details)); + }, + ); + } + + @override + void onRemove() { + game.gestureDetectors.remove(); + game.unregisterKey(const SecondaryTapDispatcherKey()); + } +} diff --git a/packages/flame/lib/src/events/messages/secondary_tap_cancel_event.dart b/packages/flame/lib/src/events/messages/secondary_tap_cancel_event.dart new file mode 100644 index 000000000..108263de9 --- /dev/null +++ b/packages/flame/lib/src/events/messages/secondary_tap_cancel_event.dart @@ -0,0 +1,22 @@ +import 'package:flame/src/events/messages/event.dart'; +import 'package:flame/src/events/messages/secondary_tap_down_event.dart'; + +/// The event propagated through the Flame engine when a secondary tap +/// (i.e. right mouse button click) on a component is cancelled. +/// +/// This event may occur for several reasons, such as: +/// - a secondary tap was converted into a drag event (for a game where +/// secondary drag events are enabled); +/// - a secondary tap was cancelled on the game widget itself -- for example, +/// if another app came into the foreground, or device turned off, etc; +/// - a secondary tap was cancelled on a particular component because that +/// component has moved away from the point of contact. +/// +/// The [SecondaryTapCancelEvent] will only occur if there was a previous +/// [SecondaryTapDownEvent]. +class SecondaryTapCancelEvent extends Event { + SecondaryTapCancelEvent() : super(raw: null); + + @override + String toString() => 'SecondaryTapCancel()'; +} diff --git a/packages/flame/lib/src/events/messages/secondary_tap_down_event.dart b/packages/flame/lib/src/events/messages/secondary_tap_down_event.dart new file mode 100644 index 000000000..9049398f7 --- /dev/null +++ b/packages/flame/lib/src/events/messages/secondary_tap_down_event.dart @@ -0,0 +1,28 @@ +import 'package:flame/events.dart'; +import 'package:flame/extensions.dart'; +import 'package:flame/src/events/messages/position_event.dart'; +import 'package:flutter/gestures.dart'; + +/// The event propagated through the Flame engine when the user starts a +/// secondary touch (i.e. right mouse click) on the game canvas. +/// +/// This is a [PositionEvent], where the position is the point of touch. +/// +/// In order for a component to be eligible to receive this event, it must add +/// the [SecondaryTapCallbacks] mixin. +class SecondaryTapDownEvent extends PositionEvent { + SecondaryTapDownEvent(super.game, TapDownDetails details) + : deviceKind = details.kind ?? PointerDeviceKind.unknown, + super( + raw: details, + devicePosition: details.globalPosition.toVector2(), + ); + + final PointerDeviceKind deviceKind; + + @override + String toString() => + 'TapDownEvent(canvasPosition: $canvasPosition, ' + 'devicePosition: $devicePosition, ' + 'deviceKind: $deviceKind)'; +} diff --git a/packages/flame/lib/src/events/messages/secondary_tap_up_event.dart b/packages/flame/lib/src/events/messages/secondary_tap_up_event.dart new file mode 100644 index 000000000..18129001e --- /dev/null +++ b/packages/flame/lib/src/events/messages/secondary_tap_up_event.dart @@ -0,0 +1,29 @@ +import 'package:flame/extensions.dart'; +import 'package:flame/src/events/messages/position_event.dart'; +import 'package:flame/src/events/messages/secondary_tap_down_event.dart'; +import 'package:flutter/gestures.dart'; + +/// The event propagated through the Flame engine when the user stops secondary +/// touching (i.e. right mouse button) the game canvas. +/// +/// This is a [PositionEvent], where the position is the point where the touch +/// has last occurred. +/// +/// The [SecondaryTapUpEvent] will only occur if there was a previous +/// [SecondaryTapDownEvent]. +class SecondaryTapUpEvent extends PositionEvent { + SecondaryTapUpEvent(super.game, TapUpDetails details) + : deviceKind = details.kind, + super( + raw: details, + devicePosition: details.globalPosition.toVector2(), + ); + + final PointerDeviceKind deviceKind; + + @override + String toString() => + 'SecondaryTapUpEvent(canvasPosition: $canvasPosition, ' + 'devicePosition: $devicePosition, ' + 'deviceKind: $deviceKind)'; +} diff --git a/packages/flame/test/events/component_mixins/secondary_tap_callbacks_test.dart b/packages/flame/test/events/component_mixins/secondary_tap_callbacks_test.dart new file mode 100644 index 000000000..587edf12a --- /dev/null +++ b/packages/flame/test/events/component_mixins/secondary_tap_callbacks_test.dart @@ -0,0 +1,272 @@ +import 'package:flame/components.dart'; +import 'package:flame/events.dart'; +import 'package:flame/game.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('SecondaryTapCallbacks', () { + testWidgets( + 'receives secondary tap event', + (tester) async { + final component = _SecondaryTapCallbacksComponent() + ..position = Vector2.all(10); + await tester.pumpWidget( + GameWidget( + game: FlameGame(children: [component]), + ), + ); + await tester.pump(); + + final gesture = await tester.createGesture( + buttons: kSecondaryButton, + ); + await gesture.down(const Offset(10, 10)); + + expect(component.secondaryTapDown, 1); + expect(component.secondaryTapCancel, 0); + expect(component.secondaryTap, 0); + + await gesture.up(); + + expect(component.secondaryTapDown, 1); + expect(component.secondaryTapCancel, 0); + expect(component.secondaryTap, 1); + + await tester.pump(kDoubleTapMinTime); + }, + ); + + testWidgets( + 'primary and secondary are received separately', + (tester) async { + final component = _BothTapCallbacksComponent() + ..position = Vector2.all(10); + await tester.pumpWidget( + GameWidget( + game: FlameGame(children: [component]), + ), + ); + await tester.pump(); + + final primaryGesture = await tester.createGesture(); + final secondaryGesture = await tester.createGesture( + buttons: kSecondaryButton, + ); + + await primaryGesture.down(const Offset(10, 10)); + expect(component.primaryTapDown, 1); + expect(component.secondaryTapDown, 0); + + await primaryGesture.up(); + expect(component.primaryTapUp, 1); + expect(component.secondaryTapUp, 0); + + await secondaryGesture.down(const Offset(10, 10)); + expect(component.primaryTapDown, 1); + expect(component.secondaryTapDown, 1); + + await secondaryGesture.up(); + expect(component.primaryTapUp, 1); + expect(component.secondaryTapUp, 1); + + await primaryGesture.down(const Offset(10, 10)); + expect(component.primaryTapDown, 2); + expect(component.secondaryTapDown, 1); + + await secondaryGesture.down(const Offset(10, 10)); + expect(component.primaryTapDown, 2); + expect(component.secondaryTapDown, 2); + + await primaryGesture.up(); + expect(component.primaryTapUp, 2); + expect(component.secondaryTapUp, 1); + + await secondaryGesture.up(); + expect(component.primaryTapUp, 2); + expect(component.secondaryTapUp, 2); + + await tester.pump(kDoubleTapMinTime); + }, + ); + + testWidgets( + '''does not receive an event when secondary-tapping a position far from the component''', + (tester) async { + final component = _SecondaryTapCallbacksComponent() + ..position = Vector2.all(10); + await tester.pumpWidget( + GameWidget( + game: FlameGame(children: [component]), + ), + ); + await tester.pump(); + await tester.pump(); + + final gesture = await tester.createGesture(); + await gesture.down(const Offset(100, 100)); + + expect(component.secondaryTapDown, 0); + expect(component.secondaryTapCancel, 0); + expect(component.secondaryTap, 0); + + await gesture.up(); + + expect(component.secondaryTapDown, 0); + expect(component.secondaryTapCancel, 0); + expect(component.secondaryTap, 0); + + await tester.pump(kDoubleTapMinTime); + }, + ); + + testWidgets( + 'receives a cancel event when gesture is canceled by drag', + (tester) async { + final component = _SecondaryTapCallbacksComponent() + ..position = Vector2.all(10); + await tester.pumpWidget( + GameWidget( + game: FlameGame(children: [component]), + ), + ); + await tester.pump(); + await tester.pump(); + + final gesture = await tester.createGesture( + buttons: kSecondaryButton, + ); + await gesture.down(const Offset(10, 10)); + + expect(component.secondaryTapDown, 1); + expect(component.secondaryTapCancel, 0); + expect(component.secondaryTap, 0); + + await gesture.moveBy(const Offset(100, 100)); + + expect(component.secondaryTapDown, 1); + expect(component.secondaryTapCancel, 1); + expect(component.secondaryTap, 0); + + await tester.pump(kDoubleTapMinTime); + }, + ); + + testWidgets( + 'receives a cancel event when gesture is canceled by cancel', + (tester) async { + final component = _SecondaryTapCallbacksComponent() + ..position = Vector2.all(10); + await tester.pumpWidget( + GameWidget( + game: FlameGame(children: [component]), + ), + ); + await tester.pump(); + await tester.pump(); + + final gesture = await tester.createGesture( + buttons: kSecondaryButton, + ); + await gesture.down(const Offset(10, 10)); + + expect(component.secondaryTapDown, 1); + expect(component.secondaryTapCancel, 0); + expect(component.secondaryTap, 0); + + await gesture.cancel(); + + expect(component.secondaryTapDown, 1); + expect(component.secondaryTapCancel, 1); + expect(component.secondaryTap, 0); + + await tester.pump(kDoubleTapMinTime); + }, + ); + + testWithFlameGame( + 'SecondaryTapDispatcher is added to game when the callback is mounted', + (game) async { + final component = _SecondaryTapCallbacksComponent(); + await game.add(component); + await game.ready(); + + expect(game.firstChild(), isNotNull); + }, + ); + }); +} + +class _SecondaryTapCallbacksComponent extends PositionComponent + with SecondaryTapCallbacks { + _SecondaryTapCallbacksComponent() { + anchor = Anchor.center; + size = Vector2.all(10); + } + + int secondaryTapDown = 0; + int secondaryTapCancel = 0; + int secondaryTap = 0; + + @override + void onSecondaryTapUp(SecondaryTapUpEvent event) { + secondaryTap++; + } + + @override + void onSecondaryTapCancel(SecondaryTapCancelEvent event) { + secondaryTapCancel++; + } + + @override + void onSecondaryTapDown(SecondaryTapDownEvent event) { + secondaryTapDown++; + } +} + +class _BothTapCallbacksComponent extends PositionComponent + with TapCallbacks, SecondaryTapCallbacks { + _BothTapCallbacksComponent() { + anchor = Anchor.center; + size = Vector2.all(10); + } + + int primaryTapDown = 0; + int primaryTapUp = 0; + int primaryTapCancel = 0; + + int secondaryTapDown = 0; + int secondaryTapUp = 0; + int secondaryTapCancel = 0; + + @override + void onTapUp(TapUpEvent event) { + primaryTapUp++; + } + + @override + void onTapCancel(TapCancelEvent event) { + primaryTapCancel++; + } + + @override + void onTapDown(TapDownEvent event) { + primaryTapDown++; + } + + @override + void onSecondaryTapUp(SecondaryTapUpEvent event) { + secondaryTapUp++; + } + + @override + void onSecondaryTapCancel(SecondaryTapCancelEvent event) { + secondaryTapCancel++; + } + + @override + void onSecondaryTapDown(SecondaryTapDownEvent event) { + secondaryTapDown++; + } +} diff --git a/packages/flame_test/lib/src/mock_tap_drag_events.dart b/packages/flame_test/lib/src/mock_tap_drag_events.dart index dd4eab7a8..b44548845 100644 --- a/packages/flame_test/lib/src/mock_tap_drag_events.dart +++ b/packages/flame_test/lib/src/mock_tap_drag_events.dart @@ -38,6 +38,38 @@ TapUpEvent createTapUpEvents({ ); } +SecondaryTapDownEvent createSecondaryTapDownEvents({ + required Game game, + PointerDeviceKind? kind, + Offset? globalPosition, + Offset? localPosition, +}) { + return SecondaryTapDownEvent( + game, + TapDownDetails( + localPosition: localPosition ?? Offset.zero, + globalPosition: globalPosition ?? Offset.zero, + kind: kind ?? PointerDeviceKind.touch, + ), + ); +} + +SecondaryTapUpEvent createSecondaryTapUpEvents({ + required Game game, + PointerDeviceKind? kind, + Offset? globalPosition, + Offset? localPosition, +}) { + return SecondaryTapUpEvent( + game, + TapUpDetails( + localPosition: localPosition ?? Offset.zero, + globalPosition: globalPosition ?? Offset.zero, + kind: kind ?? PointerDeviceKind.touch, + ), + ); +} + DragStartEvent createDragStartEvents({ required Game game, int? pointerId,