Shortcuts
Studio keyboard shortcuts are backed by a central command registry
(editor.shortcuts) rather than hardcoded key checks scattered across
panels. Every shortcut is rebindable, conflicts are detected automatically,
and menu items show their live binding.
Default shortcuts
| Command | Default | Context |
|---|---|---|
| Undo | Ctrl+Z |
Global |
| Redo | Ctrl+Shift+Z, Ctrl+Y |
Global |
| Copy | Ctrl+C |
Global |
| Cut | Ctrl+X |
Global |
| Paste | Ctrl+V |
Global |
| Play / Stop | Ctrl+P |
Global |
| Save | Ctrl+S |
Global |
| Save All | Ctrl+Shift+S |
Global |
| Close Tab | Ctrl+W |
Global |
| Next / Previous Tab | Ctrl+Tab / Ctrl+Shift+Tab |
Global |
| Move / Rotate / Scale tool | W / E / R |
Scene View (hover) |
| Focus Selection | F |
Scene View (hover) |
| Rename | F2 |
Hierarchy / Asset Browser (focused) |
| Delete | Delete, Backspace |
Hierarchy / Asset Browser (focused) |
"Global" commands fire regardless of which panel is focused. Panel-scoped
commands (Scene View's tool keys, Hierarchy/Asset Browser's rename and
navigation) only fire while that specific panel is the one you're actually
interacting with — hovering the viewport, or having clicked a row/tile —
not merely because the panel happens to be open somewhere on screen. This
is what stops, for example, the Scene View's R key from switching the
gizmo to Scale while you're typing an R into a rename field elsewhere.
Typing into any text field also suppresses every shortcut that isn't held down with Ctrl or Cmd — plain letters and Shift+letter always reach the field you're editing.
Rebinding
Open Turian ▸ Settings, select the Shortcuts category in the sidebar. Each row shows the command, which panel it's scoped to, and its current binding:
- Click the binding button and press a new key combination. Esc cancels; Backspace clears the binding entirely (the command becomes unbound, not merely "unset back to default").
- A warning icon appears next to a binding that collides with another command whose scope overlaps it (two Global commands, or a Global one and a panel-scoped one) — hover it to see which command it conflicts with. Conflicting bindings are still allowed, same as most editors; it's a warning, not a hard block.
- The revert icon next to an overridden row resets that one command back to its default. Reset All does the same for every command.
- Rebinds take effect immediately as you make them. Save writes them to the settings file; Reload discards any unsaved rebinds.
Menu labels
Menu items that have a registered command show their current shortcut right-aligned and dimmed, sourced live from the registry — a rebind in the Shortcuts settings panel is reflected in the menu on the next frame, with no separate text to keep in sync.
For plugin / module authors
Commands are registered at runtime, not declared in one central file —
each feature module owns its own commands, the same way
studio/main-window/Panels.zig lets each panel register itself:
const Shortcuts = @import("studio/services/Shortcuts.zig");
const my_commands = [_]Shortcuts.CommandDesc{
.{
.id = "myPlugin.doThing",
.title = "Do The Thing",
.context = .global, // or .scene_viewport / .hierarchy / .asset_browser / .ui_editor / .text_input
.requires_focus = false, // true if this only makes sense while your panel is actually engaged
.defaults = &.{Shortcuts.Binding.single(.{ .key = .t, .ctrl = true })},
},
};
const my_handlers = [_]?Shortcuts.Handler{myHandler};
// once, e.g. on first draw:
Shortcuts.register(&my_commands, &my_handlers);
For a command scoped to your own panel (context other than .global),
don't pass a handler — instead check it directly inside your panel's own
event loop, in place of a hardcoded key check:
for (gui.events()) |*e| {
if (Shortcuts.eventMatches(e, "myPlugin.panelAction", panel_is_engaged)) {
e.handle(@src(), my_widget_data);
doPanelAction();
}
}
panel_is_engaged is whatever "the user is looking at my panel" means for
you — real dvui keyboard focus for a widget-based panel, mouse-hover for a
non-focusable surface like a 3D viewport.
Command ids are namespaced strings (area.action) and double as the
settings key suffix (shortcut.<command_id>) — pick one you're willing to
keep stable, since renaming it orphans any user's saved rebind.