arena.dart 6.15 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
/// Whether the gesture was accepted or rejected.
Adam Barth's avatar
Adam Barth committed
6
enum GestureDisposition {
7
  /// This gesture was accepted as the interpretation of the user's input.
Adam Barth's avatar
Adam Barth committed
8
  accepted,
9 10 11

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

14 15 16 17 18 19 20 21
/// 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
/// [rejectGesture] will be called for each arena key this member was added to,
/// 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
22
abstract class GestureArenaMember {
23
  /// Called when this member wins the arena for the given key.
Adam Barth's avatar
Adam Barth committed
24 25
  void acceptGesture(Object key);

26
  /// Called when this member loses the arena for the given key.
Adam Barth's avatar
Adam Barth committed
27 28 29
  void rejectGesture(Object key);
}

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

  final GestureArena _arena;
  final Object _key;
  final GestureArenaMember _member;

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

49 50 51
class _GestureArenaState {
  final List<GestureArenaMember> members = new List<GestureArenaMember>();
  bool isOpen = true;
52
  bool isHeld = false;
53
  bool hasPendingSweep = false;
54

Hixie's avatar
Hixie committed
55 56 57 58 59 60
  /// 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
  /// participants, and if there is one, we resolve the arena it its favour at
  /// that time.
  GestureArenaMember eagerWinner;

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

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

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

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

92 93 94
  /// Forces resolution of the arena, giving the win to the first member.
  void sweep(Object arenaKey) {
    _GestureArenaState state = _arenas[arenaKey];
95 96 97
    if (state == null)
      return;  // This arena either never existed or has been resolved.
    assert(!state.isOpen);
98 99
    if (state.isHeld) {
      state.hasPendingSweep = true;
100
      return;  // This arena is being held for a long-lived member
101
    }
102
    _arenas.remove(arenaKey);
Ian Hickson's avatar
Ian Hickson committed
103
    if (state.members.isNotEmpty) {
104
      // First member wins
105
      state.members.first.acceptGesture(arenaKey);
106 107
      // Give all the other members the bad news
      for (int i = 1; i < state.members.length; i++)
108
        state.members[i].rejectGesture(arenaKey);
109 110 111
    }
  }

Florian Loitsch's avatar
Florian Loitsch committed
112
  /// Prevents the arena from being swept.
113 114 115 116 117 118 119
  void hold(Object key) {
    _GestureArenaState state = _arenas[key];
    if (state == null)
      return;  // This arena either never existed or has been resolved.
    state.isHeld = true;
  }

Florian Loitsch's avatar
Florian Loitsch committed
120 121
  /// Releases a hold, allowing the arena to be swept.
  ///
122
  /// If a sweep was attempted on a held arena, the sweep will be done
Florian Loitsch's avatar
Florian Loitsch committed
123
  /// on release.
124 125
  void release(Object arenaKey) {
    _GestureArenaState state = _arenas[arenaKey];
126 127 128 129
    if (state == null)
      return;  // This arena either never existed or has been resolved.
    state.isHeld = false;
    if (state.hasPendingSweep)
130
      sweep(arenaKey);
131 132
  }

133 134
  void _tryToResolveArena(Object arenaKey, _GestureArenaState state) {
    assert(_arenas[arenaKey] == state);
135 136
    assert(!state.isOpen);
    if (state.members.length == 1) {
137 138
      _arenas.remove(arenaKey);
      state.members.first.acceptGesture(arenaKey);
139
    } else if (state.members.isEmpty) {
140
      _arenas.remove(arenaKey);
Hixie's avatar
Hixie committed
141
    } else if (state.eagerWinner != null) {
142
      _resolveInFavorOf(arenaKey, state, state.eagerWinner);
143 144 145
    }
  }

146 147
  void _resolve(Object arenaKey, GestureArenaMember member, GestureDisposition disposition) {
    _GestureArenaState state = _arenas[arenaKey];
148
    if (state == null)
149
      return;  // This arena has already resolved.
150
    assert(state.members.contains(member));
Adam Barth's avatar
Adam Barth committed
151
    if (disposition == GestureDisposition.rejected) {
152
      state.members.remove(member);
153
      member.rejectGesture(arenaKey);
Hixie's avatar
Hixie committed
154
      if (!state.isOpen)
155
        _tryToResolveArena(arenaKey, state);
Adam Barth's avatar
Adam Barth committed
156 157
    } else {
      assert(disposition == GestureDisposition.accepted);
Hixie's avatar
Hixie committed
158
      if (state.isOpen) {
Ian Hickson's avatar
Ian Hickson committed
159
        state.eagerWinner ??= member;
Hixie's avatar
Hixie committed
160
      } else {
161
        _resolveInFavorOf(arenaKey, state, member);
Adam Barth's avatar
Adam Barth committed
162 163 164
      }
    }
  }
Hixie's avatar
Hixie committed
165

166 167
  void _resolveInFavorOf(Object arenaKey, _GestureArenaState state, GestureArenaMember member) {
    assert(state == _arenas[arenaKey]);
Hixie's avatar
Hixie committed
168 169 170
    assert(state != null);
    assert(state.eagerWinner == null || state.eagerWinner == member);
    assert(!state.isOpen);
171
    _arenas.remove(arenaKey);
Hixie's avatar
Hixie committed
172 173
    for (GestureArenaMember rejectedMember in state.members) {
      if (rejectedMember != member)
174
        rejectedMember.rejectGesture(arenaKey);
Hixie's avatar
Hixie committed
175
    }
176
    member.acceptGesture(arenaKey);
Hixie's avatar
Hixie committed
177
  }
Ian Hickson's avatar
Ian Hickson committed
178
}