diff --git a/lib/libimhex/include/hex/api/shortcut_manager.hpp b/lib/libimhex/include/hex/api/shortcut_manager.hpp index c58646c60..7ba760d76 100644 --- a/lib/libimhex/include/hex/api/shortcut_manager.hpp +++ b/lib/libimhex/include/hex/api/shortcut_manager.hpp @@ -2,137 +2,25 @@ #include #include +#include #include -#include #include #include #include -#include - struct ImGuiWindow; +struct KeyEquivalent { + bool valid; + bool ctrl, opt, cmd, shift; + int key; +}; + namespace hex { class View; - enum class Keys : u32 { - Space = GLFW_KEY_SPACE, - Apostrophe = GLFW_KEY_APOSTROPHE, - Comma = GLFW_KEY_COMMA, - Minus = GLFW_KEY_MINUS, - Period = GLFW_KEY_PERIOD, - Slash = GLFW_KEY_SLASH, - Num0 = GLFW_KEY_0, - Num1 = GLFW_KEY_1, - Num2 = GLFW_KEY_2, - Num3 = GLFW_KEY_3, - Num4 = GLFW_KEY_4, - Num5 = GLFW_KEY_5, - Num6 = GLFW_KEY_6, - Num7 = GLFW_KEY_7, - Num8 = GLFW_KEY_8, - Num9 = GLFW_KEY_9, - Semicolon = GLFW_KEY_SEMICOLON, - Equals = GLFW_KEY_EQUAL, - A = GLFW_KEY_A, - B = GLFW_KEY_B, - C = GLFW_KEY_C, - D = GLFW_KEY_D, - E = GLFW_KEY_E, - F = GLFW_KEY_F, - G = GLFW_KEY_G, - H = GLFW_KEY_H, - I = GLFW_KEY_I, - J = GLFW_KEY_J, - K = GLFW_KEY_K, - L = GLFW_KEY_L, - M = GLFW_KEY_M, - N = GLFW_KEY_N, - O = GLFW_KEY_O, - P = GLFW_KEY_P, - Q = GLFW_KEY_Q, - R = GLFW_KEY_R, - S = GLFW_KEY_S, - T = GLFW_KEY_T, - U = GLFW_KEY_U, - V = GLFW_KEY_V, - W = GLFW_KEY_W, - X = GLFW_KEY_X, - Y = GLFW_KEY_Y, - Z = GLFW_KEY_Z, - LeftBracket = GLFW_KEY_LEFT_BRACKET, - Backslash = GLFW_KEY_BACKSLASH, - RightBracket = GLFW_KEY_RIGHT_BRACKET, - GraveAccent = GLFW_KEY_GRAVE_ACCENT, - World1 = GLFW_KEY_WORLD_1, - World2 = GLFW_KEY_WORLD_2, - Escape = GLFW_KEY_ESCAPE, - Enter = GLFW_KEY_ENTER, - Tab = GLFW_KEY_TAB, - Backspace = GLFW_KEY_BACKSPACE, - Insert = GLFW_KEY_INSERT, - Delete = GLFW_KEY_DELETE, - Right = GLFW_KEY_RIGHT, - Left = GLFW_KEY_LEFT, - Down = GLFW_KEY_DOWN, - Up = GLFW_KEY_UP, - PageUp = GLFW_KEY_PAGE_UP, - PageDown = GLFW_KEY_PAGE_DOWN, - Home = GLFW_KEY_HOME, - End = GLFW_KEY_END, - CapsLock = GLFW_KEY_CAPS_LOCK, - ScrollLock = GLFW_KEY_SCROLL_LOCK, - NumLock = GLFW_KEY_NUM_LOCK, - PrintScreen = GLFW_KEY_PRINT_SCREEN, - Pause = GLFW_KEY_PAUSE, - F1 = GLFW_KEY_F1, - F2 = GLFW_KEY_F2, - F3 = GLFW_KEY_F3, - F4 = GLFW_KEY_F4, - F5 = GLFW_KEY_F5, - F6 = GLFW_KEY_F6, - F7 = GLFW_KEY_F7, - F8 = GLFW_KEY_F8, - F9 = GLFW_KEY_F9, - F10 = GLFW_KEY_F10, - F11 = GLFW_KEY_F11, - F12 = GLFW_KEY_F12, - F13 = GLFW_KEY_F13, - F14 = GLFW_KEY_F14, - F15 = GLFW_KEY_F15, - F16 = GLFW_KEY_F16, - F17 = GLFW_KEY_F17, - F18 = GLFW_KEY_F18, - F19 = GLFW_KEY_F19, - F20 = GLFW_KEY_F20, - F21 = GLFW_KEY_F21, - F22 = GLFW_KEY_F22, - F23 = GLFW_KEY_F23, - F24 = GLFW_KEY_F24, - F25 = GLFW_KEY_F25, - KeyPad0 = GLFW_KEY_KP_0, - KeyPad1 = GLFW_KEY_KP_1, - KeyPad2 = GLFW_KEY_KP_2, - KeyPad3 = GLFW_KEY_KP_3, - KeyPad4 = GLFW_KEY_KP_4, - KeyPad5 = GLFW_KEY_KP_5, - KeyPad6 = GLFW_KEY_KP_6, - KeyPad7 = GLFW_KEY_KP_7, - KeyPad8 = GLFW_KEY_KP_8, - KeyPad9 = GLFW_KEY_KP_9, - KeyPadDecimal = GLFW_KEY_KP_DECIMAL, - KeyPadDivide = GLFW_KEY_KP_DIVIDE, - KeyPadMultiply = GLFW_KEY_KP_MULTIPLY, - KeyPadSubtract = GLFW_KEY_KP_SUBTRACT, - KeyPadAdd = GLFW_KEY_KP_ADD, - KeyPadEnter = GLFW_KEY_KP_ENTER, - KeyPadEqual = GLFW_KEY_KP_EQUAL, - Menu = GLFW_KEY_MENU, - }; - - class Key { public: constexpr Key() = default; @@ -175,6 +63,7 @@ namespace hex { bool isLocal() const; std::string toString() const; + KeyEquivalent toKeyEquivalent() const; const std::set& getKeys() const; bool has(Key key) const; bool matches(const Shortcut &other) const; diff --git a/lib/libimhex/include/hex/helpers/keys.hpp b/lib/libimhex/include/hex/helpers/keys.hpp new file mode 100644 index 000000000..6fa03ee0c --- /dev/null +++ b/lib/libimhex/include/hex/helpers/keys.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include + +#if defined(__cplusplus) +enum class Keys { +#else +enum Keys { +#endif + Space = GLFW_KEY_SPACE, + Apostrophe = GLFW_KEY_APOSTROPHE, + Comma = GLFW_KEY_COMMA, + Minus = GLFW_KEY_MINUS, + Period = GLFW_KEY_PERIOD, + Slash = GLFW_KEY_SLASH, + Num0 = GLFW_KEY_0, + Num1 = GLFW_KEY_1, + Num2 = GLFW_KEY_2, + Num3 = GLFW_KEY_3, + Num4 = GLFW_KEY_4, + Num5 = GLFW_KEY_5, + Num6 = GLFW_KEY_6, + Num7 = GLFW_KEY_7, + Num8 = GLFW_KEY_8, + Num9 = GLFW_KEY_9, + Semicolon = GLFW_KEY_SEMICOLON, + Equals = GLFW_KEY_EQUAL, + A = GLFW_KEY_A, + B = GLFW_KEY_B, + C = GLFW_KEY_C, + D = GLFW_KEY_D, + E = GLFW_KEY_E, + F = GLFW_KEY_F, + G = GLFW_KEY_G, + H = GLFW_KEY_H, + I = GLFW_KEY_I, + J = GLFW_KEY_J, + K = GLFW_KEY_K, + L = GLFW_KEY_L, + M = GLFW_KEY_M, + N = GLFW_KEY_N, + O = GLFW_KEY_O, + P = GLFW_KEY_P, + Q = GLFW_KEY_Q, + R = GLFW_KEY_R, + S = GLFW_KEY_S, + T = GLFW_KEY_T, + U = GLFW_KEY_U, + V = GLFW_KEY_V, + W = GLFW_KEY_W, + X = GLFW_KEY_X, + Y = GLFW_KEY_Y, + Z = GLFW_KEY_Z, + LeftBracket = GLFW_KEY_LEFT_BRACKET, + Backslash = GLFW_KEY_BACKSLASH, + RightBracket = GLFW_KEY_RIGHT_BRACKET, + GraveAccent = GLFW_KEY_GRAVE_ACCENT, + World1 = GLFW_KEY_WORLD_1, + World2 = GLFW_KEY_WORLD_2, + Escape = GLFW_KEY_ESCAPE, + Enter = GLFW_KEY_ENTER, + Tab = GLFW_KEY_TAB, + Backspace = GLFW_KEY_BACKSPACE, + Insert = GLFW_KEY_INSERT, + Delete = GLFW_KEY_DELETE, + Right = GLFW_KEY_RIGHT, + Left = GLFW_KEY_LEFT, + Down = GLFW_KEY_DOWN, + Up = GLFW_KEY_UP, + PageUp = GLFW_KEY_PAGE_UP, + PageDown = GLFW_KEY_PAGE_DOWN, + Home = GLFW_KEY_HOME, + End = GLFW_KEY_END, + CapsLock = GLFW_KEY_CAPS_LOCK, + ScrollLock = GLFW_KEY_SCROLL_LOCK, + NumLock = GLFW_KEY_NUM_LOCK, + PrintScreen = GLFW_KEY_PRINT_SCREEN, + Pause = GLFW_KEY_PAUSE, + F1 = GLFW_KEY_F1, + F2 = GLFW_KEY_F2, + F3 = GLFW_KEY_F3, + F4 = GLFW_KEY_F4, + F5 = GLFW_KEY_F5, + F6 = GLFW_KEY_F6, + F7 = GLFW_KEY_F7, + F8 = GLFW_KEY_F8, + F9 = GLFW_KEY_F9, + F10 = GLFW_KEY_F10, + F11 = GLFW_KEY_F11, + F12 = GLFW_KEY_F12, + F13 = GLFW_KEY_F13, + F14 = GLFW_KEY_F14, + F15 = GLFW_KEY_F15, + F16 = GLFW_KEY_F16, + F17 = GLFW_KEY_F17, + F18 = GLFW_KEY_F18, + F19 = GLFW_KEY_F19, + F20 = GLFW_KEY_F20, + F21 = GLFW_KEY_F21, + F22 = GLFW_KEY_F22, + F23 = GLFW_KEY_F23, + F24 = GLFW_KEY_F24, + F25 = GLFW_KEY_F25, + KeyPad0 = GLFW_KEY_KP_0, + KeyPad1 = GLFW_KEY_KP_1, + KeyPad2 = GLFW_KEY_KP_2, + KeyPad3 = GLFW_KEY_KP_3, + KeyPad4 = GLFW_KEY_KP_4, + KeyPad5 = GLFW_KEY_KP_5, + KeyPad6 = GLFW_KEY_KP_6, + KeyPad7 = GLFW_KEY_KP_7, + KeyPad8 = GLFW_KEY_KP_8, + KeyPad9 = GLFW_KEY_KP_9, + KeyPadDecimal = GLFW_KEY_KP_DECIMAL, + KeyPadDivide = GLFW_KEY_KP_DIVIDE, + KeyPadMultiply = GLFW_KEY_KP_MULTIPLY, + KeyPadSubtract = GLFW_KEY_KP_SUBTRACT, + KeyPadAdd = GLFW_KEY_KP_ADD, + KeyPadEnter = GLFW_KEY_KP_ENTER, + KeyPadEqual = GLFW_KEY_KP_EQUAL, + Menu = GLFW_KEY_MENU, +}; diff --git a/lib/libimhex/include/hex/helpers/utils_macos.hpp b/lib/libimhex/include/hex/helpers/utils_macos.hpp index 16cd7931e..12826a6cf 100644 --- a/lib/libimhex/include/hex/helpers/utils_macos.hpp +++ b/lib/libimhex/include/hex/helpers/utils_macos.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #if defined(OS_MACOS) struct GLFWwindow; @@ -20,6 +22,8 @@ void macosSetWindowMovable(GLFWwindow *window, bool movable); bool macosIsWindowBeingResizedByUser(GLFWwindow *window); void macosMarkContentEdited(GLFWwindow *window, bool edited = true); + + void macosGetKey(Keys key, int *output); } #endif diff --git a/lib/libimhex/source/api/shortcut_manager.cpp b/lib/libimhex/source/api/shortcut_manager.cpp index 369de01f0..29154afd4 100644 --- a/lib/libimhex/source/api/shortcut_manager.cpp +++ b/lib/libimhex/source/api/shortcut_manager.cpp @@ -233,6 +233,44 @@ namespace hex { return result; } + KeyEquivalent Shortcut::toKeyEquivalent() const { + #if defined(OS_MACOS) + if (*this == None) + return { }; + + KeyEquivalent result = {}; + result.valid = true; + + for (const auto &key : m_keys) { + switch (key.getKeyCode()) { + case CTRL.getKeyCode(): + result.ctrl = true; + break; + case SHIFT.getKeyCode(): + result.shift = true; + break; + case ALT.getKeyCode(): + result.opt = true; + break; + case SUPER.getKeyCode(): + case CTRLCMD.getKeyCode(): + result.cmd = true; + break; + case CurrentView.getKeyCode(): break; + case AllowWhileTyping.getKeyCode(): break; + default: + macosGetKey(Keys(key.getKeyCode()), &result.key); + break; + } + } + + return result; + #else + return { }; + #endif + } + + void ShortcutManager::addGlobalShortcut(const Shortcut &shortcut, const std::vector &unlocalizedName, const std::function &callback, const EnabledCallback &enabledCallback) { log::debug("Adding global shortcut {} for {}", shortcut.toString(), unlocalizedName.back().get()); diff --git a/lib/libimhex/source/helpers/utils_macos.m b/lib/libimhex/source/helpers/utils_macos.m index eb1c974cf..ee0294c47 100644 --- a/lib/libimhex/source/helpers/utils_macos.m +++ b/lib/libimhex/source/helpers/utils_macos.m @@ -18,6 +18,8 @@ #import #import + #include + void errorMessageMacos(const char *cMessage) { CFStringRef strMessage = CFStringCreateWithCString(NULL, cMessage, kCFStringEncodingUTF8); CFUserNotificationDisplayAlert(0, kCFUserNotificationStopAlertLevel, NULL, NULL, NULL, strMessage, NULL, NULL, NULL, NULL, NULL); @@ -151,4 +153,123 @@ @end + void macosGetKey(enum Keys key, int *output) { + *output = 0x00; + switch (key) { + case Space: *output = ' '; break; + case Apostrophe: *output = '\''; break; + case Comma: *output = ','; break; + case Minus: *output = '-'; break; + case Period: *output = '.'; break; + case Slash: *output = '/'; break; + case Num0: *output = '0'; break; + case Num1: *output = '1'; break; + case Num2: *output = '2'; break; + case Num3: *output = '3'; break; + case Num4: *output = '4'; break; + case Num5: *output = '5'; break; + case Num6: *output = '6'; break; + case Num7: *output = '7'; break; + case Num8: *output = '8'; break; + case Num9: *output = '9'; break; + case Semicolon: *output = ';'; break; + case Equals: *output = '='; break; + case A: *output = 'a'; break; + case B: *output = 'b'; break; + case C: *output = 'c'; break; + case D: *output = 'd'; break; + case E: *output = 'e'; break; + case F: *output = 'f'; break; + case G: *output = 'g'; break; + case H: *output = 'h'; break; + case I: *output = 'i'; break; + case J: *output = 'j'; break; + case K: *output = 'k'; break; + case L: *output = 'l'; break; + case M: *output = 'm'; break; + case N: *output = 'n'; break; + case O: *output = 'o'; break; + case P: *output = 'p'; break; + case Q: *output = 'q'; break; + case R: *output = 'r'; break; + case S: *output = 's'; break; + case T: *output = 't'; break; + case U: *output = 'u'; break; + case V: *output = 'v'; break; + case W: *output = 'w'; break; + case X: *output = 'x'; break; + case Y: *output = 'y'; break; + case Z: *output = 'z'; break; + case LeftBracket: *output = '/'; break; + case Backslash: *output = '\\'; break; + case RightBracket: *output = ']'; break; + case GraveAccent: *output = '`'; break; + case World1: break; + case World2: break; + case Escape: break; + case Enter: *output = NSEnterCharacter; break; + case Tab: *output = NSTabCharacter; break; + case Backspace: *output = NSBackspaceCharacter; break; + case Insert: *output = NSInsertFunctionKey; break; + case Delete: *output = NSDeleteCharacter; break; + case Right: *output = NSRightArrowFunctionKey; break; + case Left: *output = NSLeftArrowFunctionKey; break; + case Down: *output = NSDownArrowFunctionKey; break; + case Up: *output = NSUpArrowFunctionKey; break; + case PageUp: *output = NSPageUpFunctionKey; break; + case PageDown: *output = NSPageDownFunctionKey; break; + case Home: *output = NSHomeFunctionKey; break; + case End: *output = NSEndFunctionKey; break; + case CapsLock: break; + case ScrollLock: *output = NSScrollLockFunctionKey; break; + case NumLock: break; + case PrintScreen: *output = NSPrintScreenFunctionKey; break; + case Pause: *output = NSPauseFunctionKey; break; + case F1: *output = NSF1FunctionKey; break; + case F2: *output = NSF2FunctionKey; break; + case F3: *output = NSF3FunctionKey; break; + case F4: *output = NSF4FunctionKey; break; + case F5: *output = NSF5FunctionKey; break; + case F6: *output = NSF6FunctionKey; break; + case F7: *output = NSF7FunctionKey; break; + case F8: *output = NSF8FunctionKey; break; + case F9: *output = NSF9FunctionKey; break; + case F10: *output = NSF10FunctionKey; break; + case F11: *output = NSF11FunctionKey; break; + case F12: *output = NSF12FunctionKey; break; + case F13: *output = NSF13FunctionKey; break; + case F14: *output = NSF14FunctionKey; break; + case F15: *output = NSF15FunctionKey; break; + case F16: *output = NSF16FunctionKey; break; + case F17: *output = NSF17FunctionKey; break; + case F18: *output = NSF18FunctionKey; break; + case F19: *output = NSF19FunctionKey; break; + case F20: *output = NSF20FunctionKey; break; + case F21: *output = NSF21FunctionKey; break; + case F22: *output = NSF22FunctionKey; break; + case F23: *output = NSF23FunctionKey; break; + case F24: *output = NSF24FunctionKey; break; + case F25: *output = NSF25FunctionKey; break; + case KeyPad0: *output = '0'; break; + case KeyPad1: *output = '1'; break; + case KeyPad2: *output = '2'; break; + case KeyPad3: *output = '3'; break; + case KeyPad4: *output = '4'; break; + case KeyPad5: *output = '5'; break; + case KeyPad6: *output = '6'; break; + case KeyPad7: *output = '7'; break; + case KeyPad8: *output = '8'; break; + case KeyPad9: *output = '9'; break; + case KeyPadDecimal: *output = '.'; break; + case KeyPadDivide: *output = '/'; break; + case KeyPadMultiply: *output = '*'; break; + case KeyPadSubtract: *output = '-'; break; + case KeyPadAdd: *output = '+'; break; + case KeyPadEnter: *output = NSEnterCharacter; break; + case KeyPadEqual: *output = '='; break; + case Menu: *output = NSMenuFunctionKey; break; + default: break; + } + } + #endif diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index f1b130f5e..ef3a738df 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -514,6 +514,7 @@ "hex.builtin.setting.interface.scaling.fractional_warning": "The default font does not support fractional scaling. For better results, select a custom font in the 'Font' tab.", "hex.builtin.setting.interface.show_header_command_palette": "Show Command Palette in Window Header", "hex.builtin.setting.interface.style": "Styling", + "hex.builtin.setting.interface.use_native_menu_bar": "Use native menu bar", "hex.builtin.setting.interface.window": "Window", "hex.builtin.setting.interface.pattern_data_row_bg": "Enable colored pattern background", "hex.builtin.setting.interface.wiki_explain_language": "Wikipedia Language", diff --git a/plugins/builtin/source/content/main_menu_items.cpp b/plugins/builtin/source/content/main_menu_items.cpp index deaa09239..e27a53d91 100644 --- a/plugins/builtin/source/content/main_menu_items.cpp +++ b/plugins/builtin/source/content/main_menu_items.cpp @@ -23,6 +23,7 @@ #include #include +#include using namespace std::literals::string_literals; using namespace wolv::literals; @@ -212,7 +213,7 @@ namespace hex::plugin::builtin { void drawExportLanguageMenu() { for (const auto &formatter : ContentRegistry::DataFormatter::impl::getExportMenuEntries()) { - if (ImGui::MenuItem(Lang(formatter.unlocalizedName), nullptr, false, ImHexApi::Provider::get()->getActualSize() > 0)) { + if (menu::menuItem(Lang(formatter.unlocalizedName), Shortcut::None, false, ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->getActualSize() > 0)) { fs::openFileBrowser(fs::DialogMode::Save, {}, [&formatter](const auto &path) { TaskManager::createTask("hex.builtin.task.exporting_data"_lang, TaskManager::NoProgress, [&formatter, path](auto&){ auto provider = ImHexApi::Provider::get(); @@ -379,7 +380,7 @@ namespace hex::plugin::builtin { /* Open Other */ ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.file", "hex.builtin.menu.file.open_other"}, ICON_VS_TELESCOPE, 1150, [] { for (const auto &unlocalizedProviderName : ContentRegistry::Provider::impl::getEntries()) { - if (ImGui::MenuItem(Lang(unlocalizedProviderName))) + if (menu::menuItem(Lang(unlocalizedProviderName))) ImHexApi::Provider::createProvider(unlocalizedProviderName); } }, noRunningTasks); @@ -556,7 +557,7 @@ namespace hex::plugin::builtin { if (view->hasViewMenuItemEntry()) { auto &state = view->getWindowOpenState(); - if (ImGui::MenuItemEx(Lang(view->getUnlocalizedName()), view->getIcon(), "", state, ImHexApi::Provider::isValid() && !LayoutManager::isLayoutLocked())) + if (menu::menuItemEx(Lang(view->getUnlocalizedName()), view->getIcon(), Shortcut::None, state, ImHexApi::Provider::isValid() && !LayoutManager::isLayoutLocked())) state = !state; } } @@ -577,7 +578,7 @@ namespace hex::plugin::builtin { ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.workspace", "hex.builtin.menu.workspace.layout" }, ICON_VS_LAYOUT, 1150, [] { bool locked = LayoutManager::isLayoutLocked(); - if (ImGui::MenuItemEx("hex.builtin.menu.workspace.layout.lock"_lang, ICON_VS_LOCK, nullptr, locked, ImHexApi::Provider::isValid())) { + if (menu::menuItemEx("hex.builtin.menu.workspace.layout.lock"_lang, ICON_VS_LOCK, Shortcut::None, locked, ImHexApi::Provider::isValid())) { LayoutManager::lockLayout(!locked); ContentRegistry::Settings::write("hex.builtin.setting.interface", "hex.builtin.setting.interface.layout_locked", !locked); } @@ -587,14 +588,14 @@ namespace hex::plugin::builtin { ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.workspace", "hex.builtin.menu.workspace.layout" }, 2000, [] { for (const auto &path : romfs::list("layouts")) { - if (ImGui::MenuItem(wolv::util::capitalizeString(path.stem().string()).c_str(), "", false, ImHexApi::Provider::isValid())) { + if (menu::menuItem(wolv::util::capitalizeString(path.stem().string()).c_str(), Shortcut::None, false, ImHexApi::Provider::isValid())) { LayoutManager::loadFromString(std::string(romfs::get(path).string())); } } bool shiftPressed = ImGui::GetIO().KeyShift; for (auto &[name, path] : LayoutManager::getLayouts()) { - if (ImGui::MenuItem(hex::format("{}{}", name, shiftPressed ? " " ICON_VS_X : "").c_str(), "", false, ImHexApi::Provider::isValid())) { + if (menu::menuItem(hex::format("{}{}", name, shiftPressed ? " " ICON_VS_X : "").c_str(), Shortcut::None, false, ImHexApi::Provider::isValid())) { if (shiftPressed) { LayoutManager::removeLayout(name); break; @@ -627,7 +628,7 @@ namespace hex::plugin::builtin { const auto &[name, workspace] = *it; bool canRemove = shiftPressed && !workspace.builtin; - if (ImGui::MenuItem(hex::format("{}{}", name, canRemove ? " " ICON_VS_X : "").c_str(), "", it == WorkspaceManager::getCurrentWorkspace(), ImHexApi::Provider::isValid())) { + if (menu::menuItem(hex::format("{}{}", name, canRemove ? " " ICON_VS_X : "").c_str(), Shortcut::None, it == WorkspaceManager::getCurrentWorkspace(), ImHexApi::Provider::isValid())) { if (canRemove) { WorkspaceManager::removeWorkspace(name); break; diff --git a/plugins/builtin/source/content/recent.cpp b/plugins/builtin/source/content/recent.cpp index 73e70ebe2..34a55c8cc 100644 --- a/plugins/builtin/source/content/recent.cpp +++ b/plugins/builtin/source/content/recent.cpp @@ -20,6 +20,7 @@ #include #include +#include namespace hex::plugin::builtin::recent { @@ -352,17 +353,17 @@ namespace hex::plugin::builtin::recent { #endif ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.file" }, 1200, [] { - if (ImGui::BeginMenuEx("hex.builtin.menu.file.open_recent"_lang, ICON_VS_ARCHIVE, !recent::s_recentEntriesUpdating && !s_recentEntries.empty())) { + if (menu::beginMenuEx("hex.builtin.menu.file.open_recent"_lang, ICON_VS_ARCHIVE, !recent::s_recentEntriesUpdating && !s_recentEntries.empty())) { // Copy to avoid changing list while iteration auto recentEntries = s_recentEntries; for (auto &recentEntry : recentEntries) { - if (ImGui::MenuItem(recentEntry.displayName.c_str())) { + if (menu::menuItem(recentEntry.displayName.c_str())) { loadRecentEntry(recentEntry); } } - ImGui::Separator(); - if (ImGui::MenuItem("hex.builtin.menu.file.clear_recent"_lang)) { + menu::menuSeparator(); + if (menu::menuItem("hex.builtin.menu.file.clear_recent"_lang)) { s_recentEntries.clear(); // Remove all recent files @@ -372,8 +373,8 @@ namespace hex::plugin::builtin::recent { } } - ImGui::EndMenu(); + menu::endMenu(); } }); } -} \ No newline at end of file +} diff --git a/plugins/builtin/source/content/settings_entries.cpp b/plugins/builtin/source/content/settings_entries.cpp index a90366008..7cffca949 100644 --- a/plugins/builtin/source/content/settings_entries.cpp +++ b/plugins/builtin/source/content/settings_entries.cpp @@ -833,6 +833,10 @@ namespace hex::plugin::builtin { ContentRegistry::Settings::add("hex.builtin.setting.interface", "hex.builtin.setting.interface.window", "hex.builtin.setting.interface.native_window_decorations", !getDefaultBorderlessWindowMode()).requiresRestart(); #endif + #if defined (OS_MACOS) + ContentRegistry::Settings::add("hex.builtin.setting.interface", "hex.builtin.setting.interface.window", "hex.builtin.setting.interface.use_native_menu_bar", true); + #endif + ContentRegistry::Settings::add("hex.builtin.setting.interface", "hex.builtin.setting.interface.window", "hex.builtin.setting.interface.restore_window_pos", false); ContentRegistry::Settings::add("hex.builtin.setting.hex_editor", "", "hex.builtin.setting.hex_editor.highlight_color", ImColor(0x80, 0x80, 0xC0, 0x60)); diff --git a/plugins/builtin/source/content/views/view_about.cpp b/plugins/builtin/source/content/views/view_about.cpp index 31ea88189..08e6f6776 100644 --- a/plugins/builtin/source/content/views/view_about.cpp +++ b/plugins/builtin/source/content/views/view_about.cpp @@ -20,6 +20,7 @@ #include #include +#include namespace hex::plugin::builtin { @@ -86,6 +87,9 @@ namespace hex::plugin::builtin { ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.help" }, 2000); ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.help" }, 3000, [] { + if (menu::isNativeMenuBarUsed()) + return; + static std::string content; if (ImGui::InputTextWithHint("##search", "hex.builtin.view.help.documentation_search"_lang, content, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EscapeClearsAll | ImGuiInputTextFlags_EnterReturnsTrue)) { PopupDocsQuestion::open(content); diff --git a/plugins/builtin/source/content/views/view_hex_editor.cpp b/plugins/builtin/source/content/views/view_hex_editor.cpp index c232181a3..0d3603edf 100644 --- a/plugins/builtin/source/content/views/view_hex_editor.cpp +++ b/plugins/builtin/source/content/views/view_hex_editor.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include using namespace std::literals::string_literals; @@ -1278,7 +1279,7 @@ namespace hex::plugin::builtin { bool enabled = ImHexApi::HexEditor::isSelectionValid(); for (const auto &[unlocalizedName, callback] : ContentRegistry::DataFormatter::impl::getExportMenuEntries()) { - if (ImGui::MenuItem(Lang(unlocalizedName), nullptr, false, enabled)) { + if (menu::menuItem(Lang(unlocalizedName), Shortcut::None, false, enabled)) { ImGui::SetClipboardText( callback( provider, @@ -1424,8 +1425,14 @@ namespace hex::plugin::builtin { /* Jump to */ ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.jump_to" }, ICON_VS_DEBUG_STEP_OUT, 1850, [] { - auto provider = ImHexApi::Provider::get(); - auto selection = ImHexApi::HexEditor::getSelection(); + auto provider = ImHexApi::Provider::get(); + if (provider == nullptr) + return; + const auto selection = ImHexApi::HexEditor::getSelection(); + if (!selection.has_value()) + return; + if (selection->getSize() > sizeof(u64)) + return; u64 value = 0; provider->read(selection->getStartAddress(), &value, selection->getSize()); @@ -1438,18 +1445,18 @@ namespace hex::plugin::builtin { }; ImGui::PushID(1); - if (ImGui::MenuItem(hex::format("0x{:08X}", littleEndianValue).c_str(), "hex.ui.common.little_endian"_lang, false, canJumpTo(littleEndianValue))) { + if (menu::menuItem(hex::format("{} | 0x{:08X}", "hex.ui.common.little_endian"_lang, littleEndianValue).c_str(), Shortcut::None, false, canJumpTo(littleEndianValue))) { ImHexApi::HexEditor::setSelection(littleEndianValue, 1); } ImGui::PopID(); ImGui::PushID(2); - if (ImGui::MenuItem(hex::format("0x{:08X}", bigEndianValue).c_str(), "hex.ui.common.big_endian"_lang, false, canJumpTo(bigEndianValue))) { + if (menu::menuItem(hex::format("{} | 0x{:08X}", "hex.ui.common.big_endian"_lang, bigEndianValue).c_str(), Shortcut::None, false, canJumpTo(bigEndianValue))) { ImHexApi::HexEditor::setSelection(bigEndianValue, 1); } ImGui::PopID(); - if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.jump_to.curr_pattern"_lang, "", false, selection.has_value() && ContentRegistry::PatternLanguage::getRuntime().getCreatedPatternCount() > 0)) { + if (menu::menuItem("hex.builtin.view.hex_editor.menu.edit.jump_to.curr_pattern"_lang, Shortcut::None, false, selection.has_value() && ContentRegistry::PatternLanguage::getRuntime().getCreatedPatternCount() > 0)) { auto patterns = ContentRegistry::PatternLanguage::getRuntime().getPatternsAtAddress(selection->getStartAddress()); if (!patterns.empty()) diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index a6734e0d4..8e60af151 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -32,6 +32,7 @@ #include #include +#include namespace hex::plugin::builtin { @@ -2074,12 +2075,12 @@ namespace hex::plugin::builtin { return; if (menus.size() == 1) { - if (ImGui::MenuItem(menus.front().c_str())) + if (menu::menuItem(menus.front().c_str())) function(); } else { - if (ImGui::BeginMenu(menus.front().c_str())) { + if (menu::beginMenu(menus.front().c_str())) { createNestedMenu({ menus.begin() + 1, menus.end() }, function); - ImGui::EndMenu(); + menu::endMenu(); } } } @@ -2135,30 +2136,30 @@ namespace hex::plugin::builtin { /* Place pattern... */ ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.place_pattern" }, ICON_VS_LIBRARY, 3000, [&, this] { - if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin"_lang)) { - if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.single"_lang)) { + if (menu::beginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin"_lang)) { + if (menu::beginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.single"_lang)) { for (const auto &[type, size] : Types) { - if (ImGui::MenuItem(type)) + if (menu::menuItem(type)) appendVariable(type); } - ImGui::EndMenu(); + menu::endMenu(); } - if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.array"_lang)) { + if (menu::beginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.array"_lang)) { for (const auto &[type, size] : Types) { - if (ImGui::MenuItem(type)) + if (menu::menuItem(type)) appendArray(type, size); } - ImGui::EndMenu(); + menu::endMenu(); } - ImGui::EndMenu(); + menu::endMenu(); } const auto &types = m_editorRuntime->getInternals().parser->getTypes(); const bool hasPlaceableTypes = std::ranges::any_of(types, [](const auto &type) { return !type.second->isTemplateType(); }); - if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.custom"_lang, hasPlaceableTypes)) { + if (menu::beginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.custom"_lang, hasPlaceableTypes)) { const auto &selection = ImHexApi::HexEditor::getSelection(); for (const auto &[typeName, type] : types) { @@ -2175,7 +2176,7 @@ namespace hex::plugin::builtin { }); } - ImGui::EndMenu(); + menu::endMenu(); } }, [this] { return ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid() && m_runningParsers == 0; @@ -2628,4 +2629,4 @@ namespace hex::plugin::builtin { }); } -} \ No newline at end of file +} diff --git a/plugins/builtin/source/content/window_decoration.cpp b/plugins/builtin/source/content/window_decoration.cpp index 3c6209dc8..343746a9f 100644 --- a/plugins/builtin/source/content/window_decoration.cpp +++ b/plugins/builtin/source/content/window_decoration.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -32,21 +33,23 @@ namespace hex::plugin::builtin { const auto &name = menuItems.front(); if (name.get() == ContentRegistry::Interface::impl::SeparatorValue) { - ImGui::Separator(); + menu::menuSeparator(); return; } if (name.get() == ContentRegistry::Interface::impl::SubMenuValue) { - callback(); + if (enabledCallback()) { + callback(); + } } else if (menuItems.size() == 1) { - if (ImGui::MenuItemEx(Lang(name), icon, shortcut.toString().c_str(), selectedCallback(), enabledCallback())) + if (menu::menuItemEx(Lang(name), icon, shortcut, selectedCallback(), enabledCallback())) callback(); } else { bool isSubmenu = (menuItems.begin() + 1)->get() == ContentRegistry::Interface::impl::SubMenuValue; - if (ImGui::BeginMenuEx(Lang(name), std::next(menuItems.begin())->get() == ContentRegistry::Interface::impl::SubMenuValue ? icon : nullptr, isSubmenu ? enabledCallback() : true)) { + if (menu::beginMenuEx(Lang(name), std::next(menuItems.begin())->get() == ContentRegistry::Interface::impl::SubMenuValue ? icon : nullptr, isSubmenu ? enabledCallback() : true)) { createNestedMenu({ std::next(menuItems.begin()), menuItems.end() }, icon, shortcut, callback, enabledCallback, selectedCallback); - ImGui::EndMenu(); + menu::endMenu(); } } } @@ -285,9 +288,9 @@ namespace hex::plugin::builtin { } void defineMenu(const UnlocalizedString &menuName) { - if (ImGui::BeginMenu(Lang(menuName))) { + if (menu::beginMenu(Lang(menuName))) { populateMenu(menuName); - ImGui::EndMenu(); + menu::endMenu(); } else { if (s_displayShortcutHighlights) { if (const auto lastShortcutMenu = ShortcutManager::getLastActivatedMenu(); lastShortcutMenu.has_value()) { @@ -301,57 +304,64 @@ namespace hex::plugin::builtin { } void drawMenu() { - auto cursorPos = ImGui::GetCursorPosX(); - u32 fittingItems = 0; - const auto &menuItems = ContentRegistry::Interface::impl::getMainMenuItems(); - for (const auto &[priority, menuItem] : menuItems) { - auto menuName = Lang(menuItem.unlocalizedName); - const auto padding = ImGui::GetStyle().FramePadding.x; - bool lastItem = (fittingItems + 1) == menuItems.size(); - auto width = ImGui::CalcTextSize(menuName).x + padding * (lastItem ? -3.0F : 4.0F); - - if ((cursorPos + width) > (s_searchBarPosition - ImGui::CalcTextSize(ICON_VS_ELLIPSIS).x - padding * 2)) - break; - - cursorPos += width; - fittingItems += 1; - } - - if (fittingItems <= 2) - fittingItems = 0; - - { - u32 count = 0; + if (menu::isNativeMenuBarUsed()) { for (const auto &[priority, menuItem] : menuItems) { - if (count >= fittingItems) + defineMenu(menuItem.unlocalizedName); + } + } else { + auto cursorPos = ImGui::GetCursorPosX(); + u32 fittingItems = 0; + + for (const auto &[priority, menuItem] : menuItems) { + auto menuName = Lang(menuItem.unlocalizedName); + + const auto padding = ImGui::GetStyle().FramePadding.x; + bool lastItem = (fittingItems + 1) == menuItems.size(); + auto width = ImGui::CalcTextSize(menuName).x + padding * (lastItem ? -3.0F : 4.0F); + + if ((cursorPos + width) > (s_searchBarPosition - ImGui::CalcTextSize(ICON_VS_ELLIPSIS).x - padding * 2)) break; - defineMenu(menuItem.unlocalizedName); - - count += 1; + cursorPos += width; + fittingItems += 1; } - } - if (fittingItems == 0) { - if (ImGui::BeginMenu(ICON_VS_MENU)) { + if (fittingItems <= 2) + fittingItems = 0; + + { + u32 count = 0; for (const auto &[priority, menuItem] : menuItems) { - defineMenu(menuItem.unlocalizedName); - } - ImGui::EndMenu(); - } - } else if (fittingItems < menuItems.size()) { - u32 count = 0; - if (ImGui::BeginMenu(ICON_VS_ELLIPSIS)) { - for (const auto &[priority, menuItem] : menuItems) { - ON_SCOPE_EXIT { count += 1; }; - if (count < fittingItems) - continue; + if (count >= fittingItems) + break; defineMenu(menuItem.unlocalizedName); + + count += 1; + } + } + + if (fittingItems == 0) { + if (ImGui::BeginMenu(ICON_VS_MENU)) { + for (const auto &[priority, menuItem] : menuItems) { + defineMenu(menuItem.unlocalizedName); + } + ImGui::EndMenu(); + } + } else if (fittingItems < menuItems.size()) { + u32 count = 0; + if (ImGui::BeginMenu(ICON_VS_ELLIPSIS)) { + for (const auto &[priority, menuItem] : menuItems) { + ON_SCOPE_EXIT { count += 1; }; + if (count < fittingItems) + continue; + + defineMenu(menuItem.unlocalizedName); + } + ImGui::EndMenu(); } - ImGui::EndMenu(); } } } @@ -366,13 +376,8 @@ namespace hex::plugin::builtin { ON_SCOPE_EXIT { ImGui::PopStyleVar(); }; #endif - if (ImGui::BeginMainMenuBar()) { - ImGui::Dummy({}); - - auto window = ImHexApi::System::getMainWindowHandle(); - - ImGui::PopStyleVar(2); - + auto window = ImHexApi::System::getMainWindowHandle(); + if (menu::beginMainMenuBar()) { if (ImHexApi::System::isBorderlessWindowModeEnabled()) { #if defined(OS_WINDOWS) ImGui::SetCursorPosX(5_scaled); @@ -406,8 +411,14 @@ namespace hex::plugin::builtin { ImGui::EndPopup(); } - drawMenu(); + menu::endMainMenuBar(); + } + if (ImGui::BeginMainMenuBar()) { + ImGui::Dummy({}); + + ImGui::PopStyleVar(2); + drawTitleBar(); #if defined(OS_MACOS) @@ -611,6 +622,10 @@ namespace hex::plugin::builtin { ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.display_shortcut_highlights", [](const ContentRegistry::Settings::SettingsValue &value) { s_displayShortcutHighlights = value.get(true); }); + + ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.use_native_menu_bar", [](const ContentRegistry::Settings::SettingsValue &value) { + menu::enableNativeMenuBar(value.get(true)); + }); } diff --git a/plugins/ui/CMakeLists.txt b/plugins/ui/CMakeLists.txt index 276e04409..9e23d5830 100644 --- a/plugins/ui/CMakeLists.txt +++ b/plugins/ui/CMakeLists.txt @@ -11,6 +11,7 @@ add_imhex_plugin( source/ui/hex_editor.cpp source/ui/pattern_drawer.cpp source/ui/visualizer_drawer.cpp + source/ui/menu_items.cpp INCLUDES include LIBRARIES @@ -18,3 +19,10 @@ add_imhex_plugin( fonts LIBRARY_PLUGIN ) + +if (APPLE) + target_sources(ui PRIVATE source/ui/macos_menu.m) + find_library(FOUNDATION NAMES Foundation REQUIRED) + find_library(COCOA NAMES Cocoa REQUIRED) + target_link_libraries(ui PUBLIC ${FOUNDATION} ${COCOA}) +endif() \ No newline at end of file diff --git a/plugins/ui/include/ui/menu_items.hpp b/plugins/ui/include/ui/menu_items.hpp new file mode 100644 index 000000000..30a51cad3 --- /dev/null +++ b/plugins/ui/include/ui/menu_items.hpp @@ -0,0 +1,25 @@ +#pragma once +#include + +namespace hex::menu { + + void enableNativeMenuBar(bool enabled); + bool isNativeMenuBarUsed(); + + bool beginMainMenuBar(); + void endMainMenuBar(); + + bool beginMenu(const char *label, bool enabled = true); + void endMenu(); + + bool beginMenuEx(const char* label, const char* icon, bool enabled = true); + + bool menuItem(const char *label, const Shortcut &shortcut = Shortcut::None, bool selected = false, bool enabled = true); + bool menuItem(const char *label, const Shortcut &shortcut, bool *selected, bool enabled = true); + + bool menuItemEx(const char *label, const char *icon, const Shortcut &shortcut = Shortcut::None, bool selected = false, bool enabled = true); + bool menuItemEx(const char *label, const char *icon, const Shortcut &shortcut, bool *selected, bool enabled = true); + + void menuSeparator(); + +} diff --git a/plugins/ui/source/ui/macos_menu.m b/plugins/ui/source/ui/macos_menu.m new file mode 100644 index 000000000..d1516d908 --- /dev/null +++ b/plugins/ui/source/ui/macos_menu.m @@ -0,0 +1,181 @@ +#import +#import + +struct KeyEquivalent { + bool valid; + bool ctrl, opt, cmd, shift; + int key; +}; + +const static int MenuBegin = 1; +static NSInteger s_currTag = MenuBegin; +static NSInteger s_selectedTag = -1; + +@interface MenuItemHandler : NSObject +-(void) OnClick: (id) sender; +@end + +@implementation MenuItemHandler +-(void) OnClick: (id) sender { + NSMenuItem* menu_item = sender; + s_selectedTag = menu_item.tag; +} +@end + + +static NSMenu* s_menuStack[1024]; +static int s_menuStackSize = 0; + +static MenuItemHandler* s_menuItemHandler; + +static bool s_constructingMenu = false; +static bool s_resetNeeded = true; + +void macosMenuBarInit(void) { + s_menuStack[0] = NSApp.mainMenu; + s_menuStackSize += 1; + + s_menuItemHandler = [[MenuItemHandler alloc] init]; +} + +void macosClearMenu(void) { + // Remove all items except the Application menu + while (s_menuStack[0].itemArray.count > 2) { + [s_menuStack[0] removeItemAtIndex:1]; + } + + s_currTag = MenuBegin; +} + +bool macosBeginMainMenuBar(void) { + if (s_resetNeeded) { + macosClearMenu(); + s_resetNeeded = false; + } + + return true; +} + +void macosEndMainMenuBar(void) { + s_constructingMenu = false; +} + +bool macosBeginMenu(const char* label, bool enabled) { + NSString* title = [NSString stringWithUTF8String:label]; + + // Search for menu item with the given name + NSInteger menuIndex = [s_menuStack[s_menuStackSize - 1] indexOfItemWithTitle:title]; + if (menuIndex == -1) { + // Construct the content of the menu if it doesn't exist yet + + s_constructingMenu = true; + + NSMenu* newMenu = [[NSMenu alloc] init]; + newMenu.autoenablesItems = false; + newMenu.title = title; + + NSMenuItem* menuItem = [[NSMenuItem alloc] init]; + menuItem.title = title; + [menuItem setSubmenu:newMenu]; + + // Add the new menu to the end of the list + menuIndex = [s_menuStack[s_menuStackSize - 1] numberOfItems]; + + if (s_menuStackSize == 1) + menuIndex -= 1; + + [s_menuStack[s_menuStackSize - 1] insertItem:menuItem atIndex:menuIndex]; + } + + NSMenuItem* menuItem = [s_menuStack[s_menuStackSize - 1] itemAtIndex:menuIndex]; + if (menuItem != NULL) { + menuItem.enabled = enabled; + + s_menuStack[s_menuStackSize] = menuItem.submenu; + s_menuStackSize += 1; + } + + return true; +} + +void macosEndMenu(void) { + s_menuStack[s_menuStackSize - 1] = NULL; + s_menuStackSize -= 1; +} + +bool macosMenuItem(const char* label, struct KeyEquivalent keyEquivalent, bool selected, bool enabled) { + NSString* title = [NSString stringWithUTF8String:label]; + + if (s_constructingMenu) { + NSMenuItem* menuItem = [[NSMenuItem alloc] init]; + menuItem.title = title; + menuItem.action = @selector(OnClick:); + menuItem.target = s_menuItemHandler; + + [menuItem setTag:s_currTag]; + s_currTag += 1; + + // Setup the key equivalent, aka the shortcut + if (keyEquivalent.valid) { + NSInteger flags = 0x00; + + if (keyEquivalent.ctrl) + flags |= NSEventModifierFlagControl; + if (keyEquivalent.shift) + flags |= NSEventModifierFlagShift; + if (keyEquivalent.cmd) + flags |= NSEventModifierFlagCommand; + if (keyEquivalent.opt) + flags |= NSEventModifierFlagOption; + + [menuItem setKeyEquivalentModifierMask:flags]; + menuItem.keyEquivalent = [[NSString alloc] initWithCharacters:(const unichar *)&keyEquivalent.key length:1]; + } + + [s_menuStack[s_menuStackSize - 1] addItem:menuItem]; + } + + NSInteger menuIndex = [s_menuStack[s_menuStackSize - 1] indexOfItemWithTitle:title]; + NSMenuItem* menuItem = NULL; + if (menuIndex >= 0 && menuIndex < [s_menuStack[s_menuStackSize - 1] numberOfItems]) { + menuItem = [s_menuStack[s_menuStackSize - 1] itemAtIndex:menuIndex]; + if (menuItem != NULL) { + if (s_constructingMenu == false) { + if (![title isEqualToString:menuItem.title]) { + s_resetNeeded = true; + } + } + + menuItem.enabled = enabled; + menuItem.state = selected ? NSControlStateValueOn : NSControlStateValueOff; + } + + if (enabled && menuItem != NULL) { + if ([menuItem tag] == s_selectedTag) { + s_selectedTag = -1; + return true; + } + } + } else { + s_resetNeeded = true; + } + + return false; +} + +bool macosMenuItemSelect(const char* label, struct KeyEquivalent keyEquivalent, bool* selected, bool enabled) { + if (macosMenuItem(label, keyEquivalent, selected != NULL ? *selected : false, enabled)) { + if (selected != NULL) + *selected = !(*selected); + + return true; + } + return false; +} + +void macosSeparator(void) { + if (s_constructingMenu) { + NSMenuItem* separator = [NSMenuItem separatorItem]; + [s_menuStack[s_menuStackSize - 1] addItem:separator]; + } +} diff --git a/plugins/ui/source/ui/menu_items.cpp b/plugins/ui/source/ui/menu_items.cpp new file mode 100644 index 000000000..49195b4c0 --- /dev/null +++ b/plugins/ui/source/ui/menu_items.cpp @@ -0,0 +1,156 @@ +#include + +#include +#include +#include + +#if defined(OS_MACOS) + extern "C" { + void macosMenuBarInit(void); + + bool macosBeginMainMenuBar(void); + void macosEndMainMenuBar(void); + void macosClearMenu(void); + + bool macosBeginMenu(const char* label, bool enabled); + void macosEndMenu(void); + + bool macosMenuItem(const char* label, KeyEquivalent keyEquivalent, bool selected, bool enabled); + bool macosMenuItemSelect(const char* label, KeyEquivalent keyEquivalent, bool* p_selected, bool enabled); + + void macosSeparator(void); + } +#endif + +namespace hex::menu { + + static bool s_useNativeMenuBar = false; + void enableNativeMenuBar(bool enabled) { + #if !defined (OS_MACOS) + std::ignore = enabled; + #else + if (!enabled) { + macosClearMenu(); + } + s_useNativeMenuBar = enabled; + #endif + } + + bool isNativeMenuBarUsed() { + return s_useNativeMenuBar; + } + + static bool s_initialized = false; + bool beginMainMenuBar() { + #if defined(OS_MACOS) + if (s_useNativeMenuBar) { + if (!s_initialized) { + macosMenuBarInit(); + s_initialized = true; + } + + return macosBeginMainMenuBar(); + } + #else + std::ignore = s_initialized; + #endif + + return ImGui::BeginMainMenuBar(); + } + + void endMainMenuBar() { + #if defined(OS_MACOS) + if (s_useNativeMenuBar) { + macosEndMainMenuBar(); + return; + } + #endif + + ImGui::EndMainMenuBar(); + } + + bool beginMenu(const char *label, bool enabled) { + #if defined(OS_MACOS) + if (s_useNativeMenuBar) + return macosBeginMenu(label, enabled); + #endif + + return ImGui::BeginMenu(label, enabled); + } + + bool beginMenuEx(const char *label, const char *icon, bool enabled) { + #if defined(OS_MACOS) + if (s_useNativeMenuBar) + return macosBeginMenu(label, enabled); + #endif + + return ImGui::BeginMenuEx(label, icon, enabled); + } + + void endMenu() { + #if defined(OS_MACOS) + if (s_useNativeMenuBar) { + macosEndMenu(); + return; + } + #endif + + ImGui::EndMenu(); + } + + + bool menuItem(const char *label, const Shortcut &shortcut, bool selected, bool enabled) { + #if defined(OS_MACOS) + if (s_useNativeMenuBar) + return macosMenuItem(label, shortcut.toKeyEquivalent(), selected, enabled); + #endif + + return ImGui::MenuItem(label, shortcut.toString().c_str(), selected, enabled); + } + + bool menuItem(const char *label, const Shortcut &shortcut, bool *selected, bool enabled) { + #if defined(OS_MACOS) + if (s_useNativeMenuBar) + return macosMenuItemSelect(label, shortcut.toKeyEquivalent(), selected, enabled); + #endif + + return ImGui::MenuItem(label, shortcut.toString().c_str(), selected, enabled); + } + + bool menuItemEx(const char *label, const char *icon, const Shortcut &shortcut, bool selected, bool enabled) { + #if defined(OS_MACOS) + if (s_useNativeMenuBar) + return macosMenuItem(label, shortcut.toKeyEquivalent(), selected, enabled); + #endif + + return ImGui::MenuItemEx(label, icon, shortcut.toString().c_str(), selected, enabled); + } + + bool menuItemEx(const char *label, const char *icon, const Shortcut &shortcut, bool *selected, bool enabled) { + #if defined(OS_MACOS) + if (s_useNativeMenuBar) + return macosMenuItemSelect(label, shortcut.toKeyEquivalent(), selected, enabled); + #endif + + if (ImGui::MenuItemEx(label, icon, shortcut.toString().c_str(), selected != nullptr ? *selected : false, enabled)) { + if (selected != nullptr) + *selected = !*selected; + return true; + } + + return false; + } + + + void menuSeparator() { + #if defined(OS_MACOS) + if (s_useNativeMenuBar) { + macosSeparator(); + return; + } + #endif + + ImGui::Separator(); + } + +} \ No newline at end of file