arena.dart 7.36 KB
Newer Older
Adam Barth's avatar
Adam Barth committed
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:async';

7
/// Whether the gesture was accepted or rejected.
Adam Barth's avatar
Adam Barth committed
8
enum GestureDisposition {
9
  /// This gesture was accepted as the interpretation of the user's input.
Adam Barth's avatar
Adam Barth committed
10
  accepted,
11 12 13

  /// This gesture was rejected as the interpretation  of the user's input.
  rejected,
Adam Barth's avatar
Adam Barth committed
14 15
}

16 17 18 19
/// Represents an object participating in an arena.
///
/// Receives callbacks from the GestureArena to notify the object when it wins
/// or loses a gesture negotiation. Exactly one of [acceptGesture] or
20
/// [rejectGesture] will be called for each arena this member was added to,
21 22 23
/// regardless of what caused the arena to be resolved. For example, if a
/// member resolves the arena itself, that member still receives an
/// [acceptGesture] callback.
Adam Barth's avatar
Adam Barth committed
24
abstract class GestureArenaMember {
25 26
  /// Called when this member wins the arena for the given pointer id.
  void acceptGesture(int pointer);
Adam Barth's avatar
Adam Barth committed
27

28 29
  /// Called when this member loses the arena for the given pointer id.
  void rejectGesture(int pointer);
Adam Barth's avatar
Adam Barth committed
30 31
}

Florian Loitsch's avatar
Florian Loitsch committed
32
/// An interface to information to an arena.
33 34
///
/// A given [GestureArenaMember] can have multiple entries in multiple arenas
35
/// with different pointer ids.
Adam Barth's avatar
Adam Barth committed
36
class GestureArenaEntry {
37
  GestureArenaEntry._(this._arena, this._pointer, this._member);
Adam Barth's avatar
Adam Barth committed
38

39 40
  final GestureArenaManager _arena;
  final int _pointer;
Adam Barth's avatar
Adam Barth committed
41 42 43
  final GestureArenaMember _member;

  /// Call this member to claim victory (with accepted) or admit defeat (with rejected).
44 45
  ///
  /// It's fine to attempt to resolve an arena that is already resolved.
Adam Barth's avatar
Adam Barth committed
46
  void resolve(GestureDisposition disposition) {
47
    _arena._resolve(_pointer, _member, disposition);
Adam Barth's avatar
Adam Barth committed
48 49 50
  }
}

51
class _GestureArena {
52 53
  final List<GestureArenaMember> members = new List<GestureArenaMember>();
  bool isOpen = true;
54
  bool isHeld = false;
55
  bool hasPendingSweep = false;
56

Hixie's avatar
Hixie committed
57 58
  /// If a gesture attempts to win while the arena is still open, it becomes the
  /// "eager winnner". We look for an eager winner when closing the arena to new
59
  /// participants, and if there is one, we resolve the arena in its favor at
Hixie's avatar
Hixie committed
60 61 62
  /// that time.
  GestureArenaMember eagerWinner;

63 64 65 66 67 68
  void add(GestureArenaMember member) {
    assert(isOpen);
    members.add(member);
  }
}

Adam Barth's avatar
Adam Barth committed
69
/// The first member to accept or the last member to not to reject wins.
70 71 72
///
/// See [https://flutter.io/gestures/#gesture-disambiguation] for more
/// information about the role this class plays in the gesture system.
73 74
class GestureArenaManager {
  final Map<int, _GestureArena> _arenas = new Map<int, _GestureArena>();
Adam Barth's avatar
Adam Barth committed
75

76
  /// Adds a new member (e.g., gesture recognizer) to the arena.
77 78
  GestureArenaEntry add(int pointer, GestureArenaMember member) {
    _GestureArena state = _arenas.putIfAbsent(pointer, () => new _GestureArena());
79
    state.add(member);
80
    return new GestureArenaEntry._(this, pointer, member);
Adam Barth's avatar
Adam Barth committed
81 82
  }

83 84 85
  /// Prevents new members from entering the arena.
  ///
  /// Called after the framework has finished dispatching the pointer down event.
86 87
  void close(int pointer) {
    _GestureArena state = _arenas[pointer];
88 89 90
    if (state == null)
      return;  // This arena either never existed or has been resolved.
    state.isOpen = false;
91
    _tryToResolveArena(pointer, state);
92 93
  }

94
  /// Forces resolution of the arena, giving the win to the first member.
95 96 97 98 99 100 101 102 103 104 105 106
  ///
  /// Sweep is typically after all the other processing for a [PointerUpEvent]
  /// have taken place. It ensures that multiple passive gestures do not cause a
  /// stalemate that prevents the user from interacting with the app.
  ///
  /// Recognizers that wish to delay resolving an arena past [PointerUpEvent]
  /// should call [hold] to delay sweep until [release] is called.
  ///
  /// See also:
  ///
  ///  * [hold]
  ///  * [release]
107 108
  void sweep(int pointer) {
    _GestureArena state = _arenas[pointer];
109 110 111
    if (state == null)
      return;  // This arena either never existed or has been resolved.
    assert(!state.isOpen);
112 113
    if (state.isHeld) {
      state.hasPendingSweep = true;
114
      return;  // This arena is being held for a long-lived member
115
    }
116
    _arenas.remove(pointer);
Ian Hickson's avatar
Ian Hickson committed
117
    if (state.members.isNotEmpty) {
118
      // First member wins
119
      state.members.first.acceptGesture(pointer);
120 121
      // Give all the other members the bad news
      for (int i = 1; i < state.members.length; i++)
122
        state.members[i].rejectGesture(pointer);
123 124 125
    }
  }

Florian Loitsch's avatar
Florian Loitsch committed
126
  /// Prevents the arena from being swept.
127 128 129 130 131 132 133 134 135 136 137
  ///
  /// Typically, a winner is chosen in an arena after all the other
  /// [PointerUpEvent] processing by [sweep]. If a recognizer wishes to delay
  /// resolving an arena past [PointerUpEvent], the recognizer can [hold] the
  /// arena open using this function. To release such a hold and let the arena
  /// resolve, call [release].
  ///
  /// See also:
  ///
  ///  * [sweep]
  ///  * [release]
138 139
  void hold(int pointer) {
    _GestureArena state = _arenas[pointer];
140 141 142 143 144
    if (state == null)
      return;  // This arena either never existed or has been resolved.
    state.isHeld = true;
  }

Florian Loitsch's avatar
Florian Loitsch committed
145 146
  /// Releases a hold, allowing the arena to be swept.
  ///
147
  /// If a sweep was attempted on a held arena, the sweep will be done
Florian Loitsch's avatar
Florian Loitsch committed
148
  /// on release.
149 150 151 152 153
  ///
  /// See also:
  ///
  ///  * [sweep]
  ///  * [hold]
154 155
  void release(int pointer) {
    _GestureArena state = _arenas[pointer];
156 157 158 159
    if (state == null)
      return;  // This arena either never existed or has been resolved.
    state.isHeld = false;
    if (state.hasPendingSweep)
160
      sweep(pointer);
161 162
  }

163 164 165 166 167 168 169 170 171 172 173
  void _resolveByDefault(int pointer, _GestureArena state) {
    if (!_arenas.containsKey(pointer))
      return;  // Already resolved earlier.
    assert(_arenas[pointer] == state);
    assert(!state.isOpen);
    final List<GestureArenaMember> members = state.members;
    assert(members.length == 1);
    _arenas.remove(pointer);
    state.members.first.acceptGesture(pointer);
  }

174 175
  void _tryToResolveArena(int pointer, _GestureArena state) {
    assert(_arenas[pointer] == state);
176 177
    assert(!state.isOpen);
    if (state.members.length == 1) {
178
      scheduleMicrotask(() => _resolveByDefault(pointer, state));
179
    } else if (state.members.isEmpty) {
180
      _arenas.remove(pointer);
Hixie's avatar
Hixie committed
181
    } else if (state.eagerWinner != null) {
182
      _resolveInFavorOf(pointer, state, state.eagerWinner);
183 184 185
    }
  }

186 187
  void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) {
    _GestureArena state = _arenas[pointer];
188
    if (state == null)
189
      return;  // This arena has already resolved.
190
    assert(state.members.contains(member));
Adam Barth's avatar
Adam Barth committed
191
    if (disposition == GestureDisposition.rejected) {
192
      state.members.remove(member);
193
      member.rejectGesture(pointer);
Hixie's avatar
Hixie committed
194
      if (!state.isOpen)
195
        _tryToResolveArena(pointer, state);
Adam Barth's avatar
Adam Barth committed
196 197
    } else {
      assert(disposition == GestureDisposition.accepted);
Hixie's avatar
Hixie committed
198
      if (state.isOpen) {
Ian Hickson's avatar
Ian Hickson committed
199
        state.eagerWinner ??= member;
Hixie's avatar
Hixie committed
200
      } else {
201
        _resolveInFavorOf(pointer, state, member);
Adam Barth's avatar
Adam Barth committed
202 203 204
      }
    }
  }
Hixie's avatar
Hixie committed
205

206 207
  void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
    assert(state == _arenas[pointer]);
Hixie's avatar
Hixie committed
208 209 210
    assert(state != null);
    assert(state.eagerWinner == null || state.eagerWinner == member);
    assert(!state.isOpen);
211
    _arenas.remove(pointer);
Hixie's avatar
Hixie committed
212 213
    for (GestureArenaMember rejectedMember in state.members) {
      if (rejectedMember != member)
214
        rejectedMember.rejectGesture(pointer);
Hixie's avatar
Hixie committed
215
    }
216
    member.acceptGesture(pointer);
Hixie's avatar
Hixie committed
217
  }
Ian Hickson's avatar
Ian Hickson committed
218
}