1
0
mirror of synced 2025-01-10 21:41:53 +01:00

feat: Let ImHex use the native menu bar on macOS (#2048)

This commit is contained in:
Nik 2025-01-04 15:35:06 +01:00 committed by GitHub
parent 24979d7fbd
commit 6009b5013b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 785 additions and 207 deletions

View File

@ -2,137 +2,25 @@
#include <hex.hpp> #include <hex.hpp>
#include <hex/api/localization_manager.hpp> #include <hex/api/localization_manager.hpp>
#include <hex/helpers/keys.hpp>
#include <functional> #include <functional>
#include <memory>
#include <optional> #include <optional>
#include <set> #include <set>
#include <string> #include <string>
#include <GLFW/glfw3.h>
struct ImGuiWindow; struct ImGuiWindow;
struct KeyEquivalent {
bool valid;
bool ctrl, opt, cmd, shift;
int key;
};
namespace hex { namespace hex {
class View; 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 { class Key {
public: public:
constexpr Key() = default; constexpr Key() = default;
@ -175,6 +63,7 @@ namespace hex {
bool isLocal() const; bool isLocal() const;
std::string toString() const; std::string toString() const;
KeyEquivalent toKeyEquivalent() const;
const std::set<Key>& getKeys() const; const std::set<Key>& getKeys() const;
bool has(Key key) const; bool has(Key key) const;
bool matches(const Shortcut &other) const; bool matches(const Shortcut &other) const;

View File

@ -0,0 +1,122 @@
#pragma once
#include <GLFW/glfw3.h>
#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,
};

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <hex/helpers/keys.hpp>
#if defined(OS_MACOS) #if defined(OS_MACOS)
struct GLFWwindow; struct GLFWwindow;
@ -20,6 +22,8 @@
void macosSetWindowMovable(GLFWwindow *window, bool movable); void macosSetWindowMovable(GLFWwindow *window, bool movable);
bool macosIsWindowBeingResizedByUser(GLFWwindow *window); bool macosIsWindowBeingResizedByUser(GLFWwindow *window);
void macosMarkContentEdited(GLFWwindow *window, bool edited = true); void macosMarkContentEdited(GLFWwindow *window, bool edited = true);
void macosGetKey(Keys key, int *output);
} }
#endif #endif

View File

@ -233,6 +233,44 @@ namespace hex {
return result; 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<UnlocalizedString> &unlocalizedName, const std::function<void()> &callback, const EnabledCallback &enabledCallback) { void ShortcutManager::addGlobalShortcut(const Shortcut &shortcut, const std::vector<UnlocalizedString> &unlocalizedName, const std::function<void()> &callback, const EnabledCallback &enabledCallback) {
log::debug("Adding global shortcut {} for {}", shortcut.toString(), unlocalizedName.back().get()); log::debug("Adding global shortcut {} for {}", shortcut.toString(), unlocalizedName.back().get());

View File

@ -18,6 +18,8 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#include <hex/helpers/keys.hpp>
void errorMessageMacos(const char *cMessage) { void errorMessageMacos(const char *cMessage) {
CFStringRef strMessage = CFStringCreateWithCString(NULL, cMessage, kCFStringEncodingUTF8); CFStringRef strMessage = CFStringCreateWithCString(NULL, cMessage, kCFStringEncodingUTF8);
CFUserNotificationDisplayAlert(0, kCFUserNotificationStopAlertLevel, NULL, NULL, NULL, strMessage, NULL, NULL, NULL, NULL, NULL); CFUserNotificationDisplayAlert(0, kCFUserNotificationStopAlertLevel, NULL, NULL, NULL, strMessage, NULL, NULL, NULL, NULL, NULL);
@ -151,4 +153,123 @@
@end @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 #endif

View File

@ -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.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.show_header_command_palette": "Show Command Palette in Window Header",
"hex.builtin.setting.interface.style": "Styling", "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.window": "Window",
"hex.builtin.setting.interface.pattern_data_row_bg": "Enable colored pattern background", "hex.builtin.setting.interface.pattern_data_row_bg": "Enable colored pattern background",
"hex.builtin.setting.interface.wiki_explain_language": "Wikipedia Language", "hex.builtin.setting.interface.wiki_explain_language": "Wikipedia Language",

View File

@ -23,6 +23,7 @@
#include <wolv/literals.hpp> #include <wolv/literals.hpp>
#include <romfs/romfs.hpp> #include <romfs/romfs.hpp>
#include <ui/menu_items.hpp>
using namespace std::literals::string_literals; using namespace std::literals::string_literals;
using namespace wolv::literals; using namespace wolv::literals;
@ -212,7 +213,7 @@ namespace hex::plugin::builtin {
void drawExportLanguageMenu() { void drawExportLanguageMenu() {
for (const auto &formatter : ContentRegistry::DataFormatter::impl::getExportMenuEntries()) { 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) { fs::openFileBrowser(fs::DialogMode::Save, {}, [&formatter](const auto &path) {
TaskManager::createTask("hex.builtin.task.exporting_data"_lang, TaskManager::NoProgress, [&formatter, path](auto&){ TaskManager::createTask("hex.builtin.task.exporting_data"_lang, TaskManager::NoProgress, [&formatter, path](auto&){
auto provider = ImHexApi::Provider::get(); auto provider = ImHexApi::Provider::get();
@ -379,7 +380,7 @@ namespace hex::plugin::builtin {
/* Open Other */ /* Open Other */
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.file", "hex.builtin.menu.file.open_other"}, ICON_VS_TELESCOPE, 1150, [] { 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()) { for (const auto &unlocalizedProviderName : ContentRegistry::Provider::impl::getEntries()) {
if (ImGui::MenuItem(Lang(unlocalizedProviderName))) if (menu::menuItem(Lang(unlocalizedProviderName)))
ImHexApi::Provider::createProvider(unlocalizedProviderName); ImHexApi::Provider::createProvider(unlocalizedProviderName);
} }
}, noRunningTasks); }, noRunningTasks);
@ -556,7 +557,7 @@ namespace hex::plugin::builtin {
if (view->hasViewMenuItemEntry()) { if (view->hasViewMenuItemEntry()) {
auto &state = view->getWindowOpenState(); 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; 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, [] { ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.workspace", "hex.builtin.menu.workspace.layout" }, ICON_VS_LAYOUT, 1150, [] {
bool locked = LayoutManager::isLayoutLocked(); 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); LayoutManager::lockLayout(!locked);
ContentRegistry::Settings::write<bool>("hex.builtin.setting.interface", "hex.builtin.setting.interface.layout_locked", !locked); ContentRegistry::Settings::write<bool>("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, [] { ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.workspace", "hex.builtin.menu.workspace.layout" }, 2000, [] {
for (const auto &path : romfs::list("layouts")) { 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())); LayoutManager::loadFromString(std::string(romfs::get(path).string()));
} }
} }
bool shiftPressed = ImGui::GetIO().KeyShift; bool shiftPressed = ImGui::GetIO().KeyShift;
for (auto &[name, path] : LayoutManager::getLayouts()) { 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) { if (shiftPressed) {
LayoutManager::removeLayout(name); LayoutManager::removeLayout(name);
break; break;
@ -627,7 +628,7 @@ namespace hex::plugin::builtin {
const auto &[name, workspace] = *it; const auto &[name, workspace] = *it;
bool canRemove = shiftPressed && !workspace.builtin; 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) { if (canRemove) {
WorkspaceManager::removeWorkspace(name); WorkspaceManager::removeWorkspace(name);
break; break;

View File

@ -20,6 +20,7 @@
#include <ranges> #include <ranges>
#include <unordered_set> #include <unordered_set>
#include <ui/menu_items.hpp>
namespace hex::plugin::builtin::recent { namespace hex::plugin::builtin::recent {
@ -352,17 +353,17 @@ namespace hex::plugin::builtin::recent {
#endif #endif
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.file" }, 1200, [] { 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 // Copy to avoid changing list while iteration
auto recentEntries = s_recentEntries; auto recentEntries = s_recentEntries;
for (auto &recentEntry : recentEntries) { for (auto &recentEntry : recentEntries) {
if (ImGui::MenuItem(recentEntry.displayName.c_str())) { if (menu::menuItem(recentEntry.displayName.c_str())) {
loadRecentEntry(recentEntry); loadRecentEntry(recentEntry);
} }
} }
ImGui::Separator(); menu::menuSeparator();
if (ImGui::MenuItem("hex.builtin.menu.file.clear_recent"_lang)) { if (menu::menuItem("hex.builtin.menu.file.clear_recent"_lang)) {
s_recentEntries.clear(); s_recentEntries.clear();
// Remove all recent files // Remove all recent files
@ -372,8 +373,8 @@ namespace hex::plugin::builtin::recent {
} }
} }
ImGui::EndMenu(); menu::endMenu();
} }
}); });
} }
} }

View File

@ -833,6 +833,10 @@ namespace hex::plugin::builtin {
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.interface", "hex.builtin.setting.interface.window", "hex.builtin.setting.interface.native_window_decorations", !getDefaultBorderlessWindowMode()).requiresRestart(); ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.interface", "hex.builtin.setting.interface.window", "hex.builtin.setting.interface.native_window_decorations", !getDefaultBorderlessWindowMode()).requiresRestart();
#endif #endif
#if defined (OS_MACOS)
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.interface", "hex.builtin.setting.interface.window", "hex.builtin.setting.interface.use_native_menu_bar", true);
#endif
ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.interface", "hex.builtin.setting.interface.window", "hex.builtin.setting.interface.restore_window_pos", false); ContentRegistry::Settings::add<Widgets::Checkbox>("hex.builtin.setting.interface", "hex.builtin.setting.interface.window", "hex.builtin.setting.interface.restore_window_pos", false);
ContentRegistry::Settings::add<Widgets::ColorPicker>("hex.builtin.setting.hex_editor", "", "hex.builtin.setting.hex_editor.highlight_color", ImColor(0x80, 0x80, 0xC0, 0x60)); ContentRegistry::Settings::add<Widgets::ColorPicker>("hex.builtin.setting.hex_editor", "", "hex.builtin.setting.hex_editor.highlight_color", ImColor(0x80, 0x80, 0xC0, 0x60));

View File

@ -20,6 +20,7 @@
#include <wolv/utils/string.hpp> #include <wolv/utils/string.hpp>
#include <string> #include <string>
#include <ui/menu_items.hpp>
namespace hex::plugin::builtin { namespace hex::plugin::builtin {
@ -86,6 +87,9 @@ namespace hex::plugin::builtin {
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.help" }, 2000); ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.help" }, 2000);
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.help" }, 3000, [] { ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.help" }, 3000, [] {
if (menu::isNativeMenuBarUsed())
return;
static std::string content; static std::string content;
if (ImGui::InputTextWithHint("##search", "hex.builtin.view.help.documentation_search"_lang, content, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EscapeClearsAll | ImGuiInputTextFlags_EnterReturnsTrue)) { if (ImGui::InputTextWithHint("##search", "hex.builtin.view.help.documentation_search"_lang, content, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EscapeClearsAll | ImGuiInputTextFlags_EnterReturnsTrue)) {
PopupDocsQuestion::open(content); PopupDocsQuestion::open(content);

View File

@ -21,6 +21,7 @@
#include <content/popups/popup_blocking_task.hpp> #include <content/popups/popup_blocking_task.hpp>
#include <content/popups/hex_editor/popup_hex_editor_find.hpp> #include <content/popups/hex_editor/popup_hex_editor_find.hpp>
#include <pl/patterns/pattern.hpp> #include <pl/patterns/pattern.hpp>
#include <ui/menu_items.hpp>
#include <wolv/literals.hpp> #include <wolv/literals.hpp>
using namespace std::literals::string_literals; using namespace std::literals::string_literals;
@ -1278,7 +1279,7 @@ namespace hex::plugin::builtin {
bool enabled = ImHexApi::HexEditor::isSelectionValid(); bool enabled = ImHexApi::HexEditor::isSelectionValid();
for (const auto &[unlocalizedName, callback] : ContentRegistry::DataFormatter::impl::getExportMenuEntries()) { 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( ImGui::SetClipboardText(
callback( callback(
provider, provider,
@ -1424,8 +1425,14 @@ namespace hex::plugin::builtin {
/* Jump to */ /* Jump to */
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.jump_to" }, ICON_VS_DEBUG_STEP_OUT, 1850, 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 provider = ImHexApi::Provider::get();
auto selection = ImHexApi::HexEditor::getSelection(); if (provider == nullptr)
return;
const auto selection = ImHexApi::HexEditor::getSelection();
if (!selection.has_value())
return;
if (selection->getSize() > sizeof(u64))
return;
u64 value = 0; u64 value = 0;
provider->read(selection->getStartAddress(), &value, selection->getSize()); provider->read(selection->getStartAddress(), &value, selection->getSize());
@ -1438,18 +1445,18 @@ namespace hex::plugin::builtin {
}; };
ImGui::PushID(1); 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); ImHexApi::HexEditor::setSelection(littleEndianValue, 1);
} }
ImGui::PopID(); ImGui::PopID();
ImGui::PushID(2); 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); ImHexApi::HexEditor::setSelection(bigEndianValue, 1);
} }
ImGui::PopID(); 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()); auto patterns = ContentRegistry::PatternLanguage::getRuntime().getPatternsAtAddress(selection->getStartAddress());
if (!patterns.empty()) if (!patterns.empty())

View File

@ -32,6 +32,7 @@
#include <wolv/utils/lock.hpp> #include <wolv/utils/lock.hpp>
#include <content/global_actions.hpp> #include <content/global_actions.hpp>
#include <ui/menu_items.hpp>
namespace hex::plugin::builtin { namespace hex::plugin::builtin {
@ -2074,12 +2075,12 @@ namespace hex::plugin::builtin {
return; return;
if (menus.size() == 1) { if (menus.size() == 1) {
if (ImGui::MenuItem(menus.front().c_str())) if (menu::menuItem(menus.front().c_str()))
function(); function();
} else { } else {
if (ImGui::BeginMenu(menus.front().c_str())) { if (menu::beginMenu(menus.front().c_str())) {
createNestedMenu({ menus.begin() + 1, menus.end() }, function); createNestedMenu({ menus.begin() + 1, menus.end() }, function);
ImGui::EndMenu(); menu::endMenu();
} }
} }
} }
@ -2135,30 +2136,30 @@ namespace hex::plugin::builtin {
/* Place pattern... */ /* Place pattern... */
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.place_pattern" }, ICON_VS_LIBRARY, 3000, ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.place_pattern" }, ICON_VS_LIBRARY, 3000,
[&, this] { [&, this] {
if (ImGui::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"_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.single"_lang)) {
for (const auto &[type, size] : Types) { for (const auto &[type, size] : Types) {
if (ImGui::MenuItem(type)) if (menu::menuItem(type))
appendVariable(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) { for (const auto &[type, size] : Types) {
if (ImGui::MenuItem(type)) if (menu::menuItem(type))
appendArray(type, size); appendArray(type, size);
} }
ImGui::EndMenu(); menu::endMenu();
} }
ImGui::EndMenu(); menu::endMenu();
} }
const auto &types = m_editorRuntime->getInternals().parser->getTypes(); const auto &types = m_editorRuntime->getInternals().parser->getTypes();
const bool hasPlaceableTypes = std::ranges::any_of(types, [](const auto &type) { return !type.second->isTemplateType(); }); 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(); const auto &selection = ImHexApi::HexEditor::getSelection();
for (const auto &[typeName, type] : types) { for (const auto &[typeName, type] : types) {
@ -2175,7 +2176,7 @@ namespace hex::plugin::builtin {
}); });
} }
ImGui::EndMenu(); menu::endMenu();
} }
}, [this] { }, [this] {
return ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid() && m_runningParsers == 0; return ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid() && m_runningParsers == 0;
@ -2628,4 +2629,4 @@ namespace hex::plugin::builtin {
}); });
} }
} }

View File

@ -10,6 +10,7 @@
#include <imgui.h> #include <imgui.h>
#include <imgui_internal.h> #include <imgui_internal.h>
#include <hex/ui/imgui_imhex_extensions.h> #include <hex/ui/imgui_imhex_extensions.h>
#include <ui/menu_items.hpp>
#include <fonts/vscode_icons.hpp> #include <fonts/vscode_icons.hpp>
#include <romfs/romfs.hpp> #include <romfs/romfs.hpp>
@ -32,21 +33,23 @@ namespace hex::plugin::builtin {
const auto &name = menuItems.front(); const auto &name = menuItems.front();
if (name.get() == ContentRegistry::Interface::impl::SeparatorValue) { if (name.get() == ContentRegistry::Interface::impl::SeparatorValue) {
ImGui::Separator(); menu::menuSeparator();
return; return;
} }
if (name.get() == ContentRegistry::Interface::impl::SubMenuValue) { if (name.get() == ContentRegistry::Interface::impl::SubMenuValue) {
callback(); if (enabledCallback()) {
callback();
}
} else if (menuItems.size() == 1) { } 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(); callback();
} else { } else {
bool isSubmenu = (menuItems.begin() + 1)->get() == ContentRegistry::Interface::impl::SubMenuValue; 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); 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) { void defineMenu(const UnlocalizedString &menuName) {
if (ImGui::BeginMenu(Lang(menuName))) { if (menu::beginMenu(Lang(menuName))) {
populateMenu(menuName); populateMenu(menuName);
ImGui::EndMenu(); menu::endMenu();
} else { } else {
if (s_displayShortcutHighlights) { if (s_displayShortcutHighlights) {
if (const auto lastShortcutMenu = ShortcutManager::getLastActivatedMenu(); lastShortcutMenu.has_value()) { if (const auto lastShortcutMenu = ShortcutManager::getLastActivatedMenu(); lastShortcutMenu.has_value()) {
@ -301,57 +304,64 @@ namespace hex::plugin::builtin {
} }
void drawMenu() { void drawMenu() {
auto cursorPos = ImGui::GetCursorPosX();
u32 fittingItems = 0;
const auto &menuItems = ContentRegistry::Interface::impl::getMainMenuItems(); 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; if (menu::isNativeMenuBarUsed()) {
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;
for (const auto &[priority, menuItem] : menuItems) { 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; break;
defineMenu(menuItem.unlocalizedName); cursorPos += width;
fittingItems += 1;
count += 1;
} }
}
if (fittingItems == 0) { if (fittingItems <= 2)
if (ImGui::BeginMenu(ICON_VS_MENU)) { fittingItems = 0;
{
u32 count = 0;
for (const auto &[priority, menuItem] : menuItems) { for (const auto &[priority, menuItem] : menuItems) {
defineMenu(menuItem.unlocalizedName); if (count >= fittingItems)
} break;
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); 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(); }; ON_SCOPE_EXIT { ImGui::PopStyleVar(); };
#endif #endif
if (ImGui::BeginMainMenuBar()) { auto window = ImHexApi::System::getMainWindowHandle();
ImGui::Dummy({}); if (menu::beginMainMenuBar()) {
auto window = ImHexApi::System::getMainWindowHandle();
ImGui::PopStyleVar(2);
if (ImHexApi::System::isBorderlessWindowModeEnabled()) { if (ImHexApi::System::isBorderlessWindowModeEnabled()) {
#if defined(OS_WINDOWS) #if defined(OS_WINDOWS)
ImGui::SetCursorPosX(5_scaled); ImGui::SetCursorPosX(5_scaled);
@ -406,8 +411,14 @@ namespace hex::plugin::builtin {
ImGui::EndPopup(); ImGui::EndPopup();
} }
drawMenu(); drawMenu();
menu::endMainMenuBar();
}
if (ImGui::BeginMainMenuBar()) {
ImGui::Dummy({});
ImGui::PopStyleVar(2);
drawTitleBar(); drawTitleBar();
#if defined(OS_MACOS) #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) { ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.display_shortcut_highlights", [](const ContentRegistry::Settings::SettingsValue &value) {
s_displayShortcutHighlights = value.get<bool>(true); s_displayShortcutHighlights = value.get<bool>(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<bool>(true));
});
} }

View File

@ -11,6 +11,7 @@ add_imhex_plugin(
source/ui/hex_editor.cpp source/ui/hex_editor.cpp
source/ui/pattern_drawer.cpp source/ui/pattern_drawer.cpp
source/ui/visualizer_drawer.cpp source/ui/visualizer_drawer.cpp
source/ui/menu_items.cpp
INCLUDES INCLUDES
include include
LIBRARIES LIBRARIES
@ -18,3 +19,10 @@ add_imhex_plugin(
fonts fonts
LIBRARY_PLUGIN 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()

View File

@ -0,0 +1,25 @@
#pragma once
#include <hex/api/shortcut_manager.hpp>
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();
}

View File

@ -0,0 +1,181 @@
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
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];
}
}

View File

@ -0,0 +1,156 @@
#include <ui/menu_items.hpp>
#include <imgui.h>
#include <imgui_internal.h>
#include <hex/api/shortcut_manager.hpp>
#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();
}
}