From 302138145647a230598a83c74e6b8a51329148f4 Mon Sep 17 00:00:00 2001 From: Kris Giesing Date: Fri, 9 Oct 2015 11:14:15 -0700 Subject: [PATCH 1/2] Add hold and sweep semantics to gesture arena --- packages/flutter/lib/src/gestures/arena.dart | 27 +++++++++++++++++++ .../flutter/lib/src/rendering/binding.dart | 2 ++ 2 files changed, 29 insertions(+) diff --git a/packages/flutter/lib/src/gestures/arena.dart b/packages/flutter/lib/src/gestures/arena.dart index 17e70da4ec..d9fee7f63e 100644 --- a/packages/flutter/lib/src/gestures/arena.dart +++ b/packages/flutter/lib/src/gestures/arena.dart @@ -45,6 +45,7 @@ class GestureArenaEntry { class _GestureArenaState { final List members = new List(); bool isOpen = true; + bool isHeld = false; void add(GestureArenaMember member) { assert(isOpen); @@ -72,6 +73,32 @@ 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) + return; // This arena is being held for a long-lived member + 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); + } + _arenas.remove(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; + } + 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 7de8a4ebe7..bbc9c6cc87 100644 --- a/packages/flutter/lib/src/rendering/binding.dart +++ b/packages/flutter/lib/src/rendering/binding.dart @@ -167,6 +167,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); } } } From e8a0ea359382f675ce9d49bd36237cc387018ad4 Mon Sep 17 00:00:00 2001 From: Kris Giesing Date: Wed, 14 Oct 2015 17:03:26 -0700 Subject: [PATCH 2/2] Add release semantics, add test --- packages/flutter/lib/src/gestures/arena.dart | 19 +- packages/unit/test/gestures/arena_test.dart | 172 +++++++++++++++++++ 2 files changed, 189 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/gestures/arena.dart b/packages/flutter/lib/src/gestures/arena.dart index d9fee7f63e..184a314ac2 100644 --- a/packages/flutter/lib/src/gestures/arena.dart +++ b/packages/flutter/lib/src/gestures/arena.dart @@ -46,6 +46,7 @@ class _GestureArenaState { final List members = new List(); bool isOpen = true; bool isHeld = false; + bool hasPendingSweep = false; void add(GestureArenaMember member) { assert(isOpen); @@ -79,8 +80,11 @@ class GestureArena { if (state == null) return; // This arena either never existed or has been resolved. assert(!state.isOpen); - if (state.isHeld) + 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); @@ -88,7 +92,6 @@ class GestureArena { for (int i = 1; i < state.members.length; i++) state.members[i].rejectGesture(key); } - _arenas.remove(key); } /// Prevent the arena from being swept @@ -99,6 +102,18 @@ class GestureArena { 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/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); + }); }