| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'arena.dart'; |
| import 'binding.dart'; |
| |
| class _CombiningGestureArenaEntry implements GestureArenaEntry { |
| _CombiningGestureArenaEntry(this._combiner, this._member); |
| |
| final _CombiningGestureArenaMember _combiner; |
| final GestureArenaMember _member; |
| |
| @override |
| void resolve(GestureDisposition disposition) { |
| _combiner._resolve(_member, disposition); |
| } |
| } |
| |
| class _CombiningGestureArenaMember extends GestureArenaMember { |
| _CombiningGestureArenaMember(this._owner, this._pointer); |
| |
| final GestureArenaTeam _owner; |
| final List<GestureArenaMember> _members = <GestureArenaMember>[]; |
| final int _pointer; |
| |
| bool _resolved = false; |
| GestureArenaMember _winner; |
| GestureArenaEntry _entry; |
| |
| @override |
| void acceptGesture(int pointer) { |
| assert(_pointer == pointer); |
| assert(_winner != null || _members.isNotEmpty); |
| _close(); |
| _winner ??= _owner.captain ?? _members[0]; |
| for (final GestureArenaMember member in _members) { |
| if (member != _winner) |
| member.rejectGesture(pointer); |
| } |
| _winner.acceptGesture(pointer); |
| } |
| |
| @override |
| void rejectGesture(int pointer) { |
| assert(_pointer == pointer); |
| _close(); |
| for (final GestureArenaMember member in _members) |
| member.rejectGesture(pointer); |
| } |
| |
| void _close() { |
| assert(!_resolved); |
| _resolved = true; |
| final _CombiningGestureArenaMember combiner = _owner._combiners.remove(_pointer); |
| assert(combiner == this); |
| } |
| |
| GestureArenaEntry _add(int pointer, GestureArenaMember member) { |
| assert(!_resolved); |
| assert(_pointer == pointer); |
| _members.add(member); |
| _entry ??= GestureBinding.instance.gestureArena.add(pointer, this); |
| return _CombiningGestureArenaEntry(this, member); |
| } |
| |
| void _resolve(GestureArenaMember member, GestureDisposition disposition) { |
| if (_resolved) |
| return; |
| if (disposition == GestureDisposition.rejected) { |
| _members.remove(member); |
| member.rejectGesture(_pointer); |
| if (_members.isEmpty) |
| _entry.resolve(disposition); |
| } else { |
| assert(disposition == GestureDisposition.accepted); |
| _winner ??= _owner.captain ?? member; |
| _entry.resolve(disposition); |
| } |
| } |
| } |
| |
| /// A group of [GestureArenaMember] objects that are competing as a unit in the |
| /// [GestureArenaManager]. |
| /// |
| /// Normally, a recognizer competes directly in the [GestureArenaManager] to |
| /// recognize a sequence of pointer events as a gesture. With a |
| /// [GestureArenaTeam], recognizers can compete in the arena in a group with |
| /// 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 without a captain, then once |
| /// there are no other competing gestures in the arena, the first gesture to |
| /// have been added to the team automatically wins, instead of the gestures |
| /// continuing to compete against each other. |
| /// |
| /// 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 |
| /// 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 |
| /// team, as soon as any other recognizers are out of the arena, the drag |
| /// recognizer wins, even if the user has not actually dragged yet. On the other |
| /// hand, if the tap can win outright, before the other recognizers are taken |
| /// out of the arena (e.g. if the slider is in a vertical scrolling list and the |
| /// user places their finger on the touch surface then lifts it, so that neither |
| /// the horizontal nor vertical drag recognizers can claim victory) the tap |
| /// 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, [TapGestureRecognizer]s 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 |
| /// [OneSequenceGestureRecognizer.team] to an instance of [GestureArenaTeam]. |
| class GestureArenaTeam { |
| 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. |
| /// |
| /// Used by [GestureRecognizer] subclasses that wish to compete in the arena |
| /// using this team. |
| /// |
| /// To assign a gesture recognizer to a team, see |
| /// [OneSequenceGestureRecognizer.team]. |
| GestureArenaEntry add(int pointer, GestureArenaMember member) { |
| final _CombiningGestureArenaMember combiner = _combiners.putIfAbsent( |
| pointer, () => _CombiningGestureArenaMember(this, pointer)); |
| return combiner._add(pointer, member); |
| } |
| } |