shortcut_atlas 0.1.0
shortcut_atlas: ^0.1.0 copied to clipboard
Enterprise keyboard shortcuts for Flutter desktop: bind keys to actions and reveal a contextual cheat-sheet HUD by holding Alt.
shortcut_atlas #
Enterprise keyboard shortcuts for Flutter desktop: bind keys to actions, and reveal a contextual, iPadOS-style cheat-sheet HUD by holding Alt.
Declare a shortcut once as a ShortcutCommand. That single descriptor is the
source of truth for both:
- dispatch — compiled into real Flutter
Shortcuts/Actions/Focus, so focus scoping, precedence, and contextual enable/disable are the framework's job (not a custom key router); and - discoverability — projected into a hold-Alt HUD that lists exactly the
shortcuts live for the focused screen. Because the HUD resolves enablement with
the same
Action.isEnabledthe keypress uses, the cheat-sheet can never advertise something the key wouldn't actually fire.
Status: desktop-first (Windows, macOS, Linux). Web is best-effort. The HUD, dispatch, cross-platform modifiers, the text-field auto-guard, the command palette, platform awareness, user-remappable keymaps (with pluggable persistence), and a menu bar bridge are all implemented.
Install #
dependencies:
shortcut_atlas: ^0.1.0
Quick start #
Install ShortcutAtlas once, above your app's Navigator, via
MaterialApp.builder:
MaterialApp(
builder: (context, child) => ShortcutAtlas(child: child!),
home: const HomeScreen(),
);
Then wrap a screen in a ShortcutScope and declare its commands. Hold Alt to
see them.
Example 1 — switch tabs with 1 / 2 / 3 #
command(...) binds a key to a callback. Bare (modifier-less) keys are
auto-guarded: they are disabled while a text field has focus, so typing 1
into a TextField types 1 instead of switching tabs.
ShortcutScope(
kind: ScopeKind.screen,
autofocus: true, // put the scope on the focus chain
commands: [
for (var i = 0; i < 3; i++)
command(
'tabs.open.${i + 1}',
activator: CharacterActivator('${i + 1}'),
label: 'Open Tab ${i + 1}',
group: ShortcutGroup.tabs,
onInvoke: () => _tabController.animateTo(i),
),
],
child: /* Scaffold with a TabBar / TabBarView */,
);
Example 2 — Ctrl/Cmd+S submits the same thing the button does #
There is one Intent and one Action. The button dispatches it via
AtlasActionButton (which uses Actions.handler), and Ctrl/Cmd+S binds the
same intent. isEnabled is the single gate: it lets the key fall through when
disabled, auto-disables the button, and greys the HUD row — all from one signal.
class SubmitIntent extends Intent { const SubmitIntent(); }
class SubmitAction extends Action<SubmitIntent> {
SubmitAction(this.onSubmit, this.canSubmit) {
canSubmit.addListener(notifyActionListeners);
}
final VoidCallback onSubmit;
final ValueListenable<bool> canSubmit;
@override bool isEnabled(SubmitIntent i) => canSubmit.value;
@override Object? invoke(SubmitIntent i) { onSubmit(); return null; }
}
// in build:
ShortcutScope(
kind: ScopeKind.screen,
autofocus: true,
actions: {SubmitIntent: _submitAction},
commands: [
ShortcutCommand<SubmitIntent>(
id: 'form.submit',
activator: primary(LogicalKeyboardKey.keyS), // ⌘S on macOS, Ctrl+S elsewhere
intent: const SubmitIntent(),
label: 'Save',
group: ShortcutGroup.file,
),
],
child: Column(children: [
const TextField(/* onChanged: () => dirty.value = true */),
AtlasActionButton(
intent: const SubmitIntent(),
enableWhen: dirty, // rebuilds the button as enablement flips
child: const Text('Save'),
),
]),
);
Core concepts #
| Type | Role |
|---|---|
ShortcutAtlas |
Root widget; hosts the controller, the root command action, the hold-Alt detector, and the HUD overlay. |
ShortcutScope |
Declares the commands active in a region; compiles them to Shortcuts/Actions/Focus. |
ShortcutCommand / command() |
The single descriptor: keys + label + group + behavior. |
primary() / PlatformActivator |
Ctrl on Windows/Linux, Cmd on macOS — one declaration, correct everywhere. |
ShortcutGroup |
HUD section / bucket (e.g. ShortcutGroup.tabs). |
AtlasActionButton |
A button that shares one intent + enablement gate with a shortcut. |
AtlasThemeData / AtlasTheme |
Theme the HUD panel, chips, header/footer, and animation. |
AtlasConfig |
Hold threshold, trigger keys, disabled-row policy, the auto-guard toggle, the keymap store. |
Keymap / KeyRecorder / KeymapStore |
User re-bindings (factory < admin < user), a "press a key" recorder, and pluggable persistence. |
ShortcutMenuBar / ShortcutMenu |
A Material menu bar generated from the same commands. |
How the hold-Alt HUD works (peek-then-act) #
Alt is a reveal key, not a chord prefix. Hold Alt alone past a short
threshold (~350 ms) and the panel lists the focused screen's shortcuts; release
Alt and press the actual key. Pressing any other key while Alt is held aborts the
peek so real combos (e.g. Alt+F) are untouched — the detector is a single
non-consuming HardwareKeyboard handler and never blocks dispatch.
Command palette #
A Cmd/Ctrl+K palette is installed by default (AtlasConfig.enablePalette). It
lists the currently-active commands (those with surfaces.palette), filters as
you type, and is fully keyboard-navigable (↑/↓/Enter/Esc). Open it
programmatically with ShortcutAtlas.of(context).openPalette().
Remappable keymaps #
Bindings are layered factory < admin < user. The factory layer is each
command's own activator; admin is a fixed override map; user is the
runtime re-binding. The effective chord flows everywhere — dispatch, HUD,
palette, and menu — from one place.
final controller = ShortcutAtlas.of(context);
// Re-bind "save" to whatever the user presses next:
KeyRecorder(
initial: controller.effectiveActivator(saveCommand),
onRecorded: (activator) => controller.keymap.rebind('save', activator),
);
controller.keymap.reset('save'); // back to default
controller.keymap.resetAll();
Persist re-bindings by passing a KeymapStore to AtlasConfig — implement the
two-method interface over any backend (the example wires shared_preferences):
ShortcutAtlas(
config: AtlasConfig(keymapStore: myStore),
child: child!,
);
Encoding is handled for you by ActivatorCodec; the store only moves a
Map<String, dynamic> in and out.
Menu bar bridge #
ShortcutMenuBar builds a Material MenuBar from the same ShortcutCommands,
so a command appears in the menu with its (effective, re-bound) shortcut and runs
the same behavior the key would. The hint is display-only, so it never
double-fires with the ShortcutScope binding.
ShortcutMenuBar(menus: [
ShortcutMenu('File', [newWindow, save, close]),
ShortcutMenu('Edit', [find]),
]);
Platform awareness #
AtlasPlatform exposes isDesktop, isApple, isWeb, isMobile,
supportsHud, primaryModifierLabel, and reservedReason(activator) (a
best-effort check for chords the OS or browser intercepts). The HUD and palette
only activate on desktop or web by default — flip restrictToDesktop to change
that. In debug builds, scopes warn about duplicate and reserved chords.
Theming #
The HUD panel has a titled header and a footer that counts the active
shortcuts and points at the palette. Three presets ship — the default glass,
plus AtlasThemeData.minimal(context) (blur-free) and .accent(context)
(seed-tinted) — and any field is overridable via copyWith:
ShortcutAtlas(
theme: AtlasThemeData.fallback(context).copyWith(
panelBlurSigma: 30,
panelMaxWidth: 720,
headerTitle: 'Commands',
showFooter: false,
),
child: child!,
);
Notes & limitations (0.1.0) #
- Bare keys + text fields: bare single-key shortcuts are auto-disabled while
an
EditableTexthas focus (AtlasConfig.guardBareKeysInEditables, on by default). For screens that mix typing and shortcuts, prefer a modified activator (e.g.primary(...)). - Web: browsers reserve some chords (Ctrl+S/W/T) and may not honor
preventDefault; mark such commandswebSafe: falseto hide them on web. - Menu bar:
ShortcutMenuBarrenders an in-window MaterialMenuBaron every platform (including macOS). A true native top-of-screen macOSPlatformMenuBaris intentionally not used, to avoid the OS registering the chord a second time. - Planned: richer conflict-resolution UX (duplicate/reserved detection ships in debug today) and a re-binding conflict guard.
License #
MIT © Luminest