"Fossies" - the Fresh Open Source Software Archive 
Member "flutter-3.7.1/dev/manual_tests/lib/actions.dart" (1 Feb 2023, 15606 Bytes) of package /linux/misc/flutter-3.7.1.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 last
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 'dart:collection';
6 import 'dart:io';
7
8 import 'package:flutter/foundation.dart';
9 import 'package:flutter/material.dart';
10 import 'package:flutter/services.dart';
11
12 void main() {
13 runApp(const MaterialApp(
14 title: 'Actions Demo',
15 home: FocusDemo(),
16 ));
17 }
18
19 /// A class that can hold invocation information that an [UndoableAction] can
20 /// use to undo/redo itself.
21 ///
22 /// Instances of this class are returned from [UndoableAction]s and placed on
23 /// the undo stack when they are invoked.
24 class Memento extends Object with Diagnosticable {
25 const Memento({
26 required this.name,
27 required this.undo,
28 required this.redo,
29 });
30
31 /// Returns true if this Memento can be used to undo.
32 ///
33 /// Subclasses could override to provide their own conditions when a command is
34 /// undoable.
35 bool get canUndo => true;
36
37 /// Returns true if this Memento can be used to redo.
38 ///
39 /// Subclasses could override to provide their own conditions when a command is
40 /// redoable.
41 bool get canRedo => true;
42
43 final String name;
44 final VoidCallback undo;
45 final ValueGetter<Memento> redo;
46
47 @override
48 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
49 super.debugFillProperties(properties);
50 properties.add(StringProperty('name', name));
51 properties.add(FlagProperty('undo', value: undo != null, ifTrue: 'undo'));
52 properties.add(FlagProperty('redo', value: redo != null, ifTrue: 'redo'));
53 }
54 }
55
56 /// Undoable Actions
57
58 /// An [ActionDispatcher] subclass that manages the invocation of undoable
59 /// actions.
60 class UndoableActionDispatcher extends ActionDispatcher implements Listenable {
61 /// Constructs a new [UndoableActionDispatcher].
62 ///
63 /// The [maxUndoLevels] argument must not be null.
64 UndoableActionDispatcher({
65 int maxUndoLevels = _defaultMaxUndoLevels,
66 }) : assert(maxUndoLevels != null),
67 _maxUndoLevels = maxUndoLevels;
68
69 // A stack of actions that have been performed. The most recent action
70 // performed is at the end of the list.
71 final DoubleLinkedQueue<Memento> _completedActions = DoubleLinkedQueue<Memento>();
72 // A stack of actions that can be redone. The most recent action performed is
73 // at the end of the list.
74 final List<Memento> _undoneActions = <Memento>[];
75
76 static const int _defaultMaxUndoLevels = 1000;
77
78 /// The maximum number of undo levels allowed.
79 ///
80 /// If this value is set to a value smaller than the number of completed
81 /// actions, then the stack of completed actions is truncated to only include
82 /// the last [maxUndoLevels] actions.
83 int get maxUndoLevels => _maxUndoLevels;
84 int _maxUndoLevels;
85 set maxUndoLevels(int value) {
86 _maxUndoLevels = value;
87 _pruneActions();
88 }
89
90 final Set<VoidCallback> _listeners = <VoidCallback>{};
91
92 @override
93 void addListener(VoidCallback listener) {
94 _listeners.add(listener);
95 }
96
97 @override
98 void removeListener(VoidCallback listener) {
99 _listeners.remove(listener);
100 }
101
102 /// Notifies listeners that the [ActionDispatcher] has changed state.
103 ///
104 /// May only be called by subclasses.
105 @protected
106 void notifyListeners() {
107 for (final VoidCallback callback in _listeners) {
108 callback();
109 }
110 }
111
112 @override
113 Object? invokeAction(Action<Intent> action, Intent intent, [BuildContext? context]) {
114 final Object? result = super.invokeAction(action, intent, context);
115 print('Invoking ${action is UndoableAction ? 'undoable ' : ''}$intent as $action: $this ');
116 if (action is UndoableAction) {
117 _completedActions.addLast(result! as Memento);
118 _undoneActions.clear();
119 _pruneActions();
120 notifyListeners();
121 }
122 return result;
123 }
124
125 // Enforces undo level limit.
126 void _pruneActions() {
127 while (_completedActions.length > _maxUndoLevels) {
128 _completedActions.removeFirst();
129 }
130 }
131
132 /// Returns true if there is an action on the stack that can be undone.
133 bool get canUndo {
134 if (_completedActions.isNotEmpty) {
135 return _completedActions.first.canUndo;
136 }
137 return false;
138 }
139
140 /// Returns true if an action that has been undone can be re-invoked.
141 bool get canRedo {
142 if (_undoneActions.isNotEmpty) {
143 return _undoneActions.first.canRedo;
144 }
145 return false;
146 }
147
148 /// Undoes the last action executed if possible.
149 ///
150 /// Returns true if the action was successfully undone.
151 bool undo() {
152 print('Undoing. $this');
153 if (!canUndo) {
154 return false;
155 }
156 final Memento memento = _completedActions.removeLast();
157 memento.undo();
158 _undoneActions.add(memento);
159 notifyListeners();
160 return true;
161 }
162
163 /// Re-invokes a previously undone action, if possible.
164 ///
165 /// Returns true if the action was successfully invoked.
166 bool redo() {
167 print('Redoing. $this');
168 if (!canRedo) {
169 return false;
170 }
171 final Memento memento = _undoneActions.removeLast();
172 final Memento replacement = memento.redo();
173 _completedActions.add(replacement);
174 _pruneActions();
175 notifyListeners();
176 return true;
177 }
178
179 @override
180 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
181 super.debugFillProperties(properties);
182 properties.add(IntProperty('undoable items', _completedActions.length));
183 properties.add(IntProperty('redoable items', _undoneActions.length));
184 properties.add(IterableProperty<Memento>('undo stack', _completedActions));
185 properties.add(IterableProperty<Memento>('redo stack', _undoneActions));
186 }
187 }
188
189 class UndoIntent extends Intent {
190 const UndoIntent();
191 }
192
193 class UndoAction extends Action<UndoIntent> {
194 @override
195 bool isEnabled(UndoIntent intent) {
196 final BuildContext? buildContext = primaryFocus?.context ?? FocusDemo.appKey.currentContext;
197 if (buildContext == null) {
198 return false;
199 }
200 final UndoableActionDispatcher manager = Actions.of(buildContext) as UndoableActionDispatcher;
201 return manager.canUndo;
202 }
203
204 @override
205 void invoke(UndoIntent intent) {
206 final BuildContext? buildContext = primaryFocus?.context ?? FocusDemo.appKey.currentContext;
207 if (buildContext == null) {
208 return;
209 }
210 final UndoableActionDispatcher manager = Actions.of(primaryFocus?.context ?? FocusDemo.appKey.currentContext!) as UndoableActionDispatcher;
211 manager.undo();
212 }
213 }
214
215 class RedoIntent extends Intent {
216 const RedoIntent();
217 }
218
219 class RedoAction extends Action<RedoIntent> {
220 @override
221 bool isEnabled(RedoIntent intent) {
222 final BuildContext? buildContext = primaryFocus?.context ?? FocusDemo.appKey.currentContext;
223 if (buildContext == null) {
224 return false;
225 }
226 final UndoableActionDispatcher manager = Actions.of(buildContext) as UndoableActionDispatcher;
227 return manager.canRedo;
228 }
229
230 @override
231 RedoAction invoke(RedoIntent intent) {
232 final BuildContext? buildContext = primaryFocus?.context ?? FocusDemo.appKey.currentContext;
233 if (buildContext == null) {
234 return this;
235 }
236 final UndoableActionDispatcher manager = Actions.of(buildContext) as UndoableActionDispatcher;
237 manager.redo();
238 return this;
239 }
240 }
241
242 /// An action that can be undone.
243 abstract class UndoableAction<T extends Intent> extends Action<T> {
244 /// The [Intent] this action was originally invoked with.
245 Intent? get invocationIntent => _invocationTag;
246 Intent? _invocationTag;
247
248 @protected
249 set invocationIntent(Intent? value) => _invocationTag = value;
250
251 @override
252 @mustCallSuper
253 void invoke(T intent) {
254 invocationIntent = intent;
255 }
256 }
257
258 class UndoableFocusActionBase<T extends Intent> extends UndoableAction<T> {
259 @override
260 @mustCallSuper
261 Memento invoke(T intent) {
262 super.invoke(intent);
263 final FocusNode? previousFocus = primaryFocus;
264 return Memento(name: previousFocus!.debugLabel!, undo: () {
265 previousFocus.requestFocus();
266 }, redo: () {
267 return invoke(intent);
268 });
269 }
270 }
271
272 class UndoableRequestFocusAction extends UndoableFocusActionBase<RequestFocusIntent> {
273 @override
274 Memento invoke(RequestFocusIntent intent) {
275 final Memento memento = super.invoke(intent);
276 intent.focusNode.requestFocus();
277 return memento;
278 }
279 }
280
281 /// Actions for manipulating focus.
282 class UndoableNextFocusAction extends UndoableFocusActionBase<NextFocusIntent> {
283 @override
284 Memento invoke(NextFocusIntent intent) {
285 final Memento memento = super.invoke(intent);
286 primaryFocus?.nextFocus();
287 return memento;
288 }
289 }
290
291 class UndoablePreviousFocusAction extends UndoableFocusActionBase<PreviousFocusIntent> {
292 @override
293 Memento invoke(PreviousFocusIntent intent) {
294 final Memento memento = super.invoke(intent);
295 primaryFocus?.previousFocus();
296 return memento;
297 }
298 }
299
300 class UndoableDirectionalFocusAction extends UndoableFocusActionBase<DirectionalFocusIntent> {
301 TraversalDirection? direction;
302
303 @override
304 Memento invoke(DirectionalFocusIntent intent) {
305 final Memento memento = super.invoke(intent);
306 primaryFocus?.focusInDirection(intent.direction);
307 return memento;
308 }
309 }
310
311 /// A button class that takes focus when clicked.
312 class DemoButton extends StatefulWidget {
313 const DemoButton({super.key, required this.name});
314
315 final String name;
316
317 @override
318 State<DemoButton> createState() => _DemoButtonState();
319 }
320
321 class _DemoButtonState extends State<DemoButton> {
322 late final FocusNode _focusNode = FocusNode(debugLabel: widget.name);
323 final GlobalKey _nameKey = GlobalKey();
324
325 void _handleOnPressed() {
326 print('Button ${widget.name} pressed.');
327 setState(() {
328 Actions.invoke(_nameKey.currentContext!, RequestFocusIntent(_focusNode));
329 });
330 }
331
332 @override
333 void dispose() {
334 super.dispose();
335 _focusNode.dispose();
336 }
337
338 @override
339 Widget build(BuildContext context) {
340 return TextButton(
341 focusNode: _focusNode,
342 style: ButtonStyle(
343 foregroundColor: const MaterialStatePropertyAll<Color>(Colors.black),
344 overlayColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
345 if (states.contains(MaterialState.focused)) {
346 return Colors.red;
347 }
348 if (states.contains(MaterialState.hovered)) {
349 return Colors.blue;
350 }
351 return Colors.transparent;
352 }),
353 ),
354 onPressed: () => _handleOnPressed(),
355 child: Text(widget.name, key: _nameKey),
356 );
357 }
358 }
359
360 class FocusDemo extends StatefulWidget {
361 const FocusDemo({super.key});
362
363 static GlobalKey appKey = GlobalKey();
364
365 @override
366 State<FocusDemo> createState() => _FocusDemoState();
367 }
368
369 class _FocusDemoState extends State<FocusDemo> {
370 final FocusNode outlineFocus = FocusNode(debugLabel: 'Demo Focus Node');
371 late final UndoableActionDispatcher dispatcher = UndoableActionDispatcher();
372 bool canUndo = false;
373 bool canRedo = false;
374
375 @override
376 void initState() {
377 super.initState();
378 canUndo = dispatcher.canUndo;
379 canRedo = dispatcher.canRedo;
380 dispatcher.addListener(_handleUndoStateChange);
381 }
382
383 void _handleUndoStateChange() {
384 if (dispatcher.canUndo != canUndo) {
385 setState(() {
386 canUndo = dispatcher.canUndo;
387 });
388 }
389 if (dispatcher.canRedo != canRedo) {
390 setState(() {
391 canRedo = dispatcher.canRedo;
392 });
393 }
394 }
395
396 @override
397 void dispose() {
398 dispatcher.removeListener(_handleUndoStateChange);
399 outlineFocus.dispose();
400 super.dispose();
401 }
402
403 @override
404 Widget build(BuildContext context) {
405 final TextTheme textTheme = Theme.of(context).textTheme;
406 return Actions(
407 dispatcher: dispatcher,
408 actions: <Type, Action<Intent>>{
409 RequestFocusIntent: UndoableRequestFocusAction(),
410 NextFocusIntent: UndoableNextFocusAction(),
411 PreviousFocusIntent: UndoablePreviousFocusAction(),
412 DirectionalFocusIntent: UndoableDirectionalFocusAction(),
413 UndoIntent: UndoAction(),
414 RedoIntent: RedoAction(),
415 },
416 child: FocusTraversalGroup(
417 policy: ReadingOrderTraversalPolicy(),
418 child: Shortcuts(
419 shortcuts: <ShortcutActivator, Intent>{
420 SingleActivator(LogicalKeyboardKey.keyZ, meta: Platform.isMacOS, control: !Platform.isMacOS, shift: true): const RedoIntent(),
421 SingleActivator(LogicalKeyboardKey.keyZ, meta: Platform.isMacOS, control: !Platform.isMacOS): const UndoIntent(),
422 },
423 child: FocusScope(
424 key: FocusDemo.appKey,
425 debugLabel: 'Scope',
426 autofocus: true,
427 child: DefaultTextStyle(
428 style: textTheme.headlineMedium!,
429 child: Scaffold(
430 appBar: AppBar(
431 title: const Text('Actions Demo'),
432 ),
433 body: Center(
434 child: Builder(builder: (BuildContext context) {
435 return Column(
436 mainAxisAlignment: MainAxisAlignment.center,
437 children: <Widget>[
438 Row(
439 mainAxisAlignment: MainAxisAlignment.center,
440 children: const <Widget>[
441 DemoButton(name: 'One'),
442 DemoButton(name: 'Two'),
443 DemoButton(name: 'Three'),
444 ],
445 ),
446 Row(
447 mainAxisAlignment: MainAxisAlignment.center,
448 children: const <Widget>[
449 DemoButton(name: 'Four'),
450 DemoButton(name: 'Five'),
451 DemoButton(name: 'Six'),
452 ],
453 ),
454 Row(
455 mainAxisAlignment: MainAxisAlignment.center,
456 children: const <Widget>[
457 DemoButton(name: 'Seven'),
458 DemoButton(name: 'Eight'),
459 DemoButton(name: 'Nine'),
460 ],
461 ),
462 Row(
463 mainAxisAlignment: MainAxisAlignment.center,
464 children: <Widget>[
465 Padding(
466 padding: const EdgeInsets.all(8.0),
467 child: ElevatedButton(
468 onPressed: canUndo
469 ? () {
470 Actions.invoke(context, const UndoIntent());
471 }
472 : null,
473 child: const Text('UNDO'),
474 ),
475 ),
476 Padding(
477 padding: const EdgeInsets.all(8.0),
478 child: ElevatedButton(
479 onPressed: canRedo
480 ? () {
481 Actions.invoke(context, const RedoIntent());
482 }
483 : null,
484 child: const Text('REDO'),
485 ),
486 ),
487 ],
488 ),
489 ],
490 );
491 }),
492 ),
493 ),
494 ),
495 ),
496 ),
497 ),
498 );
499 }
500 }