10#include "absl/strings/match.h"
11#include "absl/strings/str_split.h"
12#include "absl/strings/string_view.h"
15#include "imgui/imgui.h"
22 int required_mods = 0;
30 for (ImGuiKey key : keys) {
31 const int key_value =
static_cast<int>(key);
32 if (key_value & ImGuiMod_Mask_) {
44 if (mods & ImGuiMod_Ctrl)
46 if (mods & ImGuiMod_Shift)
48 if (mods & ImGuiMod_Alt)
50 if (mods & ImGuiMod_Super)
69 if (required_mods == 0) {
72 constexpr int kRelevantMods =
73 ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiMod_Alt | ImGuiMod_Super;
74 return (pressed_mods & kRelevantMods) == 0;
77 auto has = [&](
int mod) ->
bool {
78 return (pressed_mods & mod) != 0;
86 if (required_mods & ImGuiMod_Shift) {
87 if (!has(ImGuiMod_Shift))
90 if (required_mods & ImGuiMod_Alt) {
91 if (!has(ImGuiMod_Alt))
95 if (required_mods & ImGuiMod_Ctrl) {
97 if (!(has(ImGuiMod_Ctrl) || has(ImGuiMod_Super)))
100 if (!has(ImGuiMod_Ctrl))
104 if (required_mods & ImGuiMod_Super) {
106 if (!(has(ImGuiMod_Super) || has(ImGuiMod_Ctrl)))
109 if (!has(ImGuiMod_Super))
125 using absl::StartsWith;
126 using absl::StrContains;
128 if (StartsWith(name,
"File")) {
131 if (StartsWith(name,
"Edit")) {
134 if (StartsWith(name,
"Help")) {
137 if (StartsWith(name,
"Tools") || name ==
"Command Palette" ||
138 name ==
"Global Search" || name ==
"Load Last ROM" ||
139 name ==
"Show About") {
142 if (StartsWith(name,
"Layout") || StartsWith(name,
"Apply Layout") ||
143 StartsWith(name,
"Apply Profile") || StartsWith(name,
"Apply:") ||
144 StartsWith(name,
"layout:")) {
147 if (StartsWith(name,
"drawer:") ||
148 (StartsWith(name,
"View: Toggle") &&
149 (StrContains(name,
"Drawer") || StrContains(name,
"Panel") ||
150 StrContains(name,
"Notifications") || StrContains(name,
"Agent") ||
151 StrContains(name,
"Proposals") || StrContains(name,
"Settings") ||
152 StrContains(name,
"Help") || StrContains(name,
"Project") ||
153 StrContains(name,
"Properties"))) ||
154 StartsWith(name,
"View: Next Right") ||
155 StartsWith(name,
"View: Previous Right")) {
158 if (StartsWith(name,
"window:") || StartsWith(name,
"Window") ||
159 StartsWith(name,
"Panel Browser") || StartsWith(name,
"Window Browser") ||
160 StartsWith(name,
"Window Finder") || StartsWith(name,
"View: Show")) {
163 if (StartsWith(name,
"View") || StartsWith(name,
"Sidebar")) {
166 if (StrContains(name,
".")) {
173 std::vector<ImGuiKey> keys;
174 if (shortcut.empty()) {
179 std::vector<std::string> parts = absl::StrSplit(shortcut,
'+');
180 for (
auto& part : parts) {
182 while (!part.empty() && (part.front() ==
' ' || part.front() ==
'\t')) {
183 part.erase(part.begin());
185 while (!part.empty() && (part.back() ==
' ' || part.back() ==
'\t')) {
192 lower.reserve(part.size());
194 lower.push_back(
static_cast<char>(std::tolower(c)));
197 if (lower ==
"ctrl" || lower ==
"control") {
198 keys.push_back(ImGuiMod_Ctrl);
201 if (lower ==
"cmd" || lower ==
"command") {
208 if (lower ==
"win" || lower ==
"super") {
209 keys.push_back(ImGuiMod_Super);
212 if (lower ==
"alt" || lower ==
"opt" || lower ==
"option") {
213 keys.push_back(ImGuiMod_Alt);
216 if (lower ==
"shift") {
217 keys.push_back(ImGuiMod_Shift);
222 if (lower.size() >= 2 && lower[0] ==
'f') {
225 fnum = std::stoi(lower.substr(1));
229 if (fnum >= 1 && fnum <= 24) {
230 keys.push_back(
static_cast<ImGuiKey
>(ImGuiKey_F1 + (fnum - 1)));
236 if (part.size() == 1) {
238 if (mapped != ImGuiKey_COUNT) {
239 keys.push_back(mapped);
254 const ImGuiIO& io = ImGui::GetIO();
258 int scope_priority = 0;
264 auto better = [](
const Candidate& a,
const Candidate& b) ->
bool {
265 if (a.scope_priority != b.scope_priority)
266 return a.scope_priority > b.scope_priority;
267 if (a.key_count != b.key_count)
268 return a.key_count > b.key_count;
269 if (a.mod_count != b.mod_count)
270 return a.mod_count > b.mod_count;
271 return a.name < b.name;
275 bool have_best =
false;
277 for (
const auto& [name, shortcut] : shortcut_manager.
GetShortcuts()) {
278 if (!shortcut.callback) {
281 if (shortcut.keys.empty()) {
285 const ParsedChord chord = DecomposeChord(shortcut.keys);
286 if (chord.main_keys.empty()) {
291 if (io.WantTextInput && chord.required_mods == 0) {
297 if (!ModsSatisfied(io.KeyMods, chord.required_mods)) {
302 bool chord_pressed =
true;
303 for (
size_t i = 0; i + 1 < chord.main_keys.size(); ++i) {
304 if (!ImGui::IsKeyDown(chord.main_keys[i])) {
305 chord_pressed =
false;
309 if (!chord_pressed) {
312 if (!ImGui::IsKeyPressed(chord.main_keys.back(),
false )) {
317 cand.shortcut = &shortcut;
318 cand.scope_priority = ScopePriority(shortcut.scope);
319 cand.key_count =
static_cast<int>(chord.main_keys.size());
320 cand.mod_count = CountMods(chord.required_mods);
323 if (!have_best || better(cand, best)) {
324 best = std::move(cand);
329 if (have_best && best.shortcut && best.shortcut->callback) {
330 best.shortcut->callback();
335 const std::vector<ImGuiKey>& keys) {
340 it->second.keys = keys;
352 std::function<
void()> save_callback, std::function<
void()> open_callback,
353 std::function<
void()> close_callback, std::function<
void()> find_callback,
354 std::function<
void()> settings_callback) {
366 if (close_callback) {
376 if (settings_callback) {
386 std::function<
void()> focus_left, std::function<
void()> focus_right,
387 std::function<
void()> focus_up, std::function<
void()> focus_down,
388 std::function<
void()> close_window, std::function<
void()> split_horizontal,
389 std::function<
void()> split_vertical) {
417 if (split_horizontal) {
419 {ImGuiMod_Ctrl, ImGuiKey_W, ImGuiKey_S}, split_horizontal);
423 if (split_vertical) {
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_
const std::unordered_map< std::string, Shortcut > & GetShortcuts() const
bool UpdateShortcutKeys(const std::string &name, const std::vector< ImGuiKey > &keys)
ParsedChord DecomposeChord(const std::vector< ImGuiKey > &keys)
bool ModsSatisfied(int pressed_mods, int required_mods)
int ScopePriority(Shortcut::Scope scope)
std::string InferShortcutGroup(absl::string_view name)
Menu-IA group for a shortcut/command name (File, View, Drawers, …).
std::vector< ImGuiKey > ParseShortcut(const std::string &shortcut)
std::string PrintShortcut(const std::vector< ImGuiKey > &keys)
void ExecuteShortcuts(const ShortcutManager &shortcut_manager)
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::vector< ImGuiKey > main_keys