Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added the onLongTapDown event #1587

Merged
merged 22 commits into from
May 4, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions doc/flame/inputs/gesture-input.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ of these `mixin`s and its methods:
- onTap
- onTapCancel
- onTapDown
- onLongTapDown
- onTapUp

- SecondaryTapDetector
Expand Down Expand Up @@ -241,6 +242,7 @@ components, you can override the following methods on your components:
```dart
bool onTapCancel();
bool onTapDown(TapDownInfo info);
bool onLongTapDown(TapDownInfo info);
bool onTapUp(TapUpInfo info);
```

Expand Down Expand Up @@ -295,19 +297,23 @@ class MyComponent extends PositionComponent with Tappable{
bool onTapDown(TapDownInfo info) {
info.handled = true;
return true;
}
}
}

class MyGame extends FlameGame with HasTappables {
@override
void onTapDown(int pointerId, TapDownInfo info) {
if(info.handled) {
if (info.handled) {
// Do something if a child handled the event
}
}
}
```

The event `onLongTapDown` will be triggered on a component after the user "holds" it for a certain
minimum amount of time. By default, that time is 300ms, but it can be adjusted by overriding the
`longTapDelay` field of the `HasTappables` mixin.


### Draggable components

Expand Down Expand Up @@ -472,7 +478,7 @@ for the event to be registered on your component.
You can add new hitboxes to the component that has the `GestureHitboxes` mixin just like they are
added in the below `Collidable` example.

More information about how to define hitboxes can be found in the hitbox section of the
More information about how to define hitboxes can be found in the hitbox section of the
[collision detection](../collision_detection.md#shapehitbox) docs.

An example of how to use it can be seen
Expand Down
2 changes: 1 addition & 1 deletion packages/flame/lib/components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export 'src/components/mixins/hoverable.dart';
export 'src/components/mixins/keyboard_handler.dart';
export 'src/components/mixins/parent_is_a.dart';
export 'src/components/mixins/single_child_particle.dart';
export 'src/components/mixins/tappable.dart';
export 'src/components/nine_tile_box_component.dart';
export 'src/components/parallax_component.dart';
export 'src/components/particle_component.dart';
Expand All @@ -31,6 +30,7 @@ export 'src/components/sprite_group_component.dart';
export 'src/components/text_box_component.dart';
export 'src/components/text_component.dart';
export 'src/components/timer_component.dart';
export 'src/events/component_mixins/tappable.dart';
export 'src/extensions/vector2.dart';
export 'src/geometry/circle_component.dart';
export 'src/geometry/polygon_component.dart';
Expand Down
2 changes: 1 addition & 1 deletion packages/flame/lib/game.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// {@canonicalFor text.TextPaint}
/// {@canonicalFor text.TextRenderer}
export 'src/collisions/has_collision_detection.dart';
export 'src/events/flame_game_mixins/has_tappables.dart';
export 'src/extensions/vector2.dart';
export 'src/game/camera/camera.dart';
export 'src/game/camera/viewport.dart';
Expand All @@ -10,7 +11,6 @@ export 'src/game/mixins/fps_counter.dart';
export 'src/game/mixins/game.dart';
export 'src/game/mixins/has_draggables.dart';
export 'src/game/mixins/has_hoverables.dart';
export 'src/game/mixins/has_tappables.dart';
export 'src/game/mixins/loadable.dart';
export 'src/game/mixins/single_game_instance.dart';
export 'src/game/projector.dart';
Expand Down
1 change: 1 addition & 0 deletions packages/flame/lib/input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export 'src/components/input/hud_button_component.dart';
export 'src/components/input/hud_margin_component.dart';
export 'src/components/input/joystick_component.dart';
export 'src/components/input/sprite_button_component.dart';
export 'src/events/game_mixins/multi_touch_tap_detector.dart';
export 'src/extensions/vector2.dart';
export 'src/game/mixins/keyboard.dart';
export 'src/gestures/detectors.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import 'package:flutter/gestures.dart';
import 'package:meta/meta.dart';

import '../../../components.dart';
import '../../game/mixins/has_tappables.dart';
import '../../gestures/events.dart';
import '../flame_game_mixins/has_tappables.dart';

/// Mixin that can be added to any [Component] allowing it to receive tap
/// events.
///
/// When using this mixin, also add [HasTappables] to your game, which handles
/// propagation of tap events from the root game to individual components.
///
/// See [MultiTapGestureRecognizer] for the description of each individual
/// event.
mixin Tappable on Component {
st-pasha marked this conversation as resolved.
Show resolved Hide resolved
bool onTapCancel() {
return true;
}

bool onTapDown(TapDownInfo info) {
return true;
}

bool onTapUp(TapUpInfo info) {
return true;
}
// bool onTap() => true;
bool onTapDown(TapDownInfo info) => true;
bool onLongTapDown(TapDownInfo info) => true;
bool onTapUp(TapUpInfo info) => true;
bool onTapCancel() => true;

int? _currentPointerId;

Expand Down Expand Up @@ -45,14 +48,20 @@ mixin Tappable on Component {
return true;
}

bool handleLongTapDown(int pointerId, TapDownInfo info) {
if (_checkPointerId(pointerId) && containsPoint(eventPosition(info))) {
return onLongTapDown(info);
}
return true;
}

@override
@mustCallSuper
void onMount() {
super.onMount();
assert(
findGame()! is HasTappables,
'Tappable Components can only be added to a FlameGame with '
'HasTappables',
'Tappable components can only be added to a FlameGame with HasTappables',
);
}
}
76 changes: 76 additions & 0 deletions packages/flame/lib/src/events/flame_game_mixins/has_tappables.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import 'package:flutter/gestures.dart';
import 'package:meta/meta.dart';

import '../../game/flame_game.dart';
import '../../gestures/events.dart';
import '../component_mixins/tappable.dart';
import '../game_mixins/multi_touch_tap_detector.dart';
import '../interfaces/multi_tap_listener.dart';

/// Mixin that can be added to a [FlameGame] allowing it (and the components
/// attached to the game) to receive tap events.
///
/// This mixin is similar to [MultiTouchTapDetector] on Game, however, it also
/// propagates all tap events down the component tree, allowing each individual
/// component to respond to events that happen on that component.
///
/// This mixin **must be** added to a game if you plan to use any components
/// that are [Tappable].
///
/// See [MultiTapGestureRecognizer] for the description of each individual
/// event.
mixin HasTappables on FlameGame implements MultiTapListener {
@mustCallSuper
void onTapCancel(int pointerId) {
propagateToChildren(
(Tappable child) => child.handleTapCancel(pointerId),
);
}

@mustCallSuper
void onTapDown(int pointerId, TapDownInfo info) {
propagateToChildren(
(Tappable child) => child.handleTapDown(pointerId, info),
);
}

@mustCallSuper
void onTapUp(int pointerId, TapUpInfo info) {
propagateToChildren(
(Tappable child) => child.handleTapUp(pointerId, info),
);
}

@mustCallSuper
void onLongTapDown(int pointerId, TapDownInfo info) {
propagateToChildren(
(Tappable child) => child.handleLongTapDown(pointerId, info),
);
}

//#region MultiTapListener API
@override
double get longTapDelay => 0.300;

@override
void handleTap(int pointerId) {}

@override
void handleTapCancel(int pointerId) => onTapCancel(pointerId);

@override
void handleTapDown(int pointerId, TapDownDetails details) {
onTapDown(pointerId, TapDownInfo.fromDetails(this, details));
}

@override
void handleTapUp(int pointerId, TapUpDetails details) {
onTapUp(pointerId, TapUpInfo.fromDetails(this, details));
}

@override
void handleLongTapDown(int pointerId, TapDownDetails details) {
onLongTapDown(pointerId, TapDownInfo.fromDetails(this, details));
}
//#endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'package:flutter/gestures.dart';

import '../../game/mixins/game.dart';
import '../../gestures/events.dart';
import '../flame_game_mixins/has_tappables.dart';
import '../interfaces/multi_tap_listener.dart';

/// Mixin that can be added to a [Game] allowing it to receive tap events.
///
/// The user can override one of the callback methods
/// - [onTapDown]
/// - [onLongTapDown]
/// - [onTapUp]
/// - [onTapCancel]
/// - [onTap]
/// in order to respond to each corresponding event. Those events whose methods
/// are not overridden are ignored.
///
/// See [MultiTapGestureRecognizer] for the description of each individual
/// event. If your game is derived from the FlameGame class, consider using the
/// [HasTappables] mixin instead.
mixin MultiTouchTapDetector on Game implements MultiTapListener {
void onTapDown(int pointerId, TapDownInfo info) {}
void onLongTapDown(int pointerId, TapDownInfo info) {}
void onTapUp(int pointerId, TapUpInfo info) {}
void onTapCancel(int pointerId) {}
void onTap(int pointerId) {}

//#region MultiTapListener API
@override
double get longTapDelay => 0.300;

@override
void handleTap(int pointerId) => onTap(pointerId);

@override
void handleTapCancel(int pointerId) => onTapCancel(pointerId);

@override
void handleTapDown(int pointerId, TapDownDetails details) {
onTapDown(pointerId, TapDownInfo.fromDetails(this, details));
}

@override
void handleTapUp(int pointerId, TapUpDetails details) {
onTapUp(pointerId, TapUpInfo.fromDetails(this, details));
}

@override
void handleLongTapDown(int pointerId, TapDownDetails details) {
onLongTapDown(pointerId, TapDownInfo.fromDetails(this, details));
}
//#endregion
}
33 changes: 33 additions & 0 deletions packages/flame/lib/src/events/interfaces/multi_tap_listener.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'package:flutter/gestures.dart';

import '../flame_game_mixins/has_tappables.dart';
import '../game_mixins/multi_touch_tap_detector.dart';

/// Interface that must be implemented by a game in order for it to be eligible
/// to receive events from a [MultiTapGestureRecognizer].
///
/// Instead of implementing this class directly consider using one of the
/// prebuilt mixins:
/// - [HasTappables] for a `FlameGame`
/// - [MultiTouchTapDetector] for a custom `Game`
Comment on lines +9 to +12
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe I am misremembering, lmk if this comment makes sense, but can't you use MultiTouchTapDetector on FlameGames if you are not using tappables and want to listen to multi-touch events?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since FlameGame is a Game, you can absolutely use MultiTouchTapDetector on it. The HasTappables mixing is just a more concrete implementation, which ensures the event propagates down into the component tree.

abstract class MultiTapListener {
/// The amount of time before the "long tap down" event is triggered.
double get longTapDelay;

/// A tap has occurred.
void handleTap(int pointerId);

/// A pointer has touched the screen.
void handleTapDown(int pointerId, TapDownDetails details);

/// A pointer stopped contacting the screen.
void handleTapUp(int pointerId, TapUpDetails details);

/// A pointer that already triggered [handleTapDown] will not trigger
/// [handleTap].
void handleTapCancel(int pointerId);

/// A pointer that has previously triggered [handleTapDown] is still touching
/// the screen after [longTapDelay] seconds.
void handleLongTapDown(int pointerId, TapDownDetails details);
}
Loading