import { RemoveAnimationKeysCommand, RemoveAnimationTracksCommand } from '../core/commands/animationCommands'; import { RemoveFrameCommand } from '../core/commands/frameCommands'; import { ui } from '../ui'; import { confirmDeleteAsset, createContainer, removeGraphSelection, removeObjects } from './actions'; import type { EditorState } from './state'; export interface ShortcutActions { play(): void; stop(): void; togglePause(): void; /** Copy and paste of timeline keys (#102); false when nothing to do there, so the key falls through. */ copyKeys(): boolean; pasteKeys(): boolean; } /** * Global keyboard shortcuts (see the wiki page "Keyboard"). Text fields keep their own key handling; * these only run when no focused widget consumed the key. Returns the call that takes them away * again – signing out tears the editor down while the page stays up (#177). */ export function installShortcuts(state: EditorState, actions: ShortcutActions): () => void { return ui().focus.onGlobalKey((event) => { const mod = event.ctrlKey || event.metaKey; const key = event.key.toLowerCase(); if (event.key === 'Escape' && state.mode === 'play') { actions.stop(); return true; } if (mod && key === 'z') { if (event.shiftKey) state.redo(); else state.undo(); return true; } if (mod && key === 'y') { state.redo(); return true; } if (mod && key === 'c') return actions.copyKeys(); if (mod && key === 'v') return actions.pasteKeys(); if (mod && key === 'g') { const s = state.selection; if (s?.kind === 'object') createContainer(state, s.holderId, s.objectIds); return true; } if (event.key === 'Delete' || event.key === 'Backspace') { const s = state.selection; if (s?.kind === 'object') removeObjects(state, s.holderId, s.objectIds); else if (s?.kind === 'node') removeGraphSelection(state, s.assetId, s.nodeIds, s.annotationIds); else if (s?.kind === 'frame') state.execute(new RemoveFrameCommand(s.assetId, s.frameId)); else if (s?.kind === 'annotation') removeGraphSelection(state, s.assetId, [], s.annotationIds); // The keys when some are picked, else the row itself – like a frame of a texture. else if (s?.kind === 'track') state.execute( s.keys.length > 0 ? new RemoveAnimationKeysCommand(s.assetId, s.keys) : new RemoveAnimationTracksCommand(s.assetId, [s.trackId]), ); else if (s?.kind === 'asset') confirmDeleteAsset(state, s.assetId); return s !== null; } // Ctrl+Enter starts play and then holds/resumes it. A plain letter would be taken away from // the game, which owns the keyboard while playing. if (mod && event.key === 'Enter') { if (state.mode === 'edit') actions.play(); else actions.togglePause(); return true; } return false; }); }