"Fossies" - the Fresh Open Source Software Archive 
Member "flutter-3.7.0/packages/flutter/lib/src/widgets/actions.dart" (24 Jan 2023, 67850 Bytes) of package /linux/misc/flutter-3.7.0.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
See also the latest
Fossies "Diffs" side-by-side code changes report for "actions.dart":
3.3.10_vs_3.7.0.
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 import 'package:flutter/foundation.dart';
6 import 'package:flutter/gestures.dart';
7 import 'package:flutter/rendering.dart';
8 import 'package:flutter/scheduler.dart';
9 import 'package:flutter/services.dart';
10
11 import 'basic.dart';
12 import 'focus_manager.dart';
13 import 'focus_scope.dart';
14 import 'framework.dart';
15 import 'media_query.dart';
16 import 'shortcuts.dart';
17
18 // BuildContext/Element doesn't have a parent accessor, but it can be
19 // simulated with visitAncestorElements. _getParent is needed because
20 // context.getElementForInheritedWidgetOfExactType will return itself if it
21 // happens to be of the correct type. getParent should be O(1), since we
22 // always return false at the first ancestor.
23 BuildContext _getParent(BuildContext context) {
24 late final BuildContext parent;
25 context.visitAncestorElements((Element ancestor) {
26 parent = ancestor;
27 return false;
28 });
29 return parent;
30 }
31
32 /// An abstract class representing a particular configuration of an [Action].
33 ///
34 /// This class is what the [Shortcuts.shortcuts] map has as values, and is used
35 /// by an [ActionDispatcher] to look up an action and invoke it, giving it this
36 /// object to extract configuration information from.
37 ///
38 /// See also:
39 ///
40 /// * [Actions.invoke], which invokes the action associated with a specified
41 /// [Intent] using the [Actions] widget that most tightly encloses the given
42 /// [BuildContext].
43 @immutable
44 abstract class Intent with Diagnosticable {
45 /// Abstract const constructor. This constructor enables subclasses to provide
46 /// const constructors so that they can be used in const expressions.
47 const Intent();
48
49 /// An intent that is mapped to a [DoNothingAction], which, as the name
50 /// implies, does nothing.
51 ///
52 /// This Intent is mapped to an action in the [WidgetsApp] that does nothing,
53 /// so that it can be bound to a key in a [Shortcuts] widget in order to
54 /// disable a key binding made above it in the hierarchy.
55 static const DoNothingIntent doNothing = DoNothingIntent._();
56 }
57
58 /// The kind of callback that an [Action] uses to notify of changes to the
59 /// action's state.
60 ///
61 /// To register an action listener, call [Action.addActionListener].
62 typedef ActionListenerCallback = void Function(Action<Intent> action);
63
64 /// Base class for actions.
65 ///
66 /// As the name implies, an [Action] is an action or command to be performed.
67 /// They are typically invoked as a result of a user action, such as a keyboard
68 /// shortcut in a [Shortcuts] widget, which is used to look up an [Intent],
69 /// which is given to an [ActionDispatcher] to map the [Intent] to an [Action]
70 /// and invoke it.
71 ///
72 /// The [ActionDispatcher] can invoke an [Action] on the primary focus, or
73 /// without regard for focus.
74 ///
75 /// ### Action Overriding
76 ///
77 /// When using a leaf widget to build a more specialized widget, it's sometimes
78 /// desirable to change the default handling of an [Intent] defined in the leaf
79 /// widget. For instance, [TextField]'s [SelectAllTextIntent] by default selects
80 /// the text it currently contains, but in a US phone number widget that
81 /// consists of 3 different [TextField]s (area code, prefix and line number),
82 /// [SelectAllTextIntent] should instead select the text within all 3
83 /// [TextField]s.
84 ///
85 /// An overridable [Action] is a special kind of [Action] created using the
86 /// [Action.overridable] constructor. It has access to a default [Action], and a
87 /// nullable override [Action]. It has the same behavior as its override if that
88 /// exists, and mirrors the behavior of its `defaultAction` otherwise.
89 ///
90 /// The [Action.overridable] constructor creates overridable [Action]s that use
91 /// a [BuildContext] to find a suitable override in its ancestor [Actions]
92 /// widget. This can be used to provide a default implementation when creating a
93 /// general purpose leaf widget, and later override it when building a more
94 /// specialized widget using that leaf widget. Using the [TextField] example
95 /// above, the [TextField] widget uses an overridable [Action] to provide a
96 /// sensible default for [SelectAllTextIntent], while still allowing app
97 /// developers to change that if they add an ancestor [Actions] widget that maps
98 /// [SelectAllTextIntent] to a different [Action].
99 ///
100 /// See the article on [Using Actions and
101 /// Shortcuts](https://docs.flutter.dev/development/ui/advanced/actions_and_shortcuts)
102 /// for a detailed explanation.
103 ///
104 /// See also:
105 ///
106 /// * [Shortcuts], which is a widget that contains a key map, in which it looks
107 /// up key combinations in order to invoke actions.
108 /// * [Actions], which is a widget that defines a map of [Intent] to [Action]
109 /// and allows redefining of actions for its descendants.
110 /// * [ActionDispatcher], a class that takes an [Action] and invokes it, passing
111 /// a given [Intent].
112 /// * [Action.overridable] for an example on how to make an [Action]
113 /// overridable.
114 abstract class Action<T extends Intent> with Diagnosticable {
115 /// Creates an [Action].
116 Action();
117
118 /// Creates an [Action] that allows itself to be overridden by the closest
119 /// ancestor [Action] in the given [context] that handles the same [Intent],
120 /// if one exists.
121 ///
122 /// When invoked, the resulting [Action] tries to find the closest [Action] in
123 /// the given `context` that handles the same type of [Intent] as the
124 /// `defaultAction`, then calls its [Action.invoke] method. When no override
125 /// [Action]s can be found, it invokes the `defaultAction`.
126 ///
127 /// An overridable action delegates everything to its override if one exists,
128 /// and has the same behavior as its `defaultAction` otherwise. For this
129 /// reason, the override has full control over whether and how an [Intent]
130 /// should be handled, or a key event should be consumed. An override
131 /// [Action]'s [callingAction] property will be set to the [Action] it
132 /// currently overrides, giving it access to the default behavior. See the
133 /// [callingAction] property for an example.
134 ///
135 /// The `context` argument is the [BuildContext] to find the override with. It
136 /// is typically a [BuildContext] above the [Actions] widget that contains
137 /// this overridable [Action].
138 ///
139 /// The `defaultAction` argument is the [Action] to be invoked where there's
140 /// no ancestor [Action]s can't be found in `context` that handle the same
141 /// type of [Intent].
142 ///
143 /// This is useful for providing a set of default [Action]s in a leaf widget
144 /// to allow further overriding, or to allow the [Intent] to propagate to
145 /// parent widgets that also support this [Intent].
146 ///
147 /// {@tool dartpad}
148 /// This sample shows how to implement a rudimentary `CopyableText` widget
149 /// that responds to Ctrl-C by copying its own content to the clipboard.
150 ///
151 /// if `CopyableText` is to be provided in a package, developers using the
152 /// widget may want to change how copying is handled. As the author of the
153 /// package, you can enable that by making the corresponding [Action]
154 /// overridable. In the second part of the code sample, three `CopyableText`
155 /// widgets are used to build a verification code widget which overrides the
156 /// "copy" action by copying the combined numbers from all three `CopyableText`
157 /// widgets.
158 ///
159 /// ** See code in examples/api/lib/widgets/actions/action.action_overridable.0.dart **
160 /// {@end-tool}
161 factory Action.overridable({
162 required Action<T> defaultAction,
163 required BuildContext context,
164 }) {
165 return defaultAction._makeOverridableAction(context);
166 }
167
168 final ObserverList<ActionListenerCallback> _listeners = ObserverList<ActionListenerCallback>();
169
170 Action<T>? _currentCallingAction;
171 // ignore: use_setters_to_change_properties, (code predates enabling of this lint)
172 void _updateCallingAction(Action<T>? value) {
173 _currentCallingAction = value;
174 }
175
176 /// The [Action] overridden by this [Action].
177 ///
178 /// The [Action.overridable] constructor creates an overridable [Action] that
179 /// allows itself to be overridden by the closest ancestor [Action], and falls
180 /// back to its own `defaultAction` when no overrides can be found. When an
181 /// override is present, an overridable [Action] forwards all incoming
182 /// method calls to the override, and allows the override to access the
183 /// `defaultAction` via its [callingAction] property.
184 ///
185 /// Before forwarding the call to the override, the overridable [Action] is
186 /// responsible for setting [callingAction] to its `defaultAction`, which is
187 /// already taken care of by the overridable [Action] created using
188 /// [Action.overridable].
189 ///
190 /// This property is only non-null when this [Action] is an override of the
191 /// [callingAction], and is currently being invoked from [callingAction].
192 ///
193 /// Invoking [callingAction]'s methods, or accessing its properties, is
194 /// allowed and does not introduce infinite loops or infinite recursions.
195 ///
196 /// {@tool snippet}
197 /// An example `Action` that handles [PasteTextIntent] but has mostly the same
198 /// behavior as the overridable action. It's OK to call
199 /// `callingAction?.isActionEnabled` in the implementation of this `Action`.
200 ///
201 /// ```dart
202 /// class MyPasteAction extends Action<PasteTextIntent> {
203 /// @override
204 /// Object? invoke(PasteTextIntent intent) {
205 /// print(intent);
206 /// return callingAction?.invoke(intent);
207 /// }
208 ///
209 /// @override
210 /// bool get isActionEnabled => callingAction?.isActionEnabled ?? false;
211 ///
212 /// @override
213 /// bool consumesKey(PasteTextIntent intent) => callingAction?.consumesKey(intent) ?? false;
214 /// }
215 /// ```
216 /// {@end-tool}
217 @protected
218 Action<T>? get callingAction => _currentCallingAction;
219
220 /// Gets the type of intent this action responds to.
221 Type get intentType => T;
222
223 /// Returns true if the action is enabled and is ready to be invoked.
224 ///
225 /// This will be called by the [ActionDispatcher] before attempting to invoke
226 /// the action.
227 bool isEnabled(T intent) => isActionEnabled;
228
229 /// Whether this [Action] is inherently enabled.
230 ///
231 /// If [isActionEnabled] is false, then this [Action] is disabled for any
232 /// given [Intent].
233 //
234 /// If the enabled state changes, overriding subclasses must call
235 /// [notifyActionListeners] to notify any listeners of the change.
236 ///
237 /// In the case of an overridable `Action`, accessing this property creates
238 /// an dependency on the overridable `Action`s `lookupContext`.
239 bool get isActionEnabled => true;
240
241 /// Indicates whether this action should treat key events mapped to this
242 /// action as being "handled" when it is invoked via the key event.
243 ///
244 /// If the key is handled, then no other key event handlers in the focus chain
245 /// will receive the event.
246 ///
247 /// If the key event is not handled, it will be passed back to the engine, and
248 /// continue to be processed there, allowing text fields and non-Flutter
249 /// widgets to receive the key event.
250 ///
251 /// The default implementation returns true.
252 bool consumesKey(T intent) => true;
253
254 /// Called when the action is to be performed.
255 ///
256 /// This is called by the [ActionDispatcher] when an action is invoked via
257 /// [Actions.invoke], or when an action is invoked using
258 /// [ActionDispatcher.invokeAction] directly.
259 ///
260 /// This method is only meant to be invoked by an [ActionDispatcher], or by
261 /// its subclasses, and only when [isEnabled] is true.
262 ///
263 /// When overriding this method, the returned value can be any Object, but
264 /// changing the return type of the override to match the type of the returned
265 /// value provides more type safety.
266 ///
267 /// For instance, if an override of [invoke] returned an `int`, then it might
268 /// be defined like so:
269 ///
270 /// ```dart
271 /// class IncrementIntent extends Intent {
272 /// const IncrementIntent({required this.index});
273 ///
274 /// final int index;
275 /// }
276 ///
277 /// class MyIncrementAction extends Action<IncrementIntent> {
278 /// @override
279 /// int invoke(IncrementIntent intent) {
280 /// return intent.index + 1;
281 /// }
282 /// }
283 /// ```
284 ///
285 /// To receive the result of invoking an action, it must be invoked using
286 /// [Actions.invoke], or by invoking it using an [ActionDispatcher]. An action
287 /// invoked via a [Shortcuts] widget will have its return value ignored.
288 @protected
289 Object? invoke(T intent);
290
291 /// Register a callback to listen for changes to the state of this action.
292 ///
293 /// If you call this, you must call [removeActionListener] a matching number
294 /// of times, or memory leaks will occur. To help manage this and avoid memory
295 /// leaks, use of the [ActionListener] widget to register and unregister your
296 /// listener appropriately is highly recommended.
297 ///
298 /// {@template flutter.widgets.Action.addActionListener}
299 /// If a listener had been added twice, and is removed once during an
300 /// iteration (i.e. in response to a notification), it will still be called
301 /// again. If, on the other hand, it is removed as many times as it was
302 /// registered, then it will no longer be called. This odd behavior is the
303 /// result of the [Action] not being able to determine which listener
304 /// is being removed, since they are identical, and therefore conservatively
305 /// still calling all the listeners when it knows that any are still
306 /// registered.
307 ///
308 /// This surprising behavior can be unexpectedly observed when registering a
309 /// listener on two separate objects which are both forwarding all
310 /// registrations to a common upstream object.
311 /// {@endtemplate}
312 @mustCallSuper
313 void addActionListener(ActionListenerCallback listener) => _listeners.add(listener);
314
315 /// Remove a previously registered closure from the list of closures that are
316 /// notified when the object changes.
317 ///
318 /// If the given listener is not registered, the call is ignored.
319 ///
320 /// If you call [addActionListener], you must call this method a matching
321 /// number of times, or memory leaks will occur. To help manage this and avoid
322 /// memory leaks, use of the [ActionListener] widget to register and
323 /// unregister your listener appropriately is highly recommended.
324 ///
325 /// {@macro flutter.widgets.Action.addActionListener}
326 @mustCallSuper
327 void removeActionListener(ActionListenerCallback listener) => _listeners.remove(listener);
328
329 /// Call all the registered listeners.
330 ///
331 /// Subclasses should call this method whenever the object changes, to notify
332 /// any clients the object may have changed. Listeners that are added during this
333 /// iteration will not be visited. Listeners that are removed during this
334 /// iteration will not be visited after they are removed.
335 ///
336 /// Exceptions thrown by listeners will be caught and reported using
337 /// [FlutterError.reportError].
338 ///
339 /// Surprising behavior can result when reentrantly removing a listener (i.e.
340 /// in response to a notification) that has been registered multiple times.
341 /// See the discussion at [removeActionListener].
342 @protected
343 @visibleForTesting
344 @pragma('vm:notify-debugger-on-exception')
345 void notifyActionListeners() {
346 if (_listeners.isEmpty) {
347 return;
348 }
349
350 // Make a local copy so that a listener can unregister while the list is
351 // being iterated over.
352 final List<ActionListenerCallback> localListeners = List<ActionListenerCallback>.of(_listeners);
353 for (final ActionListenerCallback listener in localListeners) {
354 InformationCollector? collector;
355 assert(() {
356 collector = () => <DiagnosticsNode>[
357 DiagnosticsProperty<Action<T>>(
358 'The $runtimeType sending notification was',
359 this,
360 style: DiagnosticsTreeStyle.errorProperty,
361 ),
362 ];
363 return true;
364 }());
365 try {
366 if (_listeners.contains(listener)) {
367 listener(this);
368 }
369 } catch (exception, stack) {
370 FlutterError.reportError(FlutterErrorDetails(
371 exception: exception,
372 stack: stack,
373 library: 'widgets library',
374 context: ErrorDescription('while dispatching notifications for $runtimeType'),
375 informationCollector: collector,
376 ));
377 }
378 }
379 }
380
381 Action<T> _makeOverridableAction(BuildContext context) {
382 return _OverridableAction<T>(defaultAction: this, lookupContext: context);
383 }
384 }
385
386 /// A helper widget for making sure that listeners on an action are removed properly.
387 ///
388 /// Listeners on the [Action] class must have their listener callbacks removed
389 /// with [Action.removeActionListener] when the listener is disposed of. This widget
390 /// helps with that, by providing a lifetime for the connection between the
391 /// [listener] and the [Action], and by handling the adding and removing of
392 /// the [listener] at the right points in the widget lifecycle.
393 ///
394 /// If you listen to an [Action] widget in a widget hierarchy, you should use
395 /// this widget. If you are using an [Action] outside of a widget context, then
396 /// you must call removeListener yourself.
397 ///
398 /// {@tool dartpad}
399 /// This example shows how ActionListener handles adding and removing of
400 /// the [listener] in the widget lifecycle.
401 ///
402 /// ** See code in examples/api/lib/widgets/actions/action_listener.0.dart **
403 /// {@end-tool}
404 ///
405 @immutable
406 class ActionListener extends StatefulWidget {
407 /// Create a const [ActionListener].
408 ///
409 /// The [listener], [action], and [child] arguments must not be null.
410 const ActionListener({
411 super.key,
412 required this.listener,
413 required this.action,
414 required this.child,
415 }) : assert(listener != null),
416 assert(action != null),
417 assert(child != null);
418
419 /// The [ActionListenerCallback] callback to register with the [action].
420 ///
421 /// Must not be null.
422 final ActionListenerCallback listener;
423
424 /// The [Action] that the callback will be registered with.
425 ///
426 /// Must not be null.
427 final Action<Intent> action;
428
429 /// {@macro flutter.widgets.ProxyWidget.child}
430 final Widget child;
431
432 @override
433 State<ActionListener> createState() => _ActionListenerState();
434 }
435
436 class _ActionListenerState extends State<ActionListener> {
437 @override
438 void initState() {
439 super.initState();
440 widget.action.addActionListener(widget.listener);
441 }
442
443 @override
444 void didUpdateWidget(ActionListener oldWidget) {
445 super.didUpdateWidget(oldWidget);
446 if (oldWidget.action == widget.action && oldWidget.listener == widget.listener) {
447 return;
448 }
449 oldWidget.action.removeActionListener(oldWidget.listener);
450 widget.action.addActionListener(widget.listener);
451 }
452
453 @override
454 void dispose() {
455 widget.action.removeActionListener(widget.listener);
456 super.dispose();
457 }
458
459 @override
460 Widget build(BuildContext context) => widget.child;
461 }
462
463 /// An abstract [Action] subclass that adds an optional [BuildContext] to the
464 /// [invoke] method to be able to provide context to actions.
465 ///
466 /// [ActionDispatcher.invokeAction] checks to see if the action it is invoking
467 /// is a [ContextAction], and if it is, supplies it with a context.
468 abstract class ContextAction<T extends Intent> extends Action<T> {
469 /// Called when the action is to be performed.
470 ///
471 /// This is called by the [ActionDispatcher] when an action is invoked via
472 /// [Actions.invoke], or when an action is invoked using
473 /// [ActionDispatcher.invokeAction] directly.
474 ///
475 /// This method is only meant to be invoked by an [ActionDispatcher], or by
476 /// its subclasses, and only when [isEnabled] is true.
477 ///
478 /// The optional `context` parameter is the context of the invocation of the
479 /// action, and in the case of an action invoked by a [ShortcutManager], via
480 /// a [Shortcuts] widget, will be the context of the [Shortcuts] widget.
481 ///
482 /// When overriding this method, the returned value can be any Object, but
483 /// changing the return type of the override to match the type of the returned
484 /// value provides more type safety.
485 ///
486 /// For instance, if an override of [invoke] returned an `int`, then it might
487 /// be defined like so:
488 ///
489 /// ```dart
490 /// class IncrementIntent extends Intent {
491 /// const IncrementIntent({required this.index});
492 ///
493 /// final int index;
494 /// }
495 ///
496 /// class MyIncrementAction extends ContextAction<IncrementIntent> {
497 /// @override
498 /// int invoke(IncrementIntent intent, [BuildContext? context]) {
499 /// return intent.index + 1;
500 /// }
501 /// }
502 /// ```
503 @protected
504 @override
505 Object? invoke(T intent, [BuildContext? context]);
506
507 @override
508 ContextAction<T> _makeOverridableAction(BuildContext context) {
509 return _OverridableContextAction<T>(defaultAction: this, lookupContext: context);
510 }
511 }
512
513 /// The signature of a callback accepted by [CallbackAction].
514 typedef OnInvokeCallback<T extends Intent> = Object? Function(T intent);
515
516 /// An [Action] that takes a callback in order to configure it without having to
517 /// create an explicit [Action] subclass just to call a callback.
518 ///
519 /// See also:
520 ///
521 /// * [Shortcuts], which is a widget that contains a key map, in which it looks
522 /// up key combinations in order to invoke actions.
523 /// * [Actions], which is a widget that defines a map of [Intent] to [Action]
524 /// and allows redefining of actions for its descendants.
525 /// * [ActionDispatcher], a class that takes an [Action] and invokes it using a
526 /// [FocusNode] for context.
527 class CallbackAction<T extends Intent> extends Action<T> {
528 /// A constructor for a [CallbackAction].
529 ///
530 /// The `intentKey` and [onInvoke] parameters must not be null.
531 /// The [onInvoke] parameter is required.
532 CallbackAction({required this.onInvoke}) : assert(onInvoke != null);
533
534 /// The callback to be called when invoked.
535 ///
536 /// Must not be null.
537 @protected
538 final OnInvokeCallback<T> onInvoke;
539
540 @override
541 Object? invoke(T intent) => onInvoke(intent);
542 }
543
544 /// An action dispatcher that simply invokes the actions given to it.
545 ///
546 /// See also:
547 ///
548 /// - [ShortcutManager], that uses this class to invoke actions.
549 /// - [Shortcuts] widget, which defines key mappings to [Intent]s.
550 /// - [Actions] widget, which defines a mapping between a in [Intent] type and
551 /// an [Action].
552 class ActionDispatcher with Diagnosticable {
553 /// Creates an action dispatcher that invokes actions directly.
554 const ActionDispatcher();
555
556 /// Invokes the given `action`, passing it the given `intent`.
557 ///
558 /// The action will be invoked with the given `context`, if given, but only if
559 /// the action is a [ContextAction] subclass. If no `context` is given, and
560 /// the action is a [ContextAction], then the context from the [primaryFocus]
561 /// is used.
562 ///
563 /// Returns the object returned from [Action.invoke].
564 ///
565 /// The caller must receive a `true` result from [Action.isEnabled] before
566 /// calling this function. This function will assert if the action is not
567 /// enabled when called.
568 Object? invokeAction(
569 covariant Action<Intent> action,
570 covariant Intent intent, [
571 BuildContext? context,
572 ]) {
573 assert(action != null);
574 assert(intent != null);
575 assert(action.isEnabled(intent), 'Action must be enabled when calling invokeAction');
576 if (action is ContextAction) {
577 context ??= primaryFocus?.context;
578 return action.invoke(intent, context);
579 } else {
580 return action.invoke(intent);
581 }
582 }
583 }
584
585 /// A widget that establishes an [ActionDispatcher] and a map of [Intent] to
586 /// [Action] to be used by its descendants when invoking an [Action].
587 ///
588 /// Actions are typically invoked using [Actions.invoke] with the context
589 /// containing the ambient [Actions] widget.
590 ///
591 /// {@tool dartpad}
592 /// This example creates a custom [Action] subclass `ModifyAction` for modifying
593 /// a model, and another, `SaveAction` for saving it.
594 ///
595 /// This example demonstrates passing arguments to the [Intent] to be carried to
596 /// the [Action]. Actions can get data either from their own construction (like
597 /// the `model` in this example), or from the intent passed to them when invoked
598 /// (like the increment `amount` in this example).
599 ///
600 /// This example also demonstrates how to use Intents to limit a widget's
601 /// dependencies on its surroundings. The `SaveButton` widget defined in this
602 /// example can invoke actions defined in its ancestor widgets, which can be
603 /// customized to match the part of the widget tree that it is in. It doesn't
604 /// need to know about the `SaveAction` class, only the `SaveIntent`, and it
605 /// only needs to know about a value notifier, not the entire model.
606 ///
607 /// ** See code in examples/api/lib/widgets/actions/actions.0.dart **
608 /// {@end-tool}
609 ///
610 /// See also:
611 ///
612 /// * [ActionDispatcher], the object that this widget uses to manage actions.
613 /// * [Action], a class for containing and defining an invocation of a user
614 /// action.
615 /// * [Intent], a class that holds a unique [LocalKey] identifying an action,
616 /// as well as configuration information for running the [Action].
617 /// * [Shortcuts], a widget used to bind key combinations to [Intent]s.
618 class Actions extends StatefulWidget {
619 /// Creates an [Actions] widget.
620 ///
621 /// The [child], [actions], and [dispatcher] arguments must not be null.
622 const Actions({
623 super.key,
624 this.dispatcher,
625 required this.actions,
626 required this.child,
627 }) : assert(actions != null),
628 assert(child != null);
629
630 /// The [ActionDispatcher] object that invokes actions.
631 ///
632 /// This is what is returned from [Actions.of], and used by [Actions.invoke].
633 ///
634 /// If this [dispatcher] is null, then [Actions.of] and [Actions.invoke] will
635 /// look up the tree until they find an Actions widget that has a dispatcher
636 /// set. If not such widget is found, then they will return/use a
637 /// default-constructed [ActionDispatcher].
638 final ActionDispatcher? dispatcher;
639
640 /// {@template flutter.widgets.actions.actions}
641 /// A map of [Intent] keys to [Action<Intent>] objects that defines which
642 /// actions this widget knows about.
643 ///
644 /// For performance reasons, it is recommended that a pre-built map is
645 /// passed in here (e.g. a final variable from your widget class) instead of
646 /// defining it inline in the build function.
647 /// {@endtemplate}
648 final Map<Type, Action<Intent>> actions;
649
650 /// {@macro flutter.widgets.ProxyWidget.child}
651 final Widget child;
652
653 // Visits the Actions widget ancestors of the given element using
654 // getElementForInheritedWidgetOfExactType. Returns true if the visitor found
655 // what it was looking for.
656 static bool _visitActionsAncestors(BuildContext context, bool Function(InheritedElement element) visitor) {
657 InheritedElement? actionsElement = context.getElementForInheritedWidgetOfExactType<_ActionsMarker>();
658 while (actionsElement != null) {
659 if (visitor(actionsElement) == true) {
660 break;
661 }
662 // _getParent is needed here because
663 // context.getElementForInheritedWidgetOfExactType will return itself if it
664 // happens to be of the correct type.
665 final BuildContext parent = _getParent(actionsElement);
666 actionsElement = parent.getElementForInheritedWidgetOfExactType<_ActionsMarker>();
667 }
668 return actionsElement != null;
669 }
670
671 // Finds the nearest valid ActionDispatcher, or creates a new one if it
672 // doesn't find one.
673 static ActionDispatcher _findDispatcher(BuildContext context) {
674 ActionDispatcher? dispatcher;
675 _visitActionsAncestors(context, (InheritedElement element) {
676 final ActionDispatcher? found = (element.widget as _ActionsMarker).dispatcher;
677 if (found != null) {
678 dispatcher = found;
679 return true;
680 }
681 return false;
682 });
683 return dispatcher ?? const ActionDispatcher();
684 }
685
686 /// Returns a [VoidCallback] handler that invokes the bound action for the
687 /// given `intent` if the action is enabled, and returns null if the action is
688 /// not enabled, or no matching action is found.
689 ///
690 /// This is intended to be used in widgets which have something similar to an
691 /// `onTap` handler, which takes a `VoidCallback`, and can be set to the
692 /// result of calling this function.
693 ///
694 /// Creates a dependency on the [Actions] widget that maps the bound action so
695 /// that if the actions change, the context will be rebuilt and find the
696 /// updated action.
697 static VoidCallback? handler<T extends Intent>(BuildContext context, T intent) {
698 final Action<T>? action = Actions.maybeFind<T>(context);
699 if (action != null && action.isEnabled(intent)) {
700 return () {
701 // Could be that the action was enabled when the closure was created,
702 // but is now no longer enabled, so check again.
703 if (action.isEnabled(intent)) {
704 Actions.of(context).invokeAction(action, intent, context);
705 }
706 };
707 }
708 return null;
709 }
710
711 /// Finds the [Action] bound to the given intent type `T` in the given `context`.
712 ///
713 /// Creates a dependency on the [Actions] widget that maps the bound action so
714 /// that if the actions change, the context will be rebuilt and find the
715 /// updated action.
716 ///
717 /// The optional `intent` argument supplies the type of the intent to look for
718 /// if the concrete type of the intent sought isn't available. If not
719 /// supplied, then `T` is used.
720 ///
721 /// If no [Actions] widget surrounds the given context, this function will
722 /// assert in debug mode, and throw an exception in release mode.
723 ///
724 /// See also:
725 ///
726 /// * [maybeFind], which is similar to this function, but will return null if
727 /// no [Actions] ancestor is found.
728 static Action<T> find<T extends Intent>(BuildContext context, { T? intent }) {
729 final Action<T>? action = maybeFind(context, intent: intent);
730
731 assert(() {
732 if (action == null) {
733 final Type type = intent?.runtimeType ?? T;
734 throw FlutterError(
735 'Unable to find an action for a $type in an $Actions widget '
736 'in the given context.\n'
737 "$Actions.find() was called on a context that doesn't contain an "
738 '$Actions widget with a mapping for the given intent type.\n'
739 'The context used was:\n'
740 ' $context\n'
741 'The intent type requested was:\n'
742 ' $type',
743 );
744 }
745 return true;
746 }());
747 return action!;
748 }
749
750 /// Finds the [Action] bound to the given intent type `T` in the given `context`.
751 ///
752 /// Creates a dependency on the [Actions] widget that maps the bound action so
753 /// that if the actions change, the context will be rebuilt and find the
754 /// updated action.
755 ///
756 /// The optional `intent` argument supplies the type of the intent to look for
757 /// if the concrete type of the intent sought isn't available. If not
758 /// supplied, then `T` is used.
759 ///
760 /// If no [Actions] widget surrounds the given context, this function will
761 /// return null.
762 ///
763 /// See also:
764 ///
765 /// * [find], which is similar to this function, but will throw if
766 /// no [Actions] ancestor is found.
767 static Action<T>? maybeFind<T extends Intent>(BuildContext context, { T? intent }) {
768 Action<T>? action;
769
770 // Specialize the type if a runtime example instance of the intent is given.
771 // This allows this function to be called by code that doesn't know the
772 // concrete type of the intent at compile time.
773 final Type type = intent?.runtimeType ?? T;
774 assert(
775 type != Intent,
776 'The type passed to "find" resolved to "Intent": either a non-Intent '
777 'generic type argument or an example intent derived from Intent must be '
778 'specified. Intent may be used as the generic type as long as the optional '
779 '"intent" argument is passed.',
780 );
781
782 _visitActionsAncestors(context, (InheritedElement element) {
783 final _ActionsMarker actions = element.widget as _ActionsMarker;
784 final Action<T>? result = _castAction(actions, intent: intent);
785 if (result != null) {
786 context.dependOnInheritedElement(element);
787 action = result;
788 return true;
789 }
790 return false;
791 });
792
793 return action;
794 }
795
796 static Action<T>? _maybeFindWithoutDependingOn<T extends Intent>(BuildContext context, { T? intent }) {
797 Action<T>? action;
798
799 // Specialize the type if a runtime example instance of the intent is given.
800 // This allows this function to be called by code that doesn't know the
801 // concrete type of the intent at compile time.
802 final Type type = intent?.runtimeType ?? T;
803 assert(
804 type != Intent,
805 'The type passed to "find" resolved to "Intent": either a non-Intent '
806 'generic type argument or an example intent derived from Intent must be '
807 'specified. Intent may be used as the generic type as long as the optional '
808 '"intent" argument is passed.',
809 );
810
811 _visitActionsAncestors(context, (InheritedElement element) {
812 final _ActionsMarker actions = element.widget as _ActionsMarker;
813 final Action<T>? result = _castAction(actions, intent: intent);
814 if (result != null) {
815 action = result;
816 return true;
817 }
818 return false;
819 });
820
821 return action;
822 }
823
824 // Find the [Action] that handles the given `intent` in the given
825 // `_ActionsMarker`, and verify it has the right type parameter.
826 static Action<T>? _castAction<T extends Intent>(_ActionsMarker actionsMarker, { T? intent }) {
827 final Action<Intent>? mappedAction = actionsMarker.actions[intent?.runtimeType ?? T];
828 if (mappedAction is Action<T>?) {
829 return mappedAction;
830 } else {
831 assert(
832 false,
833 '$T cannot be handled by an Action of runtime type ${mappedAction.runtimeType}.'
834 );
835 return null;
836 }
837 }
838
839 /// Returns the [ActionDispatcher] associated with the [Actions] widget that
840 /// most tightly encloses the given [BuildContext].
841 ///
842 /// Will return a newly created [ActionDispatcher] if no ambient [Actions]
843 /// widget is found.
844 static ActionDispatcher of(BuildContext context) {
845 assert(context != null);
846 final _ActionsMarker? marker = context.dependOnInheritedWidgetOfExactType<_ActionsMarker>();
847 return marker?.dispatcher ?? _findDispatcher(context);
848 }
849
850 /// Invokes the action associated with the given [Intent] using the
851 /// [Actions] widget that most tightly encloses the given [BuildContext].
852 ///
853 /// This method returns the result of invoking the action's [Action.invoke]
854 /// method.
855 ///
856 /// The `context` and `intent` arguments must not be null.
857 ///
858 /// If the given `intent` doesn't map to an action, then it will look to the
859 /// next ancestor [Actions] widget in the hierarchy until it reaches the root.
860 ///
861 /// This method will throw an exception if no ambient [Actions] widget is
862 /// found, or when a suitable [Action] is found but it returns false for
863 /// [Action.isEnabled].
864 static Object? invoke<T extends Intent>(
865 BuildContext context,
866 T intent,
867 ) {
868 assert(intent != null);
869 assert(context != null);
870 Object? returnValue;
871
872 final bool actionFound = _visitActionsAncestors(context, (InheritedElement element) {
873 final _ActionsMarker actions = element.widget as _ActionsMarker;
874 final Action<T>? result = _castAction(actions, intent: intent);
875 if (result != null && result.isEnabled(intent)) {
876 // Invoke the action we found using the relevant dispatcher from the Actions
877 // Element we found.
878 returnValue = _findDispatcher(element).invokeAction(result, intent, context);
879 }
880 return result != null;
881 });
882
883 assert(() {
884 if (!actionFound) {
885 throw FlutterError(
886 'Unable to find an action for an Intent with type '
887 '${intent.runtimeType} in an $Actions widget in the given context.\n'
888 '$Actions.invoke() was unable to find an $Actions widget that '
889 "contained a mapping for the given intent, or the intent type isn't the "
890 'same as the type argument to invoke (which is $T - try supplying a '
891 'type argument to invoke if one was not given)\n'
892 'The context used was:\n'
893 ' $context\n'
894 'The intent type requested was:\n'
895 ' ${intent.runtimeType}',
896 );
897 }
898 return true;
899 }());
900 return returnValue;
901 }
902
903 /// Invokes the action associated with the given [Intent] using the
904 /// [Actions] widget that most tightly encloses the given [BuildContext].
905 ///
906 /// This method returns the result of invoking the action's [Action.invoke]
907 /// method. If no action mapping was found for the specified intent, or if the
908 /// first action found was disabled, or the action itself returns null
909 /// from [Action.invoke], then this method returns null.
910 ///
911 /// The `context` and `intent` arguments must not be null.
912 ///
913 /// If the given `intent` doesn't map to an action, then it will look to the
914 /// next ancestor [Actions] widget in the hierarchy until it reaches the root.
915 /// If a suitable [Action] is found but its [Action.isEnabled] returns false,
916 /// the search will stop and this method will return null.
917 static Object? maybeInvoke<T extends Intent>(
918 BuildContext context,
919 T intent,
920 ) {
921 assert(intent != null);
922 assert(context != null);
923 Object? returnValue;
924
925 _visitActionsAncestors(context, (InheritedElement element) {
926 final _ActionsMarker actions = element.widget as _ActionsMarker;
927 final Action<T>? result = _castAction(actions, intent: intent);
928 if (result != null && result.isEnabled(intent)) {
929 // Invoke the action we found using the relevant dispatcher from the Actions
930 // Element we found.
931 returnValue = _findDispatcher(element).invokeAction(result, intent, context);
932 }
933 return result != null;
934 });
935 return returnValue;
936 }
937
938 @override
939 State<Actions> createState() => _ActionsState();
940
941 @override
942 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
943 super.debugFillProperties(properties);
944 properties.add(DiagnosticsProperty<ActionDispatcher>('dispatcher', dispatcher));
945 properties.add(DiagnosticsProperty<Map<Type, Action<Intent>>>('actions', actions));
946 }
947 }
948
949 class _ActionsState extends State<Actions> {
950 // The set of actions that this Actions widget is current listening to.
951 Set<Action<Intent>>? listenedActions = <Action<Intent>>{};
952 // Used to tell the marker to rebuild its dependencies when the state of an
953 // action in the map changes.
954 Object rebuildKey = Object();
955
956 @override
957 void initState() {
958 super.initState();
959 _updateActionListeners();
960 }
961
962 void _handleActionChanged(Action<Intent> action) {
963 // Generate a new key so that the marker notifies dependents.
964 setState(() {
965 rebuildKey = Object();
966 });
967 }
968
969 void _updateActionListeners() {
970 final Set<Action<Intent>> widgetActions = widget.actions.values.toSet();
971 final Set<Action<Intent>> removedActions = listenedActions!.difference(widgetActions);
972 final Set<Action<Intent>> addedActions = widgetActions.difference(listenedActions!);
973
974 for (final Action<Intent> action in removedActions) {
975 action.removeActionListener(_handleActionChanged);
976 }
977 for (final Action<Intent> action in addedActions) {
978 action.addActionListener(_handleActionChanged);
979 }
980 listenedActions = widgetActions;
981 }
982
983 @override
984 void didUpdateWidget(Actions oldWidget) {
985 super.didUpdateWidget(oldWidget);
986 _updateActionListeners();
987 }
988
989 @override
990 void dispose() {
991 super.dispose();
992 for (final Action<Intent> action in listenedActions!) {
993 action.removeActionListener(_handleActionChanged);
994 }
995 listenedActions = null;
996 }
997
998 @override
999 Widget build(BuildContext context) {
1000 return _ActionsMarker(
1001 actions: widget.actions,
1002 dispatcher: widget.dispatcher,
1003 rebuildKey: rebuildKey,
1004 child: widget.child,
1005 );
1006 }
1007 }
1008
1009 // An inherited widget used by Actions widget for fast lookup of the Actions
1010 // widget information.
1011 class _ActionsMarker extends InheritedWidget {
1012 const _ActionsMarker({
1013 required this.dispatcher,
1014 required this.actions,
1015 required this.rebuildKey,
1016 required super.child,
1017 }) : assert(child != null),
1018 assert(actions != null);
1019
1020 final ActionDispatcher? dispatcher;
1021 final Map<Type, Action<Intent>> actions;
1022 final Object rebuildKey;
1023
1024 @override
1025 bool updateShouldNotify(_ActionsMarker oldWidget) {
1026 return rebuildKey != oldWidget.rebuildKey
1027 || oldWidget.dispatcher != dispatcher
1028 || !mapEquals<Type, Action<Intent>>(oldWidget.actions, actions);
1029 }
1030 }
1031
1032 /// A widget that combines the functionality of [Actions], [Shortcuts],
1033 /// [MouseRegion] and a [Focus] widget to create a detector that defines actions
1034 /// and key bindings, and provides callbacks for handling focus and hover
1035 /// highlights.
1036 ///
1037 /// {@youtube 560 315 https://www.youtube.com/watch?v=R84AGg0lKs8}
1038 ///
1039 /// This widget can be used to give a control the required detection modes for
1040 /// focus and hover handling. It is most often used when authoring a new control
1041 /// widget, and the new control should be enabled for keyboard traversal and
1042 /// activation.
1043 ///
1044 /// {@tool dartpad}
1045 /// This example shows how keyboard interaction can be added to a custom control
1046 /// that changes color when hovered and focused, and can toggle a light when
1047 /// activated, either by touch or by hitting the `X` key on the keyboard when
1048 /// the "And Me" button has the keyboard focus (be sure to use TAB to move the
1049 /// focus to the "And Me" button before trying it out).
1050 ///
1051 /// This example defines its own key binding for the `X` key, but in this case,
1052 /// there is also a default key binding for [ActivateAction] in the default key
1053 /// bindings created by [WidgetsApp] (the parent for [MaterialApp], and
1054 /// [CupertinoApp]), so the `ENTER` key will also activate the buttons.
1055 ///
1056 /// ** See code in examples/api/lib/widgets/actions/focusable_action_detector.0.dart **
1057 /// {@end-tool}
1058 ///
1059 /// This widget doesn't have any visual representation, it is just a detector that
1060 /// provides focus and hover capabilities.
1061 ///
1062 /// It hosts its own [FocusNode] or uses [focusNode], if given.
1063 class FocusableActionDetector extends StatefulWidget {
1064 /// Create a const [FocusableActionDetector].
1065 ///
1066 /// The [enabled], [autofocus], [mouseCursor], and [child] arguments must not be null.
1067 const FocusableActionDetector({
1068 super.key,
1069 this.enabled = true,
1070 this.focusNode,
1071 this.autofocus = false,
1072 this.descendantsAreFocusable = true,
1073 this.descendantsAreTraversable = true,
1074 this.shortcuts,
1075 this.actions,
1076 this.onShowFocusHighlight,
1077 this.onShowHoverHighlight,
1078 this.onFocusChange,
1079 this.mouseCursor = MouseCursor.defer,
1080 this.includeFocusSemantics = true,
1081 required this.child,
1082 }) : assert(enabled != null),
1083 assert(autofocus != null),
1084 assert(mouseCursor != null),
1085 assert(child != null);
1086
1087 /// Is this widget enabled or not.
1088 ///
1089 /// If disabled, will not send any notifications needed to update highlight or
1090 /// focus state, and will not define or respond to any actions or shortcuts.
1091 ///
1092 /// When disabled, adds [Focus] to the widget tree, but sets
1093 /// [Focus.canRequestFocus] to false.
1094 final bool enabled;
1095
1096 /// {@macro flutter.widgets.Focus.focusNode}
1097 final FocusNode? focusNode;
1098
1099 /// {@macro flutter.widgets.Focus.autofocus}
1100 final bool autofocus;
1101
1102 /// {@macro flutter.widgets.Focus.descendantsAreFocusable}
1103 final bool descendantsAreFocusable;
1104
1105 /// {@macro flutter.widgets.Focus.descendantsAreTraversable}
1106 final bool descendantsAreTraversable;
1107
1108 /// {@macro flutter.widgets.actions.actions}
1109 final Map<Type, Action<Intent>>? actions;
1110
1111 /// {@macro flutter.widgets.shortcuts.shortcuts}
1112 final Map<ShortcutActivator, Intent>? shortcuts;
1113
1114 /// A function that will be called when the focus highlight should be shown or
1115 /// hidden.
1116 ///
1117 /// This method is not triggered at the unmount of the widget.
1118 final ValueChanged<bool>? onShowFocusHighlight;
1119
1120 /// A function that will be called when the hover highlight should be shown or hidden.
1121 ///
1122 /// This method is not triggered at the unmount of the widget.
1123 final ValueChanged<bool>? onShowHoverHighlight;
1124
1125 /// A function that will be called when the focus changes.
1126 ///
1127 /// Called with true if the [focusNode] has primary focus.
1128 final ValueChanged<bool>? onFocusChange;
1129
1130 /// The cursor for a mouse pointer when it enters or is hovering over the
1131 /// widget.
1132 ///
1133 /// The [mouseCursor] defaults to [MouseCursor.defer], deferring the choice of
1134 /// cursor to the next region behind it in hit-test order.
1135 final MouseCursor mouseCursor;
1136
1137 /// Whether to include semantics from [Focus].
1138 ///
1139 /// Defaults to true.
1140 final bool includeFocusSemantics;
1141
1142 /// The child widget for this [FocusableActionDetector] widget.
1143 ///
1144 /// {@macro flutter.widgets.ProxyWidget.child}
1145 final Widget child;
1146
1147 @override
1148 State<FocusableActionDetector> createState() => _FocusableActionDetectorState();
1149 }
1150
1151 class _FocusableActionDetectorState extends State<FocusableActionDetector> {
1152 @override
1153 void initState() {
1154 super.initState();
1155 SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
1156 _updateHighlightMode(FocusManager.instance.highlightMode);
1157 });
1158 FocusManager.instance.addHighlightModeListener(_handleFocusHighlightModeChange);
1159 }
1160
1161 @override
1162 void dispose() {
1163 FocusManager.instance.removeHighlightModeListener(_handleFocusHighlightModeChange);
1164 super.dispose();
1165 }
1166
1167 bool _canShowHighlight = false;
1168 void _updateHighlightMode(FocusHighlightMode mode) {
1169 _mayTriggerCallback(task: () {
1170 switch (FocusManager.instance.highlightMode) {
1171 case FocusHighlightMode.touch:
1172 _canShowHighlight = false;
1173 break;
1174 case FocusHighlightMode.traditional:
1175 _canShowHighlight = true;
1176 break;
1177 }
1178 });
1179 }
1180
1181 // Have to have this separate from the _updateHighlightMode because it gets
1182 // called in initState, where things aren't mounted yet.
1183 // Since this method is a highlight mode listener, it is only called
1184 // immediately following pointer events.
1185 void _handleFocusHighlightModeChange(FocusHighlightMode mode) {
1186 if (!mounted) {
1187 return;
1188 }
1189 _updateHighlightMode(mode);
1190 }
1191
1192 bool _hovering = false;
1193 void _handleMouseEnter(PointerEnterEvent event) {
1194 if (!_hovering) {
1195 _mayTriggerCallback(task: () {
1196 _hovering = true;
1197 });
1198 }
1199 }
1200
1201 void _handleMouseExit(PointerExitEvent event) {
1202 if (_hovering) {
1203 _mayTriggerCallback(task: () {
1204 _hovering = false;
1205 });
1206 }
1207 }
1208
1209 bool _focused = false;
1210 void _handleFocusChange(bool focused) {
1211 if (_focused != focused) {
1212 _mayTriggerCallback(task: () {
1213 _focused = focused;
1214 });
1215 widget.onFocusChange?.call(_focused);
1216 }
1217 }
1218
1219 // Record old states, do `task` if not null, then compare old states with the
1220 // new states, and trigger callbacks if necessary.
1221 //
1222 // The old states are collected from `oldWidget` if it is provided, or the
1223 // current widget (before doing `task`) otherwise. The new states are always
1224 // collected from the current widget.
1225 void _mayTriggerCallback({VoidCallback? task, FocusableActionDetector? oldWidget}) {
1226 bool shouldShowHoverHighlight(FocusableActionDetector target) {
1227 return _hovering && target.enabled && _canShowHighlight;
1228 }
1229
1230 bool canRequestFocus(FocusableActionDetector target) {
1231 final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional;
1232 switch (mode) {
1233 case NavigationMode.traditional:
1234 return target.enabled;
1235 case NavigationMode.directional:
1236 return true;
1237 }
1238 }
1239
1240 bool shouldShowFocusHighlight(FocusableActionDetector target) {
1241 return _focused && _canShowHighlight && canRequestFocus(target);
1242 }
1243
1244 assert(SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks);
1245 final FocusableActionDetector oldTarget = oldWidget ?? widget;
1246 final bool didShowHoverHighlight = shouldShowHoverHighlight(oldTarget);
1247 final bool didShowFocusHighlight = shouldShowFocusHighlight(oldTarget);
1248 if (task != null) {
1249 task();
1250 }
1251 final bool doShowHoverHighlight = shouldShowHoverHighlight(widget);
1252 final bool doShowFocusHighlight = shouldShowFocusHighlight(widget);
1253 if (didShowFocusHighlight != doShowFocusHighlight) {
1254 widget.onShowFocusHighlight?.call(doShowFocusHighlight);
1255 }
1256 if (didShowHoverHighlight != doShowHoverHighlight) {
1257 widget.onShowHoverHighlight?.call(doShowHoverHighlight);
1258 }
1259 }
1260
1261 @override
1262 void didUpdateWidget(FocusableActionDetector oldWidget) {
1263 super.didUpdateWidget(oldWidget);
1264 if (widget.enabled != oldWidget.enabled) {
1265 SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
1266 _mayTriggerCallback(oldWidget: oldWidget);
1267 });
1268 }
1269 }
1270
1271 bool get _canRequestFocus {
1272 final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional;
1273 switch (mode) {
1274 case NavigationMode.traditional:
1275 return widget.enabled;
1276 case NavigationMode.directional:
1277 return true;
1278 }
1279 }
1280
1281 // This global key is needed to keep only the necessary widgets in the tree
1282 // while maintaining the subtree's state.
1283 //
1284 // See https://github.com/flutter/flutter/issues/64058 for an explanation of
1285 // why using a global key over keeping the shape of the tree.
1286 final GlobalKey _mouseRegionKey = GlobalKey();
1287
1288 @override
1289 Widget build(BuildContext context) {
1290 Widget child = MouseRegion(
1291 key: _mouseRegionKey,
1292 onEnter: _handleMouseEnter,
1293 onExit: _handleMouseExit,
1294 cursor: widget.mouseCursor,
1295 child: Focus(
1296 focusNode: widget.focusNode,
1297 autofocus: widget.autofocus,
1298 descendantsAreFocusable: widget.descendantsAreFocusable,
1299 descendantsAreTraversable: widget.descendantsAreTraversable,
1300 canRequestFocus: _canRequestFocus,
1301 onFocusChange: _handleFocusChange,
1302 includeSemantics: widget.includeFocusSemantics,
1303 child: widget.child,
1304 ),
1305 );
1306 if (widget.enabled && widget.actions != null && widget.actions!.isNotEmpty) {
1307 child = Actions(actions: widget.actions!, child: child);
1308 }
1309 if (widget.enabled && widget.shortcuts != null && widget.shortcuts!.isNotEmpty) {
1310 child = Shortcuts(shortcuts: widget.shortcuts!, child: child);
1311 }
1312 return child;
1313 }
1314 }
1315
1316 /// An [Intent] that keeps a [VoidCallback] to be invoked by a
1317 /// [VoidCallbackAction] when it receives this intent.
1318 class VoidCallbackIntent extends Intent {
1319 /// Creates a [VoidCallbackIntent].
1320 const VoidCallbackIntent(this.callback);
1321
1322 /// The callback that is to be called by the [VoidCallbackAction] that
1323 /// receives this intent.
1324 final VoidCallback callback;
1325 }
1326
1327 /// An [Action] that invokes the [VoidCallback] given to it in the
1328 /// [VoidCallbackIntent] passed to it when invoked.
1329 ///
1330 /// See also:
1331 ///
1332 /// * [CallbackAction], which is an action that will invoke a callback with the
1333 /// intent passed to the action's invoke method. The callback is configured
1334 /// on the action, not the intent, like this class.
1335 class VoidCallbackAction extends Action<VoidCallbackIntent> {
1336 @override
1337 Object? invoke(VoidCallbackIntent intent) {
1338 intent.callback();
1339 return null;
1340 }
1341 }
1342
1343 /// An [Intent] that is bound to a [DoNothingAction].
1344 ///
1345 /// Attaching a [DoNothingIntent] to a [Shortcuts] mapping is one way to disable
1346 /// a keyboard shortcut defined by a widget higher in the widget hierarchy and
1347 /// consume any key event that triggers it via a shortcut.
1348 ///
1349 /// This intent cannot be subclassed.
1350 ///
1351 /// See also:
1352 ///
1353 /// * [DoNothingAndStopPropagationIntent], a similar intent that will not
1354 /// handle the key event, but will still keep it from being passed to other key
1355 /// handlers in the focus chain.
1356 class DoNothingIntent extends Intent {
1357 /// Creates a const [DoNothingIntent].
1358 const factory DoNothingIntent() = DoNothingIntent._;
1359
1360 // Make DoNothingIntent constructor private so it can't be subclassed.
1361 const DoNothingIntent._();
1362 }
1363
1364 /// An [Intent] that is bound to a [DoNothingAction], but, in addition to not
1365 /// performing an action, also stops the propagation of the key event bound to
1366 /// this intent to other key event handlers in the focus chain.
1367 ///
1368 /// Attaching a [DoNothingAndStopPropagationIntent] to a [Shortcuts.shortcuts]
1369 /// mapping is one way to disable a keyboard shortcut defined by a widget higher
1370 /// in the widget hierarchy. In addition, the bound [DoNothingAction] will
1371 /// return false from [DoNothingAction.consumesKey], causing the key bound to
1372 /// this intent to be passed on to the platform embedding as "not handled" with
1373 /// out passing it to other key handlers in the focus chain (e.g. parent
1374 /// `Shortcuts` widgets higher up in the chain).
1375 ///
1376 /// This intent cannot be subclassed.
1377 ///
1378 /// See also:
1379 ///
1380 /// * [DoNothingIntent], a similar intent that will handle the key event.
1381 class DoNothingAndStopPropagationIntent extends Intent {
1382 /// Creates a const [DoNothingAndStopPropagationIntent].
1383 const factory DoNothingAndStopPropagationIntent() = DoNothingAndStopPropagationIntent._;
1384
1385 // Make DoNothingAndStopPropagationIntent constructor private so it can't be subclassed.
1386 const DoNothingAndStopPropagationIntent._();
1387 }
1388
1389 /// An [Action] that doesn't perform any action when invoked.
1390 ///
1391 /// Attaching a [DoNothingAction] to an [Actions.actions] mapping is a way to
1392 /// disable an action defined by a widget higher in the widget hierarchy.
1393 ///
1394 /// If [consumesKey] returns false, then not only will this action do nothing,
1395 /// but it will stop the propagation of the key event used to trigger it to
1396 /// other widgets in the focus chain and tell the embedding that the key wasn't
1397 /// handled, allowing text input fields or other non-Flutter elements to receive
1398 /// that key event. The return value of [consumesKey] can be set via the
1399 /// `consumesKey` argument to the constructor.
1400 ///
1401 /// This action can be bound to any [Intent].
1402 ///
1403 /// See also:
1404 /// - [DoNothingIntent], which is an intent that can be bound to a [KeySet] in
1405 /// a [Shortcuts] widget to do nothing.
1406 /// - [DoNothingAndStopPropagationIntent], which is an intent that can be bound
1407 /// to a [KeySet] in a [Shortcuts] widget to do nothing and also stop key event
1408 /// propagation to other key handlers in the focus chain.
1409 class DoNothingAction extends Action<Intent> {
1410 /// Creates a [DoNothingAction].
1411 ///
1412 /// The optional [consumesKey] argument defaults to true.
1413 DoNothingAction({bool consumesKey = true}) : _consumesKey = consumesKey;
1414
1415 @override
1416 bool consumesKey(Intent intent) => _consumesKey;
1417 final bool _consumesKey;
1418
1419 @override
1420 void invoke(Intent intent) {}
1421 }
1422
1423 /// An [Intent] that activates the currently focused control.
1424 ///
1425 /// This intent is bound by default to the [LogicalKeyboardKey.space] key on all
1426 /// platforms, and also to the [LogicalKeyboardKey.enter] key on all platforms
1427 /// except the web, where ENTER doesn't toggle selection. On the web, ENTER is
1428 /// bound to [ButtonActivateIntent] instead.
1429 ///
1430 /// See also:
1431 ///
1432 /// * [WidgetsApp.defaultShortcuts], which contains the default shortcuts used
1433 /// in apps.
1434 /// * [WidgetsApp.shortcuts], which defines the shortcuts to use in an
1435 /// application (and defaults to [WidgetsApp.defaultShortcuts]).
1436 class ActivateIntent extends Intent {
1437 /// Creates an intent that activates the currently focused control.
1438 const ActivateIntent();
1439 }
1440
1441 /// An [Intent] that activates the currently focused button.
1442 ///
1443 /// This intent is bound by default to the [LogicalKeyboardKey.enter] key on the
1444 /// web, where ENTER can be used to activate buttons, but not toggle selection.
1445 /// All other platforms bind [LogicalKeyboardKey.enter] to [ActivateIntent].
1446 ///
1447 /// See also:
1448 ///
1449 /// * [WidgetsApp.defaultShortcuts], which contains the default shortcuts used
1450 /// in apps.
1451 /// * [WidgetsApp.shortcuts], which defines the shortcuts to use in an
1452 /// application (and defaults to [WidgetsApp.defaultShortcuts]).
1453 class ButtonActivateIntent extends Intent {
1454 /// Creates an intent that the currently focused control, if it's a button.
1455 const ButtonActivateIntent();
1456 }
1457
1458 /// An [Action] that activates the currently focused control.
1459 ///
1460 /// This is an abstract class that serves as a base class for actions that
1461 /// activate a control. By default, is bound to [LogicalKeyboardKey.enter],
1462 /// [LogicalKeyboardKey.gameButtonA], and [LogicalKeyboardKey.space] in the
1463 /// default keyboard map in [WidgetsApp].
1464 abstract class ActivateAction extends Action<ActivateIntent> { }
1465
1466 /// An [Intent] that selects the currently focused control.
1467 class SelectIntent extends Intent {
1468 /// Creates an intent that selects the currently focused control.
1469 const SelectIntent();
1470 }
1471
1472 /// An action that selects the currently focused control.
1473 ///
1474 /// This is an abstract class that serves as a base class for actions that
1475 /// select something. It is not bound to any key by default.
1476 abstract class SelectAction extends Action<SelectIntent> { }
1477
1478 /// An [Intent] that dismisses the currently focused widget.
1479 ///
1480 /// The [WidgetsApp.defaultShortcuts] binds this intent to the
1481 /// [LogicalKeyboardKey.escape] and [LogicalKeyboardKey.gameButtonB] keys.
1482 ///
1483 /// See also:
1484 /// - [ModalRoute] which listens for this intent to dismiss modal routes
1485 /// (dialogs, pop-up menus, drawers, etc).
1486 class DismissIntent extends Intent {
1487 /// Creates an intent that dismisses the currently focused widget.
1488 const DismissIntent();
1489 }
1490
1491 /// An [Action] that dismisses the focused widget.
1492 ///
1493 /// This is an abstract class that serves as a base class for dismiss actions.
1494 abstract class DismissAction extends Action<DismissIntent> { }
1495
1496 /// An [Intent] that evaluates a series of specified [orderedIntents] for
1497 /// execution.
1498 ///
1499 /// The first intent that matches an enabled action is used.
1500 class PrioritizedIntents extends Intent {
1501 /// Creates an intent that is used with [PrioritizedAction] to specify a list
1502 /// of intents, the first available of which will be used.
1503 const PrioritizedIntents({
1504 required this.orderedIntents,
1505 }) : assert(orderedIntents != null);
1506
1507 /// List of intents to be evaluated in order for execution. When an
1508 /// [Action.isEnabled] returns true, that action will be invoked and
1509 /// progression through the ordered intents stops.
1510 final List<Intent> orderedIntents;
1511 }
1512
1513 /// An [Action] that iterates through a list of [Intent]s, invoking the first
1514 /// that is enabled.
1515 class PrioritizedAction extends Action<PrioritizedIntents> {
1516 late Action<dynamic> _selectedAction;
1517 late Intent _selectedIntent;
1518
1519 @override
1520 bool isEnabled(PrioritizedIntents intent) {
1521 final FocusNode? focus = primaryFocus;
1522 if (focus == null || focus.context == null) {
1523 return false;
1524 }
1525 for (final Intent candidateIntent in intent.orderedIntents) {
1526 final Action<Intent>? candidateAction = Actions.maybeFind<Intent>(
1527 focus.context!,
1528 intent: candidateIntent,
1529 );
1530 if (candidateAction != null && candidateAction.isEnabled(candidateIntent)) {
1531 _selectedAction = candidateAction;
1532 _selectedIntent = candidateIntent;
1533 return true;
1534 }
1535 }
1536 return false;
1537 }
1538
1539 @override
1540 void invoke(PrioritizedIntents intent) {
1541 assert(_selectedAction != null);
1542 assert(_selectedIntent != null);
1543 _selectedAction.invoke(_selectedIntent);
1544 }
1545 }
1546
1547 mixin _OverridableActionMixin<T extends Intent> on Action<T> {
1548 // When debugAssertMutuallyRecursive is true, this action will throw an
1549 // assertion error when the override calls this action's "invoke" method and
1550 // the override is already being invoked from within the "invoke" method.
1551 bool debugAssertMutuallyRecursive = false;
1552 bool debugAssertIsActionEnabledMutuallyRecursive = false;
1553 bool debugAssertIsEnabledMutuallyRecursive = false;
1554 bool debugAssertConsumeKeyMutuallyRecursive = false;
1555
1556 // The default action to invoke if an enabled override Action can't be found
1557 // using [lookupContext];
1558 Action<T> get defaultAction;
1559
1560 // The [BuildContext] used to find the override of this [Action].
1561 BuildContext get lookupContext;
1562
1563 // How to invoke [defaultAction], given the caller [fromAction].
1564 Object? invokeDefaultAction(T intent, Action<T>? fromAction, BuildContext? context);
1565
1566 Action<T>? getOverrideAction({ bool declareDependency = false }) {
1567 final Action<T>? override = declareDependency
1568 ? Actions.maybeFind(lookupContext)
1569 : Actions._maybeFindWithoutDependingOn(lookupContext);
1570 assert(!identical(override, this));
1571 return override;
1572 }
1573
1574 @override
1575 void _updateCallingAction(Action<T>? value) {
1576 super._updateCallingAction(value);
1577 defaultAction._updateCallingAction(value);
1578 }
1579
1580 Object? _invokeOverride(Action<T> overrideAction, T intent, BuildContext? context) {
1581 assert(!debugAssertMutuallyRecursive);
1582 assert(() {
1583 debugAssertMutuallyRecursive = true;
1584 return true;
1585 }());
1586 overrideAction._updateCallingAction(defaultAction);
1587 final Object? returnValue = overrideAction is ContextAction<T>
1588 ? overrideAction.invoke(intent, context)
1589 : overrideAction.invoke(intent);
1590 overrideAction._updateCallingAction(null);
1591 assert(() {
1592 debugAssertMutuallyRecursive = false;
1593 return true;
1594 }());
1595 return returnValue;
1596 }
1597
1598 @override
1599 Object? invoke(T intent, [BuildContext? context]) {
1600 final Action<T>? overrideAction = getOverrideAction();
1601 final Object? returnValue = overrideAction == null
1602 ? invokeDefaultAction(intent, callingAction, context)
1603 : _invokeOverride(overrideAction, intent, context);
1604 return returnValue;
1605 }
1606
1607 bool isOverrideActionEnabled(Action<T> overrideAction) {
1608 assert(!debugAssertIsActionEnabledMutuallyRecursive);
1609 assert(() {
1610 debugAssertIsActionEnabledMutuallyRecursive = true;
1611 return true;
1612 }());
1613 overrideAction._updateCallingAction(defaultAction);
1614 final bool isOverrideEnabled = overrideAction.isActionEnabled;
1615 overrideAction._updateCallingAction(null);
1616 assert(() {
1617 debugAssertIsActionEnabledMutuallyRecursive = false;
1618 return true;
1619 }());
1620 return isOverrideEnabled;
1621 }
1622
1623 @override
1624 bool get isActionEnabled {
1625 final Action<T>? overrideAction = getOverrideAction(declareDependency: true);
1626 final bool returnValue = overrideAction != null
1627 ? isOverrideActionEnabled(overrideAction)
1628 : defaultAction.isActionEnabled;
1629 return returnValue;
1630 }
1631
1632 @override
1633 bool isEnabled(T intent) {
1634 assert(!debugAssertIsEnabledMutuallyRecursive);
1635 assert(() {
1636 debugAssertIsEnabledMutuallyRecursive = true;
1637 return true;
1638 }());
1639
1640 final Action<T>? overrideAction = getOverrideAction();
1641 overrideAction?._updateCallingAction(defaultAction);
1642 final bool returnValue = (overrideAction ?? defaultAction).isEnabled(intent);
1643 overrideAction?._updateCallingAction(null);
1644 assert(() {
1645 debugAssertIsEnabledMutuallyRecursive = false;
1646 return true;
1647 }());
1648 return returnValue;
1649 }
1650
1651 @override
1652 bool consumesKey(T intent) {
1653 assert(!debugAssertConsumeKeyMutuallyRecursive);
1654 assert(() {
1655 debugAssertConsumeKeyMutuallyRecursive = true;
1656 return true;
1657 }());
1658 final Action<T>? overrideAction = getOverrideAction();
1659 overrideAction?._updateCallingAction(defaultAction);
1660 final bool isEnabled = (overrideAction ?? defaultAction).consumesKey(intent);
1661 overrideAction?._updateCallingAction(null);
1662 assert(() {
1663 debugAssertConsumeKeyMutuallyRecursive = false;
1664 return true;
1665 }());
1666 return isEnabled;
1667 }
1668
1669 @override
1670 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1671 super.debugFillProperties(properties);
1672 properties.add(DiagnosticsProperty<Action<T>>('defaultAction', defaultAction));
1673 }
1674 }
1675
1676 class _OverridableAction<T extends Intent> extends ContextAction<T> with _OverridableActionMixin<T> {
1677 _OverridableAction({ required this.defaultAction, required this.lookupContext }) ;
1678
1679 @override
1680 final Action<T> defaultAction;
1681
1682 @override
1683 final BuildContext lookupContext;
1684
1685 @override
1686 Object? invokeDefaultAction(T intent, Action<T>? fromAction, BuildContext? context) {
1687 if (fromAction == null) {
1688 return defaultAction.invoke(intent);
1689 } else {
1690 final Object? returnValue = defaultAction.invoke(intent);
1691 return returnValue;
1692 }
1693 }
1694
1695 @override
1696 ContextAction<T> _makeOverridableAction(BuildContext context) {
1697 return _OverridableAction<T>(defaultAction: defaultAction, lookupContext: context);
1698 }
1699 }
1700
1701 class _OverridableContextAction<T extends Intent> extends ContextAction<T> with _OverridableActionMixin<T> {
1702 _OverridableContextAction({ required this.defaultAction, required this.lookupContext });
1703
1704 @override
1705 final ContextAction<T> defaultAction;
1706
1707 @override
1708 final BuildContext lookupContext;
1709
1710 @override
1711 Object? _invokeOverride(Action<T> overrideAction, T intent, BuildContext? context) {
1712 assert(context != null);
1713 assert(!debugAssertMutuallyRecursive);
1714 assert(() {
1715 debugAssertMutuallyRecursive = true;
1716 return true;
1717 }());
1718
1719 // Wrap the default Action together with the calling context in case
1720 // overrideAction is not a ContextAction and thus have no access to the
1721 // calling BuildContext.
1722 final Action<T> wrappedDefault = _ContextActionToActionAdapter<T>(invokeContext: context!, action: defaultAction);
1723 overrideAction._updateCallingAction(wrappedDefault);
1724 final Object? returnValue = overrideAction is ContextAction<T>
1725 ? overrideAction.invoke(intent, context)
1726 : overrideAction.invoke(intent);
1727 overrideAction._updateCallingAction(null);
1728
1729 assert(() {
1730 debugAssertMutuallyRecursive = false;
1731 return true;
1732 }());
1733 return returnValue;
1734 }
1735
1736 @override
1737 Object? invokeDefaultAction(T intent, Action<T>? fromAction, BuildContext? context) {
1738 if (fromAction == null) {
1739 return defaultAction.invoke(intent, context);
1740 } else {
1741 final Object? returnValue = defaultAction.invoke(intent, context);
1742 return returnValue;
1743 }
1744 }
1745
1746 @override
1747 ContextAction<T> _makeOverridableAction(BuildContext context) {
1748 return _OverridableContextAction<T>(defaultAction: defaultAction, lookupContext: context);
1749 }
1750 }
1751
1752 class _ContextActionToActionAdapter<T extends Intent> extends Action<T> {
1753 _ContextActionToActionAdapter({required this.invokeContext, required this.action});
1754
1755 final BuildContext invokeContext;
1756 final ContextAction<T> action;
1757
1758 @override
1759 void _updateCallingAction(Action<T>? value) {
1760 action._updateCallingAction(value);
1761 }
1762
1763 @override
1764 Action<T>? get callingAction => action.callingAction;
1765
1766 @override
1767 bool isEnabled(T intent) => action.isEnabled(intent);
1768
1769 @override
1770 bool get isActionEnabled => action.isActionEnabled;
1771
1772 @override
1773 bool consumesKey(T intent) => action.consumesKey(intent);
1774
1775 @override
1776 void addActionListener(ActionListenerCallback listener) {
1777 super.addActionListener(listener);
1778 action.addActionListener(listener);
1779 }
1780
1781 @override
1782 void removeActionListener(ActionListenerCallback listener) {
1783 super.removeActionListener(listener);
1784 action.removeActionListener(listener);
1785 }
1786
1787 @override
1788 @protected
1789 void notifyActionListeners() => action.notifyActionListeners();
1790
1791 @override
1792 Object? invoke(T intent) => action.invoke(intent, invokeContext);
1793 }