Browse Source

[OSX] Refactor Native App Menu. Move default menu initialization code from ObjC to C#. (#6909)

* [NativeMenu] [Refactoring] Move Default Menu creation from native lib to C# (C# side of code)

* fix return type for IAvnApplicationCommands

* [Native] menu refactoring (ObjC side)

* fix nullref

* minor refactor

Co-authored-by: Jumar Macato <16554748+jmacato@users.noreply.github.com>
Co-authored-by: Dan Walmsley <dan@walms.co.uk>
pull/7132/head
Sergey Mikolaytis 4 years ago
committed by GitHub
parent
commit
8578160fbc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      native/Avalonia.Native/src/OSX/app.mm
  2. 15
      native/Avalonia.Native/src/OSX/common.h
  3. 44
      native/Avalonia.Native/src/OSX/main.mm
  4. 53
      native/Avalonia.Native/src/OSX/menu.mm
  5. 12
      src/Avalonia.Controls/Platform/INativeApplicationCommands.cs
  6. 73
      src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
  7. 5
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  8. 5
      src/Avalonia.Native/IAvnMenuItem.cs
  9. 35
      src/Avalonia.Native/MacOSNativeMenuCommands.cs
  10. 11
      src/Avalonia.Native/avn.idl

27
native/Avalonia.Native/src/OSX/app.mm

@ -97,3 +97,30 @@ extern void InitializeAvnApp(IAvnApplicationEvents* events)
id delegate = [[AvnAppDelegate alloc] initWithEvents:events]; id delegate = [[AvnAppDelegate alloc] initWithEvents:events];
[app setDelegate:delegate]; [app setDelegate:delegate];
} }
HRESULT AvnApplicationCommands::HideApp()
{
START_COM_CALL;
[[NSApplication sharedApplication] hide:[NSApp delegate]];
return S_OK;
}
HRESULT AvnApplicationCommands::ShowAll()
{
START_COM_CALL;
[[NSApplication sharedApplication] unhideAllApplications:[NSApp delegate]];
return S_OK;
}
HRESULT AvnApplicationCommands::HideOthers()
{
START_COM_CALL;
[[NSApplication sharedApplication] hideOtherApplications:[NSApp delegate]];
return S_OK;
}
extern IAvnApplicationCommands* CreateApplicationCommands()
{
return new AvnApplicationCommands();
}

15
native/Avalonia.Native/src/OSX/common.h

@ -25,12 +25,12 @@ extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* events);
extern IAvnTrayIcon* CreateTrayIcon(); extern IAvnTrayIcon* CreateTrayIcon();
extern IAvnMenuItem* CreateAppMenuItem(); extern IAvnMenuItem* CreateAppMenuItem();
extern IAvnMenuItem* CreateAppMenuItemSeparator(); extern IAvnMenuItem* CreateAppMenuItemSeparator();
extern IAvnApplicationCommands* CreateApplicationCommands();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent); extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
extern void SetAppMenu (NSString* appName, IAvnMenu* appMenu); extern void SetAppMenu (NSString* appName, IAvnMenu* appMenu);
extern void SetServicesMenu (IAvnMenu* menu);
extern IAvnMenu* GetAppMenu (); extern IAvnMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem (); extern NSMenuItem* GetAppMenuItem ();
extern void SetAutoGenerateDefaultAppMenuItems (bool enabled);
extern bool GetAutoGenerateDefaultAppMenuItems ();
extern void InitializeAvnApp(IAvnApplicationEvents* events); extern void InitializeAvnApp(IAvnApplicationEvents* events);
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy; extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
@ -67,4 +67,15 @@ public:
~AvnInsidePotentialDeadlock(); ~AvnInsidePotentialDeadlock();
}; };
class AvnApplicationCommands : public ComSingleObject<IAvnApplicationCommands, &IID_IAvnApplicationCommands>
{
public:
FORWARD_IUNKNOWN()
virtual HRESULT HideApp() override;
virtual HRESULT ShowAll() override;
virtual HRESULT HideOthers() override;
};
#endif #endif

44
native/Avalonia.Native/src/OSX/main.mm

@ -2,7 +2,6 @@
#define COM_GUIDS_MATERIALIZE #define COM_GUIDS_MATERIALIZE
#include "common.h" #include "common.h"
static bool s_generateDefaultAppMenuItems = true;
static NSString* s_appTitle = @"Avalonia"; static NSString* s_appTitle = @"Avalonia";
// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
@ -134,16 +133,6 @@ public:
} }
} }
virtual HRESULT SetDisableDefaultApplicationMenuItems (bool enabled) override
{
START_COM_CALL;
@autoreleasepool
{
SetAutoGenerateDefaultAppMenuItems(!enabled);
return S_OK;
}
}
}; };
/// See "Using POSIX Threads in a Cocoa Application" section here: /// See "Using POSIX Threads in a Cocoa Application" section here:
@ -357,6 +346,29 @@ public:
return S_OK; return S_OK;
} }
} }
virtual HRESULT SetServicesMenu (IAvnMenu* servicesMenu) override
{
START_COM_CALL;
@autoreleasepool
{
::SetServicesMenu(servicesMenu);
return S_OK;
}
}
virtual HRESULT CreateApplicationCommands (IAvnApplicationCommands** ppv) override
{
START_COM_CALL;
@autoreleasepool
{
*ppv = ::CreateApplicationCommands();
return S_OK;
}
}
}; };
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative() extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()
@ -410,13 +422,3 @@ CGFloat PrimaryDisplayHeight()
{ {
return NSMaxY([[[NSScreen screens] firstObject] frame]); return NSMaxY([[[NSScreen screens] firstObject] frame]);
} }
void SetAutoGenerateDefaultAppMenuItems (bool enabled)
{
s_generateDefaultAppMenuItems = enabled;
}
bool GetAutoGenerateDefaultAppMenuItems ()
{
return s_generateDefaultAppMenuItems;
}

53
native/Avalonia.Native/src/OSX/menu.mm

@ -490,53 +490,6 @@ extern void SetAppMenu (NSString* appName, IAvnMenu* menu)
{ {
[s_appMenuItem setSubmenu:[NSMenu new]]; [s_appMenuItem setSubmenu:[NSMenu new]];
} }
auto appMenu = [s_appMenuItem submenu];
if(GetAutoGenerateDefaultAppMenuItems())
{
[appMenu addItem:[NSMenuItem separatorItem]];
// Services item and menu
auto servicesItem = [[NSMenuItem alloc] init];
servicesItem.title = @"Services";
NSMenu *servicesMenu = [[NSMenu alloc] initWithTitle:@"Services"];
servicesItem.submenu = servicesMenu;
[NSApplication sharedApplication].servicesMenu = servicesMenu;
[appMenu addItem:servicesItem];
[appMenu addItem:[NSMenuItem separatorItem]];
// Hide Application
auto hideItem = [[NSMenuItem alloc] initWithTitle:[@"Hide " stringByAppendingString:appName] action:@selector(hide:) keyEquivalent:@"h"];
[appMenu addItem:hideItem];
// Hide Others
auto hideAllOthersItem = [[NSMenuItem alloc] initWithTitle:@"Hide Others"
action:@selector(hideOtherApplications:)
keyEquivalent:@"h"];
hideAllOthersItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagOption;
[appMenu addItem:hideAllOthersItem];
// Show All
auto showAllItem = [[NSMenuItem alloc] initWithTitle:@"Show All"
action:@selector(unhideAllApplications:)
keyEquivalent:@""];
[appMenu addItem:showAllItem];
[appMenu addItem:[NSMenuItem separatorItem]];
// Quit Application
auto quitItem = [[NSMenuItem alloc] init];
quitItem.title = [@"Quit " stringByAppendingString:appName];
quitItem.keyEquivalent = @"q";
quitItem.target = [AvnWindow class];
quitItem.action = @selector(closeAll);
[appMenu addItem:quitItem];
}
} }
else else
{ {
@ -544,6 +497,12 @@ extern void SetAppMenu (NSString* appName, IAvnMenu* menu)
} }
} }
extern void SetServicesMenu (IAvnMenu* menu)
{
auto nativeMenu = dynamic_cast<AvnAppMenu*>(menu);
[NSApplication sharedApplication].servicesMenu = nativeMenu->GetNative();
}
extern IAvnMenu* GetAppMenu () extern IAvnMenu* GetAppMenu ()
{ {
return s_appMenu; return s_appMenu;

12
src/Avalonia.Controls/Platform/INativeApplicationCommands.cs

@ -0,0 +1,12 @@
namespace Avalonia.Controls.Platform
{
/// <summary>
/// Native Menu Default Application Commands
/// </summary>
public interface INativeApplicationCommands
{
void HideApp();
void ShowAll();
void HideOthers();
}
}

73
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Dialogs; using Avalonia.Dialogs;
using Avalonia.Input;
using Avalonia.Native.Interop; using Avalonia.Native.Interop;
using Avalonia.Native.Interop.Impl; using Avalonia.Native.Interop.Impl;
using Avalonia.Threading; using Avalonia.Threading;
@ -18,11 +19,13 @@ namespace Avalonia.Native
private NativeMenu _menu; private NativeMenu _menu;
private __MicroComIAvnMenuProxy _nativeMenu; private __MicroComIAvnMenuProxy _nativeMenu;
private readonly IAvnTrayIcon _trayIcon; private readonly IAvnTrayIcon _trayIcon;
private readonly IAvnApplicationCommands _applicationCommands;
public AvaloniaNativeMenuExporter(IAvnWindow nativeWindow, IAvaloniaNativeFactory factory) public AvaloniaNativeMenuExporter(IAvnWindow nativeWindow, IAvaloniaNativeFactory factory)
{ {
_factory = factory; _factory = factory;
_nativeWindow = nativeWindow; _nativeWindow = nativeWindow;
_applicationCommands = _factory.CreateApplicationCommands();
DoLayoutReset(); DoLayoutReset();
} }
@ -30,6 +33,7 @@ namespace Avalonia.Native
public AvaloniaNativeMenuExporter(IAvaloniaNativeFactory factory) public AvaloniaNativeMenuExporter(IAvaloniaNativeFactory factory)
{ {
_factory = factory; _factory = factory;
_applicationCommands = _factory.CreateApplicationCommands();
DoLayoutReset(); DoLayoutReset();
} }
@ -38,7 +42,8 @@ namespace Avalonia.Native
{ {
_factory = factory; _factory = factory;
_trayIcon = trayIcon; _trayIcon = trayIcon;
_applicationCommands = _factory.CreateApplicationCommands();
DoLayoutReset(); DoLayoutReset();
} }
@ -60,15 +65,11 @@ namespace Avalonia.Native
} }
} }
private static NativeMenu CreateDefaultAppMenu() private NativeMenu CreateDefaultAppMenu()
{ {
var result = new NativeMenu(); var result = new NativeMenu();
var aboutItem = new NativeMenuItem var aboutItem = new NativeMenuItem("About Avalonia");
{
Header = "About Avalonia",
};
aboutItem.Click += async (sender, e) => aboutItem.Click += async (sender, e) =>
{ {
var dialog = new AboutAvaloniaDialog(); var dialog = new AboutAvaloniaDialog();
@ -77,9 +78,65 @@ namespace Avalonia.Native
await dialog.ShowDialog(mainWindow); await dialog.ShowDialog(mainWindow);
}; };
result.Add(aboutItem); result.Add(aboutItem);
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>();
if (macOpts == null || !macOpts.DisableDefaultApplicationMenuItems)
{
result.Add(new NativeMenuItemSeparator());
var servicesMenu = new NativeMenuItem("Services");
servicesMenu.Menu = new NativeMenu
{
[MacOSNativeMenuCommands.IsServicesSubmenuProperty] = true
};
result.Add(servicesMenu);
result.Add(new NativeMenuItemSeparator());
var hideItem = new NativeMenuItem("Hide " + Application.Current.Name)
{
Gesture = new KeyGesture(Key.H, KeyModifiers.Meta)
};
hideItem.Click += (sender, args) =>
{
_applicationCommands.HideApp();
};
result.Add(hideItem);
var hideOthersItem = new NativeMenuItem("Hide Others")
{
Gesture = new KeyGesture(Key.Q, KeyModifiers.Meta | KeyModifiers.Alt)
};
hideOthersItem.Click += (sender, args) =>
{
_applicationCommands.HideOthers();
};
result.Add(hideOthersItem);
var showAllItem = new NativeMenuItem("Show All");
showAllItem.Click += (sender, args) =>
{
_applicationCommands.ShowAll();
};
result.Add(showAllItem);
result.Add(new NativeMenuItemSeparator());
var quitItem = new NativeMenuItem("Quit")
{
Gesture = new KeyGesture(Key.Q, KeyModifiers.Meta)
};
quitItem.Click += (sender, args) =>
{
_applicationCommands.ShowAll();
};
result.Add(quitItem);
}
return result; return result;
} }

5
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -93,8 +93,6 @@ namespace Avalonia.Native
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>(); var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>();
_factory.MacOptions.SetShowInDock(macOpts?.ShowInDock != false ? 1 : 0); _factory.MacOptions.SetShowInDock(macOpts?.ShowInDock != false ? 1 : 0);
_factory.MacOptions.SetDisableDefaultApplicationMenuItems(
macOpts?.DisableDefaultApplicationMenuItems == true ? 1 : 0);
} }
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
@ -112,7 +110,8 @@ namespace Avalonia.Native
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt)) .Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt))
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider()) .Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
.Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory)) .Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform); .Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform)
.Bind<INativeApplicationCommands>().ToConstant(new MacOSNativeMenuCommands(_factory.CreateApplicationCommands()));
var hotkeys = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>(); var hotkeys = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers)); hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers));

5
src/Avalonia.Native/IAvnMenuItem.cs

@ -150,6 +150,11 @@ namespace Avalonia.Native.Interop.Impl
{ {
_subMenu = __MicroComIAvnMenuProxy.Create(factory); _subMenu = __MicroComIAvnMenuProxy.Create(factory);
if (item.Menu.GetValue(MacOSNativeMenuCommands.IsServicesSubmenuProperty))
{
factory.SetServicesMenu(_subMenu);
}
_subMenu.Initialize(exporter, item.Menu, item.Header); _subMenu.Initialize(exporter, item.Menu, item.Header);
SetSubMenu(_subMenu); SetSubMenu(_subMenu);

35
src/Avalonia.Native/MacOSNativeMenuCommands.cs

@ -0,0 +1,35 @@
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Native.Interop;
namespace Avalonia.Native
{
internal class MacOSNativeMenuCommands : INativeApplicationCommands
{
private readonly IAvnApplicationCommands _commands;
public MacOSNativeMenuCommands(IAvnApplicationCommands commands)
{
_commands = commands;
}
public void HideApp()
{
_commands.HideApp();
}
public void ShowAll()
{
_commands.ShowAll();
}
public void HideOthers()
{
_commands.HideOthers();
}
public static readonly AttachedProperty<bool> IsServicesSubmenuProperty =
AvaloniaProperty.RegisterAttached<MacOSNativeMenuCommands, NativeMenu, bool>("IsServicesSubmenu", false);
}
}

11
src/Avalonia.Native/avn.idl

@ -424,10 +424,12 @@ interface IAvaloniaNativeFactory : IUnknown
HRESULT CreateCursorFactory(IAvnCursorFactory** ppv); HRESULT CreateCursorFactory(IAvnCursorFactory** ppv);
HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv); HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv);
HRESULT SetAppMenu(IAvnMenu* menu); HRESULT SetAppMenu(IAvnMenu* menu);
HRESULT SetServicesMenu(IAvnMenu* menu);
HRESULT CreateMenu(IAvnMenuEvents* cb, IAvnMenu** ppv); HRESULT CreateMenu(IAvnMenuEvents* cb, IAvnMenu** ppv);
HRESULT CreateMenuItem(IAvnMenuItem** ppv); HRESULT CreateMenuItem(IAvnMenuItem** ppv);
HRESULT CreateMenuItemSeparator(IAvnMenuItem** ppv); HRESULT CreateMenuItemSeparator(IAvnMenuItem** ppv);
HRESULT CreateTrayIcon(IAvnTrayIcon** ppv); HRESULT CreateTrayIcon(IAvnTrayIcon** ppv);
HRESULT CreateApplicationCommands(IAvnApplicationCommands** ppv);
} }
[uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)] [uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)]
@ -539,7 +541,6 @@ interface IAvnMacOptions : IUnknown
{ {
HRESULT SetShowInDock(int show); HRESULT SetShowInDock(int show);
HRESULT SetApplicationTitle(char* utf8string); HRESULT SetApplicationTitle(char* utf8string);
HRESULT SetDisableDefaultApplicationMenuItems(bool enabled);
} }
[uuid(04c1b049-1f43-418a-9159-cae627ec1367)] [uuid(04c1b049-1f43-418a-9159-cae627ec1367)]
@ -753,3 +754,11 @@ interface IAvnApplicationEvents : IUnknown
void FilesOpened (IAvnStringArray* urls); void FilesOpened (IAvnStringArray* urls);
bool TryShutdown(); bool TryShutdown();
} }
[uuid(b4284791-055b-4313-8c2e-50f0a8c72ce9)]
interface IAvnApplicationCommands : IUnknown
{
HRESULT HideApp();
HRESULT ShowAll();
HRESULT HideOthers();
}

Loading…
Cancel
Save