yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
shortcut_configurator.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <map>
5#include <optional>
6#include <unordered_map>
7
8#include "absl/functional/bind_front.h"
9#include "absl/strings/str_format.h"
33#include "core/project.h"
34#include "imgui/imgui.h"
35#include "util/log.h"
36
37namespace yaze::editor {
38
39namespace {
40
41// Matches Global Font Scale slider in settings_panel.cc (0.5–2.0).
42constexpr float kUiFontScaleStep = 0.05f;
43constexpr float kUiFontScaleMin = 0.5f;
44constexpr float kUiFontScaleMax = 2.0f;
45
46void AdjustUiFontScaleBy(EditorManager* editor_manager, float delta) {
47 if (!editor_manager || ImGui::GetIO().WantTextInput) {
48 return;
49 }
50 float s = editor_manager->user_settings().prefs().font_global_scale + delta;
51 editor_manager->SetFontGlobalScale(
52 std::clamp(s, kUiFontScaleMin, kUiFontScaleMax));
53}
54
55void RegisterIfValid(ShortcutManager* shortcut_manager, const std::string& name,
56 const std::vector<ImGuiKey>& keys,
57 std::function<void()> callback,
59 if (!shortcut_manager || !callback) {
60 return;
61 }
62 shortcut_manager->RegisterShortcut(name, keys, std::move(callback), scope);
63}
64
65void RegisterIfValid(ShortcutManager* shortcut_manager, const std::string& name,
66 ImGuiKey key, std::function<void()> callback,
68 if (!shortcut_manager || !callback) {
69 return;
70 }
71 shortcut_manager->RegisterShortcut(name, key, std::move(callback), scope);
72}
73
75 const std::string& name,
76 const std::vector<ImGuiKey>& keys,
77 std::function<void()> callback,
78 EditorType editor_type) {
79 if (!shortcut_manager || !callback) {
80 return;
81 }
82 shortcut_manager->RegisterEditorShortcut(name, keys, std::move(callback),
83 editor_type);
84}
85
86// Register a keyless palette alias for a legacy command name. The keyed entry
87// lives under the canonical name so one chord maps to one shortcut.
88void RegisterAlias(ShortcutManager* shortcut_manager, const std::string& alias,
89 const std::string& canonical) {
90 const Shortcut* target = shortcut_manager->FindShortcut(canonical);
91 if (!target || !target->callback) {
92 return;
93 }
94 shortcut_manager->RegisterCommand(alias, target->callback, target->scope);
95}
96
98 std::string id;
99 std::vector<ImGuiKey> keys;
100 std::string description;
101};
102
103const std::vector<EditorShortcutDef> kMusicEditorShortcuts = {
104 {"music.play_pause", {ImGuiKey_Space}, "Play/Pause current song"},
105 {"music.stop", {ImGuiKey_Escape}, "Stop playback"},
106 {"music.speed_up", {ImGuiKey_Equal}, "Increase playback speed"},
107 {"music.speed_up_keypad",
108 {ImGuiKey_KeypadAdd},
109 "Increase playback speed (keypad)"},
110 {"music.speed_down", {ImGuiKey_Minus}, "Decrease playback speed"},
111 {"music.speed_down_keypad",
112 {ImGuiKey_KeypadSubtract},
113 "Decrease playback speed (keypad)"},
114};
115
116const std::vector<EditorShortcutDef> kDungeonEditorShortcuts = {
117 {"dungeon.room.navigate_up",
118 {ImGuiMod_Ctrl, ImGuiKey_UpArrow},
119 "Adjacent room above"},
120 {"dungeon.room.navigate_down",
121 {ImGuiMod_Ctrl, ImGuiKey_DownArrow},
122 "Adjacent room below"},
123 {"dungeon.room.navigate_left",
124 {ImGuiMod_Ctrl, ImGuiKey_LeftArrow},
125 "Adjacent room to the left"},
126 {"dungeon.room.navigate_right",
127 {ImGuiMod_Ctrl, ImGuiKey_RightArrow},
128 "Adjacent room to the right"},
129 {"dungeon.room.previous",
130 {ImGuiMod_Ctrl, ImGuiKey_PageUp},
131 "Previous open/recent room"},
132 {"dungeon.room.next",
133 {ImGuiMod_Ctrl, ImGuiKey_PageDown},
134 "Next open/recent room"},
135 {"dungeon.open_room_list",
136 {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_L},
137 "Open Room List"},
138 {"dungeon.open_entrances",
139 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_E},
140 "Open Entrances"},
141 {"dungeon.open_matrix",
142 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_M},
143 "Open Room Matrix"},
144 {"dungeon.open_room_graphics",
145 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_G},
146 "Open Room Graphics"},
147 {"dungeon.open_palette",
148 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiMod_Alt, ImGuiKey_P},
149 "Open Palette Editor"},
150 {"dungeon.object.select_tool", {ImGuiKey_S}, "Select tool"},
151 {"dungeon.object.place_tool", {ImGuiKey_P}, "Place tool"},
152 {"dungeon.object.delete_tool", {ImGuiKey_D}, "Delete tool"},
153 {"dungeon.object.next_object", {ImGuiKey_RightBracket}, "Next object"},
154 {"dungeon.object.prev_object", {ImGuiKey_LeftBracket}, "Previous object"},
155 // Ctrl+C / Ctrl+V go through the generic Copy/Paste editor shortcuts,
156 // which route to DungeonEditorV2::Copy()/Paste().
157 {"dungeon.object.delete", {ImGuiKey_Delete}, "Delete selection"},
158};
159
160const std::vector<EditorShortcutDef> kOverworldShortcuts = {
161 {"overworld.brush_toggle", {ImGuiKey_B}, "Toggle brush"},
162 {"overworld.fill", {ImGuiKey_F}, "Fill tool"},
163 {"overworld.next_tile", {ImGuiKey_RightBracket}, "Next tile"},
164 {"overworld.prev_tile", {ImGuiKey_LeftBracket}, "Previous tile"},
165 {"overworld.toggle_fullscreen", {ImGuiKey_F11}, "Toggle canvas fullscreen"},
166 {"overworld.toggle_lock", {ImGuiMod_Ctrl, ImGuiKey_L}, "Toggle map lock"},
167 {"overworld.toggle_map_properties", {ImGuiKey_M}, "Toggle map properties"},
168 {"overworld.toggle_tile16_editor",
169 {ImGuiMod_Ctrl, ImGuiKey_T},
170 "Toggle Tile16 editor"},
171 {"overworld.toggle_item_list",
172 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_I},
173 "Toggle item list"},
174 // Plain digits 1-8 are tools/entity modes (interaction coordinator);
175 // Cmd/Ctrl+digits switch editors, so worlds use Alt+digits.
176 {"overworld.world_light", {ImGuiMod_Alt, ImGuiKey_1}, "Light World"},
177 {"overworld.world_dark", {ImGuiMod_Alt, ImGuiKey_2}, "Dark World"},
178 {"overworld.world_special", {ImGuiMod_Alt, ImGuiKey_3}, "Special World"},
179 {"overworld.map_left",
180 {ImGuiMod_Alt, ImGuiKey_LeftArrow},
181 "Map to the left"},
182 {"overworld.map_right",
183 {ImGuiMod_Alt, ImGuiKey_RightArrow},
184 "Map to the right"},
185 {"overworld.map_up", {ImGuiMod_Alt, ImGuiKey_UpArrow}, "Map above"},
186 {"overworld.map_down", {ImGuiMod_Alt, ImGuiKey_DownArrow}, "Map below"},
187 {"overworld.zoom_in", {ImGuiKey_Equal}, "Zoom in"},
188 {"overworld.zoom_in_keypad", {ImGuiKey_KeypadAdd}, "Zoom in (keypad)"},
189 {"overworld.zoom_out", {ImGuiKey_Minus}, "Zoom out"},
190 {"overworld.zoom_out_keypad",
191 {ImGuiKey_KeypadSubtract},
192 "Zoom out (keypad)"},
193 {"overworld.zoom_fit", {ImGuiKey_0}, "Zoom to fit world"},
194 {"overworld.center_map", {ImGuiKey_Home}, "Center on selected map"},
195 {"overworld.toggle_entities", {ImGuiKey_E}, "Show/hide entities"},
196 {"overworld.toggle_grid", {ImGuiKey_G}, "Show/hide grid"},
197};
198
199// Graphics shortcuts are editor-scoped to EditorType::kGraphics, so the
200// single-letter tool keys and =/- zoom only fire while the graphics editor
201// is focused and never steal keys from the overworld or music editors.
202const std::vector<EditorShortcutDef> kGraphicsShortcuts = {
203 // Sheet navigation
204 {"graphics.next_sheet", {ImGuiKey_PageDown}, "Next sheet"},
205 {"graphics.prev_sheet", {ImGuiKey_PageUp}, "Previous sheet"},
206
207 // Tool selection shortcuts
208 {"graphics.tool.select", {ImGuiKey_M}, "Select tool"},
209 {"graphics.tool.hand", {ImGuiKey_H}, "Hand tool (pan)"},
210 {"graphics.tool.pencil", {ImGuiKey_B}, "Pencil tool"},
211 {"graphics.tool.brush", {ImGuiKey_P}, "Brush tool"},
212 {"graphics.tool.eraser", {ImGuiKey_E}, "Eraser tool"},
213 {"graphics.tool.fill", {ImGuiKey_G}, "Fill tool"},
214 {"graphics.tool.line", {ImGuiKey_L}, "Line tool"},
215 {"graphics.tool.rectangle", {ImGuiKey_R}, "Rectangle tool"},
216 {"graphics.tool.eyedropper", {ImGuiKey_I}, "Eyedropper tool"},
217
218 // Zoom controls
219 {"graphics.zoom_in", {ImGuiKey_Equal}, "Zoom in"},
220 {"graphics.zoom_in_keypad", {ImGuiKey_KeypadAdd}, "Zoom in (keypad)"},
221 {"graphics.zoom_out", {ImGuiKey_Minus}, "Zoom out"},
222 {"graphics.zoom_out_keypad",
223 {ImGuiKey_KeypadSubtract},
224 "Zoom out (keypad)"},
225
226 // View toggles
227 {"graphics.toggle_grid", {ImGuiMod_Ctrl, ImGuiKey_G}, "Toggle grid"},
228};
229
230} // namespace
231
233 ShortcutManager* shortcut_manager) {
234 if (!shortcut_manager) {
235 return;
236 }
237
238 auto* editor_manager = deps.editor_manager;
239 auto* ui_coordinator = deps.ui_coordinator;
240 auto* popup_manager = deps.popup_manager;
241 auto* window_manager = deps.window_manager;
242
243 if (editor_manager) {
244 shortcut_manager->SetActiveEditorProvider(
245 [editor_manager]() -> std::optional<EditorType> {
246 auto* current_editor = editor_manager->GetCurrentEditor();
247 if (!current_editor) {
248 return std::nullopt;
249 }
250 return current_editor->type();
251 });
252 }
253
254 // Toggle activity bar (48px icon strip) visibility
255 RegisterIfValid(
256 shortcut_manager, "view.toggle_activity_bar", {ImGuiMod_Ctrl, ImGuiKey_B},
257 [window_manager]() {
258 if (window_manager) {
259 window_manager->ToggleSidebarVisibility();
260 }
261 },
263
264 // Toggle side panel (250px expanded panel) expansion
265 RegisterIfValid(
266 shortcut_manager, "view.toggle_side_panel",
267 {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_E},
268 [window_manager]() {
269 if (window_manager) {
270 window_manager->ToggleSidebarExpanded();
271 }
272 },
274
275 // Interface / font scale (Ctrl+/- on Windows/Linux, Cmd+/- on macOS).
276 RegisterIfValid(
277 shortcut_manager, "ui.font_scale_increase",
278 {ImGuiMod_Ctrl, ImGuiKey_Equal},
279 [editor_manager]() {
280 AdjustUiFontScaleBy(editor_manager, kUiFontScaleStep);
281 },
283
284 RegisterIfValid(
285 shortcut_manager, "ui.font_scale_increase_keypad",
286 {ImGuiMod_Ctrl, ImGuiKey_KeypadAdd},
287 [editor_manager]() {
288 AdjustUiFontScaleBy(editor_manager, kUiFontScaleStep);
289 },
291
292 RegisterIfValid(
293 shortcut_manager, "ui.font_scale_decrease",
294 {ImGuiMod_Ctrl, ImGuiKey_Minus},
295 [editor_manager]() {
296 AdjustUiFontScaleBy(editor_manager, -kUiFontScaleStep);
297 },
299
300 RegisterIfValid(
301 shortcut_manager, "ui.font_scale_decrease_keypad",
302 {ImGuiMod_Ctrl, ImGuiKey_KeypadSubtract},
303 [editor_manager]() {
304 AdjustUiFontScaleBy(editor_manager, -kUiFontScaleStep);
305 },
307
308 RegisterIfValid(
309 shortcut_manager, "ui.font_scale_reset", {ImGuiMod_Ctrl, ImGuiKey_0},
310 [editor_manager]() {
311 if (!editor_manager || ImGui::GetIO().WantTextInput) {
312 return;
313 }
314 editor_manager->SetFontGlobalScale(1.0f);
315 },
317
318 RegisterIfValid(
319 shortcut_manager, "Open", {ImGuiMod_Ctrl, ImGuiKey_O},
320 [editor_manager]() {
321 if (editor_manager) {
322 editor_manager->LoadRom();
323 }
324 },
326
327 RegisterIfValid(
328 shortcut_manager, "Save", {ImGuiMod_Ctrl, ImGuiKey_S},
329 [editor_manager]() {
330 if (editor_manager) {
331 const auto status = editor_manager->SaveCurrentSession();
332 if (!status.ok() && !absl::IsCancelled(status)) {
333 editor_manager->toast_manager()->Show(
334 absl::StrFormat("Failed to save: %s", status.message()),
336 }
337 }
338 });
339
340 RegisterIfValid(
341 shortcut_manager, "Save As", {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_S},
342 [editor_manager]() {
343 if (editor_manager) {
344 const auto status = editor_manager->SaveCurrentSessionAs();
345 if (!status.ok() && !absl::IsCancelled(status)) {
346 editor_manager->toast_manager()->Show(
347 absl::StrFormat("Failed to save a copy: %s", status.message()),
349 }
350 }
351 },
353
354 RegisterIfValid(
355 shortcut_manager, "Close ROM", {ImGuiMod_Ctrl, ImGuiKey_W},
356 [editor_manager]() {
357 // CloseRom() works with a single session and asks about unsaved
358 // edits; CloseCurrentSession() refuses to close the last session.
359 if (editor_manager) {
360 editor_manager->CloseRom();
361 }
362 },
364
365 RegisterIfValid(
366 shortcut_manager, "Quit", {ImGuiMod_Ctrl, ImGuiKey_Q},
367 [editor_manager]() {
368 if (editor_manager) {
369 editor_manager->Quit();
370 }
371 },
373
374 RegisterIfValid(
375 shortcut_manager, "Undo", {ImGuiMod_Ctrl, ImGuiKey_Z},
376 [editor_manager]() {
377 if (editor_manager && editor_manager->GetCurrentEditor()) {
378 editor_manager->GetCurrentEditor()->Undo();
379 }
380 },
382
383 RegisterIfValid(
384 shortcut_manager, "Redo", {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_Z},
385 [editor_manager]() {
386 if (editor_manager && editor_manager->GetCurrentEditor()) {
387 editor_manager->GetCurrentEditor()->Redo();
388 }
389 },
391
392 // Menus advertise Ctrl+Y; keep Ctrl+Shift+Z as the primary binding.
393 RegisterIfValid(
394 shortcut_manager, "Redo (Alt)", {ImGuiMod_Ctrl, ImGuiKey_Y},
395 [editor_manager]() {
396 if (editor_manager && editor_manager->GetCurrentEditor()) {
397 editor_manager->GetCurrentEditor()->Redo();
398 }
399 },
401
402 RegisterIfValid(
403 shortcut_manager, "Cut", {ImGuiMod_Ctrl, ImGuiKey_X},
404 [editor_manager]() {
405 if (editor_manager && editor_manager->GetCurrentEditor()) {
406 editor_manager->GetCurrentEditor()->Cut();
407 }
408 },
410
411 RegisterIfValid(
412 shortcut_manager, "Copy", {ImGuiMod_Ctrl, ImGuiKey_C},
413 [editor_manager]() {
414 if (editor_manager && editor_manager->GetCurrentEditor()) {
415 editor_manager->GetCurrentEditor()->Copy();
416 }
417 },
419
420 RegisterIfValid(
421 shortcut_manager, "Paste", {ImGuiMod_Ctrl, ImGuiKey_V},
422 [editor_manager]() {
423 if (editor_manager && editor_manager->GetCurrentEditor()) {
424 editor_manager->GetCurrentEditor()->Paste();
425 }
426 },
428
429 RegisterIfValid(
430 shortcut_manager, "Find", {ImGuiMod_Ctrl, ImGuiKey_F},
431 [editor_manager]() {
432 if (editor_manager && editor_manager->GetCurrentEditor()) {
433 editor_manager->GetCurrentEditor()->Find();
434 }
435 },
437
438 RegisterIfValid(
439 shortcut_manager, "Command Palette",
440 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_P},
441 [ui_coordinator]() {
442 if (ui_coordinator) {
443 ui_coordinator->ShowCommandPalette();
444 }
445 },
447
448 RegisterIfValid(
449 shortcut_manager, "Window Finder", {ImGuiMod_Ctrl, ImGuiKey_P},
450 [ui_coordinator]() {
451 if (ui_coordinator) {
452 ui_coordinator->ShowPanelFinder();
453 }
454 },
456
457 RegisterIfValid(
458 shortcut_manager, "Keyboard Shortcuts",
459 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_Slash},
460 [ui_coordinator]() {
461 if (ui_coordinator) {
462 ui_coordinator->ShowShortcutsBrowser();
463 }
464 },
466
467 RegisterIfValid(
468 shortcut_manager, "Global Search",
469 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_K},
470 [ui_coordinator]() {
471 if (ui_coordinator) {
472 ui_coordinator->ShowGlobalSearch();
473 }
474 },
476
477 RegisterIfValid(
478 shortcut_manager, "Load Last ROM", {ImGuiMod_Ctrl, ImGuiKey_R},
479 [editor_manager]() {
481 if (!recent.GetRecentFiles().empty() && editor_manager) {
482 editor_manager->OpenRomOrProject(recent.GetRecentFiles().front());
483 }
484 },
486
487 RegisterIfValid(
488 shortcut_manager, "Show About", ImGuiKey_F1,
489 [popup_manager]() {
490 if (popup_manager) {
491 popup_manager->Show("About");
492 }
493 },
495
496 auto register_editor_shortcut = [&](EditorType type, ImGuiKey key) {
497 RegisterIfValid(shortcut_manager,
498 absl::StrFormat("switch.%d", static_cast<int>(type)),
499 {ImGuiMod_Ctrl, key}, [editor_manager, type]() {
500 if (editor_manager) {
501 editor_manager->SwitchToEditor(type);
502 }
503 });
504 };
505
506 register_editor_shortcut(EditorType::kOverworld, ImGuiKey_1);
507 register_editor_shortcut(EditorType::kDungeon, ImGuiKey_2);
508 register_editor_shortcut(EditorType::kGraphics, ImGuiKey_3);
509 register_editor_shortcut(EditorType::kSprite, ImGuiKey_4);
510 register_editor_shortcut(EditorType::kMessage, ImGuiKey_5);
511 register_editor_shortcut(EditorType::kMusic, ImGuiKey_6);
512 register_editor_shortcut(EditorType::kPalette, ImGuiKey_7);
513 register_editor_shortcut(EditorType::kScreen, ImGuiKey_8);
514 register_editor_shortcut(EditorType::kAssembly, ImGuiKey_9);
515 // Ctrl/Cmd+0 is "reset font scale"; Settings uses the platform-standard
516 // Ctrl/Cmd+, instead.
517 register_editor_shortcut(EditorType::kSettings, ImGuiKey_Comma);
518
519 // ============================================================================
520 // Editor Switch Commands (command palette with friendly names)
521 // ============================================================================
522 auto register_editor_command = [&](EditorType type, const std::string& name) {
523 shortcut_manager->RegisterCommand(
524 absl::StrFormat("Switch to %s Editor", name), [editor_manager, type]() {
525 if (editor_manager) {
526 editor_manager->SwitchToEditor(type);
527 }
528 });
529 };
530
531 register_editor_command(EditorType::kOverworld, "Overworld");
532 register_editor_command(EditorType::kDungeon, "Dungeon");
533 register_editor_command(EditorType::kGraphics, "Graphics");
534 register_editor_command(EditorType::kSprite, "Sprite");
535 register_editor_command(EditorType::kMessage, "Message");
536 register_editor_command(EditorType::kMusic, "Music");
537 register_editor_command(EditorType::kPalette, "Palette");
538 register_editor_command(EditorType::kScreen, "Screen");
539 register_editor_command(EditorType::kAssembly, "Assembly");
540 register_editor_command(EditorType::kSettings, "Settings");
541 register_editor_command(EditorType::kPlanning, "Planning");
542
543 // Editor-scoped Music shortcuts (toggle playback, speed controls)
544 if (editor_manager) {
545 for (const auto& def : kMusicEditorShortcuts) {
546 RegisterEditorIfValid(
547 shortcut_manager, def.id, def.keys,
548 [editor_manager, id = def.id]() {
549 if (!editor_manager)
550 return;
551 auto* editor_set = editor_manager->GetCurrentEditorSet();
552 auto* music_editor =
553 editor_set ? editor_set->GetMusicEditor() : nullptr;
554 if (!music_editor)
555 return;
556
557 if (id == "music.play_pause") {
558 music_editor->TogglePlayPause();
559 } else if (id == "music.stop") {
560 music_editor->StopPlayback();
561 } else if (id == "music.speed_up" ||
562 id == "music.speed_up_keypad") {
563 music_editor->SpeedUp();
564 } else if (id == "music.speed_down" ||
565 id == "music.speed_down_keypad") {
566 music_editor->SlowDown();
567 }
568 },
570 }
571 }
572
573 // Editor-scoped Dungeon shortcuts (object tools)
574 if (editor_manager) {
575 for (const auto& def : kDungeonEditorShortcuts) {
576 RegisterEditorIfValid(
577 shortcut_manager, def.id, def.keys,
578 [editor_manager, id = def.id]() {
579 if (!editor_manager)
580 return;
581 auto* editor_set = editor_manager->GetCurrentEditorSet();
582 auto* dungeon_editor =
583 editor_set ? editor_set->GetDungeonEditor() : nullptr;
584 if (!dungeon_editor)
585 return;
586
587 if (id == "dungeon.room.navigate_up") {
588 dungeon_editor->NavigateToAdjacentRoom(
589 DungeonRoomNavigationDirection::kUp);
590 } else if (id == "dungeon.room.navigate_down") {
591 dungeon_editor->NavigateToAdjacentRoom(
593 } else if (id == "dungeon.room.navigate_left") {
594 dungeon_editor->NavigateToAdjacentRoom(
596 } else if (id == "dungeon.room.navigate_right") {
597 dungeon_editor->NavigateToAdjacentRoom(
599 } else if (id == "dungeon.room.previous") {
600 dungeon_editor->CycleRoomSelection(-1);
601 } else if (id == "dungeon.room.next") {
602 dungeon_editor->CycleRoomSelection(1);
603 } else if (id == "dungeon.open_room_list") {
604 dungeon_editor->OpenWindow(DungeonEditorV2::kRoomSelectorId);
605 } else if (id == "dungeon.open_entrances") {
606 dungeon_editor->OpenWindow("dungeon.entrance_properties");
607 } else if (id == "dungeon.open_matrix") {
608 dungeon_editor->OpenWindow(DungeonEditorV2::kRoomMatrixId);
609 } else if (id == "dungeon.open_room_graphics") {
610 dungeon_editor->OpenWindow(DungeonEditorV2::kRoomGraphicsId);
611 } else if (id == "dungeon.open_palette") {
612 dungeon_editor->OpenWindow(DungeonEditorV2::kPaletteEditorId);
613 } else if (id == "dungeon.object.select_tool") {
614 // Unified mode: cancel placement to switch to selection.
615 if (auto* selector = dungeon_editor->object_editor_panel())
616 selector->CancelPlacement();
617 } else if (id == "dungeon.object.next_object") {
618 (void)dungeon_editor->CycleObjectSelection(1);
619 } else if (id == "dungeon.object.prev_object") {
620 (void)dungeon_editor->CycleObjectSelection(-1);
621 } else if (id == "dungeon.object.delete_tool" ||
622 id == "dungeon.object.delete") {
623 dungeon_editor->QueueRoomCanvasDeleteShortcut();
624 }
625 },
627 if (def.id.starts_with("dungeon.room.")) {
628 shortcut_manager->SetShortcutEnabled(def.id, [editor_manager]() {
629 if (!editor_manager) {
630 return false;
631 }
632 auto* editor_set = editor_manager->GetCurrentEditorSet();
633 auto* dungeon_editor =
634 editor_set ? editor_set->GetDungeonEditor() : nullptr;
635 return dungeon_editor != nullptr &&
636 dungeon_editor->CanHandleDungeonNavigationShortcut();
637 });
638 }
639 }
640 }
641
642 // Editor-scoped Overworld shortcuts (basic tools)
643 if (editor_manager) {
644 for (const auto& def : kOverworldShortcuts) {
646 shortcut_manager, def.id, def.keys,
647 [editor_manager, id = def.id]() {
648 if (!editor_manager)
649 return;
650 auto* editor_set = editor_manager->GetCurrentEditorSet();
651 auto* overworld_editor =
652 editor_set ? editor_set->GetOverworldEditor() : nullptr;
653 if (!overworld_editor)
654 return;
655
656 if (id == "overworld.brush_toggle") {
657 overworld_editor->ToggleBrushTool();
658 } else if (id == "overworld.fill") {
659 overworld_editor->ActivateFillTool();
660 } else if (id == "overworld.next_tile") {
661 overworld_editor->CycleTileSelection(1);
662 } else if (id == "overworld.prev_tile") {
663 overworld_editor->CycleTileSelection(-1);
664 } else if (id == "overworld.toggle_fullscreen") {
665 overworld_editor->ToggleCanvasFullscreen();
666 } else if (id == "overworld.toggle_lock") {
667 overworld_editor->ToggleMapLock();
668 } else if (id == "overworld.toggle_map_properties") {
669 overworld_editor->OpenMapPropertiesWindow();
670 } else if (id == "overworld.toggle_tile16_editor") {
671 overworld_editor->ToggleTile16EditorWindow();
672 } else if (id == "overworld.toggle_item_list") {
673 overworld_editor->ToggleItemListWindow();
674 } else if (id == "overworld.world_light") {
675 overworld_editor->SwitchToWorld(0);
676 } else if (id == "overworld.world_dark") {
677 overworld_editor->SwitchToWorld(1);
678 } else if (id == "overworld.world_special") {
679 overworld_editor->SwitchToWorld(2);
680 } else if (id == "overworld.map_left") {
681 overworld_editor->SelectAdjacentMap(-1, 0);
682 } else if (id == "overworld.map_right") {
683 overworld_editor->SelectAdjacentMap(1, 0);
684 } else if (id == "overworld.map_up") {
685 overworld_editor->SelectAdjacentMap(0, -1);
686 } else if (id == "overworld.map_down") {
687 overworld_editor->SelectAdjacentMap(0, 1);
688 } else if (id == "overworld.zoom_in" ||
689 id == "overworld.zoom_in_keypad") {
690 overworld_editor->ZoomIn();
691 } else if (id == "overworld.zoom_out" ||
692 id == "overworld.zoom_out_keypad") {
693 overworld_editor->ZoomOut();
694 } else if (id == "overworld.zoom_fit") {
695 overworld_editor->ZoomToFit();
696 } else if (id == "overworld.center_map") {
697 overworld_editor->CenterOverworldView();
698 } else if (id == "overworld.toggle_entities") {
699 overworld_editor->ToggleEntityVisibility();
700 } else if (id == "overworld.toggle_grid") {
701 overworld_editor->ToggleGrid();
702 }
703 },
704 EditorType::kOverworld);
705 }
706 }
707
708 // Editor-scoped Graphics shortcuts (sheet navigation)
709 if (editor_manager) {
710 for (const auto& def : kGraphicsShortcuts) {
712 shortcut_manager, def.id, def.keys,
713 [editor_manager, id = def.id]() {
714 if (!editor_manager)
715 return;
716 auto* editor_set = editor_manager->GetCurrentEditorSet();
717 auto* graphics_editor =
718 editor_set ? editor_set->GetGraphicsEditor() : nullptr;
719 if (!graphics_editor)
720 return;
721
722 static const std::map<std::string, PixelTool> kTools = {
723 {"graphics.tool.select", PixelTool::kSelect},
724 {"graphics.tool.hand", PixelTool::kHand},
725 {"graphics.tool.pencil", PixelTool::kPencil},
726 {"graphics.tool.brush", PixelTool::kBrush},
727 {"graphics.tool.eraser", PixelTool::kEraser},
728 {"graphics.tool.fill", PixelTool::kFill},
729 {"graphics.tool.line", PixelTool::kLine},
730 {"graphics.tool.rectangle", PixelTool::kRectangle},
731 {"graphics.tool.eyedropper", PixelTool::kEyedropper},
732 };
733 if (id == "graphics.next_sheet") {
734 graphics_editor->NextSheet();
735 } else if (id == "graphics.prev_sheet") {
736 graphics_editor->PrevSheet();
737 } else if (auto tool = kTools.find(id); tool != kTools.end()) {
738 graphics_editor->SetPixelTool(tool->second);
739 } else if (id == "graphics.zoom_in" ||
740 id == "graphics.zoom_in_keypad") {
741 graphics_editor->ZoomIn();
742 } else if (id == "graphics.zoom_out" ||
743 id == "graphics.zoom_out_keypad") {
744 graphics_editor->ZoomOut();
745 } else if (id == "graphics.toggle_grid") {
746 graphics_editor->ToggleGrid();
747 }
748 },
749 EditorType::kGraphics);
750 }
751 }
752
754 shortcut_manager, "Editor Selection", {ImGuiMod_Ctrl, ImGuiKey_E},
755 [ui_coordinator]() {
756 if (ui_coordinator) {
757 ui_coordinator->ShowEditorSelection();
758 }
759 },
760 Shortcut::Scope::kGlobal);
761
763 shortcut_manager, "Window Browser",
764 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_B},
765 [ui_coordinator]() {
766 if (ui_coordinator) {
767 ui_coordinator->ShowWindowBrowser();
768 }
769 },
770 Shortcut::Scope::kGlobal);
772 shortcut_manager, "Window Browser (Alt)",
773 {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_P},
774 [ui_coordinator]() {
775 if (ui_coordinator) {
776 ui_coordinator->ShowWindowBrowser();
777 }
778 },
779 Shortcut::Scope::kGlobal);
780 RegisterAlias(shortcut_manager, "Panel Browser", "Window Browser");
781 RegisterAlias(shortcut_manager, "Panel Browser (Alt)",
782 "Window Browser (Alt)");
783
785 shortcut_manager, "View: Previous Right Drawer",
786 {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_LeftBracket},
787 [editor_manager]() {
788 if (!editor_manager) {
789 return;
790 }
791 if (auto* right_drawer = editor_manager->right_drawer_manager()) {
792 right_drawer->CycleToPreviousDrawer();
793 }
794 },
795 Shortcut::Scope::kGlobal);
796
798 shortcut_manager, "View: Next Right Drawer",
799 {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_RightBracket},
800 [editor_manager]() {
801 if (!editor_manager) {
802 return;
803 }
804 if (auto* right_drawer = editor_manager->right_drawer_manager()) {
805 right_drawer->CycleToNextDrawer();
806 }
807 },
808 Shortcut::Scope::kGlobal);
809 RegisterAlias(shortcut_manager, "View: Previous Right Panel",
810 "View: Previous Right Drawer");
811 RegisterAlias(shortcut_manager, "View: Next Right Panel",
812 "View: Next Right Drawer");
813
814 if (window_manager) {
815 // Note: Using Ctrl+Alt for panel shortcuts to avoid conflicts with Save As
816 // (Ctrl+Shift+S)
818 shortcut_manager, "Show Dungeon Windows",
819 {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_D},
820 [window_manager]() {
821 window_manager->ShowAllWindowsInCategory(
822 window_manager->GetActiveSessionId(), "Dungeon");
823 },
824 Shortcut::Scope::kEditor);
825 RegisterAlias(shortcut_manager, "Show Dungeon Panels",
826 "Show Dungeon Windows");
828 shortcut_manager, "Show Graphics Windows",
829 {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_G},
830 [window_manager]() {
831 window_manager->ShowAllWindowsInCategory(
832 window_manager->GetActiveSessionId(), "Graphics");
833 },
834 Shortcut::Scope::kEditor);
835 RegisterAlias(shortcut_manager, "Show Graphics Panels",
836 "Show Graphics Windows");
838 shortcut_manager, "Show Screen Windows",
839 {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_S},
840 [window_manager]() {
841 window_manager->ShowAllWindowsInCategory(
842 window_manager->GetActiveSessionId(), "Screen");
843 },
844 Shortcut::Scope::kEditor);
845 RegisterAlias(shortcut_manager, "Show Screen Panels",
846 "Show Screen Windows");
847 }
848
849#ifdef YAZE_BUILD_AGENT_UI
850 RegisterIfValid(shortcut_manager, "Agent Editor",
851 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_A},
852 [editor_manager]() {
853 if (editor_manager) {
854 editor_manager->ShowAIAgent();
855 }
856 });
857
858 RegisterIfValid(shortcut_manager, "Agent Sidebar",
859 {ImGuiMod_Ctrl, ImGuiKey_H}, [editor_manager]() {
860 if (editor_manager) {
861 editor_manager->ShowChatHistory();
862 }
863 });
864
865 RegisterIfValid(shortcut_manager, "Proposal Drawer",
866 {ImGuiMod_Ctrl, ImGuiMod_Shift,
867 ImGuiKey_R}, // Changed from Ctrl+P to Ctrl+Shift+R
868 [editor_manager]() {
869 if (editor_manager) {
870 editor_manager->ShowProposalDrawer();
871 }
872 });
873#endif
874
875 // ============================================================================
876 // Layout Presets and Profiles (command palette only - no keyboard shortcuts)
877 // ============================================================================
878 shortcut_manager->RegisterCommand("Layout Profile: Code", [editor_manager]() {
879 if (editor_manager) {
880 editor_manager->ApplyLayoutProfile("code");
881 }
882 });
883 shortcut_manager->RegisterCommand(
884 "Layout Profile: Debug", [editor_manager]() {
885 if (editor_manager) {
886 editor_manager->ApplyLayoutProfile("debug");
887 }
888 });
889 shortcut_manager->RegisterCommand(
890 "Layout Profile: Mapping", [editor_manager]() {
891 if (editor_manager) {
892 editor_manager->ApplyLayoutProfile("mapping");
893 }
894 });
895 shortcut_manager->RegisterCommand("Layout Profile: Chat", [editor_manager]() {
896 if (editor_manager) {
897 editor_manager->ApplyLayoutProfile("chat");
898 }
899 });
900 shortcut_manager->RegisterCommand(
901 "Layout Snapshot: Capture", [editor_manager]() {
902 if (editor_manager) {
903 editor_manager->CaptureTemporaryLayoutSnapshot();
904 }
905 });
906 shortcut_manager->RegisterCommand(
907 "Layout Snapshot: Restore", [editor_manager]() {
908 if (editor_manager) {
909 editor_manager->RestoreTemporaryLayoutSnapshot();
910 }
911 });
912 shortcut_manager->RegisterCommand(
913 "Layout Snapshot: Clear", [editor_manager]() {
914 if (editor_manager) {
915 editor_manager->ClearTemporaryLayoutSnapshot();
916 }
917 });
918
919 shortcut_manager->RegisterCommand(
920 "Layout: Apply Minimal Preset", [editor_manager]() {
921 if (editor_manager) {
922 editor_manager->ApplyLayoutPreset("Minimal");
923 }
924 });
925 shortcut_manager->RegisterCommand(
926 "Layout: Apply Developer Preset", [editor_manager]() {
927 if (editor_manager) {
928 editor_manager->ApplyLayoutPreset("Developer");
929 }
930 });
931 shortcut_manager->RegisterCommand(
932 "Layout: Apply Designer Preset", [editor_manager]() {
933 if (editor_manager) {
934 editor_manager->ApplyLayoutPreset("Designer");
935 }
936 });
937 shortcut_manager->RegisterCommand(
938 "Layout: Apply Modder Preset", [editor_manager]() {
939 if (editor_manager) {
940 editor_manager->ApplyLayoutPreset("Modder");
941 }
942 });
943 shortcut_manager->RegisterCommand(
944 "Layout: Apply Overworld Expert Preset", [editor_manager]() {
945 if (editor_manager) {
946 editor_manager->ApplyLayoutPreset("Overworld Expert");
947 }
948 });
949 shortcut_manager->RegisterCommand(
950 "Layout: Apply Dungeon Expert Preset", [editor_manager]() {
951 if (editor_manager) {
952 editor_manager->ApplyLayoutPreset("Dungeon Expert");
953 }
954 });
955 shortcut_manager->RegisterCommand(
956 "Layout: Apply Testing Preset", [editor_manager]() {
957 if (editor_manager) {
958 editor_manager->ApplyLayoutPreset("Testing");
959 }
960 });
961 shortcut_manager->RegisterCommand(
962 "Layout: Apply Audio Preset", [editor_manager]() {
963 if (editor_manager) {
964 editor_manager->ApplyLayoutPreset("Audio");
965 }
966 });
967 shortcut_manager->RegisterCommand(
968 "Layout: Reset Current Editor", [editor_manager]() {
969 if (editor_manager) {
970 editor_manager->ResetCurrentEditorLayout();
971 }
972 });
973
974 // ============================================================================
975 // Right Drawer / Panel Toggle Commands (command palette only)
976 // ============================================================================
977 if (editor_manager) {
978 using DrawerType = RightDrawerManager::DrawerType;
979 auto* right_drawer_manager = editor_manager->right_drawer_manager();
980 if (right_drawer_manager) {
981 struct DrawerCmd {
982 const char* label;
983 DrawerType type;
984 };
985 DrawerCmd drawer_cmds[] = {
986 {"View: Toggle AI Agent Panel", DrawerType::kAgentChat},
987 {"View: Toggle AI Agent Drawer", DrawerType::kAgentChat},
988 {"View: Toggle Proposals Panel", DrawerType::kProposals},
989 {"View: Toggle Proposals Drawer", DrawerType::kProposals},
990 {"View: Toggle Settings Panel", DrawerType::kSettings},
991 {"View: Toggle Settings Drawer", DrawerType::kSettings},
992 {"View: Toggle Help Panel", DrawerType::kHelp},
993 {"View: Toggle Help Drawer", DrawerType::kHelp},
994 {"View: Toggle Notifications", DrawerType::kNotifications},
995 {"View: Toggle Notifications Drawer", DrawerType::kNotifications},
996 {"View: Toggle Properties Panel", DrawerType::kProperties},
997 {"View: Toggle Properties Drawer", DrawerType::kProperties},
998 {"View: Toggle Project Panel", DrawerType::kProject},
999 {"View: Toggle Project Drawer", DrawerType::kProject},
1000 };
1001 for (const auto& cmd : drawer_cmds) {
1002 shortcut_manager->RegisterCommand(
1003 cmd.label, [right_drawer_manager, drawer_type = cmd.type]() {
1004 right_drawer_manager->ToggleDrawer(drawer_type);
1005 });
1006 }
1007 // The Previous/Next Right Drawer cycle commands are registered above
1008 // with their Ctrl+Alt+[ / ] chords; re-registering them here as
1009 // keyless commands used to erase those chords.
1010 }
1011 }
1012
1013 // ============================================================================
1014 // Window + Panel Visibility Commands (command palette only)
1015 // ============================================================================
1016 if (window_manager) {
1017 auto categories = window_manager->GetAllCategories();
1018 const size_t registration_session_id = window_manager->GetActiveSessionId();
1019
1020 shortcut_manager->RegisterCommand(
1021 "View: Show Panel Browser",
1022 [window_manager]() { window_manager->TriggerShowWindowBrowser(); });
1023 shortcut_manager->RegisterCommand(
1024 "View: Show Window Browser",
1025 [window_manager]() { window_manager->TriggerShowWindowBrowser(); });
1026
1027 for (const auto& category : categories) {
1028 auto windows = window_manager->GetWindowsInCategory(
1029 registration_session_id, category);
1030 for (const auto& window : windows) {
1031 const std::string window_id = window.card_id;
1032 const std::string display_name = window.display_name;
1033
1034 shortcut_manager->RegisterCommand(
1035 absl::StrFormat("View: Show %s", display_name),
1036 [window_manager, window_id]() {
1037 if (window_manager) {
1038 window_manager->OpenWindow(window_manager->GetActiveSessionId(),
1039 window_id);
1040 }
1041 });
1042 shortcut_manager->RegisterCommand(
1043 absl::StrFormat("View: Hide %s", display_name),
1044 [window_manager, window_id]() {
1045 if (window_manager) {
1046 window_manager->CloseWindow(
1047 window_manager->GetActiveSessionId(), window_id);
1048 }
1049 });
1050 shortcut_manager->RegisterCommand(
1051 absl::StrFormat("View: Toggle %s", display_name),
1052 [window_manager, window_id]() {
1053 if (window_manager) {
1054 window_manager->ToggleWindow(
1055 window_manager->GetActiveSessionId(), window_id);
1056 }
1057 });
1058
1059 shortcut_manager->RegisterCommand(
1060 absl::StrFormat("View: Open %s Window", display_name),
1061 [window_manager, window_id]() {
1062 if (window_manager) {
1063 window_manager->OpenWindow(window_manager->GetActiveSessionId(),
1064 window_id);
1065 }
1066 });
1067 shortcut_manager->RegisterCommand(
1068 absl::StrFormat("View: Close %s Window", display_name),
1069 [window_manager, window_id]() {
1070 if (window_manager) {
1071 window_manager->CloseWindow(
1072 window_manager->GetActiveSessionId(), window_id);
1073 }
1074 });
1075 shortcut_manager->RegisterCommand(
1076 absl::StrFormat("View: Toggle %s Window", display_name),
1077 [window_manager, window_id]() {
1078 if (window_manager) {
1079 window_manager->ToggleWindow(
1080 window_manager->GetActiveSessionId(), window_id);
1081 }
1082 });
1083 }
1084
1085 shortcut_manager->RegisterCommand(
1086 absl::StrFormat("View: Show All %s Panels", category),
1087 [window_manager, category]() {
1088 if (window_manager) {
1089 window_manager->ShowAllWindowsInCategory(
1090 window_manager->GetActiveSessionId(), category);
1091 }
1092 });
1093 shortcut_manager->RegisterCommand(
1094 absl::StrFormat("View: Hide All %s Panels", category),
1095 [window_manager, category]() {
1096 if (window_manager) {
1097 window_manager->HideAllWindowsInCategory(
1098 window_manager->GetActiveSessionId(), category);
1099 }
1100 });
1101 shortcut_manager->RegisterCommand(
1102 absl::StrFormat("View: Show All %s Windows", category),
1103 [window_manager, category]() {
1104 if (window_manager) {
1105 window_manager->ShowAllWindowsInCategory(
1106 window_manager->GetActiveSessionId(), category);
1107 }
1108 });
1109 shortcut_manager->RegisterCommand(
1110 absl::StrFormat("View: Hide All %s Windows", category),
1111 [window_manager, category]() {
1112 if (window_manager) {
1113 window_manager->HideAllWindowsInCategory(
1114 window_manager->GetActiveSessionId(), category);
1115 }
1116 });
1117 }
1118 }
1119}
1120
1122 ShortcutManager* shortcut_manager) {
1123 if (!shortcut_manager) {
1124 return;
1125 }
1126
1127 auto* menu_orchestrator = deps.menu_orchestrator;
1128 auto* session_coordinator = deps.session_coordinator;
1129 auto* workspace_manager = deps.workspace_manager;
1130 auto* editor_manager = deps.editor_manager;
1131
1132 RegisterIfValid(shortcut_manager, "New Session",
1133 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_N},
1134 [session_coordinator]() {
1135 if (session_coordinator) {
1136 session_coordinator->CreateNewSession();
1137 }
1138 });
1139
1140 RegisterIfValid(shortcut_manager, "Duplicate Session",
1141 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_D},
1142 [session_coordinator]() {
1143 if (session_coordinator) {
1144 session_coordinator->DuplicateCurrentSession();
1145 }
1146 });
1147
1148 RegisterIfValid(shortcut_manager, "Close Session",
1149 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_W},
1150 [editor_manager, session_coordinator]() {
1151 if (editor_manager) {
1152 editor_manager->CloseCurrentSession();
1153 } else if (session_coordinator) {
1154 session_coordinator->CloseCurrentSession();
1155 }
1156 });
1157
1158 RegisterIfValid(shortcut_manager, "Session Switcher",
1159 {ImGuiMod_Ctrl, ImGuiKey_Tab}, [session_coordinator]() {
1160 if (session_coordinator) {
1161 session_coordinator->ShowSessionSwitcher();
1162 }
1163 });
1164
1165 RegisterIfValid(shortcut_manager, "Save Layout",
1166 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_L},
1167 [workspace_manager]() {
1168 if (workspace_manager) {
1169 workspace_manager->SaveWorkspaceLayout();
1170 }
1171 });
1172
1173 RegisterIfValid(shortcut_manager, "Load Layout",
1174 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_O},
1175 [workspace_manager]() {
1176 if (workspace_manager) {
1177 workspace_manager->LoadWorkspaceLayout();
1178 }
1179 });
1180
1181 // Note: Changed from Ctrl+Shift+R to Ctrl+Alt+R to avoid conflict with
1182 // Proposal Drawer
1183 RegisterIfValid(shortcut_manager, "Reset Layout",
1184 {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_R},
1185 [menu_orchestrator]() {
1186 if (menu_orchestrator) {
1187 menu_orchestrator->OnResetWorkspaceLayout();
1188 }
1189 });
1190
1191 // F11 and Ctrl+T (Test Dashboard) are global; the overworld editor's
1192 // editor-scoped F11 (canvas fullscreen) and Ctrl+T (Tile16 editor) win
1193 // while it is active.
1194 RegisterIfValid(shortcut_manager, "Maximize Window", ImGuiKey_F11,
1195 [workspace_manager]() {
1196 if (workspace_manager) {
1197 workspace_manager->MaximizeCurrentWindow();
1198 }
1199 });
1200
1201#ifdef YAZE_ENABLE_TESTING
1202 RegisterIfValid(shortcut_manager, "Test Dashboard",
1203 {ImGuiMod_Ctrl, ImGuiKey_T}, [menu_orchestrator]() {
1204 if (menu_orchestrator) {
1205 menu_orchestrator->OnShowTestDashboard();
1206 }
1207 });
1208#endif
1209}
1210
1212 ShortcutManager* shortcut_manager) {
1213 if (!shortcut_manager) {
1214 return;
1215 }
1216 auto apply = [&](const std::unordered_map<std::string, std::string>& map) {
1217 for (const auto& [name, binding] : map) {
1218 if (!shortcut_manager->FindShortcut(name)) {
1219 continue; // Stale entry for a shortcut that no longer exists.
1220 }
1221 if (binding.empty()) {
1222 // Explicitly unbound by the user.
1223 shortcut_manager->UpdateShortcutKeys(name, {});
1224 continue;
1225 }
1226 auto keys = ParseShortcut(binding);
1227 if (keys.empty()) {
1228 LOG_WARN("Shortcuts", "Ignoring unparseable binding '%s' for '%s'",
1229 binding.c_str(), name.c_str());
1230 continue;
1231 }
1232 shortcut_manager->UpdateShortcutKeys(name, keys);
1233 }
1234 };
1235 apply(user_settings.prefs().global_shortcuts);
1236 apply(user_settings.prefs().editor_shortcuts);
1237}
1238
1240 ShortcutManager* shortcut_manager) {
1241 if (!shortcut_manager) {
1242 return;
1243 }
1244
1245 auto* window_manager = deps.window_manager;
1246 auto* user_settings = deps.user_settings;
1247
1248 // Global/editor rebinds from Settings are applied before panel bindings so
1249 // the panel collision check below sees the user's effective chords.
1250 if (user_settings) {
1251 ApplyUserShortcutOverrides(*user_settings, shortcut_manager);
1252 }
1253
1254 if (!window_manager) {
1255 return;
1256 }
1257
1258 size_t session_id = deps.session_coordinator
1260 : 0;
1261
1262 // Get all categories and panels
1263 auto categories = window_manager->GetAllCategories();
1264
1265 for (const auto& category : categories) {
1266 // Panels belong to the editor that owns their category; their toggles only
1267 // compete for a chord while that editor is active.
1268 std::optional<EditorType> owner;
1269 const EditorType category_type =
1270 EditorRegistry::GetEditorTypeFromCategory(category);
1271 if (EditorRegistry::GetEditorCategory(category_type) == category) {
1272 owner = category_type;
1273 }
1274
1275 auto panels = window_manager->GetWindowsInCategory(session_id, category);
1276
1277 for (const auto& panel : panels) {
1278 if (panel.shortcut_scope != WindowDescriptor::ShortcutScope::kPanel) {
1279 continue;
1280 }
1281
1282 std::string shortcut_string;
1283 bool user_defined = false;
1284
1285 // Check for user-defined shortcut first
1286 if (user_settings) {
1287 auto it = user_settings->prefs().panel_shortcuts.find(panel.card_id);
1288 if (it != user_settings->prefs().panel_shortcuts.end()) {
1289 shortcut_string = it->second;
1290 user_defined = !shortcut_string.empty();
1291 }
1292 }
1293
1294 // Fall back to default shortcut_hint
1295 if (shortcut_string.empty() && !panel.shortcut_hint.empty()) {
1296 shortcut_string = panel.shortcut_hint;
1297 }
1298
1299 if (shortcut_string.empty()) {
1300 continue;
1301 }
1302 auto keys = ParseShortcut(shortcut_string);
1303 if (keys.empty()) {
1304 continue;
1305 }
1306
1307 const std::string toggle_id = "view.toggle." + panel.card_id;
1308 // A default hint must not shadow an existing binding (Command Palette,
1309 // Save As, Window Browser, an editor's own keys, ...). The hint stays
1310 // visible in the UI; the user can bind it explicitly in Settings.
1311 if (!user_defined) {
1312 const auto conflicts =
1313 shortcut_manager->FindConflicts(keys, owner, toggle_id);
1314 if (!conflicts.empty()) {
1315 LOG_DEBUG("Shortcuts",
1316 "Skipping default panel shortcut %s for '%s' (used by "
1317 "'%s')",
1318 shortcut_string.c_str(), panel.card_id.c_str(),
1319 conflicts.front().c_str());
1320 continue;
1321 }
1322 }
1323
1324 std::string panel_id_copy = panel.card_id;
1325 RegisterIfValid(
1326 shortcut_manager, toggle_id, keys,
1327 [window_manager, panel_id_copy]() {
1328 window_manager->ToggleWindow(window_manager->GetActiveSessionId(),
1329 panel_id_copy);
1330 },
1331 Shortcut::Scope::kPanel);
1332 shortcut_manager->SetShortcutEditor(toggle_id, owner);
1333 }
1334 }
1335}
1336
1337} // namespace yaze::editor
static constexpr const char * kRoomGraphicsId
static constexpr const char * kRoomMatrixId
static constexpr const char * kRoomSelectorId
static constexpr const char * kPaletteEditorId
The EditorManager controls the main editor window and manages the various editor classes.
void SetFontGlobalScale(float scale)
EditorType type() const
Definition editor.h:355
size_t GetActiveSessionId() const
Stable workspace identity that is never reused while this coordinator lives.
const Shortcut * FindShortcut(const std::string &name) const
void RegisterCommand(const std::string &name, std::function< void()> callback, Shortcut::Scope scope=Shortcut::Scope::kGlobal)
Register a command without keyboard shortcut (command palette only)
bool SetShortcutEditor(const std::string &name, std::optional< EditorType > editor_type)
Restrict an existing shortcut to one editor. Returns false if unknown.
void RegisterShortcut(const std::string &name, const std::vector< ImGuiKey > &keys, Shortcut::Scope scope=Shortcut::Scope::kGlobal)
void SetActiveEditorProvider(ActiveEditorProvider provider)
bool SetShortcutEnabled(const std::string &name, std::function< bool()> enabled)
Attach an applicability predicate. Returns false if unknown.
void RegisterEditorShortcut(const std::string &name, const std::vector< ImGuiKey > &keys, std::function< void()> callback, EditorType editor_type)
Register an editor-owned shortcut (Scope::kEditor).
std::vector< std::string > FindConflicts(const std::string &name) const
Names of other shortcuts bound to the same chord that can be active at the same time (same editor own...
bool UpdateShortcutKeys(const std::string &name, const std::vector< ImGuiKey > &keys)
Manages user preferences and settings persistence.
static RecentFilesManager & GetInstance()
Definition project.h:632
#define LOG_DEBUG(category, format,...)
Definition log.h:133
#define LOG_WARN(category, format,...)
Definition log.h:138
void RegisterIfValid(ShortcutManager *shortcut_manager, const std::string &name, const std::vector< ImGuiKey > &keys, std::function< void()> callback, Shortcut::Scope scope=Shortcut::Scope::kGlobal)
void AdjustUiFontScaleBy(EditorManager *editor_manager, float delta)
void RegisterEditorIfValid(ShortcutManager *shortcut_manager, const std::string &name, const std::vector< ImGuiKey > &keys, std::function< void()> callback, EditorType editor_type)
void RegisterAlias(ShortcutManager *shortcut_manager, const std::string &alias, const std::string &canonical)
Editors are the view controllers for the application.
void ConfigureMenuShortcuts(const ShortcutDependencies &deps, ShortcutManager *shortcut_manager)
std::vector< ImGuiKey > ParseShortcut(const std::string &shortcut)
void ApplyUserShortcutOverrides(const UserSettings &user_settings, ShortcutManager *shortcut_manager)
Apply persisted global/editor rebinds (UserSettings global_shortcuts and editor_shortcuts) to already...
void ConfigurePanelShortcuts(const ShortcutDependencies &deps, ShortcutManager *shortcut_manager)
Final configuration pass: apply user rebinds, then register panel toggle shortcuts.
void ConfigureEditorShortcuts(const ShortcutDependencies &deps, ShortcutManager *shortcut_manager)
std::function< void()> callback
std::unordered_map< std::string, std::string > panel_shortcuts
std::unordered_map< std::string, std::string > editor_shortcuts
std::unordered_map< std::string, std::string > global_shortcuts