10#include "absl/strings/str_split.h"
13#include "imgui/imgui.h"
20 int required_mods = 0;
21 std::vector<ImGuiKey> main_keys;
26 out.main_keys.reserve(keys.size());
28 for (ImGuiKey key : keys) {
29 const int key_value =
static_cast<int>(
key);
30 if (key_value & ImGuiMod_Mask_) {
31 out.required_mods |= key_value & ImGuiMod_Mask_;
34 out.main_keys.push_back(key);
42 if (mods & ImGuiMod_Ctrl) ++count;
43 if (mods & ImGuiMod_Shift) ++count;
44 if (mods & ImGuiMod_Alt) ++count;
45 if (mods & ImGuiMod_Super) ++count;
63 if (required_mods == 0) {
67 auto has = [&](
int mod) ->
bool {
return (pressed_mods & mod) != 0; };
74 if (required_mods & ImGuiMod_Shift) {
75 if (!has(ImGuiMod_Shift))
return false;
77 if (required_mods & ImGuiMod_Alt) {
78 if (!has(ImGuiMod_Alt))
return false;
81 if (required_mods & ImGuiMod_Ctrl) {
83 if (!(has(ImGuiMod_Ctrl) || has(ImGuiMod_Super)))
return false;
85 if (!has(ImGuiMod_Ctrl))
return false;
88 if (required_mods & ImGuiMod_Super) {
90 if (!(has(ImGuiMod_Super) || has(ImGuiMod_Ctrl)))
return false;
92 if (!has(ImGuiMod_Super))
return false;
100std::string
PrintShortcut(
const std::vector<ImGuiKey>& keys) {
106std::vector<ImGuiKey>
ParseShortcut(
const std::string& shortcut) {
107 std::vector<ImGuiKey> keys;
108 if (shortcut.empty()) {
113 std::vector<std::string> parts = absl::StrSplit(shortcut,
'+');
114 for (
auto& part : parts) {
116 while (!part.empty() && (part.front() ==
' ' || part.front() ==
'\t')) {
117 part.erase(part.begin());
119 while (!part.empty() && (part.back() ==
' ' || part.back() ==
'\t')) {
122 if (part.empty())
continue;
125 lower.reserve(part.size());
126 for (
char c : part) lower.push_back(static_cast<char>(std::tolower(c)));
129 if (lower ==
"ctrl" || lower ==
"control") {
130 keys.push_back(ImGuiMod_Ctrl);
133 if (lower ==
"cmd" || lower ==
"command") {
140 if (lower ==
"win" || lower ==
"super") {
141 keys.push_back(ImGuiMod_Super);
144 if (lower ==
"alt" || lower ==
"opt" || lower ==
"option") {
145 keys.push_back(ImGuiMod_Alt);
148 if (lower ==
"shift") {
149 keys.push_back(ImGuiMod_Shift);
154 if (lower.size() >= 2 && lower[0] ==
'f') {
157 fnum = std::stoi(lower.substr(1));
161 if (fnum >= 1 && fnum <= 24) {
162 keys.push_back(
static_cast<ImGuiKey
>(ImGuiKey_F1 + (fnum - 1)));
168 if (part.size() == 1) {
170 if (mapped != ImGuiKey_COUNT) {
171 keys.push_back(mapped);
186 const ImGuiIO& io = ImGui::GetIO();
189 const Shortcut* shortcut =
nullptr;
190 int scope_priority = 0;
196 auto better = [](
const Candidate& a,
const Candidate& b) ->
bool {
197 if (a.scope_priority != b.scope_priority)
198 return a.scope_priority > b.scope_priority;
199 if (a.key_count != b.key_count)
return a.key_count > b.key_count;
200 if (a.mod_count != b.mod_count)
return a.mod_count > b.mod_count;
201 return a.name < b.name;
205 bool have_best =
false;
207 for (
const auto& [name, shortcut] : shortcut_manager.GetShortcuts()) {
208 if (!shortcut.callback) {
211 if (shortcut.keys.empty()) {
216 if (chord.main_keys.empty()) {
221 if (io.WantTextInput && chord.required_mods == 0) {
232 bool chord_pressed =
true;
233 for (
size_t i = 0; i + 1 < chord.main_keys.size(); ++i) {
234 if (!ImGui::IsKeyDown(chord.main_keys[i])) {
235 chord_pressed =
false;
239 if (!chord_pressed) {
242 if (!ImGui::IsKeyPressed(chord.main_keys.back(),
false )) {
247 cand.shortcut = &shortcut;
249 cand.key_count =
static_cast<int>(chord.main_keys.size());
250 cand.mod_count =
CountMods(chord.required_mods);
253 if (!have_best || better(cand, best)) {
254 best = std::move(cand);
259 if (have_best && best.shortcut && best.shortcut->callback) {
260 best.shortcut->callback();
265 const std::vector<ImGuiKey>& keys) {
270 it->second.keys = keys;
282 std::function<
void()> save_callback, std::function<
void()> open_callback,
283 std::function<
void()> close_callback, std::function<
void()> find_callback,
284 std::function<
void()> settings_callback) {
296 if (close_callback) {
306 if (settings_callback) {
316 std::function<
void()> focus_left, std::function<
void()> focus_right,
317 std::function<
void()> focus_up, std::function<
void()> focus_down,
318 std::function<
void()> close_window, std::function<
void()> split_horizontal,
319 std::function<
void()> split_vertical) {
347 if (split_horizontal) {
349 {ImGuiMod_Ctrl, ImGuiKey_W, ImGuiKey_S}, split_horizontal);
353 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_
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::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)