diff --git a/packages/flutter/lib/src/gestures/arena.dart b/packages/flutter/lib/src/gestures/arena.dart index 17e70da4ec..184a314ac2 100644 --- a/packages/flutter/lib/src/gestures/arena.dart +++ b/packages/flutter/lib/src/gestures/arena.dart @@ -45,6 +45,8 @@ class GestureArenaEntry { class _GestureArenaState { final List members = new List(); bool isOpen = true; + bool isHeld = false; + bool hasPendingSweep = false; void add(GestureArenaMember member) { assert(isOpen); @@ -72,6 +74,46 @@ class GestureArena { _tryToResolveArena(key, state); } + /// Force resolution on this arena, giving the win to the first member + void sweep(Object key) { + _GestureArenaState state = _arenas[key]; + if (state == null) + return; // This arena either never existed or has been resolved. + assert(!state.isOpen); + if (state.isHeld) { + state.hasPendingSweep = true; + return; // This arena is being held for a long-lived member + } + _arenas.remove(key); + if (!state.members.isEmpty) { + // First member wins + state.members.first.acceptGesture(key); + // Give all the other members the bad news + for (int i = 1; i < state.members.length; i++) + state.members[i].rejectGesture(key); + } + } + + /// Prevent the arena from being swept + void hold(Object key) { + _GestureArenaState state = _arenas[key]; + if (state == null) + return; // This arena either never existed or has been resolved. + state.isHeld = true; + } + + /// Release a hold, allowing the arena to be swept + /// If a sweep was attempted on a held arena, the sweep will be done + /// on release + void release(Object key) { + _GestureArenaState state = _arenas[key]; + if (state == null) + return; // This arena either never existed or has been resolved. + state.isHeld = false; + if (state.hasPendingSweep) + sweep(key); + } + void _tryToResolveArena(Object key, _GestureArenaState state) { assert(_arenas[key] == state); assert(!state.isOpen); diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart index 3484b40469..e501ff39fa 100644 --- a/packages/flutter/lib/src/rendering/binding.dart +++ b/packages/flutter/lib/src/rendering/binding.dart @@ -244,6 +244,8 @@ class FlutterBinding extends HitTestTarget { pointerRouter.route(event); if (event.type == 'pointerdown') GestureArena.instance.close(event.pointer); + else if (event.type == 'pointerup') + GestureArena.instance.sweep(event.pointer); } } } diff --git a/packages/unit/test/gestures/arena_test.dart b/packages/unit/test/gestures/arena_test.dart index ccc861b50a..4f0d062fd6 100644 --- a/packages/unit/test/gestures/arena_test.dart +++ b/packages/unit/test/gestures/arena_test.dart @@ -66,4 +66,176 @@ void main() { expect(secondAcceptRan, isFalse); expect(secondRejectRan, isTrue); }); + + test('Should win by sweep', () { + GestureArena arena = new GestureArena(); + + int primaryKey = 4; + bool firstAcceptRan = false; + bool firstRejectRan = false; + bool secondAcceptRan = false; + bool secondRejectRan = false; + + TestGestureArenaMember first = new TestGestureArenaMember( + onAcceptGesture: (int key) { + expect(key, equals(primaryKey)); + firstAcceptRan = true; + }, + onRejectGesture: (int key) { + expect(key, equals(primaryKey)); + firstRejectRan = true; + } + ); + + TestGestureArenaMember second = new TestGestureArenaMember( + onAcceptGesture: (int key) { + expect(key, equals(primaryKey)); + secondAcceptRan = true; + }, + onRejectGesture: (int key) { + expect(key, equals(primaryKey)); + secondRejectRan = true; + } + ); + + arena.add(primaryKey, first); + arena.add(primaryKey, second); + arena.close(primaryKey); + + expect(firstAcceptRan, isFalse); + expect(firstRejectRan, isFalse); + expect(secondAcceptRan, isFalse); + expect(secondRejectRan, isFalse); + + arena.sweep(primaryKey); + + expect(firstAcceptRan, isTrue); + expect(firstRejectRan, isFalse); + expect(secondAcceptRan, isFalse); + expect(secondRejectRan, isTrue); + }); + + test('Should win on release after hold sweep release', () { + GestureArena arena = new GestureArena(); + + int primaryKey = 4; + bool firstAcceptRan = false; + bool firstRejectRan = false; + bool secondAcceptRan = false; + bool secondRejectRan = false; + + TestGestureArenaMember first = new TestGestureArenaMember( + onAcceptGesture: (int key) { + expect(key, equals(primaryKey)); + firstAcceptRan = true; + }, + onRejectGesture: (int key) { + expect(key, equals(primaryKey)); + firstRejectRan = true; + } + ); + + TestGestureArenaMember second = new TestGestureArenaMember( + onAcceptGesture: (int key) { + expect(key, equals(primaryKey)); + secondAcceptRan = true; + }, + onRejectGesture: (int key) { + expect(key, equals(primaryKey)); + secondRejectRan = true; + } + ); + + arena.add(primaryKey, first); + arena.add(primaryKey, second); + arena.close(primaryKey); + + expect(firstAcceptRan, isFalse); + expect(firstRejectRan, isFalse); + expect(secondAcceptRan, isFalse); + expect(secondRejectRan, isFalse); + + arena.hold(primaryKey); + + expect(firstAcceptRan, isFalse); + expect(firstRejectRan, isFalse); + expect(secondAcceptRan, isFalse); + expect(secondRejectRan, isFalse); + + arena.sweep(primaryKey); + + expect(firstAcceptRan, isFalse); + expect(firstRejectRan, isFalse); + expect(secondAcceptRan, isFalse); + expect(secondRejectRan, isFalse); + + arena.release(primaryKey); + + expect(firstAcceptRan, isTrue); + expect(firstRejectRan, isFalse); + expect(secondAcceptRan, isFalse); + expect(secondRejectRan, isTrue); + }); + + test('Should win on sweep after hold release sweep', () { + GestureArena arena = new GestureArena(); + + int primaryKey = 4; + bool firstAcceptRan = false; + bool firstRejectRan = false; + bool secondAcceptRan = false; + bool secondRejectRan = false; + + TestGestureArenaMember first = new TestGestureArenaMember( + onAcceptGesture: (int key) { + expect(key, equals(primaryKey)); + firstAcceptRan = true; + }, + onRejectGesture: (int key) { + expect(key, equals(primaryKey)); + firstRejectRan = true; + } + ); + + TestGestureArenaMember second = new TestGestureArenaMember( + onAcceptGesture: (int key) { + expect(key, equals(primaryKey)); + secondAcceptRan = true; + }, + onRejectGesture: (int key) { + expect(key, equals(primaryKey)); + secondRejectRan = true; + } + ); + + arena.add(primaryKey, first); + arena.add(primaryKey, second); + arena.close(primaryKey); + + expect(firstAcceptRan, isFalse); + expect(firstRejectRan, isFalse); + expect(secondAcceptRan, isFalse); + expect(secondRejectRan, isFalse); + + arena.hold(primaryKey); + + expect(firstAcceptRan, isFalse); + expect(firstRejectRan, isFalse); + expect(secondAcceptRan, isFalse); + expect(secondRejectRan, isFalse); + + arena.release(primaryKey); + + expect(firstAcceptRan, isFalse); + expect(firstRejectRan, isFalse); + expect(secondAcceptRan, isFalse); + expect(secondRejectRan, isFalse); + + arena.sweep(primaryKey); + + expect(firstAcceptRan, isTrue); + expect(firstRejectRan, isFalse); + expect(secondAcceptRan, isFalse); + expect(secondRejectRan, isTrue); + }); }