-
-
Notifications
You must be signed in to change notification settings - Fork 1k
fix: Recognize every input callbacks mixin when hit testing #3994
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| /// Marker interface implemented by every input callbacks mixin. | ||
| /// | ||
| /// See `PointerInputCallbacks` for the positional subset. | ||
| abstract interface class InputCallbacks {} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this and the other interface define methods?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. they don't actually share any methods or fields atm - this is just a marker interface for type-checking purposes |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import 'package:flame/src/events/callbacks/input_callbacks.dart'; | ||
|
|
||
| /// Marker interface implemented by every input callbacks mixin whose events | ||
| /// carry a position (taps, drags, scrolls, hover) and thus participate in | ||
| /// hit-testing. | ||
|
luanpotter marked this conversation as resolved.
|
||
| /// | ||
| /// Non-positional input, such as keyboard, implements [InputCallbacks]. | ||
| abstract interface class PointerInputCallbacks implements InputCallbacks {} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -254,11 +254,7 @@ class FlameGame<W extends World> extends ComponentTreeRoot | |
| return true; | ||
| } | ||
| for (final component in super.componentsAtPoint(position)) { | ||
| if (component is TapCallbacks || | ||
| component is DragCallbacks || | ||
| component is DoubleTapCallbacks || | ||
| component is ScaleCallbacks || | ||
| component is SecondaryTapCallbacks) { | ||
| if (component is PointerInputCallbacks) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is the fix |
||
| return true; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| 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/material.dart'; | ||
| import 'package:flutter_test/flutter_test.dart'; | ||
|
|
||
| void main() { | ||
| group('FlameGame.containsEventHandlerAt', () { | ||
| testWithGame( | ||
| 'detects any component implementing the marker', | ||
| FlameGame.new, | ||
| (game) async { | ||
| await game.ensureAdd(_CustomInputComponent()); | ||
|
|
||
| expect(game.containsEventHandlerAt(Vector2(30, 30)), isTrue); | ||
| expect(game.containsEventHandlerAt(Vector2(400, 300)), isFalse); | ||
| }, | ||
| ); | ||
|
|
||
| testWithGame( | ||
| 'ignores components that handle no input', | ||
| FlameGame.new, | ||
| (game) async { | ||
| await game.ensureAdd(_PlainComponent()); | ||
|
|
||
| expect(game.containsEventHandlerAt(Vector2(30, 30)), isFalse); | ||
| }, | ||
| ); | ||
|
|
||
| testWithGame( | ||
| 'detects a built-in mixin', | ||
| FlameGame.new, | ||
| (game) async { | ||
| await game.ensureAdd(_ScrollComponent()); | ||
|
|
||
| expect(game.containsEventHandlerAt(Vector2(30, 30)), isTrue); | ||
| }, | ||
| ); | ||
|
|
||
| testWithGame( | ||
| 'detects a game that handles input itself', | ||
| _ScrollGame.new, | ||
| (game) async { | ||
| // FlameGame is itself a Component, so componentsAtPoint yields the | ||
| // game and any in-bounds point counts as a hit. | ||
| expect(game.containsEventHandlerAt(Vector2(400, 300)), isTrue); | ||
| expect(game.containsEventHandlerAt(Vector2(900, 700)), isFalse); | ||
| }, | ||
| ); | ||
| }); | ||
|
|
||
| group('GameWidget hit test', () { | ||
| testWidgets( | ||
| 'long presses reach a LongPressCallbacks component under deferToChild', | ||
| (tester) async { | ||
| var buttonTapped = false; | ||
| final component = _LongPressComponent() | ||
| ..size = Vector2(800, 600) | ||
| ..position = Vector2.zero(); | ||
| final game = _TransparentGame()..add(component); | ||
|
|
||
| await tester.pumpWidget( | ||
| MaterialApp( | ||
| home: Scaffold( | ||
| body: Stack( | ||
| children: [ | ||
| Center( | ||
| child: ElevatedButton( | ||
| onPressed: () => buttonTapped = true, | ||
| child: const Text('Tap me'), | ||
| ), | ||
| ), | ||
| Positioned.fill( | ||
| child: GameWidget( | ||
| game: game, | ||
| behavior: HitTestBehavior.deferToChild, | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ), | ||
| ), | ||
| ); | ||
| await tester.pump(); | ||
| await tester.pump(); | ||
| expect(component.isMounted, isTrue); | ||
|
|
||
| await tester.longPressAt(const Offset(400, 300)); | ||
| await tester.pump(const Duration(milliseconds: 100)); | ||
|
|
||
| expect(component.longPressCount, equals(1)); | ||
| expect(buttonTapped, isFalse); | ||
| }, | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
| mixin _CustomInputCallbacks on Component implements PointerInputCallbacks {} | ||
|
|
||
| class _TransparentGame extends FlameGame { | ||
| @override | ||
| Color backgroundColor() => const Color(0x00000000); | ||
| } | ||
|
|
||
| class _ScrollGame extends FlameGame with ScrollCallbacks {} | ||
|
|
||
| class _Box extends PositionComponent { | ||
| _Box() : super(position: Vector2.all(10), size: Vector2.all(50)); | ||
| } | ||
|
|
||
| class _PlainComponent extends _Box {} | ||
|
|
||
| class _CustomInputComponent extends _Box with _CustomInputCallbacks {} | ||
|
|
||
| class _ScrollComponent extends _Box with ScrollCallbacks {} | ||
|
|
||
| class _LongPressComponent extends _Box with LongPressCallbacks { | ||
| int longPressCount = 0; | ||
|
|
||
| @override | ||
| void onLongPressStart(LongPressStartEvent event) { | ||
| super.onLongPressStart(event); | ||
| longPressCount++; | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.