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:
Martín Lucas Golini
2026-08-24 02:38:36 -03:00
parent 6b27ea2dc8
commit 9752be89ad
24 changed files with 912 additions and 141 deletions
+14 -4
View File
@@ -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 = "" );
+12
View File
@@ -5,9 +5,12 @@
#include <eepp/ui/uipopupmenu.hpp>
#include <eepp/ui/uiselectbutton.hpp>
#include <eepp/ui/uiwidget.hpp>
#include <memory>
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<PlatformMenuBar> mPlatformMenuBar;
Uint32 getMenuIndex( UIPopUpMenu* menu );
@@ -83,6 +93,8 @@ class EE_API UIMenuBar : public UIWidget {
void destroyMenus();
void autoHeight();
void syncGlobalMenuBar();
};
}} // namespace EE::UI
+2 -2
View File
@@ -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();
};
+13 -2
View File
@@ -1,10 +1,13 @@
#ifndef EE_UICUIMENUITEM_HPP
#define EE_UICUIMENUITEM_HPP
#include <eepp/ui/keyboardshortcut.hpp>
#include <eepp/ui/uipushbutton.hpp>
namespace EE { namespace UI {
enum class MenuRole : Uint8 { NoRole, About, Preferences, Quit };
class EE_API UIMenuItem : public UIPushButton {
public:
typedef std::function<bool( UIMenuItem* item )> 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();
+2 -2
View File
@@ -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();
};
+2
View File
@@ -27,6 +27,8 @@ class EE_API UIMenuSubMenu : public UIMenuItem {
void showSubMenu();
void notifySubMenuWillShow();
const Time& getMouseOverTimeShowMenu() const;
void setMouseOverTimeShowMenu( const Time& maxTime );