Unverified Commit e24ac508 authored by amirh's avatar amirh Committed by GitHub

Add support for GestureArenaTeam captains. (#20883)

The team captain wins the arena on behalf of the team.
When any of the team members claims victory for the arena the captain
accepts the gesture.

This is used when embeddeding platform views - we allow configuring a
set of gestures that should be forwarded to the platform view.
We add the set of gesture recognizers to a GestureArenaTeam with a
captain, and if the captain accepts the gesture we forward it to the
platform view.
parent ee8754df
...@@ -33,7 +33,7 @@ class _CombiningGestureArenaMember extends GestureArenaMember { ...@@ -33,7 +33,7 @@ class _CombiningGestureArenaMember extends GestureArenaMember {
assert(_pointer == pointer); assert(_pointer == pointer);
assert(_winner != null || _members.isNotEmpty); assert(_winner != null || _members.isNotEmpty);
_close(); _close();
_winner ??= _members[0]; _winner ??= _owner.captain ?? _members[0];
for (GestureArenaMember member in _members) { for (GestureArenaMember member in _members) {
if (member != _winner) if (member != _winner)
member.rejectGesture(pointer); member.rejectGesture(pointer);
...@@ -74,7 +74,7 @@ class _CombiningGestureArenaMember extends GestureArenaMember { ...@@ -74,7 +74,7 @@ class _CombiningGestureArenaMember extends GestureArenaMember {
_entry.resolve(disposition); _entry.resolve(disposition);
} else { } else {
assert(disposition == GestureDisposition.accepted); assert(disposition == GestureDisposition.accepted);
_winner ??= member; _winner ??= _owner.captain ?? member;
_entry.resolve(disposition); _entry.resolve(disposition);
} }
} }
...@@ -86,14 +86,19 @@ class _CombiningGestureArenaMember extends GestureArenaMember { ...@@ -86,14 +86,19 @@ class _CombiningGestureArenaMember extends GestureArenaMember {
/// Normally, a recognizer competes directly in the [GestureArenaManager] to /// Normally, a recognizer competes directly in the [GestureArenaManager] to
/// recognize a sequence of pointer events as a gesture. With a /// recognize a sequence of pointer events as a gesture. With a
/// [GestureArenaTeam], recognizers can compete in the arena in a group with /// [GestureArenaTeam], recognizers can compete in the arena in a group with
/// other recognizers. /// other recognizers. Arena teams may have a captain which wins the arena on
/// behalf of its team.
/// ///
/// When gesture recognizers are in a team together, then once there are no /// When gesture recognizers are in a team together without a captain, then once
/// other competing gestures in the arena, the first gesture to have been added /// there are no other competing gestures in the arena, the first gesture to
/// to the team automatically wins, instead of the gestures continuing to /// have been added to the team automatically wins, instead of the gestures
/// compete against each other. /// continuing to compete against each other.
/// ///
/// For example, [Slider] uses this to support both a /// When gesture recognizers are in a team with a captain, then once one of the
/// team members claims victory or there are no other competing gestures in the
/// arena, the captain wins the arena, and all other team members lose.
///
/// For example, [Slider] uses a team without a captain to support both a
/// [HorizontalDragGestureRecognizer] and a [TapGestureRecognizer], but without /// [HorizontalDragGestureRecognizer] and a [TapGestureRecognizer], but without
/// the drag recognizer having to wait until the user has dragged outside the /// the drag recognizer having to wait until the user has dragged outside the
/// slop region of the tap gesture before triggering. Since they compete as a /// slop region of the tap gesture before triggering. Since they compete as a
...@@ -105,11 +110,27 @@ class _CombiningGestureArenaMember extends GestureArenaMember { ...@@ -105,11 +110,27 @@ class _CombiningGestureArenaMember extends GestureArenaMember {
/// the horizontal nor vertical drag recognizers can claim victory) the tap /// the horizontal nor vertical drag recognizers can claim victory) the tap
/// recognizer still actually wins, despite being in the team. /// recognizer still actually wins, despite being in the team.
/// ///
/// [AndroidView] uses a team with a captain to decide which gestures are
/// forwarded to the native view. For example if we want to forward taps and
/// vertical scrolls to a native Android view, [TapGestureRecognizers] and
/// [VerticalDragGestureRecognizer] are added to a team with a captain(the captain is set to be a
/// gesture recognizer that never explicitly claims the gesture).
/// The captain allows [AndroidView] to know when any gestures in the team has been
/// recognized (or all other arena members are out), once the captain wins the
/// gesture is forwarded to the Android view.
///
/// To assign a gesture recognizer to a team, set /// To assign a gesture recognizer to a team, set
/// [OneSequenceGestureRecognizer.team] to an instance of [GestureArenaTeam]. /// [OneSequenceGestureRecognizer.team] to an instance of [GestureArenaTeam].
class GestureArenaTeam { class GestureArenaTeam {
final Map<int, _CombiningGestureArenaMember> _combiners = <int, _CombiningGestureArenaMember>{}; final Map<int, _CombiningGestureArenaMember> _combiners = <int, _CombiningGestureArenaMember>{};
/// A member that wins on behalf of the entire team.
///
/// If not null, when any one of the [GestureArenaTeam] members claims victory
/// the captain accepts the gesture.
/// If null, the member that claims a victory accepts the gesture.
GestureArenaMember captain;
/// Adds a new member to the arena on behalf of this team. /// Adds a new member to the arena on behalf of this team.
/// ///
/// Used by [GestureRecognizer] subclasses that wish to compete in the arena /// Used by [GestureRecognizer] subclasses that wish to compete in the arena
......
...@@ -11,7 +11,6 @@ void main() { ...@@ -11,7 +11,6 @@ void main() {
setUp(ensureGestureBinding); setUp(ensureGestureBinding);
testGesture('GestureArenaTeam rejection test', (GestureTester tester) { testGesture('GestureArenaTeam rejection test', (GestureTester tester) {
final GestureArenaTeam team = new GestureArenaTeam(); final GestureArenaTeam team = new GestureArenaTeam();
final HorizontalDragGestureRecognizer horizontalDrag = new HorizontalDragGestureRecognizer()..team = team; final HorizontalDragGestureRecognizer horizontalDrag = new HorizontalDragGestureRecognizer()..team = team;
final VerticalDragGestureRecognizer verticalDrag = new VerticalDragGestureRecognizer()..team = team; final VerticalDragGestureRecognizer verticalDrag = new VerticalDragGestureRecognizer()..team = team;
...@@ -55,4 +54,87 @@ void main() { ...@@ -55,4 +54,87 @@ void main() {
verticalDrag.dispose(); verticalDrag.dispose();
tap.dispose(); tap.dispose();
}); });
testGesture('GestureArenaTeam captain', (GestureTester tester) {
final GestureArenaTeam team = new GestureArenaTeam();
final PassiveGestureRecognizer captain = new PassiveGestureRecognizer()..team = team;
final HorizontalDragGestureRecognizer horizontalDrag = new HorizontalDragGestureRecognizer()..team = team;
final VerticalDragGestureRecognizer verticalDrag = new VerticalDragGestureRecognizer()..team = team;
final TapGestureRecognizer tap = new TapGestureRecognizer();
team.captain = captain;
final List<String> log = <String>[];
captain.onGestureAccepted = () { log.add('captain accepted gesture'); };
horizontalDrag.onStart = (DragStartDetails details) { log.add('horizontal-drag-start'); };
verticalDrag.onStart = (DragStartDetails details) { log.add('vertical-drag-start'); };
tap.onTap = () { log.add('tap'); };
void test(Offset delta) {
const Offset origin = Offset(10.0, 10.0);
final TestPointer pointer = new TestPointer(5);
final PointerDownEvent down = pointer.down(origin);
captain.addPointer(down);
horizontalDrag.addPointer(down);
verticalDrag.addPointer(down);
tap.addPointer(down);
expect(log, isEmpty);
tester.closeArena(5);
expect(log, isEmpty);
tester.route(down);
expect(log, isEmpty);
tester.route(pointer.move(origin + delta));
tester.route(pointer.up());
}
test(Offset.zero);
expect(log, <String>['tap']);
log.clear();
test(const Offset(0.0, 30.0));
expect(log, <String>['captain accepted gesture']);
log.clear();
horizontalDrag.dispose();
verticalDrag.dispose();
tap.dispose();
captain.dispose();
});
}
typedef void GestureAcceptedCallback();
class PassiveGestureRecognizer extends OneSequenceGestureRecognizer {
GestureAcceptedCallback onGestureAccepted;
@override
void addPointer(PointerDownEvent event) {
startTrackingPointer(event.pointer);
}
@override
String get debugDescription => 'passive';
@override
void didStopTrackingLastPointer(int pointer) {
resolve(GestureDisposition.rejected);
}
@override
void handleEvent(PointerEvent event) {
if (event is PointerUpEvent || event is PointerCancelEvent) {
stopTrackingPointer(event.pointer);
}
}
@override
void acceptGesture(int pointer) {
if (onGestureAccepted != null) {
onGestureAccepted();
}
}
@override
void rejectGesture(int pointer) { }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment