From a1711ccfa657ff7c3a71eaa8166c78e47f44ca0f Mon Sep 17 00:00:00 2001 From: WerWolv Date: Sat, 20 Dec 2025 14:07:17 +0100 Subject: [PATCH] impr: Added icons to all menu items on macOS --- lib/libimhex/source/api/imhex_api.cpp | 8 ++ lib/libimhex/source/helpers/macos_menu.m | 133 ++++++++++++++++++++++- plugins/ui/source/ui/menu_items.cpp | 21 ++-- 3 files changed, 148 insertions(+), 14 deletions(-) diff --git a/lib/libimhex/source/api/imhex_api.cpp b/lib/libimhex/source/api/imhex_api.cpp index 357871094..2bb00550d 100644 --- a/lib/libimhex/source/api/imhex_api.cpp +++ b/lib/libimhex/source/api/imhex_api.cpp @@ -47,6 +47,10 @@ #if defined(OS_WEB) #include +#elif defined(OS_MACOS) + extern "C" { + void macosRegisterFont(const unsigned char *data, size_t size); + } #endif namespace hex { @@ -1205,6 +1209,10 @@ namespace hex { offset, fontSizeMultiplier ); + + #if defined(OS_MACOS) + macosRegisterFont(data.data(), data.size_bytes()); + #endif } void registerFont(const Font& font) { diff --git a/lib/libimhex/source/helpers/macos_menu.m b/lib/libimhex/source/helpers/macos_menu.m index a8175bd95..c099bb5ac 100644 --- a/lib/libimhex/source/helpers/macos_menu.m +++ b/lib/libimhex/source/helpers/macos_menu.m @@ -61,7 +61,120 @@ void macosEndMainMenuBar(void) { s_constructingMenu = false; } -bool macosBeginMenu(const char* label, bool enabled) { + +static NSMutableArray* g_RegisteredIconFontDescriptors = nil; +void macosRegisterFont(const unsigned char* fontBytes, size_t fontLength) { + if (!fontBytes || fontLength == 0 || fontLength > 100 * 1024 * 1024) { // Max 100MB sanity check + NSLog(@"Invalid font data: bytes=%p, length=%zu", fontBytes, fontLength); + return; + } + + // Initialize array on first use + if (!g_RegisteredIconFontDescriptors) { + g_RegisteredIconFontDescriptors = [[NSMutableArray alloc] init]; + } + + // Create NSData - this will copy the bytes + NSData *fontData = [NSData dataWithBytes:fontBytes length:fontLength]; + if (!fontData) { + NSLog(@"Failed to create NSData from font bytes"); + return; + } + + CFErrorRef error = NULL; + CFArrayRef descriptors = CTFontManagerCreateFontDescriptorsFromData((__bridge CFDataRef)fontData); + + if (descriptors && CFArrayGetCount(descriptors) > 0) { + // Register all descriptors from this font file + CTFontManagerRegisterFontDescriptors(descriptors, kCTFontManagerScopeProcess, true, NULL); + if (true) { + // Store each descriptor for later use + for (CFIndex i = 0; i < CFArrayGetCount(descriptors); i++) { + CTFontDescriptorRef descriptor = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descriptors, i); + [g_RegisteredIconFontDescriptors addObject:(__bridge id)descriptor]; + + // Log the font name for debugging + CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, 16.0, NULL); + if (font) { + CFRelease(font); + } + } + } else { + NSLog(@"Failed to register font descriptors"); + } + + CFRelease(descriptors); + } else { + NSLog(@"Failed to create font descriptors from data (length: %zu)", fontLength); + if (error) { + NSLog(@"Error: %@", (__bridge NSError *)error); + CFRelease(error); + } + } +} + +static NSImage* imageFromIconFont(NSString* character, + CGFloat size, + NSColor* color) { + + if (!character || [character length] == 0) { + return nil; + } + + if (!g_RegisteredIconFontDescriptors || [g_RegisteredIconFontDescriptors count] == 0) { + NSLog(@"No icon fonts registered"); + return nil; + } + + NSFont *font = nil; + unichar unicode = [character characterAtIndex:0]; + + for (id descriptorObj in g_RegisteredIconFontDescriptors) { + CTFontDescriptorRef descriptor = (__bridge CTFontDescriptorRef)descriptorObj; + CTFontRef ctFont = CTFontCreateWithFontDescriptor(descriptor, size, NULL); + + if (ctFont) { + CGGlyph glyph; + UniChar unichar = unicode; + if (CTFontGetGlyphsForCharacters(ctFont, &unichar, &glyph, 1)) { + font = (__bridge NSFont *)ctFont; + break; + } + CFRelease(ctFont); + } + } + + if (!font) { + NSLog(@"No font found with glyph for character U+%04X (string: '%@', length: %lu)", + unicode, character, (unsigned long)[character length]); + return nil; + } + + NSDictionary *attributes = @{ + NSFontAttributeName: font, + NSForegroundColorAttributeName: color + }; + NSAttributedString *attrString = [[NSAttributedString alloc] + initWithString:character + attributes:attributes]; + + NSSize stringSize = [attrString size]; + + NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize(size, size)]; + + [image lockFocus]; + + NSPoint drawPoint = NSMakePoint((size - stringSize.width) / 2.0, + (size - stringSize.height) / 2.0); + [attrString drawAtPoint:drawPoint]; + + [image unlockFocus]; + [image setTemplate:YES]; + + return image; +} + +bool macosBeginMenu(const char* label, const char *icon, bool enabled) { NSString* title = [NSString stringWithUTF8String:label]; // Search for menu item with the given name @@ -79,6 +192,12 @@ bool macosBeginMenu(const char* label, bool enabled) { menuItem.title = title; [menuItem setSubmenu:newMenu]; + if (icon != NULL) { + NSString *iconString = [NSString stringWithUTF8String:icon]; + NSImage* iconImage = imageFromIconFont(iconString, 16.0, [NSColor blackColor]); + [menuItem setImage:iconImage]; + } + // Add the new menu to the end of the list menuIndex = [s_menuStack[s_menuStackSize - 1] numberOfItems]; @@ -104,7 +223,7 @@ void macosEndMenu(void) { s_menuStackSize -= 1; } -bool macosMenuItem(const char* label, struct KeyEquivalent keyEquivalent, bool selected, bool enabled) { +bool macosMenuItem(const char* label, const char *icon, struct KeyEquivalent keyEquivalent, bool selected, bool enabled) { NSString* title = [NSString stringWithUTF8String:label]; if (s_constructingMenu) { @@ -113,6 +232,12 @@ bool macosMenuItem(const char* label, struct KeyEquivalent keyEquivalent, bool s menuItem.action = @selector(OnClick:); menuItem.target = s_menuItemHandler; + if (icon != NULL) { + NSString *iconString = [NSString stringWithUTF8String:icon]; + NSImage* iconImage = imageFromIconFont(iconString, 16.0, [NSColor blackColor]); + [menuItem setImage:iconImage]; + } + [menuItem setTag:s_currTag]; s_currTag += 1; @@ -164,8 +289,8 @@ bool macosMenuItem(const char* label, struct KeyEquivalent keyEquivalent, bool s return false; } -bool macosMenuItemSelect(const char* label, struct KeyEquivalent keyEquivalent, bool* selected, bool enabled) { - if (macosMenuItem(label, keyEquivalent, selected != NULL ? *selected : false, enabled)) { +bool macosMenuItemSelect(const char* label, const char *icon, struct KeyEquivalent keyEquivalent, bool* selected, bool enabled) { + if (macosMenuItem(label, icon, keyEquivalent, selected != NULL ? *selected : false, enabled)) { if (selected != NULL) *selected = !(*selected); diff --git a/plugins/ui/source/ui/menu_items.cpp b/plugins/ui/source/ui/menu_items.cpp index f4fe92a27..0148c46c7 100644 --- a/plugins/ui/source/ui/menu_items.cpp +++ b/plugins/ui/source/ui/menu_items.cpp @@ -1,4 +1,4 @@ -#include <../../../../lib/libimhex/include/hex/helpers/menu_items.hpp> +#include #include #include @@ -12,13 +12,14 @@ void macosEndMainMenuBar(void); void macosClearMenu(void); - bool macosBeginMenu(const char* label, bool enabled); + bool macosBeginMenu(const char* label, const char *icon, 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); + bool macosMenuItem(const char* label, const char *icon, KeyEquivalent keyEquivalent, bool selected, bool enabled); + bool macosMenuItemSelect(const char* label, const char *icon, KeyEquivalent keyEquivalent, bool* p_selected, bool enabled); void macosSeparator(void); + } #endif @@ -71,7 +72,7 @@ namespace hex::menu { bool beginMenu(const char *label, bool enabled) { #if defined(OS_MACOS) if (s_useNativeMenuBar) - return macosBeginMenu(label, enabled); + return macosBeginMenu(label, nullptr, enabled); #endif return ImGui::BeginMenu(label, enabled); @@ -80,7 +81,7 @@ namespace hex::menu { bool beginMenuEx(const char *label, const char *icon, bool enabled) { #if defined(OS_MACOS) if (s_useNativeMenuBar) - return macosBeginMenu(label, enabled); + return macosBeginMenu(label, icon, enabled); #endif return ImGui::BeginMenuEx(label, icon, enabled); @@ -101,7 +102,7 @@ namespace hex::menu { 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); + return macosMenuItem(label, nullptr, shortcut.toKeyEquivalent(), selected, enabled); #endif return ImGui::MenuItem(label, shortcut.toString().c_str(), selected, enabled); @@ -110,7 +111,7 @@ namespace hex::menu { 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); + return macosMenuItemSelect(label, nullptr, shortcut.toKeyEquivalent(), selected, enabled); #endif return ImGui::MenuItem(label, shortcut.toString().c_str(), selected, enabled); @@ -119,7 +120,7 @@ namespace hex::menu { 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); + return macosMenuItem(label, icon, shortcut.toKeyEquivalent(), selected, enabled); #endif return ImGui::MenuItemEx(label, icon, shortcut.toString().c_str(), selected, enabled); @@ -128,7 +129,7 @@ namespace hex::menu { 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); + return macosMenuItemSelect(label, icon, shortcut.toKeyEquivalent(), selected, enabled); #endif if (ImGui::MenuItemEx(label, icon, shortcut.toString().c_str(), selected != nullptr ? *selected : false, enabled)) {