1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | /// @docImport 'package:flutter/widgets.dart'; |
6 | /// |
7 | /// @docImport 'binding.dart'; |
8 | library; |
9 | |
10 | import 'package:flutter/foundation.dart'; |
11 | |
12 | import 'events.dart'; |
13 | |
14 | export 'events.dart' show PointerSignalEvent; |
15 | |
16 | /// The callback to register with a [PointerSignalResolver] to express |
17 | /// interest in a pointer signal event. |
18 | typedef PointerSignalResolvedCallback = void Function(PointerSignalEvent event); |
19 | |
20 | bool _isSameEvent(PointerSignalEvent event1, PointerSignalEvent event2) { |
21 | return (event1.original ?? event1) == (event2.original ?? event2); |
22 | } |
23 | |
24 | /// Mediates disputes over which listener should handle pointer signal events |
25 | /// when multiple listeners wish to handle those events. |
26 | /// |
27 | /// Pointer signals (such as [PointerScrollEvent]) are immediate, so unlike |
28 | /// events that participate in the gesture arena, pointer signals always |
29 | /// resolve at the end of event dispatch. Yet if objects interested in handling |
30 | /// these signal events were to handle them directly, it would cause issues |
31 | /// such as multiple [Scrollable] widgets in the widget hierarchy responding |
32 | /// to the same mouse wheel event. Using this class, these events will only |
33 | /// be dispatched to the first registered handler, which will in turn |
34 | /// correspond to the widget that's deepest in the widget hierarchy. |
35 | /// |
36 | /// To use this class, objects should register their event handler like so: |
37 | /// |
38 | /// ```dart |
39 | /// void handleSignalEvent(PointerSignalEvent event) { |
40 | /// GestureBinding.instance.pointerSignalResolver.register(event, (PointerSignalEvent event) { |
41 | /// // handle the event... |
42 | /// }); |
43 | /// } |
44 | /// ``` |
45 | /// |
46 | /// {@tool dartpad} |
47 | /// Here is an example that demonstrates the effect of not using the resolver |
48 | /// versus using it. |
49 | /// |
50 | /// When this example is set to _not_ use the resolver, then triggering the |
51 | /// mouse wheel over the outer box will cause only the outer box to change |
52 | /// color, but triggering the mouse wheel over the inner box will cause _both_ |
53 | /// the outer and the inner boxes to change color (because they're both |
54 | /// receiving the event). |
55 | /// |
56 | /// When this example is set to _use_ the resolver, then only the box located |
57 | /// directly under the cursor will change color when the mouse wheel is |
58 | /// triggered. |
59 | /// |
60 | /// ** See code in examples/api/lib/gestures/pointer_signal_resolver/pointer_signal_resolver.0.dart ** |
61 | /// {@end-tool} |
62 | class PointerSignalResolver { |
63 | PointerSignalResolvedCallback? _firstRegisteredCallback; |
64 | |
65 | PointerSignalEvent? _currentEvent; |
66 | |
67 | /// Registers interest in handling [event]. |
68 | /// |
69 | /// This method may be called multiple times (typically from different parts |
70 | /// of the widget hierarchy) for the same `event`, with different `callback`s, |
71 | /// as the event is being dispatched across the tree. Once the dispatching is |
72 | /// complete, the [GestureBinding] calls [resolve], and the first registered |
73 | /// callback is called. |
74 | /// |
75 | /// The `callback` is invoked with one argument, the `event`. |
76 | /// |
77 | /// Once the [register] method has been called with a particular `event`, it |
78 | /// must not be called for other `event`s until after [resolve] has been |
79 | /// called. Only one event disambiguation can be in flight at a time. In |
80 | /// normal use this is achieved by only registering callbacks for an event as |
81 | /// it is actively being dispatched (for example, in |
82 | /// [Listener.onPointerSignal]). |
83 | /// |
84 | /// See the documentation for the [PointerSignalResolver] class for an example |
85 | /// of using this method. |
86 | void register(PointerSignalEvent event, PointerSignalResolvedCallback callback) { |
87 | assert(_currentEvent == null || _isSameEvent(_currentEvent!, event)); |
88 | if (_firstRegisteredCallback != null) { |
89 | return; |
90 | } |
91 | _currentEvent = event; |
92 | _firstRegisteredCallback = callback; |
93 | } |
94 | |
95 | /// Resolves the event, calling the first registered callback if there was |
96 | /// one. |
97 | /// |
98 | /// This is called by the [GestureBinding] after the framework has finished |
99 | /// dispatching the pointer signal event. |
100 | @pragma('vm:notify-debugger-on-exception' ) |
101 | void resolve(PointerSignalEvent event) { |
102 | if (_firstRegisteredCallback == null) { |
103 | assert(_currentEvent == null); |
104 | // Nothing in the framework/app wants to handle the `event`. Allow the |
105 | // platform to trigger any default native actions. |
106 | event.respond(allowPlatformDefault: true); |
107 | return; |
108 | } |
109 | assert(_isSameEvent(_currentEvent!, event)); |
110 | try { |
111 | _firstRegisteredCallback!(_currentEvent!); |
112 | } catch (exception, stack) { |
113 | InformationCollector? collector; |
114 | assert(() { |
115 | collector = |
116 | () => <DiagnosticsNode>[ |
117 | DiagnosticsProperty<PointerSignalEvent>( |
118 | 'Event' , |
119 | event, |
120 | style: DiagnosticsTreeStyle.errorProperty, |
121 | ), |
122 | ]; |
123 | return true; |
124 | }()); |
125 | FlutterError.reportError( |
126 | FlutterErrorDetails( |
127 | exception: exception, |
128 | stack: stack, |
129 | library: 'gesture library' , |
130 | context: ErrorDescription('while resolving a PointerSignalEvent' ), |
131 | informationCollector: collector, |
132 | ), |
133 | ); |
134 | } |
135 | _firstRegisteredCallback = null; |
136 | _currentEvent = null; |
137 | } |
138 | } |
139 | |