mirror of
https://github.com/flame-engine/flame.git
synced 2025-10-29 16:05:47 +08:00
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.
This commit is contained in:
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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<void> 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++;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -154,7 +154,9 @@ class MultiTapDispatcher extends Component implements MultiTapListener {
|
||||
@override
|
||||
void onMount() {
|
||||
game.gestureDetectors.add<MultiTapGestureRecognizer>(
|
||||
MultiTapGestureRecognizer.new,
|
||||
() => MultiTapGestureRecognizer(
|
||||
allowedButtonsFilter: (buttons) => buttons == kPrimaryButton,
|
||||
),
|
||||
(MultiTapGestureRecognizer instance) {
|
||||
instance.longTapDelay = Duration(
|
||||
milliseconds: (longTapDelay * 1000).toInt(),
|
||||
|
||||
@ -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<FlameGame> {
|
||||
final _components = <SecondaryTapCallbacks>{};
|
||||
|
||||
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<TapGestureRecognizer>();
|
||||
game.unregisterKey(const SecondaryTapDispatcherKey());
|
||||
}
|
||||
}
|
||||
@ -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<void> {
|
||||
SecondaryTapCancelEvent() : super(raw: null);
|
||||
|
||||
@override
|
||||
String toString() => 'SecondaryTapCancel()';
|
||||
}
|
||||
@ -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<TapDownDetails> {
|
||||
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)';
|
||||
}
|
||||
@ -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<TapUpDetails> {
|
||||
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)';
|
||||
}
|
||||
@ -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<SecondaryTapDispatcher>(), 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++;
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user