12#include "absl/strings/match.h"
13#include "absl/strings/str_split.h"
14#include "absl/strings/string_view.h"
17#include "imgui/imgui.h"
24 int required_mods = 0;
32 for (ImGuiKey key : keys) {
33 const int key_value =
static_cast<int>(key);
34 if (key_value & ImGuiMod_Mask_) {
46 if (mods & ImGuiMod_Ctrl)
48 if (mods & ImGuiMod_Shift)
50 if (mods & ImGuiMod_Alt)
52 if (mods & ImGuiMod_Super)
77 const int primary = chord.
required_mods & (ImGuiMod_Ctrl | ImGuiMod_Super);
78 if (primary == 0 || (chord.
required_mods & ImGuiMod_Alt) != 0) {
88 case ImGuiKey_LeftArrow:
89 case ImGuiKey_RightArrow:
90 case ImGuiKey_UpArrow:
91 case ImGuiKey_DownArrow:
94 case ImGuiKey_Backspace:
117 out.
mods = (out.
mods & ~ImGuiMod_Super) | ImGuiMod_Ctrl;
120 out.
main_keys.push_back(
static_cast<int>(key));
130 static const std::pair<const char*, ImGuiKey> kAliases[] = {
131 {
"escape", ImGuiKey_Escape},
132 {
"return", ImGuiKey_Enter},
133 {
"del", ImGuiKey_Delete},
134 {
"keypadadd", ImGuiKey_KeypadAdd},
135 {
"keypadsubtract", ImGuiKey_KeypadSubtract},
136 {
"equal", ImGuiKey_Equal},
137 {
"minus", ImGuiKey_Minus},
138 {
"comma", ImGuiKey_Comma},
140 for (
const auto& [alias, key] : kAliases) {
141 if (lower == alias) {
145 for (
int k = ImGuiKey_NamedKey_BEGIN; k < ImGuiKey_NamedKey_END; ++k) {
146 const ImGuiKey key =
static_cast<ImGuiKey
>(k);
151 for (
char& c : name) {
152 c =
static_cast<char>(std::tolower(
static_cast<unsigned char>(c)));
158 return ImGuiKey_None;
162 if (required_mods == 0) {
165 constexpr int kRelevantMods =
166 ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiMod_Alt | ImGuiMod_Super;
167 return (pressed_mods & kRelevantMods) == 0;
170 auto has = [&](
int mod) ->
bool {
171 return (pressed_mods & mod) != 0;
179 if (required_mods & ImGuiMod_Shift) {
180 if (!has(ImGuiMod_Shift))
183 if (required_mods & ImGuiMod_Alt) {
184 if (!has(ImGuiMod_Alt))
188 if (required_mods & ImGuiMod_Ctrl) {
190 if (!(has(ImGuiMod_Ctrl) || has(ImGuiMod_Super)))
193 if (!has(ImGuiMod_Ctrl))
197 if (required_mods & ImGuiMod_Super) {
199 if (!(has(ImGuiMod_Super) || has(ImGuiMod_Ctrl)))
202 if (!has(ImGuiMod_Super))
218 using absl::StartsWith;
219 using absl::StrContains;
221 if (StartsWith(name,
"File")) {
224 if (StartsWith(name,
"Edit")) {
227 if (StartsWith(name,
"Help")) {
230 if (StartsWith(name,
"Tools") || name ==
"Command Palette" ||
231 name ==
"Global Search" || name ==
"Load Last ROM" ||
232 name ==
"Show About") {
235 if (StartsWith(name,
"Layout") || StartsWith(name,
"Apply Layout") ||
236 StartsWith(name,
"Apply Profile") || StartsWith(name,
"Apply:") ||
237 StartsWith(name,
"layout:")) {
240 if (StartsWith(name,
"drawer:") ||
241 (StartsWith(name,
"View: Toggle") &&
242 (StrContains(name,
"Drawer") || StrContains(name,
"Panel") ||
243 StrContains(name,
"Notifications") || StrContains(name,
"Agent") ||
244 StrContains(name,
"Proposals") || StrContains(name,
"Settings") ||
245 StrContains(name,
"Help") || StrContains(name,
"Project") ||
246 StrContains(name,
"Properties"))) ||
247 StartsWith(name,
"View: Next Right") ||
248 StartsWith(name,
"View: Previous Right")) {
251 if (StartsWith(name,
"window:") || StartsWith(name,
"Window") ||
252 StartsWith(name,
"Panel Browser") || StartsWith(name,
"Window Browser") ||
253 StartsWith(name,
"Window Finder") || StartsWith(name,
"View: Show")) {
256 if (StartsWith(name,
"View") || StartsWith(name,
"Sidebar")) {
259 if (StrContains(name,
".")) {
266 std::vector<ImGuiKey> keys;
267 if (shortcut.empty()) {
272 std::vector<std::string> parts = absl::StrSplit(shortcut,
'+');
273 for (
auto& part : parts) {
275 while (!part.empty() && (part.front() ==
' ' || part.front() ==
'\t')) {
276 part.erase(part.begin());
278 while (!part.empty() && (part.back() ==
' ' || part.back() ==
'\t')) {
285 lower.reserve(part.size());
287 lower.push_back(
static_cast<char>(std::tolower(c)));
290 if (lower ==
"ctrl" || lower ==
"control") {
291 keys.push_back(ImGuiMod_Ctrl);
294 if (lower ==
"cmd" || lower ==
"command") {
301 if (lower ==
"win" || lower ==
"super") {
302 keys.push_back(ImGuiMod_Super);
305 if (lower ==
"alt" || lower ==
"opt" || lower ==
"option") {
306 keys.push_back(ImGuiMod_Alt);
309 if (lower ==
"shift") {
310 keys.push_back(ImGuiMod_Shift);
315 if (lower.size() >= 2 && lower[0] ==
'f') {
318 fnum = std::stoi(lower.substr(1));
322 if (fnum >= 1 && fnum <= 24) {
323 keys.push_back(
static_cast<ImGuiKey
>(ImGuiKey_F1 + (fnum - 1)));
329 if (part.size() == 1) {
331 if (mapped != ImGuiKey_COUNT) {
332 keys.push_back(mapped);
338 const ImGuiKey named = LookupNamedKey(lower);
339 if (named != ImGuiKey_None) {
340 keys.push_back(named);
354 const ImGuiIO& io = ImGui::GetIO();
358 int scope_priority = 0;
359 bool editor_owned =
false;
365 auto better = [](
const Candidate& a,
const Candidate& b) ->
bool {
366 if (a.scope_priority != b.scope_priority)
367 return a.scope_priority > b.scope_priority;
368 if (a.editor_owned != b.editor_owned)
369 return a.editor_owned;
370 if (a.key_count != b.key_count)
371 return a.key_count > b.key_count;
372 if (a.mod_count != b.mod_count)
373 return a.mod_count > b.mod_count;
374 return a.name < b.name;
378 bool have_best =
false;
380 for (
const auto& [name, shortcut] : shortcut_manager.
GetShortcuts()) {
381 if (!shortcut.callback) {
384 if (shortcut.keys.empty()) {
388 const ParsedChord chord = DecomposeChord(shortcut.keys);
389 if (chord.main_keys.empty()) {
395 if (io.WantTextInput &&
396 (chord.required_mods == 0 || IsTextEditingChord(chord))) {
402 if (!ModsSatisfied(io.KeyMods, chord.required_mods)) {
407 bool chord_pressed =
true;
408 for (
size_t i = 0; i + 1 < chord.main_keys.size(); ++i) {
409 if (!ImGui::IsKeyDown(chord.main_keys[i])) {
410 chord_pressed =
false;
414 if (!chord_pressed) {
417 if (!ImGui::IsKeyPressed(chord.main_keys.back(),
false )) {
428 cand.shortcut = &shortcut;
429 cand.scope_priority = ScopePriority(shortcut.scope);
430 cand.editor_owned = shortcut.editor_type.has_value();
431 cand.key_count =
static_cast<int>(chord.main_keys.size());
432 cand.mod_count = CountMods(chord.required_mods);
435 if (!have_best || better(cand, best)) {
436 best = std::move(cand);
441 if (have_best && best.shortcut && best.shortcut->callback) {
442 best.shortcut->callback();
446bool SameChord(
const std::vector<ImGuiKey>& a,
const std::vector<ImGuiKey>& b) {
447 if (a.empty() || b.empty()) {
450 return Normalize(a) == Normalize(b);
454 const std::vector<ImGuiKey>& keys) {
459 it->second.keys = keys;
468 it->second.keys = it->second.default_keys;
473 std::optional<EditorType> editor_type) {
478 it->second.editor_type = editor_type;
483 std::function<
bool()> enabled) {
488 it->second.enabled = std::move(enabled);
495 if (!active.has_value() || *active != *shortcut.
editor_type) {
507 if (it ==
shortcuts_.end() || it->second.keys.empty()) {
514 const std::string& name)
const {
519 return FindConflicts(it->second.keys, it->second.editor_type, name);
523 const std::vector<ImGuiKey>& keys, std::optional<EditorType> editor_type,
524 const std::string& exclude_name)
const {
525 std::vector<std::string> conflicts;
529 for (
const auto& [other_name, other] :
shortcuts_) {
530 if (other_name == exclude_name || other.keys.empty()) {
534 if (editor_type.has_value() && other.editor_type.has_value() &&
535 *editor_type != *other.editor_type) {
539 conflicts.push_back(other_name);
542 std::sort(conflicts.begin(), conflicts.end());
554 std::function<
void()> save_callback, std::function<
void()> open_callback,
555 std::function<
void()> close_callback, std::function<
void()> find_callback,
556 std::function<
void()> settings_callback) {
568 if (close_callback) {
578 if (settings_callback) {
588 std::function<
void()> focus_left, std::function<
void()> focus_right,
589 std::function<
void()> focus_up, std::function<
void()> focus_down,
590 std::function<
void()> close_window, std::function<
void()> split_horizontal,
591 std::function<
void()> split_vertical) {
596 {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_LeftArrow},
602 {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_RightArrow},
608 {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_UpArrow}, focus_up);
613 {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_DownArrow},
624 if (split_horizontal) {
626 {ImGuiMod_Ctrl, ImGuiKey_W, ImGuiKey_S}, split_horizontal);
630 if (split_vertical) {
bool SetShortcutEditor(const std::string &name, std::optional< EditorType > editor_type)
Restrict an existing shortcut to one editor. Returns false if unknown.
ActiveEditorProvider active_editor_provider_
void RegisterShortcut(const std::string &name, const std::vector< ImGuiKey > &keys, Shortcut::Scope scope=Shortcut::Scope::kGlobal)
bool IsShortcutApplicable(const Shortcut &shortcut) const
True when the shortcut may fire now (editor owner + enabled predicate).
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)
bool SetShortcutEnabled(const std::string &name, std::function< bool()> enabled)
Attach an applicability predicate. Returns false if unknown.
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::string GetDisplayString(const std::string &name) const
Live, platform-formatted binding for a shortcut name.
bool ResetShortcutKeys(const std::string &name)
Restore the keys the shortcut was registered with.
std::unordered_map< std::string, Shortcut > shortcuts_
const std::unordered_map< std::string, Shortcut > & GetShortcuts() const
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)
ParsedChord DecomposeChord(const std::vector< ImGuiKey > &keys)
ImGuiKey LookupNamedKey(const std::string &lower)
bool ModsSatisfied(int pressed_mods, int required_mods)
int ScopePriority(Shortcut::Scope scope)
NormalizedChord Normalize(const std::vector< ImGuiKey > &keys)
bool IsTextEditingChord(const ParsedChord &chord)
std::string InferShortcutGroup(absl::string_view name)
Menu-IA group for a shortcut/command name (File, View, Drawers, …).
bool SameChord(const std::vector< ImGuiKey > &a, const std::vector< ImGuiKey > &b)
True when two key lists describe the same chord (order-insensitive).
std::vector< ImGuiKey > ParseShortcut(const std::string &shortcut)
std::string PrintShortcut(const std::vector< ImGuiKey > &keys)
void ExecuteShortcuts(const ShortcutManager &shortcut_manager)
Fire at most one shortcut for this frame's key press.
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.
bool IsMacPlatform()
Check if running on macOS (native or web)
ImGuiKey MapKeyToImGuiKey(char key)
std::function< bool()> enabled
std::optional< EditorType > editor_type
std::vector< int > main_keys
bool operator==(const NormalizedChord &other) const
std::vector< ImGuiKey > main_keys