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