Created
February 17, 2022 19:07
-
-
Save rodydavis/e98bff13e4a75ee5fa2883c528336e64 to your computer and use it in GitHub Desktop.
Action manager example for flutter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:flutter_bloc/flutter_bloc.dart'; | |
import '../model.dart'; | |
abstract class AppAction extends Intent { | |
LogicalKeySet? get keys => null; | |
void action(AppModel model); | |
String get label; | |
void execute(BuildContext context) { | |
final model = context.read<AppModel>(); | |
model.addAction(this); | |
} | |
bool get canUndo; | |
} | |
abstract class MultiNode extends AppAction { | |
final List<String> nodeIds; | |
MultiNode({required this.nodeIds}); | |
} | |
abstract class SingleNode extends AppAction { | |
final String nodeId; | |
SingleNode({required this.nodeId}); | |
} | |
class GroupAction extends AppAction { | |
GroupAction(this.actions); | |
final List<AppAction> actions; | |
@override | |
String get label => 'Group of actions'; | |
@override | |
void action(AppModel model) { | |
for (final action in actions) { | |
action.action(model); | |
} | |
} | |
@override | |
bool get canUndo => false; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:flutter/services.dart'; | |
import 'package:flutter_bloc/flutter_bloc.dart'; | |
import 'package:menubar/menubar.dart'; | |
import 'package:widget_studio/src/commands/link_nodes.dart'; | |
import '../model.dart'; | |
import '../utils.dart'; | |
import 'actions.dart'; | |
import 'delete_nodes.dart'; | |
import 'deselect_all.dart'; | |
import 'duplicate_nodes.dart'; | |
import 'group_nodes.dart'; | |
import 'move_back.dart'; | |
import 'move_front.dart'; | |
import 'move_to_back.dart'; | |
import 'move_to_front.dart'; | |
import 'pan_canvas.dart'; | |
import 'redo.dart'; | |
import 'reset_canvas.dart'; | |
import 'rotate_canvas.dart'; | |
import 'select_all.dart'; | |
import 'undo.dart'; | |
import 'ungroup_nodes.dart'; | |
import 'zoom_canvas.dart'; | |
class ActionScope extends StatefulWidget { | |
const ActionScope({Key? key, required this.builder}) : super(key: key); | |
final Widget Function( | |
BuildContext context, | |
Map<LogicalKeyboardKey, bool> pressed, | |
) builder; | |
@override | |
State<ActionScope> createState() => _ActionScopeState(); | |
} | |
class _ActionScopeState extends State<ActionScope> { | |
final keys = <LogicalKeyboardKey, bool>{}; | |
List<AppAction> buildActions(AppModel model, Project project) { | |
final actions = <AppAction>[]; | |
if (project.selection.length != project.nodeOrder.length) { | |
actions.add(SelectAll()); | |
} | |
if (project.selection.isNotEmpty) { | |
if (project.selection.length == 1) { | |
// Single Node Actions | |
final nodeId = project.selection.first; | |
final node = project.nodes[nodeId]; | |
if (node != null) { | |
actions.add(MoveToBack(nodeId)); | |
actions.add(MoveBackwards(nodeId)); | |
actions.add(MoveForwards(nodeId)); | |
actions.add(MoveToFront(nodeId)); | |
if (node is GroupBase) { | |
actions.add(UnGroup(nodeId)); | |
} | |
} | |
} else { | |
if (project.selection.length == 2) { | |
actions.add(LinkNodes(project.selection)); | |
} | |
// Multi Node Actions | |
actions.add(GroupNodes(project.selection)); | |
} | |
// Any Node Action | |
actions.add(DeleteNodes(project.selection)); | |
actions.add(DuplicateNodes(project.selection)); | |
actions.add(DeselectAll()); | |
} | |
// Canvas Actions | |
actions.add(PanRight()); | |
actions.add(PanLeft()); | |
actions.add(PanUp()); | |
actions.add(PanDown()); | |
actions.add(ZoomIn()); | |
actions.add(ZoomOut()); | |
actions.add(RotateLeft()); | |
actions.add(RotateRight()); | |
actions.add(ResetCanvas()); | |
actions.add(Undo()); | |
actions.add(Redo()); | |
return actions; | |
} | |
void setMenu(AppModel model, List<AppAction> actions) { | |
// ignore: prefer_const_constructors | |
final edit = Submenu(label: 'Selection', children: []); | |
for (final a in actions) { | |
edit.children.add(MenuItem( | |
label: a.label, | |
shortcut: a.keys, | |
onClicked: () => model.addAction(a), | |
)); | |
} | |
if (isDesktop) { | |
setApplicationMenu([edit]); | |
} | |
} | |
@override | |
Widget build(BuildContext context) { | |
final model = context.read<AppModel>(); | |
return BlocBuilder<AppModel, AppState>( | |
builder: (context, state) { | |
final project = state.currentProject!; | |
final shortcuts = buildActions(model, project); | |
setMenu(model, shortcuts); | |
return Shortcuts( | |
key: Key('shortcut-scope-${state.hashCode}'), | |
shortcuts: <LogicalKeySet, Intent>{ | |
for (final shortcut in shortcuts.where((e) { | |
return e.keys != null; | |
}).toList()) | |
shortcut.keys!: shortcut | |
}, | |
child: Actions( | |
key: Key('action-scope-${state.hashCode}'), | |
actions: { | |
for (final shortcut in shortcuts) | |
shortcut.runtimeType: CallbackAction( | |
onInvoke: (intent) => model.addAction(shortcut), | |
), | |
}, | |
child: Focus( | |
autofocus: true, | |
child: widget.builder(context, keys), | |
onKey: (context, event) { | |
if (event is RawKeyDownEvent) { | |
keys[event.logicalKey] = true; | |
if (event.logicalKey == LogicalKeyboardKey.shiftLeft || | |
event.logicalKey == LogicalKeyboardKey.shiftRight) { | |
keys[LogicalKeyboardKey.shift] = event.isShiftPressed; | |
} | |
if (event.logicalKey == LogicalKeyboardKey.controlLeft || | |
event.logicalKey == LogicalKeyboardKey.controlRight) { | |
keys[LogicalKeyboardKey.control] = event.isControlPressed; | |
} | |
if (event.logicalKey == LogicalKeyboardKey.altLeft || | |
event.logicalKey == LogicalKeyboardKey.altRight) { | |
keys[LogicalKeyboardKey.alt] = event.isAltPressed; | |
} | |
if (event.logicalKey == LogicalKeyboardKey.metaLeft || | |
event.logicalKey == LogicalKeyboardKey.metaRight) { | |
keys[LogicalKeyboardKey.meta] = event.isMetaPressed; | |
} | |
} else if (event is RawKeyUpEvent) { | |
keys[event.logicalKey] = false; | |
} | |
return KeyEventResult.ignored; | |
}, | |
), | |
), | |
); | |
}, | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment