yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
shortcut_manager.cc
Go to the documentation of this file.
1#include "shortcut_manager.h"
2
3#include <algorithm>
4#include <cstddef>
5#include <functional>
6#include <string>
7#include <utility>
8#include <vector>
9
10#include "absl/strings/str_split.h"
11#include "app/gui/core/input.h"
13#include "imgui/imgui.h"
14
15namespace yaze {
16namespace editor {
17
18namespace {
19constexpr std::pair<ImGuiKey, const char*> kKeyNames[] = {
20 {ImGuiKey_Tab, "Tab"},
21 {ImGuiKey_LeftArrow, "Left"},
22 {ImGuiKey_RightArrow, "Right"},
23 {ImGuiKey_UpArrow, "Up"},
24 {ImGuiKey_DownArrow, "Down"},
25 {ImGuiKey_PageUp, "PageUp"},
26 {ImGuiKey_PageDown, "PageDown"},
27 {ImGuiKey_Home, "Home"},
28 {ImGuiKey_End, "End"},
29 {ImGuiKey_Insert, "Insert"},
30 {ImGuiKey_Delete, "Delete"},
31 {ImGuiKey_Backspace, "Backspace"},
32 {ImGuiKey_Space, "Space"},
33 {ImGuiKey_Enter, "Enter"},
34 {ImGuiKey_Escape, "Escape"},
35 {ImGuiMod_Ctrl, "Ctrl"},
36 {ImGuiMod_Shift, "Shift"},
37 {ImGuiMod_Alt, "Alt"},
38 {ImGuiMod_Super, "Super"},
39 {ImGuiKey_A, "A"},
40 {ImGuiKey_B, "B"},
41 {ImGuiKey_C, "C"},
42 {ImGuiKey_D, "D"},
43 {ImGuiKey_E, "E"},
44 {ImGuiKey_F, "F"},
45 {ImGuiKey_G, "G"},
46 {ImGuiKey_H, "H"},
47 {ImGuiKey_I, "I"},
48 {ImGuiKey_J, "J"},
49 {ImGuiKey_K, "K"},
50 {ImGuiKey_L, "L"},
51 {ImGuiKey_M, "M"},
52 {ImGuiKey_N, "N"},
53 {ImGuiKey_O, "O"},
54 {ImGuiKey_P, "P"},
55 {ImGuiKey_Q, "Q"},
56 {ImGuiKey_R, "R"},
57 {ImGuiKey_S, "S"},
58 {ImGuiKey_T, "T"},
59 {ImGuiKey_U, "U"},
60 {ImGuiKey_V, "V"},
61 {ImGuiKey_W, "W"},
62 {ImGuiKey_X, "X"},
63 {ImGuiKey_Y, "Y"},
64 {ImGuiKey_Z, "Z"},
65 {ImGuiKey_F1, "F1"},
66 {ImGuiKey_F2, "F2"},
67 {ImGuiKey_F3, "F3"},
68 {ImGuiKey_F4, "F4"},
69 {ImGuiKey_F5, "F5"},
70 {ImGuiKey_F6, "F6"},
71 {ImGuiKey_F7, "F7"},
72 {ImGuiKey_F8, "F8"},
73 {ImGuiKey_F9, "F9"},
74 {ImGuiKey_F10, "F10"},
75 {ImGuiKey_F11, "F11"},
76 {ImGuiKey_F12, "F12"},
77 {ImGuiKey_F13, "F13"},
78 {ImGuiKey_F14, "F14"},
79 {ImGuiKey_F15, "F15"},
80};
81
82constexpr const char* GetKeyName(ImGuiKey key) {
83 for (const auto& pair : kKeyNames) {
84 if (pair.first == key) {
85 return pair.second;
86 }
87 }
88 return "";
89}
90
92 int mods = 0;
93 bool shortcut = false; // Primary modifier (Cmd on macOS, Ctrl elsewhere)
94 bool alt = false;
95 bool shift = false;
96 bool super = false; // Physical Super/Cmd key
97};
98
100 ModifierState state;
101 state.mods = io.KeyMods;
102 state.super =
103 io.KeySuper || ((state.mods & ImGuiMod_Super) == ImGuiMod_Super);
104 state.alt = io.KeyAlt || ((state.mods & ImGuiMod_Alt) == ImGuiMod_Alt);
105 state.shift =
106 io.KeyShift || ((state.mods & ImGuiMod_Shift) == ImGuiMod_Shift);
107 state.shortcut =
108 io.KeyCtrl || io.KeySuper ||
109 ((io.KeyMods & ImGuiMod_Shortcut) == ImGuiMod_Shortcut);
110 return state;
111}
112
113bool ModifiersSatisfied(int required_mods, const ModifierState& state) {
114 // Primary modifier: allow either Ctrl or Cmd/Super to satisfy "Ctrl"
115 if ((required_mods & ImGuiMod_Ctrl) && !state.shortcut) {
116 return false;
117 }
118 if ((required_mods & ImGuiMod_Alt) && !state.alt) {
119 return false;
120 }
121 if ((required_mods & ImGuiMod_Shift) && !state.shift) {
122 return false;
123 }
124 if ((required_mods & ImGuiMod_Super) && !state.super) {
125 return false;
126 }
127 return true;
128}
129} // namespace
130
131std::string PrintShortcut(const std::vector<ImGuiKey>& keys) {
132 // Use the platform-aware FormatShortcut from platform_keys.h
133 // This handles Ctrl→Cmd and Alt→Opt conversions for macOS/WASM
134 return gui::FormatShortcut(keys);
135}
136
137std::vector<ImGuiKey> ParseShortcut(const std::string& shortcut) {
138 std::vector<ImGuiKey> keys;
139 if (shortcut.empty()) {
140 return keys;
141 }
142
143 // Split on '+' and trim whitespace
144 std::vector<std::string> parts = absl::StrSplit(shortcut, '+');
145 for (auto& part : parts) {
146 // Trim leading/trailing spaces
147 while (!part.empty() && (part.front() == ' ' || part.front() == '\t')) {
148 part.erase(part.begin());
149 }
150 while (!part.empty() && (part.back() == ' ' || part.back() == '\t')) {
151 part.pop_back();
152 }
153 if (part.empty()) continue;
154
155 std::string lower;
156 lower.reserve(part.size());
157 for (char c : part) lower.push_back(static_cast<char>(std::tolower(c)));
158
159 // Modifiers (support platform aliases)
160 if (lower == "ctrl" || lower == "control") {
161 keys.push_back(ImGuiMod_Ctrl);
162 continue;
163 }
164 if (lower == "cmd" || lower == "command" || lower == "win" ||
165 lower == "super") {
166 keys.push_back(ImGuiMod_Super);
167 continue;
168 }
169 if (lower == "alt" || lower == "opt" || lower == "option") {
170 keys.push_back(ImGuiMod_Alt);
171 continue;
172 }
173 if (lower == "shift") {
174 keys.push_back(ImGuiMod_Shift);
175 continue;
176 }
177
178 // Function keys
179 if (lower.size() >= 2 && lower[0] == 'f') {
180 int fnum = 0;
181 try {
182 fnum = std::stoi(lower.substr(1));
183 } catch (...) {
184 fnum = 0;
185 }
186 if (fnum >= 1 && fnum <= 24) {
187 keys.push_back(static_cast<ImGuiKey>(ImGuiKey_F1 + (fnum - 1)));
188 continue;
189 }
190 }
191
192 // Single character keys
193 if (part.size() == 1) {
194 ImGuiKey mapped = gui::MapKeyToImGuiKey(part[0]);
195 if (mapped != ImGuiKey_COUNT) {
196 keys.push_back(mapped);
197 continue;
198 }
199 }
200 }
201
202 return keys;
203}
204
205void ExecuteShortcuts(const ShortcutManager& shortcut_manager) {
206 // Check for keyboard shortcuts using the shortcut manager. Modifier handling
207 // is normalized so Cmd (macOS) and Ctrl (other platforms) map to the same
208 // registered shortcuts.
209 const ImGuiIO& io = ImGui::GetIO();
210
211 // Skip shortcut processing when ImGui wants keyboard input (typing in text fields)
212 if (io.WantCaptureKeyboard || io.WantTextInput) {
213 return;
214 }
215
216 const ModifierState mod_state = BuildModifierState(io);
217
218 for (const auto& shortcut : shortcut_manager.GetShortcuts()) {
219 int required_mods = 0;
220 std::vector<ImGuiKey> main_keys;
221
222 // Decompose the shortcut into modifier mask + main keys
223 for (const auto& key : shortcut.second.keys) {
224 // Handle combined modifier entries (e.g., ImGuiMod_Ctrl | ImGuiMod_Shift)
225 int key_value = static_cast<int>(key);
226 if (key_value & ImGuiMod_Mask_) {
227 required_mods |= key_value & ImGuiMod_Mask_;
228 continue;
229 }
230 // Treat ImGuiMod_Shortcut (alias of Ctrl) the same way
231 if (key == ImGuiMod_Shortcut || key == ImGuiMod_Ctrl ||
232 key == ImGuiMod_Alt || key == ImGuiMod_Shift ||
233 key == ImGuiMod_Super) {
234 required_mods |= key_value;
235 continue;
236 }
237
238 main_keys.push_back(key);
239 }
240
241 // Fast path: single-key chords leverage ImGui's chord helper, which
242 // already accounts for macOS Cmd/Ctrl translation.
243 if (main_keys.size() == 1) {
244 ImGuiKeyChord chord = static_cast<ImGuiKeyChord>(required_mods) | main_keys.back();
245 if (ImGui::IsKeyChordPressed(chord) && shortcut.second.callback) {
246 shortcut.second.callback();
247 }
248 continue;
249 }
250
251 // Require modifiers first for multi-key chords (e.g., Ctrl+W then C)
252 if (!ModifiersSatisfied(required_mods, mod_state)) {
253 continue;
254 }
255
256 // Require all non-mod keys, with the last key triggering on press
257 bool chord_pressed = !main_keys.empty();
258 for (size_t i = 0; i + 1 < main_keys.size(); ++i) {
259 if (!ImGui::IsKeyDown(main_keys[i])) {
260 chord_pressed = false;
261 break;
262 }
263 }
264
265 if (chord_pressed && !main_keys.empty()) {
266 chord_pressed =
267 ImGui::IsKeyPressed(main_keys.back(), false /* repeat */);
268 }
269
270 if (chord_pressed && shortcut.second.callback) {
271 shortcut.second.callback();
272 }
273 }
274}
275
276bool ShortcutManager::UpdateShortcutKeys(const std::string& name,
277 const std::vector<ImGuiKey>& keys) {
278 auto it = shortcuts_.find(name);
279 if (it == shortcuts_.end()) {
280 return false;
281 }
282 it->second.keys = keys;
283 return true;
284}
285
286} // namespace editor
287} // namespace yaze
288
289// Implementation in header file (inline methods)
290namespace yaze {
291namespace editor {
292
294 std::function<void()> save_callback, std::function<void()> open_callback,
295 std::function<void()> close_callback, std::function<void()> find_callback,
296 std::function<void()> settings_callback) {
297 // Ctrl+S - Save
298 if (save_callback) {
299 RegisterShortcut("save", {ImGuiMod_Ctrl, ImGuiKey_S}, save_callback);
300 }
301
302 // Ctrl+O - Open
303 if (open_callback) {
304 RegisterShortcut("open", {ImGuiMod_Ctrl, ImGuiKey_O}, open_callback);
305 }
306
307 // Ctrl+W - Close
308 if (close_callback) {
309 RegisterShortcut("close", {ImGuiMod_Ctrl, ImGuiKey_W}, close_callback);
310 }
311
312 // Ctrl+F - Find
313 if (find_callback) {
314 RegisterShortcut("find", {ImGuiMod_Ctrl, ImGuiKey_F}, find_callback);
315 }
316
317 // Ctrl+, - Settings
318 if (settings_callback) {
319 RegisterShortcut("settings", {ImGuiMod_Ctrl, ImGuiKey_Comma},
320 settings_callback);
321 }
322
323 // Ctrl+Tab - Next tab (placeholder for now)
324 // Ctrl+Shift+Tab - Previous tab (placeholder for now)
325}
326
328 std::function<void()> focus_left, std::function<void()> focus_right,
329 std::function<void()> focus_up, std::function<void()> focus_down,
330 std::function<void()> close_window, std::function<void()> split_horizontal,
331 std::function<void()> split_vertical) {
332 // Ctrl+Arrow keys for window navigation
333 if (focus_left) {
334 RegisterShortcut("focus_left", {ImGuiMod_Ctrl, ImGuiKey_LeftArrow},
335 focus_left);
336 }
337
338 if (focus_right) {
339 RegisterShortcut("focus_right", {ImGuiMod_Ctrl, ImGuiKey_RightArrow},
340 focus_right);
341 }
342
343 if (focus_up) {
344 RegisterShortcut("focus_up", {ImGuiMod_Ctrl, ImGuiKey_UpArrow}, focus_up);
345 }
346
347 if (focus_down) {
348 RegisterShortcut("focus_down", {ImGuiMod_Ctrl, ImGuiKey_DownArrow},
349 focus_down);
350 }
351
352 // Ctrl+W, C - Close current window
353 if (close_window) {
354 RegisterShortcut("close_window", {ImGuiMod_Ctrl, ImGuiKey_W, ImGuiKey_C},
355 close_window);
356 }
357
358 // Ctrl+W, S - Split horizontal
359 if (split_horizontal) {
360 RegisterShortcut("split_horizontal",
361 {ImGuiMod_Ctrl, ImGuiKey_W, ImGuiKey_S}, split_horizontal);
362 }
363
364 // Ctrl+W, V - Split vertical
365 if (split_vertical) {
366 RegisterShortcut("split_vertical", {ImGuiMod_Ctrl, ImGuiKey_W, ImGuiKey_V},
367 split_vertical);
368 }
369}
370
371} // namespace editor
372} // namespace yaze
void RegisterShortcut(const std::string &name, const std::vector< ImGuiKey > &keys, Shortcut::Scope scope=Shortcut::Scope::kGlobal)
void RegisterStandardShortcuts(std::function< void()> save_callback, std::function< void()> open_callback, std::function< void()> close_callback, std::function< void()> find_callback, std::function< void()> settings_callback)
void RegisterWindowNavigationShortcuts(std::function< void()> focus_left, std::function< void()> focus_right, std::function< void()> focus_up, std::function< void()> focus_down, std::function< void()> close_window, std::function< void()> split_horizontal, std::function< void()> split_vertical)
std::unordered_map< std::string, Shortcut > shortcuts_
bool UpdateShortcutKeys(const std::string &name, const std::vector< ImGuiKey > &keys)
bool ModifiersSatisfied(int required_mods, const ModifierState &state)
constexpr std::pair< ImGuiKey, const char * > kKeyNames[]
std::vector< ImGuiKey > ParseShortcut(const std::string &shortcut)
std::string PrintShortcut(const std::vector< ImGuiKey > &keys)
void ExecuteShortcuts(const ShortcutManager &shortcut_manager)
const char * GetKeyName(ImGuiKey key)
Get the ImGui key name as a string.
std::string FormatShortcut(const std::vector< ImGuiKey > &keys)
Format a list of ImGui keys into a human-readable shortcut string.
ImGuiKey MapKeyToImGuiKey(char key)
Definition input.cc:577