diff --git a/include/eepp/ui/uimenu.hpp b/include/eepp/ui/uimenu.hpp index 923fe7c49..efd6da627 100644 --- a/include/eepp/ui/uimenu.hpp +++ b/include/eepp/ui/uimenu.hpp @@ -11,6 +11,8 @@ namespace EE { namespace UI { +enum class MenuBarRole : Uint8 { Normal, Window, Help }; + class EE_API UIMenu : public UIWidget { public: static UIMenu* New(); @@ -26,8 +28,6 @@ class EE_API UIMenu : public UIWidget { UIMenuItem* add( const String& text, DrawablePtr icon = {}, const String& shortcutText = "" ); - UIWidget* add( UIWidget* widget ); - UIMenuSeparator* addSeparator(); UIMenuCheckBox* addCheckBox( const String& text, const bool& active = false, @@ -35,8 +35,7 @@ class EE_API UIMenu : public UIWidget { UIMenuRadioButton* addRadioButton( const String& text, const bool& active = false ); - UIMenuSubMenu* addSubMenu( const String& text, DrawablePtr icon = {}, - UIMenu* subMenu = NULL ); + UIMenuSubMenu* addSubMenu( const String& text, DrawablePtr icon = {}, UIMenu* subMenu = NULL ); UIWidget* getItem( const Uint32& index ); @@ -88,6 +87,14 @@ class EE_API UIMenu : public UIWidget { const Clock& getInactiveTime() const; + void notifyMenuWillShow(); + + void notifyMenuDidHide(); + + MenuBarRole getMenuBarRole() const; + + UIMenu* setMenuBarRole( MenuBarRole role ); + protected: friend class UIMenuItem; friend class UIMenuCheckBox; @@ -103,6 +110,7 @@ class EE_API UIMenu : public UIWidget { UIWidget* mItemSelected; Uint32 mItemSelectedIndex; bool mResizing; + MenuBarRole mMenuBarRole{ MenuBarRole::Normal }; UIWidget* mOwnerNode; Sizei mIconMinSize; UIMenu* mCurrentSubMenu{ nullptr }; @@ -122,6 +130,8 @@ class EE_API UIMenu : public UIWidget { void resizeMe(); + UIWidget* add( UIWidget* widget ); + UIMenuItem* createMenuItem( const String& text, DrawablePtr icon, const String& shortcutText = "" ); diff --git a/include/eepp/ui/uimenubar.hpp b/include/eepp/ui/uimenubar.hpp index ddcdb31f3..42c16b9aa 100644 --- a/include/eepp/ui/uimenubar.hpp +++ b/include/eepp/ui/uimenubar.hpp @@ -5,9 +5,12 @@ #include #include #include +#include namespace EE { namespace UI { +class PlatformMenuBar; + class EE_API UIMenuBar : public UIWidget { public: static UIMenuBar* New(); @@ -56,6 +59,12 @@ class EE_API UIMenuBar : public UIWidget { void showPrevMenu(); + bool isGlobalMenuBarSupported() const; + + UIMenuBar* setGlobalMenuBarEnabled( bool enabled ); + + bool isGlobalMenuBarEnabled() const; + protected: UIMenuBar(); @@ -65,6 +74,7 @@ class EE_API UIMenuBar : public UIWidget { UIPopUpMenu* mCurrentMenu; MenuBarList mButtons; UIPopUpMenu* mWaitingUp; + std::unique_ptr mPlatformMenuBar; Uint32 getMenuIndex( UIPopUpMenu* menu ); @@ -83,6 +93,8 @@ class EE_API UIMenuBar : public UIWidget { void destroyMenus(); void autoHeight(); + + void syncGlobalMenuBar(); }; }} // namespace EE::UI diff --git a/include/eepp/ui/uimenucheckbox.hpp b/include/eepp/ui/uimenucheckbox.hpp index 4edf3de94..b70f41c30 100644 --- a/include/eepp/ui/uimenucheckbox.hpp +++ b/include/eepp/ui/uimenucheckbox.hpp @@ -23,6 +23,8 @@ class EE_API UIMenuCheckBox : public UIMenuItem { void switchActive(); + virtual void activate(); + virtual bool applyProperty( const StyleSheetProperty& attribute ); virtual std::string getPropertyString( const PropertyDefinition* propertyDef, @@ -37,8 +39,6 @@ class EE_API UIMenuCheckBox : public UIMenuItem { UIMenuCheckBox(); - Uint32 onMessage( const NodeMessage* msg ); - virtual void onStateChange(); }; diff --git a/include/eepp/ui/uimenuitem.hpp b/include/eepp/ui/uimenuitem.hpp index f2766b36d..ef21f6015 100644 --- a/include/eepp/ui/uimenuitem.hpp +++ b/include/eepp/ui/uimenuitem.hpp @@ -1,10 +1,13 @@ #ifndef EE_UICUIMENUITEM_HPP #define EE_UICUIMENUITEM_HPP +#include #include namespace EE { namespace UI { +enum class MenuRole : Uint8 { NoRole, About, Preferences, Quit }; + class EE_API UIMenuItem : public UIPushButton { public: typedef std::function OnShouldCloseCb; @@ -19,8 +22,12 @@ class EE_API UIMenuItem : public UIPushButton { virtual void setTheme( UITheme* Theme ); + virtual void activate(); + virtual UIMenuItem* setShortcutText( const String& text ); + const KeyBindings::Shortcut& getShortcut() const; + UITextView* getShortcutView() const; virtual UIWidget* getExtraInnerWidget() const; @@ -29,9 +36,15 @@ class EE_API UIMenuItem : public UIPushButton { UIMenuItem* setOnShouldCloseCb( const OnShouldCloseCb& onShouldCloseCb ); + MenuRole getMenuRole() const; + + UIMenuItem* setMenuRole( MenuRole role ); + protected: UITextView* mShortcutView; OnShouldCloseCb mOnShouldCloseCb; + mutable KeyBindings::Shortcut mShortcut; + MenuRole mMenuRole{ MenuRole::NoRole }; UIMenuItem(); @@ -47,8 +60,6 @@ class EE_API UIMenuItem : public UIPushButton { virtual Uint32 onMouseLeave( const Vector2i& pos, const Uint32& flags ); - virtual Uint32 onMouseClick( const Vector2i& pos, const Uint32& flags ); - void createShortcutView(); void refreshShortcut(); diff --git a/include/eepp/ui/uimenuradiobutton.hpp b/include/eepp/ui/uimenuradiobutton.hpp index a5f07055b..38f8bf649 100644 --- a/include/eepp/ui/uimenuradiobutton.hpp +++ b/include/eepp/ui/uimenuradiobutton.hpp @@ -23,6 +23,8 @@ class EE_API UIMenuRadioButton : public UIMenuItem { void switchActive(); + virtual void activate(); + virtual bool applyProperty( const StyleSheetProperty& attribute ); virtual std::string getPropertyString( const PropertyDefinition* propertyDef, @@ -37,8 +39,6 @@ class EE_API UIMenuRadioButton : public UIMenuItem { UIMenuRadioButton(); - virtual Uint32 onMouseUp( const Vector2i& position, const Uint32& flags ); - virtual void onStateChange(); }; diff --git a/include/eepp/ui/uimenusubmenu.hpp b/include/eepp/ui/uimenusubmenu.hpp index 3955e93f5..1123ad733 100644 --- a/include/eepp/ui/uimenusubmenu.hpp +++ b/include/eepp/ui/uimenusubmenu.hpp @@ -27,6 +27,8 @@ class EE_API UIMenuSubMenu : public UIMenuItem { void showSubMenu(); + void notifySubMenuWillShow(); + const Time& getMouseOverTimeShowMenu() const; void setMouseOverTimeShowMenu( const Time& maxTime ); diff --git a/premake4.lua b/premake4.lua index d2606701c..0101e8c2f 100644 --- a/premake4.lua +++ b/premake4.lua @@ -691,7 +691,7 @@ function generate_os_links() elseif os.is_real("mingw64") then multiple_insert( os_links, { "opengl32", "glu32", "gdi32", "ws2_32", "winmm", "ole32", "uuid", "dwrite" } ) elseif os.is_real("macosx") then - multiple_insert( os_links, { "OpenGL.framework", "CoreFoundation.framework", "CoreText.framework" } ) + multiple_insert( os_links, { "eepp-macos-helper-static", "Cocoa.framework", "OpenGL.framework", "CoreFoundation.framework", "CoreText.framework" } ) elseif os.is_real("freebsd") then multiple_insert( os_links, { "rt", "pthread", "GL" } ) elseif os.is_real("haiku") then @@ -1610,6 +1610,20 @@ solution "eepp" end build_base_cpp_configuration( "languages-syntax-highlighting" ) + if os.is_real("macosx") then + project "eepp-macos-helper-static" + kind "StaticLib" + language "C++" + set_targetdir("libs/" .. os.get_real() .. "/") + includedirs { "include", "src" } + files { "src/eepp/ui/platform/macos/macosmenubar.mm" } + buildoptions { "-x objective-c++" } + if not is_vs() then + buildoptions{ "-std=c++20" } + end + build_base_cpp_configuration( "eepp-macos-helper" ) + end + -- Library if not _OPTIONS["disable-static-build"] then project "eepp-static" diff --git a/premake5.lua b/premake5.lua index ab79b203f..c8c7dae81 100644 --- a/premake5.lua +++ b/premake5.lua @@ -656,7 +656,7 @@ function generate_os_links() elseif os.istarget("mingw32") then multiple_insert( os_links, { "opengl32", "glu32", "gdi32", "ws2_32", "winmm", "ole32", "uuid", "dwrite" } ) elseif os.istarget("macosx") then - multiple_insert( os_links, { "OpenGL.framework", "CoreFoundation.framework", "CoreText.framework" } ) + multiple_insert( os_links, { "eepp-macos-helper-static", "Cocoa.framework", "OpenGL.framework", "CoreFoundation.framework", "CoreText.framework" } ) elseif os.istarget("bsd") then multiple_insert( os_links, { "rt", "pthread", "GL" } ) elseif os.istarget("haiku") then @@ -1652,6 +1652,22 @@ workspace "eepp" filter { "action:export-compile-commands", "system:macosx" } buildoptions { "-std=c++20" } + if os.istarget("macosx") then + project "eepp-macos-helper-static" + kind "StaticLib" + language "C++" + cppdialect "C++20" + incdirs { "include", "src" } + files { "src/eepp/ui/platform/macos/macosmenubar.mm" } + buildoptions { "-x objective-c++" } + build_base_cpp_configuration( "eepp-macos-helper" ) + target_dir_lib( "" ) + filter "action:not vs*" + buildoptions { "-Wall" } + filter { "action:export-compile-commands", "system:macosx" } + buildoptions { "-std=c++20" } + end + -- Library if not _OPTIONS["disable-static-build"] then project "eepp-static" diff --git a/projects/macos/ee.files b/projects/macos/ee.files index 94e09839b..fc65f96d2 100644 --- a/projects/macos/ee.files +++ b/projects/macos/ee.files @@ -1181,6 +1181,9 @@ ../../src/eepp/ui/uiloader.cpp ../../src/eepp/ui/uimanager.cpp ../../src/eepp/ui/uimenubar.cpp +../../src/eepp/ui/platformmenubar.hpp +../../src/eepp/ui/platformmenubar.cpp +../../src/eepp/ui/platform/macos/macosmenubar.mm ../../src/eepp/ui/uimenucheckbox.cpp ../../src/eepp/ui/uimenu.cpp ../../src/eepp/ui/uimenuitem.cpp diff --git a/src/eepp/ui/platform/macos/macosmenubar.mm b/src/eepp/ui/platform/macos/macosmenubar.mm new file mode 100644 index 000000000..35bc7430c --- /dev/null +++ b/src/eepp/ui/platform/macos/macosmenubar.mm @@ -0,0 +1,539 @@ +#define Rect AppleRect +#import +#undef Rect +#undef BSD + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace EE; +using namespace EE::UI; +using namespace EE::Window; + +#if __has_feature( objc_arc ) +#define EE_OBJC_RELEASE( object ) +#else +#define EE_OBJC_RELEASE( object ) [object release] +#endif + +namespace { + +NSString* toNSString( const String& string ) { + const std::string utf8( string.toUtf8() ); + return [NSString stringWithUTF8String:utf8.c_str()]; +} + +NSString* applicationName() { + NSBundle* bundle = [NSBundle mainBundle]; + NSString* name = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"]; + if ( nil == name || 0 == name.length ) + name = [bundle objectForInfoDictionaryKey:@"CFBundleName"]; + if ( nil == name || 0 == name.length ) + name = [[NSProcessInfo processInfo] processName]; + return nil != name && name.length > 0 ? name : @"Application"; +} + +NSString* keyEquivalent( Keycode key ) { + if ( key >= KEY_F1 && key <= KEY_F12 ) { + unichar character = NSF1FunctionKey + ( key - KEY_F1 ); + return [NSString stringWithCharacters:&character length:1]; + } + if ( key >= KEY_F13 && key <= KEY_F24 ) { + unichar character = NSF13FunctionKey + ( key - KEY_F13 ); + return [NSString stringWithCharacters:&character length:1]; + } + + unichar character = 0; + switch ( key ) { + case KEY_RETURN: + case KEY_KP_ENTER: + character = NSCarriageReturnCharacter; + break; + case KEY_ESCAPE: + character = 0x1B; + break; + case KEY_TAB: + character = NSTabCharacter; + break; + case KEY_BACKSPACE: + character = NSBackspaceCharacter; + break; + case KEY_DELETE: + character = NSDeleteFunctionKey; + break; + case KEY_INSERT: + character = NSInsertFunctionKey; + break; + case KEY_HOME: + character = NSHomeFunctionKey; + break; + case KEY_END: + character = NSEndFunctionKey; + break; + case KEY_PAGEUP: + character = NSPageUpFunctionKey; + break; + case KEY_PAGEDOWN: + character = NSPageDownFunctionKey; + break; + case KEY_LEFT: + character = NSLeftArrowFunctionKey; + break; + case KEY_RIGHT: + character = NSRightArrowFunctionKey; + break; + case KEY_UP: + character = NSUpArrowFunctionKey; + break; + case KEY_DOWN: + character = NSDownArrowFunctionKey; + break; + default: + if ( key > 0 && key < 0x80 ) + character = static_cast( key ); + break; + } + + if ( 0 == character ) + return @""; + character = [[[NSString stringWithCharacters:&character + length:1] lowercaseString] characterAtIndex:0]; + return [NSString stringWithCharacters:&character length:1]; +} + +NSEventModifierFlags modifierMask( Uint32 modifiers ) { + NSEventModifierFlags mask = 0; + if ( modifiers & KEYMOD_META ) + mask |= NSEventModifierFlagCommand; + if ( modifiers & KEYMOD_ALT ) + mask |= NSEventModifierFlagOption; + if ( modifiers & KEYMOD_CTRL ) + mask |= NSEventModifierFlagControl; + if ( modifiers & KEYMOD_SHIFT ) + mask |= NSEventModifierFlagShift; + return mask; +} + +struct NativeMenuSource { + UIMenu* menu; + UIMenuSubMenu* owner; +}; + +} // namespace + +@interface EEPPMenuBarBridge : NSObject { + @private + UIMenuBar* _menuBar; + NSMenu* _installedMainMenu; + NSMenu* _previousMainMenu; + NSMenu* _servicesMenu; + NSMenu* _windowsMenu; + NSMenu* _helpMenu; + BOOL _capturedPreviousMainMenu; + std::unordered_map _menuMap; + std::unordered_map _itemMap; +} + +- (instancetype)initWithMenuBar:(UIMenuBar*)menuBar; +- (void)uninstall; +- (void)syncTopLevel; +- (void)menuItemActivated:(NSMenuItem*)sender; + +@end + +@implementation EEPPMenuBarBridge + +- (instancetype)initWithMenuBar:(UIMenuBar*)menuBar { + self = [super init]; + if ( nil != self ) + _menuBar = menuBar; + return self; +} + +- (void)dealloc { + [self uninstall]; +#if !__has_feature( objc_arc ) + [super dealloc]; +#endif +} + +- (UIMenuItem*)findItemWithRole:(MenuRole)role inMenu:(UIMenu*)menu { + if ( nullptr == menu ) + return nullptr; + for ( Uint32 i = 0; i < menu->getCount(); ++i ) { + UIWidget* widget = menu->getItem( i ); + if ( widget->isType( UI_TYPE_MENUITEM ) ) { + UIMenuItem* item = widget->asType(); + if ( item->getMenuRole() == role ) + return item; + } + if ( widget->isType( UI_TYPE_MENUSUBMENU ) ) { + UIMenuItem* found = + [self findItemWithRole:role inMenu:widget->asType()->getSubMenu()]; + if ( nullptr != found ) + return found; + } + } + return nullptr; +} + +- (UIMenuItem*)findItemWithRole:(MenuRole)role { + if ( nullptr == _menuBar ) + return nullptr; + for ( Uint32 i = 0; i < _menuBar->getButtonsCount(); ++i ) { + UIMenuItem* item = [self findItemWithRole:role inMenu:_menuBar->getPopUpMenu( i )]; + if ( nullptr != item ) + return item; + } + return nullptr; +} + +- (NSMenuItem*)mirroredItemWithTitle:(NSString*)title source:(UIMenuItem*)source { + const KeyBindings::Shortcut& shortcut = source->getShortcut(); + NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:title + action:@selector( menuItemActivated: ) + keyEquivalent:keyEquivalent( shortcut.key )]; + [item setTarget:self]; + [item setEnabled:source->isEnabled()]; + [item setKeyEquivalentModifierMask:modifierMask( shortcut.mod )]; + _itemMap[item] = source; + return item; +} + +- (void)addRoleItem:(UIMenuItem*)source + title:(NSString*)title + toMenu:(NSMenu*)menu + defaultAction:(SEL)defaultAction { + NSMenuItem* item; + if ( nullptr != source ) { + item = [self mirroredItemWithTitle:title source:source]; + if ( source->getMenuRole() == MenuRole::Quit ) { + [item setKeyEquivalent:@"q"]; + [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand]; + } + } else { + item = [[NSMenuItem alloc] initWithTitle:title action:defaultAction keyEquivalent:@""]; + [item setTarget:NSApp]; + } + [menu addItem:item]; + EE_OBJC_RELEASE( item ); +} + +- (void)buildApplicationMenuInMainMenu:(NSMenu*)mainMenu { + NSString* appName = applicationName(); + NSMenu* appMenu = [[NSMenu alloc] initWithTitle:appName]; + [appMenu setAutoenablesItems:NO]; + + NSString* title = [@"About " stringByAppendingString:appName]; + [self addRoleItem:[self findItemWithRole:MenuRole::About] + title:title + toMenu:appMenu + defaultAction:@selector( orderFrontStandardAboutPanel: )]; + + UIMenuItem* preferences = [self findItemWithRole:MenuRole::Preferences]; + if ( nullptr != preferences ) { + [appMenu addItem:[NSMenuItem separatorItem]]; + [self addRoleItem:preferences title:@"Settings…" toMenu:appMenu defaultAction:nil]; + } + + [appMenu addItem:[NSMenuItem separatorItem]]; + _servicesMenu = [[NSMenu alloc] initWithTitle:@"Services"]; + NSMenuItem* servicesItem = [[NSMenuItem alloc] initWithTitle:@"Services" + action:nil + keyEquivalent:@""]; + [servicesItem setSubmenu:_servicesMenu]; + [appMenu addItem:servicesItem]; + [NSApp setServicesMenu:_servicesMenu]; + EE_OBJC_RELEASE( servicesItem ); + EE_OBJC_RELEASE( _servicesMenu ); + + [appMenu addItem:[NSMenuItem separatorItem]]; + title = [@"Hide " stringByAppendingString:appName]; + NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:title + action:@selector( hide: ) + keyEquivalent:@"h"]; + [item setTarget:NSApp]; + [appMenu addItem:item]; + EE_OBJC_RELEASE( item ); + + item = [[NSMenuItem alloc] initWithTitle:@"Hide Others" + action:@selector( hideOtherApplications: ) + keyEquivalent:@"h"]; + [item setTarget:NSApp]; + [item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption]; + [appMenu addItem:item]; + EE_OBJC_RELEASE( item ); + + item = [[NSMenuItem alloc] initWithTitle:@"Show All" + action:@selector( unhideAllApplications: ) + keyEquivalent:@""]; + [item setTarget:NSApp]; + [appMenu addItem:item]; + EE_OBJC_RELEASE( item ); + + [appMenu addItem:[NSMenuItem separatorItem]]; + title = [@"Quit " stringByAppendingString:appName]; + UIMenuItem* quit = [self findItemWithRole:MenuRole::Quit]; + if ( nullptr != quit ) { + [self addRoleItem:quit title:title toMenu:appMenu defaultAction:nil]; + } else { + item = [[NSMenuItem alloc] initWithTitle:title + action:@selector( terminate: ) + keyEquivalent:@"q"]; + [item setTarget:NSApp]; + [appMenu addItem:item]; + EE_OBJC_RELEASE( item ); + } + + NSMenuItem* appMenuItem = [[NSMenuItem alloc] initWithTitle:appName + action:nil + keyEquivalent:@""]; + [appMenuItem setSubmenu:appMenu]; + [mainMenu addItem:appMenuItem]; + EE_OBJC_RELEASE( appMenuItem ); + EE_OBJC_RELEASE( appMenu ); +} + +- (void)removeMappingsForMenu:(NSMenu*)menu removeMenu:(BOOL)removeMenu { + for ( NSMenuItem* item in [menu itemArray] ) { + _itemMap.erase( item ); + if ( nil != item.submenu ) + [self removeMappingsForMenu:item.submenu removeMenu:YES]; + } + if ( removeMenu ) + _menuMap.erase( menu ); +} + +- (NSMenuItem*)createNativeItem:(UIWidget*)widget { + if ( widget->isType( UI_TYPE_MENU_SEPARATOR ) ) + return [NSMenuItem separatorItem]; + if ( !widget->isType( UI_TYPE_MENUITEM ) ) { + Log::warning( "Global menu bar: unsupported widget type %u skipped", widget->getType() ); + return nil; + } + + UIMenuItem* source = widget->asType(); + if ( source->getMenuRole() != MenuRole::NoRole ) + return nil; + + NSMenuItem* item; + if ( widget->isType( UI_TYPE_MENUSUBMENU ) ) { + item = [[NSMenuItem alloc] initWithTitle:toNSString( source->getText() ) + action:nil + keyEquivalent:@""]; + [item setEnabled:source->isEnabled()]; + UIMenu* eeSubMenu = widget->asType()->getSubMenu(); + if ( nullptr != eeSubMenu ) { + NSMenu* subMenu = [[NSMenu alloc] initWithTitle:toNSString( source->getText() )]; + [subMenu setAutoenablesItems:NO]; + [subMenu setDelegate:self]; + [item setSubmenu:subMenu]; + _menuMap[subMenu] = { eeSubMenu, widget->asType() }; + EE_OBJC_RELEASE( subMenu ); + } + } else { + item = [self mirroredItemWithTitle:toNSString( source->getText() ) source:source]; + if ( widget->isType( UI_TYPE_MENUCHECKBOX ) ) { + [item setState:widget->asType()->isActive() ? NSControlStateValueOn + : NSControlStateValueOff]; + } else if ( widget->isType( UI_TYPE_MENURADIOBUTTON ) ) { + [item setState:widget->asType()->isActive() + ? NSControlStateValueOn + : NSControlStateValueOff]; + } + } + return item; +} + +- (void)rebuildMenu:(NSMenu*)nativeMenu fromMenu:(UIMenu*)eeMenu { + [self removeMappingsForMenu:nativeMenu removeMenu:NO]; + [nativeMenu removeAllItems]; + bool lastWasSeparator = true; + for ( Uint32 i = 0; i < eeMenu->getCount(); ++i ) { + UIWidget* widget = eeMenu->getItem( i ); + if ( !widget->isVisible() ) + continue; + const bool isSeparator = widget->isType( UI_TYPE_MENU_SEPARATOR ); + if ( isSeparator && lastWasSeparator ) + continue; + NSMenuItem* item = [self createNativeItem:widget]; + if ( nil == item ) + continue; + [nativeMenu addItem:item]; + lastWasSeparator = isSeparator; + if ( !isSeparator ) + EE_OBJC_RELEASE( item ); + } + if ( lastWasSeparator && [nativeMenu numberOfItems] > 0 ) + [nativeMenu removeItemAtIndex:[nativeMenu numberOfItems] - 1]; +} + +- (void)syncTopLevel { + eeASSERT( [NSThread isMainThread] ); + if ( nullptr == _menuBar ) + return; + + if ( nil == NSApp ) + [NSApplication sharedApplication]; + if ( !_capturedPreviousMainMenu ) { + _previousMainMenu = [NSApp mainMenu]; +#if !__has_feature( objc_arc ) + [_previousMainMenu retain]; +#endif + _capturedPreviousMainMenu = YES; + } + + if ( nil != _installedMainMenu ) { + [self removeMappingsForMenu:_installedMainMenu removeMenu:YES]; + _itemMap.clear(); + _menuMap.clear(); + EE_OBJC_RELEASE( _installedMainMenu ); + } + _installedMainMenu = [[NSMenu alloc] initWithTitle:@""]; + [_installedMainMenu setAutoenablesItems:NO]; + [self buildApplicationMenuInMainMenu:_installedMainMenu]; + + _windowsMenu = nil; + _helpMenu = nil; + for ( Uint32 i = 0; i < _menuBar->getButtonsCount(); ++i ) { + UIPopUpMenu* eeMenu = _menuBar->getPopUpMenu( i ); + if ( nullptr == eeMenu ) + continue; + NSString* title = toNSString( _menuBar->getButton( i )->getText() ); + NSMenu* nativeMenu = [[NSMenu alloc] initWithTitle:title]; + [nativeMenu setAutoenablesItems:NO]; + [nativeMenu setDelegate:self]; + _menuMap[nativeMenu] = { eeMenu, nullptr }; + + NSMenuItem* topLevelItem = [[NSMenuItem alloc] initWithTitle:title + action:nil + keyEquivalent:@""]; + [topLevelItem setSubmenu:nativeMenu]; + [_installedMainMenu addItem:topLevelItem]; + + if ( eeMenu->getMenuBarRole() == MenuBarRole::Window ) + _windowsMenu = nativeMenu; + else if ( eeMenu->getMenuBarRole() == MenuBarRole::Help ) + _helpMenu = nativeMenu; + + EE_OBJC_RELEASE( topLevelItem ); + EE_OBJC_RELEASE( nativeMenu ); + } + + [NSApp setMainMenu:_installedMainMenu]; + [NSApp setWindowsMenu:_windowsMenu]; + [NSApp setHelpMenu:_helpMenu]; +} + +- (void)uninstall { + eeASSERT( [NSThread isMainThread] ); + if ( nullptr == _menuBar && nil == _installedMainMenu ) + return; + + _menuBar = nullptr; + if ( [NSApp servicesMenu] == _servicesMenu ) + [NSApp setServicesMenu:nil]; + if ( [NSApp windowsMenu] == _windowsMenu ) + [NSApp setWindowsMenu:nil]; + if ( [NSApp helpMenu] == _helpMenu ) + [NSApp setHelpMenu:nil]; + if ( [NSApp mainMenu] == _installedMainMenu ) + [NSApp setMainMenu:_previousMainMenu]; + + if ( nil != _installedMainMenu ) + [self removeMappingsForMenu:_installedMainMenu removeMenu:YES]; + _itemMap.clear(); + _menuMap.clear(); + EE_OBJC_RELEASE( _installedMainMenu ); + EE_OBJC_RELEASE( _previousMainMenu ); + _installedMainMenu = nil; + _previousMainMenu = nil; + _servicesMenu = nil; + _windowsMenu = nil; + _helpMenu = nil; + _capturedPreviousMainMenu = NO; +} + +- (void)menuNeedsUpdate:(NSMenu*)menu { + eeASSERT( [NSThread isMainThread] ); + auto found = _menuMap.find( menu ); + if ( found == _menuMap.end() || nullptr == found->second.menu ) + return; + NativeMenuSource source = found->second; + if ( nullptr != source.owner ) { + source.owner->notifySubMenuWillShow(); + found = _menuMap.find( menu ); + if ( found == _menuMap.end() || found->second.owner != source.owner ) + return; + source.menu = source.owner->getSubMenu(); + if ( nullptr == source.menu ) + return; + found->second.menu = source.menu; + } + UIMenu* eeMenu = source.menu; + eeMenu->notifyMenuWillShow(); + found = _menuMap.find( menu ); + if ( found != _menuMap.end() && found->second.menu == eeMenu && + found->second.owner == source.owner ) + [self rebuildMenu:menu fromMenu:eeMenu]; +} + +- (void)menuDidClose:(NSMenu*)menu { + auto found = _menuMap.find( menu ); + if ( found != _menuMap.end() && nullptr != found->second.menu ) + found->second.menu->notifyMenuDidHide(); +} + +- (void)menuItemActivated:(NSMenuItem*)sender { + eeASSERT( [NSThread isMainThread] ); + auto found = _itemMap.find( sender ); + if ( found != _itemMap.end() && nullptr != found->second ) + found->second->activate(); +} + +@end + +namespace EE { namespace UI { + +class MacOSMenuBar final : public PlatformMenuBar { + public: + ~MacOSMenuBar() { uninstall(); } + + void install( UIMenuBar* menuBar ) { + eeASSERT( [NSThread isMainThread] ); + uninstall(); + mBridge = [[EEPPMenuBarBridge alloc] initWithMenuBar:menuBar]; + [mBridge syncTopLevel]; + } + + void uninstall() { + if ( nil == mBridge ) + return; + [mBridge uninstall]; + EE_OBJC_RELEASE( mBridge ); + mBridge = nil; + } + + void syncTopLevel() { + if ( nil != mBridge ) + [mBridge syncTopLevel]; + } + + private: + EEPPMenuBarBridge* mBridge{ nil }; +}; + +std::unique_ptr createMacOSPlatformMenuBar() { + return std::make_unique(); +} + +}} // namespace EE::UI diff --git a/src/eepp/ui/platformmenubar.cpp b/src/eepp/ui/platformmenubar.cpp new file mode 100644 index 000000000..254bd15c1 --- /dev/null +++ b/src/eepp/ui/platformmenubar.cpp @@ -0,0 +1,25 @@ +#include + +namespace EE { namespace UI { + +#if EE_PLATFORM == EE_PLATFORM_MACOS +std::unique_ptr createMacOSPlatformMenuBar(); +#endif + +bool PlatformMenuBar::isSupported() { +#if EE_PLATFORM == EE_PLATFORM_MACOS + return true; +#else + return false; +#endif +} + +std::unique_ptr PlatformMenuBar::create() { +#if EE_PLATFORM == EE_PLATFORM_MACOS + return createMacOSPlatformMenuBar(); +#else + return nullptr; +#endif +} + +}} // namespace EE::UI diff --git a/src/eepp/ui/platformmenubar.hpp b/src/eepp/ui/platformmenubar.hpp new file mode 100644 index 000000000..fc98684c3 --- /dev/null +++ b/src/eepp/ui/platformmenubar.hpp @@ -0,0 +1,28 @@ +#ifndef EE_UI_PLATFORMMENUBAR_HPP +#define EE_UI_PLATFORMMENUBAR_HPP + +#include +#include + +namespace EE { namespace UI { + +class UIMenuBar; + +class PlatformMenuBar { + public: + virtual ~PlatformMenuBar() = default; + + virtual void install( UIMenuBar* menuBar ) = 0; + + virtual void uninstall() = 0; + + virtual void syncTopLevel() = 0; + + static bool isSupported(); + + static std::unique_ptr create(); +}; + +}} // namespace EE::UI + +#endif diff --git a/src/eepp/ui/uimenu.cpp b/src/eepp/ui/uimenu.cpp index 68e5c5831..9f14c8c81 100644 --- a/src/eepp/ui/uimenu.cpp +++ b/src/eepp/ui/uimenu.cpp @@ -305,8 +305,13 @@ Uint32 UIMenu::onMessage( const NodeMessage* msg ) { } case NodeMessage::MouseUp: { if ( msg->getSender()->getParent() == this && ( msg->getFlags() & EE_BUTTONS_LRM ) ) { - Event itemEvent( msg->getSender(), Event::OnItemClicked ); - sendEvent( &itemEvent ); + if ( msg->getSender()->isType( UI_TYPE_MENUITEM ) && + !msg->getSender()->isType( UI_TYPE_MENUSUBMENU ) ) { + msg->getSender()->asType()->activate(); + } else { + Event itemEvent( msg->getSender(), Event::OnItemClicked ); + sendEvent( &itemEvent ); + } return 1; } break; @@ -386,18 +391,41 @@ void UIMenu::resizeMe() { } bool UIMenu::show() { + const bool notify = !isVisible(); setEnabled( true ); setVisible( true ); + if ( notify ) + notifyMenuWillShow(); return true; } bool UIMenu::hide() { + const bool notify = isVisible(); setEnabled( false ); setVisible( false ); safeHide(); + if ( notify ) + notifyMenuDidHide(); return true; } +void UIMenu::notifyMenuWillShow() { + sendCommonEvent( Event::OnMenuShow ); +} + +void UIMenu::notifyMenuDidHide() { + sendCommonEvent( Event::OnMenuHide ); +} + +MenuBarRole UIMenu::getMenuBarRole() const { + return mMenuBarRole; +} + +UIMenu* UIMenu::setMenuBarRole( MenuBarRole role ) { + mMenuBarRole = role; + return this; +} + void UIMenu::safeHide() { if ( mOwnerNode && mOwnerNode->isType( UI_TYPE_MENUSUBMENU ) ) { UIMenu* menu = mOwnerNode->getParent()->asType(); diff --git a/src/eepp/ui/uimenubar.cpp b/src/eepp/ui/uimenubar.cpp index 544262bff..bbe16bf1d 100644 --- a/src/eepp/ui/uimenubar.cpp +++ b/src/eepp/ui/uimenubar.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -88,6 +89,7 @@ void UIMenuBar::showPrevMenu() { } UIMenuBar::~UIMenuBar() { + setGlobalMenuBarEnabled( false ); destroyMenus(); } @@ -153,6 +155,7 @@ void UIMenuBar::addMenuButton( const String& buttonText, UIPopUpMenu* menu ) { button->setThemeSkin( mTheme, "menubarbutton" ); refreshButtons(); + syncGlobalMenuBar(); } void UIMenuBar::setTheme( UITheme* theme ) { @@ -176,6 +179,7 @@ void UIMenuBar::removeMenuButton( const String& buttonText ) { it->second->close(); mButtons.erase( it ); refreshButtons(); + syncGlobalMenuBar(); break; } } @@ -208,9 +212,38 @@ UIPopUpMenu* UIMenuBar::getPopUpMenu( const Uint32& index ) const { UIMenuBar* UIMenuBar::setPopUpMenu( const Uint32& index, UIPopUpMenu* menu ) { eeASSERT( index < mButtons.size() ); mButtons[index].second = menu; + syncGlobalMenuBar(); return this; } +bool UIMenuBar::isGlobalMenuBarSupported() const { + return PlatformMenuBar::isSupported(); +} + +UIMenuBar* UIMenuBar::setGlobalMenuBarEnabled( bool enabled ) { + if ( enabled == isGlobalMenuBarEnabled() ) + return this; + + if ( enabled ) { + mPlatformMenuBar = PlatformMenuBar::create(); + if ( mPlatformMenuBar ) + mPlatformMenuBar->install( this ); + } else if ( mPlatformMenuBar ) { + mPlatformMenuBar->uninstall(); + mPlatformMenuBar.reset(); + } + return this; +} + +bool UIMenuBar::isGlobalMenuBarEnabled() const { + return nullptr != mPlatformMenuBar; +} + +void UIMenuBar::syncGlobalMenuBar() { + if ( mPlatformMenuBar ) + mPlatformMenuBar->syncTopLevel(); +} + size_t UIMenuBar::getButtonsCount() const { return mButtons.size(); } diff --git a/src/eepp/ui/uimenucheckbox.cpp b/src/eepp/ui/uimenucheckbox.cpp index 3f1a964c5..295e9f774 100644 --- a/src/eepp/ui/uimenucheckbox.cpp +++ b/src/eepp/ui/uimenucheckbox.cpp @@ -106,15 +106,11 @@ void UIMenuCheckBox::switchActive() { setActive( !mActive ); } -Uint32 UIMenuCheckBox::onMessage( const NodeMessage* msg ) { - switch ( msg->getMsg() ) { - case NodeMessage::MouseUp: { - if ( msg->getFlags() & EE_BUTTON_LMASK ) - switchActive(); - break; - } - } - return 0; +void UIMenuCheckBox::activate() { + if ( !isEnabled() ) + return; + switchActive(); + UIMenuItem::activate(); } void UIMenuCheckBox::onStateChange() { diff --git a/src/eepp/ui/uimenuitem.cpp b/src/eepp/ui/uimenuitem.cpp index b27eaa743..85e105f13 100644 --- a/src/eepp/ui/uimenuitem.cpp +++ b/src/eepp/ui/uimenuitem.cpp @@ -36,7 +36,25 @@ void UIMenuItem::setTheme( UITheme* Theme ) { onThemeLoaded(); } +void UIMenuItem::activate() { + if ( !isEnabled() || nullptr == getParent() || !getParent()->isType( UI_TYPE_MENU ) ) + return; + + UIMenu* menu = getParent()->asType(); + Input* input = nullptr != getUISceneNode() ? getInput() : nullptr; + if ( ( nullptr == input || !input->isShiftPressed() ) && + ( !mOnShouldCloseCb || mOnShouldCloseCb( this ) ) ) { + menu->backpropagateHide(); + } + + Event itemEvent( this, Event::OnItemClicked ); + menu->sendEvent( &itemEvent ); +} + UIMenuItem* UIMenuItem::setShortcutText( const String& text ) { + mShortcut = {}; + if ( !text.empty() && nullptr != getUISceneNode() ) + mShortcut = KeyBindings::toShortcut( getInput(), text.toUtf8() ); if ( !text.empty() ) createShortcutView(); if ( mShortcutView ) @@ -44,6 +62,14 @@ UIMenuItem* UIMenuItem::setShortcutText( const String& text ) { return this; } +const KeyBindings::Shortcut& UIMenuItem::getShortcut() const { + if ( mShortcut.empty() && nullptr != mShortcutView && !mShortcutView->getText().empty() && + nullptr != getUISceneNode() ) { + mShortcut = KeyBindings::toShortcut( getInput(), mShortcutView->getText().toUtf8() ); + } + return mShortcut; +} + UITextView* UIMenuItem::getShortcutView() const { return mShortcutView; } @@ -75,14 +101,6 @@ Uint32 UIMenuItem::onMouseLeave( const Vector2i& pos, const Uint32& flags ) { return 1; } -Uint32 UIMenuItem::onMouseClick( const Vector2i&, const Uint32& flags ) { - if ( !getInput()->isShiftPressed() && ( flags & EE_BUTTON_LMASK ) && - ( !mOnShouldCloseCb || mOnShouldCloseCb( this ) ) ) { - getParent()->asType()->backpropagateHide(); - } - return 1; -} - UIWidget* UIMenuItem::getExtraInnerWidget() const { return mShortcutView; } @@ -96,6 +114,15 @@ UIMenuItem* UIMenuItem::setOnShouldCloseCb( const OnShouldCloseCb& onShouldClose return this; } +MenuRole UIMenuItem::getMenuRole() const { + return mMenuRole; +} + +UIMenuItem* UIMenuItem::setMenuRole( MenuRole role ) { + mMenuRole = role; + return this; +} + void UIMenuItem::createShortcutView() { if ( mShortcutView ) return; diff --git a/src/eepp/ui/uimenuradiobutton.cpp b/src/eepp/ui/uimenuradiobutton.cpp index c7f3653a7..beff8d713 100644 --- a/src/eepp/ui/uimenuradiobutton.cpp +++ b/src/eepp/ui/uimenuradiobutton.cpp @@ -119,13 +119,12 @@ void UIMenuRadioButton::switchActive() { setActive( !mActive ); } -Uint32 UIMenuRadioButton::onMouseUp( const Vector2i& Pos, const Uint32& Flags ) { - UIMenuItem::onMouseUp( Pos, Flags ); - - if ( getParent()->isVisible() && ( Flags & EE_BUTTONS_LRM ) && !mActive ) - switchActive(); - - return 1; +void UIMenuRadioButton::activate() { + if ( !isEnabled() ) + return; + if ( !mActive ) + setActive( true ); + UIMenuItem::activate(); } void UIMenuRadioButton::onStateChange() { diff --git a/src/eepp/ui/uimenusubmenu.cpp b/src/eepp/ui/uimenusubmenu.cpp index 738a09ad0..8b9108e43 100644 --- a/src/eepp/ui/uimenusubmenu.cpp +++ b/src/eepp/ui/uimenusubmenu.cpp @@ -97,7 +97,9 @@ UIMenu* UIMenuSubMenu::getSubMenu() const { } void UIMenuSubMenu::showSubMenu() { - sendCommonEvent( Event::OnMenuShow ); + notifySubMenuWillShow(); + if ( nullptr == mSubMenu ) + return; UIMenu* menu = getParent()->asType(); mSubMenu->setParent( menu->getParent() ); Vector2f pos = getPixelsPosition(); @@ -114,6 +116,10 @@ void UIMenuSubMenu::showSubMenu() { } } +void UIMenuSubMenu::notifySubMenuWillShow() { + sendCommonEvent( Event::OnMenuShow ); +} + Uint32 UIMenuSubMenu::onMouseOver( const Vector2i& pos, const Uint32& flags ) { if ( nullptr == mCurWait ) { mCurWait = Actions::Runnable::New( diff --git a/src/eepp/ui/uipopupmenu.cpp b/src/eepp/ui/uipopupmenu.cpp index 7ed363c62..bbf6d1df7 100644 --- a/src/eepp/ui/uipopupmenu.cpp +++ b/src/eepp/ui/uipopupmenu.cpp @@ -52,7 +52,7 @@ bool UIPopUpMenu::show() { getUISceneNode()->getUIThemeManager()->getWidgetsFadeOutTime() ), Actions::Spawn::New( Actions::Enable::New(), Actions::Visible::New( true ) ) ) ); } - sendCommonEvent( Event::OnMenuShow ); + notifyMenuWillShow(); setFocus(); return true; } @@ -92,7 +92,7 @@ bool UIPopUpMenu::hide() { close(); } safeHide(); - sendCommonEvent( Event::OnMenuHide ); + notifyMenuDidHide(); return true; } return false; diff --git a/src/tests/unit_tests/uimenu_tests.cpp b/src/tests/unit_tests/uimenu_tests.cpp new file mode 100644 index 000000000..ca8e1e586 --- /dev/null +++ b/src/tests/unit_tests/uimenu_tests.cpp @@ -0,0 +1,87 @@ +#include "utest.h" + +#include +#include +#include +#include +#include +#include + +using namespace EE; +using namespace EE::System; +using namespace EE::UI; +using namespace EE::Window; + +UTEST( UIMenu, SemanticActivationLifecycleAndRoles ) { + UIApplication app( + WindowSettings( 320, 240, "eepp - UIMenu Test", WindowStyle::Default, + WindowBackend::Default, 32, {}, 1, false, true ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + UIMenu* menu = UIMenu::New(); + menu->setParent( app.getUI() ); + + int clicked = 0; + int shown = 0; + int hidden = 0; + menu->on( Event::OnItemClicked, [&clicked]( const Event* ) { ++clicked; } ); + menu->on( Event::OnMenuShow, [&shown]( const Event* ) { ++shown; } ); + menu->on( Event::OnMenuHide, [&hidden]( const Event* ) { ++hidden; } ); + + UIMenuItem* item = menu->add( "Item" ); + EXPECT_EQ( item->getMenuRole(), MenuRole::NoRole ); + EXPECT_EQ( item->setMenuRole( MenuRole::About ), item ); + EXPECT_EQ( item->getMenuRole(), MenuRole::About ); + item->activate(); + EXPECT_EQ( clicked, 1 ); + item->setEnabled( false ); + item->activate(); + EXPECT_EQ( clicked, 1 ); + item->setEnabled( true ); + + menu->show(); + item->setOnShouldCloseCb( []( UIMenuItem* ) { return false; } ); + item->activate(); + EXPECT_TRUE( menu->isVisible() ); + EXPECT_EQ( clicked, 2 ); + item->setOnShouldCloseCb( {} ); + menu->hide(); + + UIMenuCheckBox* checkBox = menu->addCheckBox( "Check" ); + EXPECT_FALSE( checkBox->isActive() ); + checkBox->activate(); + EXPECT_TRUE( checkBox->isActive() ); + checkBox->activate(); + EXPECT_FALSE( checkBox->isActive() ); + EXPECT_EQ( clicked, 4 ); + + UIMenuRadioButton* radioA = menu->addRadioButton( "A", true ); + UIMenuRadioButton* radioB = menu->addRadioButton( "B" ); + radioB->activate(); + EXPECT_FALSE( radioA->isActive() ); + EXPECT_TRUE( radioB->isActive() ); + radioB->activate(); + EXPECT_TRUE( radioB->isActive() ); + EXPECT_EQ( clicked, 6 ); + + menu->notifyMenuWillShow(); + menu->notifyMenuDidHide(); + EXPECT_EQ( shown, 2 ); + EXPECT_EQ( hidden, 3 ); + + EXPECT_EQ( menu->getMenuBarRole(), MenuBarRole::Normal ); + EXPECT_EQ( menu->setMenuBarRole( MenuBarRole::Help ), menu ); + EXPECT_EQ( menu->getMenuBarRole(), MenuBarRole::Help ); + + UIPopUpMenu* childMenu = UIPopUpMenu::New(); + childMenu->setParent( app.getUI() ); + UIMenuSubMenu* subMenu = menu->addSubMenu( "Submenu", {}, childMenu ); + int subMenuShowStep = 0; + subMenu->on( Event::OnMenuShow, [&subMenuShowStep]( const Event* ) { + subMenuShowStep = 0 == subMenuShowStep ? 1 : -1; + } ); + childMenu->on( Event::OnMenuShow, [&subMenuShowStep]( const Event* ) { + subMenuShowStep = 1 == subMenuShowStep ? 2 : -1; + } ); + subMenu->showSubMenu(); + EXPECT_EQ( subMenuShowStep, 2 ); +} diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 53ee199f0..6b1411588 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -4689,7 +4689,6 @@ void App::init( InitParameters& params ) { EE_PLATFORM == EE_PLATFORM_BSD #if EE_PLATFORM == EE_PLATFORM_MACOS - macOS_createApplicationMenus(); macOS_enableScrollMomentum(); macOS_removeTitleBarSeparator( mWindow->getWindowHandler() ); #endif @@ -5144,6 +5143,8 @@ void App::init( InitParameters& params ) { mSettings = std::make_unique(); mSettings->createSettingsMenu( this, mMenuBar ); + if ( mMenuBar->isGlobalMenuBarSupported() ) + mMenuBar->setGlobalMenuBarEnabled( true ); mSplitter->createEditorWithTabWidget( mBaseLayout ); diff --git a/src/tools/ecode/macos/macos.hpp b/src/tools/ecode/macos/macos.hpp index fede2f729..a34362ee6 100644 --- a/src/tools/ecode/macos/macos.hpp +++ b/src/tools/ecode/macos/macos.hpp @@ -5,8 +5,6 @@ extern "C" { #endif -void macOS_createApplicationMenus(); - void macOS_enableScrollMomentum(); void macOS_removeTitleBarSeparator( void* nsWindow ); diff --git a/src/tools/ecode/macos/macos.m b/src/tools/ecode/macos/macos.m index dfd8282fe..02381be97 100644 --- a/src/tools/ecode/macos/macos.m +++ b/src/tools/ecode/macos/macos.m @@ -3,67 +3,6 @@ #include "macos.hpp" -/* setAppleMenu disappeared from the headers in 10.4 */ -@interface NSApplication(NSAppleMenu) -- (void)setAppleMenu:(NSMenu *)menu; -@end - -// Recreates the menubar replacing the default SDL menubar -void macOS_createApplicationMenus() { - NSString *appName; - NSString *title; - NSMenu *appleMenu; - NSMenu *windowMenu; - NSMenuItem *menuItem; - NSMenu *mainMenu; - - if (NSApp == nil) { - return; - } - - mainMenu = [[NSApplication sharedApplication] mainMenu]; - [mainMenu removeAllItems]; - - appName = @"ecode"; - appleMenu = [[NSMenu alloc] initWithTitle:@""]; - - title = [@"About " stringByAppendingString:appName]; - [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; - - [appleMenu addItem:[NSMenuItem separatorItem]]; - - [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; - - [appleMenu addItem:[NSMenuItem separatorItem]]; - - title = [@"Quit " stringByAppendingString:appName]; - [appleMenu addItemWithTitle:title action:@selector(performClose:) keyEquivalent:@"q"]; - - menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; - [menuItem setSubmenu:appleMenu]; - - [[NSApp mainMenu] addItem:menuItem]; - - [NSApp setAppleMenu:appleMenu]; - - windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; - - menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(miniaturize:) keyEquivalent:@"m"]; - [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagCommand]; - [windowMenu addItem:menuItem]; - - menuItem = [[NSMenuItem alloc] initWithTitle:@"Toggle Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"]; - [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand]; - [windowMenu addItem:menuItem]; - - menuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; - [menuItem setSubmenu:windowMenu]; - - [[NSApp mainMenu] addItem:menuItem]; - - [NSApp setWindowsMenu:windowMenu]; -} - void macOS_enableScrollMomentum() { [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"AppleMomentumScrollSupported"]; } diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index a215d501d..d73d4c136 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -177,6 +177,7 @@ void SettingsMenu::createSettingsMenu( App* app, UIMenuBar* menuBar ) { toolsMenuButton->on( Event::OnMenuShow, lazyBuildToolsMenu ); mWindowMenu = UIPopUpMenu::New(); + mWindowMenu->setMenuBarRole( MenuBarRole::Window ); const auto lazyBuildWindowMenu = [this]( const Event* ) { if ( mWindowMenu->getCount() == 0 ) { createWindowMenu(); @@ -192,6 +193,7 @@ void SettingsMenu::createSettingsMenu( App* app, UIMenuBar* menuBar ) { windowMenuButton->on( Event::OnMenuShow, lazyBuildWindowMenu ); mHelpMenu = UIPopUpMenu::New(); + mHelpMenu->setMenuBarRole( MenuBarRole::Help ); const auto lazyBuildHelpMenu = [this]( const Event* ) { if ( mHelpMenu->getCount() == 0 ) { createHelpMenu(); @@ -215,6 +217,7 @@ void SettingsMenu::createSettingsMenu( App* app, UIMenuBar* menuBar ) { ->setId( "close-folder" ); mSettingsMenu->addSeparator(); mSettingsMenu->add( i18n( "quit", "Quit" ), findIcon( "quit" ), getKeybind( "close-app" ) ) + ->setMenuRole( MenuRole::Quit ) ->setId( "close-app" ); mSettingsButton = mUISceneNode->find( "settings" ); mSettingsButton->on( Event::MouseClick, [this]( const Event* ) { toggleSettingsMenu(); } ); @@ -289,6 +292,7 @@ void SettingsMenu::createSettingsMenu( App* app, UIMenuBar* menuBar ) { onMenuShowEvent( mToolsMenu, toolsMenuButton, 6 ); onMenuShowEvent( mWindowMenu, windowMenuButton, 7 ); onMenuShowEvent( mHelpMenu, helpMenuButton, 8 ); + createHelpMenu(); updateMenu(); @@ -1071,6 +1075,7 @@ UIMenu* SettingsMenu::createHelpMenu() { mHelpMenu->add( i18n( "check_for_updates", "Check for Updates" ), findIcon( "refresh" ) ) ->setId( "check-for-updates" ); mHelpMenu->add( i18n( "about_ecode", "About ecode" ), findIcon( "ecode" ) ) + ->setMenuRole( MenuRole::About ) ->setId( "about-ecode" ); mHelpMenu->on( Event::OnItemClicked, [this]( const Event* event ) { runCommand( event->getNode()->getId() ); } ); diff --git a/src/tools/eeiv/eeiv.hpp b/src/tools/eeiv/eeiv.hpp index f3b0abab5..c53e48dcb 100644 --- a/src/tools/eeiv/eeiv.hpp +++ b/src/tools/eeiv/eeiv.hpp @@ -93,6 +93,6 @@ class App { UIConsole* mConsole{ nullptr }; UIWelcomeScreen* mWelcomeScreen{ nullptr }; bool mCursorVisible{ true }; - Texture::Filter mTextureFilter{ Texture::Filter::Linear }; + Texture::Filter mTextureFilter{ Texture::Filter::Nearest }; std::unique_ptr mConsoleCommands; };