mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-01 18:50:16 +03:00
Add native macOS global menu bar integration
Mirror UIMenuBar hierarchies into AppKit while keeping eepp menus as the source of truth. Add semantic item activation and menu lifecycle hooks so native actions, lazy menus, checkboxes, radio buttons, visibility, enabled state, and submenus preserve existing eepp behavior. Support shortcuts and standard About, Preferences, Quit, Window, Help, and Services menu integration. Keep the platform adapter private to the UI implementation and support safe installation, synchronization, and removal of the native menu. Migrate ecode away from its hand-written Cocoa menu, assign semantic menu roles, and populate the UI Language submenu synchronously so it is ready on its first opening. Add unit tests covering activation, menu lifecycle, roles, and submenu preparation. Refs: SpartanJ/ecode#823
This commit is contained in:
@@ -0,0 +1,539 @@
|
||||
#define Rect AppleRect
|
||||
#import <AppKit/AppKit.h>
|
||||
#undef Rect
|
||||
#undef BSD
|
||||
|
||||
#include <eepp/system/log.hpp>
|
||||
#include <eepp/ui/platformmenubar.hpp>
|
||||
#include <eepp/ui/uimenu.hpp>
|
||||
#include <eepp/ui/uimenubar.hpp>
|
||||
#include <eepp/ui/uimenucheckbox.hpp>
|
||||
#include <eepp/ui/uimenuitem.hpp>
|
||||
#include <eepp/ui/uimenuradiobutton.hpp>
|
||||
#include <eepp/ui/uimenuseparator.hpp>
|
||||
#include <eepp/ui/uimenusubmenu.hpp>
|
||||
#include <eepp/window/keycodes.hpp>
|
||||
#include <unordered_map>
|
||||
|
||||
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<unichar>( 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 <NSMenuDelegate> {
|
||||
@private
|
||||
UIMenuBar* _menuBar;
|
||||
NSMenu* _installedMainMenu;
|
||||
NSMenu* _previousMainMenu;
|
||||
NSMenu* _servicesMenu;
|
||||
NSMenu* _windowsMenu;
|
||||
NSMenu* _helpMenu;
|
||||
BOOL _capturedPreviousMainMenu;
|
||||
std::unordered_map<NSMenu*, NativeMenuSource> _menuMap;
|
||||
std::unordered_map<NSMenuItem*, UIMenuItem*> _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<UIMenuItem>();
|
||||
if ( item->getMenuRole() == role )
|
||||
return item;
|
||||
}
|
||||
if ( widget->isType( UI_TYPE_MENUSUBMENU ) ) {
|
||||
UIMenuItem* found =
|
||||
[self findItemWithRole:role inMenu:widget->asType<UIMenuSubMenu>()->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<UIMenuItem>();
|
||||
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<UIMenuSubMenu>()->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<UIMenuSubMenu>() };
|
||||
EE_OBJC_RELEASE( subMenu );
|
||||
}
|
||||
} else {
|
||||
item = [self mirroredItemWithTitle:toNSString( source->getText() ) source:source];
|
||||
if ( widget->isType( UI_TYPE_MENUCHECKBOX ) ) {
|
||||
[item setState:widget->asType<UIMenuCheckBox>()->isActive() ? NSControlStateValueOn
|
||||
: NSControlStateValueOff];
|
||||
} else if ( widget->isType( UI_TYPE_MENURADIOBUTTON ) ) {
|
||||
[item setState:widget->asType<UIMenuRadioButton>()->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<PlatformMenuBar> createMacOSPlatformMenuBar() {
|
||||
return std::make_unique<MacOSMenuBar>();
|
||||
}
|
||||
|
||||
}} // namespace EE::UI
|
||||
@@ -0,0 +1,25 @@
|
||||
#include <eepp/ui/platformmenubar.hpp>
|
||||
|
||||
namespace EE { namespace UI {
|
||||
|
||||
#if EE_PLATFORM == EE_PLATFORM_MACOS
|
||||
std::unique_ptr<PlatformMenuBar> createMacOSPlatformMenuBar();
|
||||
#endif
|
||||
|
||||
bool PlatformMenuBar::isSupported() {
|
||||
#if EE_PLATFORM == EE_PLATFORM_MACOS
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<PlatformMenuBar> PlatformMenuBar::create() {
|
||||
#if EE_PLATFORM == EE_PLATFORM_MACOS
|
||||
return createMacOSPlatformMenuBar();
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
}} // namespace EE::UI
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef EE_UI_PLATFORMMENUBAR_HPP
|
||||
#define EE_UI_PLATFORMMENUBAR_HPP
|
||||
|
||||
#include <eepp/config.hpp>
|
||||
#include <memory>
|
||||
|
||||
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<PlatformMenuBar> create();
|
||||
};
|
||||
|
||||
}} // namespace EE::UI
|
||||
|
||||
#endif
|
||||
+30
-2
@@ -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<UIMenuItem>()->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<UIMenu>();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <eepp/graphics/textureregion.hpp>
|
||||
#include <eepp/scene/scenemanager.hpp>
|
||||
#include <eepp/ui/css/propertydefinition.hpp>
|
||||
#include <eepp/ui/platformmenubar.hpp>
|
||||
#include <eepp/ui/uimenubar.hpp>
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
#include <eepp/ui/uithememanager.hpp>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<UIMenu>();
|
||||
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<UIMenu>()->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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -97,7 +97,9 @@ UIMenu* UIMenuSubMenu::getSubMenu() const {
|
||||
}
|
||||
|
||||
void UIMenuSubMenu::showSubMenu() {
|
||||
sendCommonEvent( Event::OnMenuShow );
|
||||
notifySubMenuWillShow();
|
||||
if ( nullptr == mSubMenu )
|
||||
return;
|
||||
UIMenu* menu = getParent()->asType<UIMenu>();
|
||||
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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
#include "utest.h"
|
||||
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/system/sys.hpp>
|
||||
#include <eepp/ui/uiapplication.hpp>
|
||||
#include <eepp/ui/uimenu.hpp>
|
||||
#include <eepp/ui/uipopupmenu.hpp>
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
|
||||
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 );
|
||||
}
|
||||
@@ -4681,7 +4681,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
|
||||
@@ -5136,6 +5135,8 @@ void App::init( InitParameters& params ) {
|
||||
|
||||
mSettings = std::make_unique<SettingsMenu>();
|
||||
mSettings->createSettingsMenu( this, mMenuBar );
|
||||
if ( mMenuBar->isGlobalMenuBarSupported() )
|
||||
mMenuBar->setGlobalMenuBarEnabled( true );
|
||||
|
||||
mSplitter->createEditorWithTabWidget( mBaseLayout );
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void macOS_createApplicationMenus();
|
||||
|
||||
void macOS_enableScrollMomentum();
|
||||
|
||||
void macOS_removeTitleBarSeparator( void* nsWindow );
|
||||
|
||||
@@ -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"];
|
||||
}
|
||||
|
||||
@@ -168,6 +168,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();
|
||||
@@ -183,6 +184,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();
|
||||
@@ -206,6 +208,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<UIWidget>( "settings" );
|
||||
mSettingsButton->on( Event::MouseClick, [this]( const Event* ) { toggleSettingsMenu(); } );
|
||||
@@ -280,6 +283,7 @@ void SettingsMenu::createSettingsMenu( App* app, UIMenuBar* menuBar ) {
|
||||
onMenuShowEvent( mToolsMenu, toolsMenuButton, 6 );
|
||||
onMenuShowEvent( mWindowMenu, windowMenuButton, 7 );
|
||||
onMenuShowEvent( mHelpMenu, helpMenuButton, 8 );
|
||||
createHelpMenu();
|
||||
|
||||
updateMenu();
|
||||
|
||||
@@ -2464,6 +2468,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() ); } );
|
||||
@@ -2524,46 +2529,38 @@ UIMenu* SettingsMenu::createLanguagesMenu() {
|
||||
if ( curLang.empty() )
|
||||
curLang = "en";
|
||||
|
||||
mApp->getThreadPool()->run( [this, menu, curLang] {
|
||||
auto files =
|
||||
FileSystem::filesInfoGetInPath( mApp->geti18nPath(), false, true, false, true );
|
||||
auto files = FileSystem::filesInfoGetInPath( mApp->geti18nPath(), false, true, false, true );
|
||||
|
||||
std::map<std::string, std::string> languages;
|
||||
for ( const auto& file : files ) {
|
||||
if ( file.getExtension() != "xml" )
|
||||
continue;
|
||||
auto name( FileSystem::fileRemoveExtension( file.getFileName() ) );
|
||||
std::string data;
|
||||
FileSystem::fileGet( file.getFilepath(), data );
|
||||
std::string lptrn( "title=\"(.-)\"" );
|
||||
LuaPattern pattern( lptrn );
|
||||
PatternMatcher::Range matches[2];
|
||||
if ( pattern.matches( data, matches ) ) {
|
||||
std::string title(
|
||||
data.substr( matches[1].start, matches[1].end - matches[1].start ) );
|
||||
languages[title] = name;
|
||||
} else {
|
||||
languages[name] = name;
|
||||
}
|
||||
std::map<std::string, std::string> languages;
|
||||
for ( const auto& file : files ) {
|
||||
if ( file.getExtension() != "xml" )
|
||||
continue;
|
||||
auto name( FileSystem::fileRemoveExtension( file.getFileName() ) );
|
||||
std::string data;
|
||||
FileSystem::fileGet( file.getFilepath(), data );
|
||||
std::string lptrn( "title=\"(.-)\"" );
|
||||
LuaPattern pattern( lptrn );
|
||||
PatternMatcher::Range matches[2];
|
||||
if ( pattern.matches( data, matches ) ) {
|
||||
std::string title( data.substr( matches[1].start, matches[1].end - matches[1].start ) );
|
||||
languages[title] = name;
|
||||
} else {
|
||||
languages[name] = name;
|
||||
}
|
||||
if ( languages.empty() )
|
||||
return;
|
||||
}
|
||||
|
||||
menu->runOnMainThread( [this, menu, curLang, languages] {
|
||||
for ( const auto& lang : languages )
|
||||
menu->addRadioButton( lang.first, curLang == lang.second )->setId( lang.second );
|
||||
for ( const auto& lang : languages )
|
||||
menu->addRadioButton( lang.first, curLang == lang.second )->setId( lang.second );
|
||||
|
||||
menu->on( Event::OnItemClicked, [this]( const Event* event ) {
|
||||
auto id = event->getNode()->getId();
|
||||
mApp->getConfig().ui.language = id;
|
||||
UIMessageBox* msg = UIMessageBox::New(
|
||||
UIMessageBox::OK,
|
||||
i18n( "new_ui_language", "New language assigned.\nPlease restart the "
|
||||
"application to see the complete changes." ) );
|
||||
msg->showWhenReady();
|
||||
mApp->setFocusEditorOnClose( msg );
|
||||
} );
|
||||
} );
|
||||
menu->on( Event::OnItemClicked, [this]( const Event* event ) {
|
||||
auto id = event->getNode()->getId();
|
||||
mApp->getConfig().ui.language = id;
|
||||
UIMessageBox* msg = UIMessageBox::New( UIMessageBox::OK,
|
||||
i18n( "new_ui_language",
|
||||
"New language assigned.\nPlease restart the "
|
||||
"application to see the complete changes." ) );
|
||||
msg->showWhenReady();
|
||||
mApp->setFocusEditorOnClose( msg );
|
||||
} );
|
||||
|
||||
return menu;
|
||||
|
||||
Reference in New Issue
Block a user