// 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'; export 'arena.dart' show GestureArenaEntry, GestureArenaMember; 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); } }