diff --git a/native/Avalonia.Native/src/OSX/app.mm b/native/Avalonia.Native/src/OSX/app.mm index 5dc994fb6b..092bde9c07 100644 --- a/native/Avalonia.Native/src/OSX/app.mm +++ b/native/Avalonia.Native/src/OSX/app.mm @@ -1,11 +1,13 @@ #include "common.h" #include "AvnString.h" +#include "menu.h" @interface AvnAppDelegate : NSObject -(AvnAppDelegate* _Nonnull) initWithEvents: (IAvnApplicationEvents* _Nonnull) events; -(void) releaseEvents; @end NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular; +static NSMenu* s_dockMenu = nil; @implementation AvnAppDelegate ComPtr _events; @@ -86,6 +88,11 @@ ComPtr _events; return _events->TryShutdown() ? NSTerminateNow : NSTerminateCancel; } +- (NSMenu *)applicationDockMenu:(NSApplication *)sender +{ + return s_dockMenu; +} + @end @interface AvnApplication : NSApplication @@ -180,3 +187,8 @@ extern IAvnApplicationCommands* CreateApplicationCommands() { return new AvnApplicationCommands(); } + +extern void SetDockMenu(NSMenu* menu) +{ + s_dockMenu = menu; +} diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index fae03984fd..a993784fc4 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -38,6 +38,7 @@ extern void SetAppMenu(IAvnMenu *menu); extern void SetServicesMenu (IAvnMenu* menu); extern IAvnMenu* GetAppMenu (); extern NSMenuItem* GetAppMenuItem (); +extern void SetDockMenu(NSMenu* menu); extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDelegate); extern void ReleaseAvnAppEvents(); diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 2a92eb3bcf..2f7e15c8ed 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -1,6 +1,7 @@ //This file will contain actual IID structures #define COM_GUIDS_MATERIALIZE #include "common.h" +#include "menu.h" static NSString* s_appTitle = @"Avalonia"; static int disableSetProcessName = 0; @@ -475,14 +476,24 @@ public: return *ppv != nullptr ? S_OK : E_FAIL; } - HRESULT CreateMemoryManagementHelper(IAvnNativeObjectsMemoryManagement **ppv) override { + HRESULT CreateMemoryManagementHelper(IAvnNativeObjectsMemoryManagement **ppv) override { START_COM_CALL; *ppv = ::CreateMemoryManagementHelper(); return S_OK; } - - - + + virtual HRESULT SetDockMenu(IAvnMenu* dockMenu) override + { + START_COM_CALL; + + @autoreleasepool + { + auto nativeMenu = dynamic_cast(dockMenu); + ::SetDockMenu(nativeMenu != nullptr ? nativeMenu->GetNative() : nil); + return S_OK; + } + } + }; extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative() diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 022118d3ab..179f64233e 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -59,6 +59,15 @@ + + + + + + + + + diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index b08df1223d..f14fbb1fa3 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -64,6 +64,40 @@ namespace ControlCatalog base.OnFrameworkInitializationCompleted(); } + public void OnDockNewWindowClicked(object? sender, EventArgs e) + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime) + { + var window = new MainWindow(); + window.Show(); + } + } + + public void OnDockShowMainWindowClicked(object? sender, EventArgs e) + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime) + { + desktopLifetime.MainWindow?.Activate(); + } + } + + private int _dockMenuItemCount; + + public void OnDockAddItemClicked(object? sender, EventArgs e) + { + var dockMenu = NativeDock.GetMenu(this); + if (dockMenu is not null) + { + _dockMenuItemCount++; + var item = new NativeMenuItem($"New item {_dockMenuItemCount}"); + item.Click += (_, _) => + { + dockMenu.Items.Remove(item); + }; + dockMenu.Items.Insert(0, item); + } + } + private CatalogTheme _prevTheme; public static CatalogTheme CurrentTheme => ((App)Current!)._prevTheme; public static void SetCatalogThemes(CatalogTheme theme) diff --git a/samples/IntegrationTestApp/App.axaml b/samples/IntegrationTestApp/App.axaml index 60a2c56542..e8c91fe580 100644 --- a/samples/IntegrationTestApp/App.axaml +++ b/samples/IntegrationTestApp/App.axaml @@ -7,6 +7,13 @@ + + + + + (name).IsChecked = true; }); + DockMenuCommand = MiniCommand.Create(name => + { + // This is for the "Show Main Window" dock menu item in the test. + // It doesn't actually show the main window, but sets the checkbox to true in the page. + var checkbox = _mainWindow!.GetLogicalDescendants().OfType().FirstOrDefault(x => x.Name == name); + if (checkbox != null) checkbox.IsChecked = true; + }); DataContext = this; } @@ -37,5 +46,21 @@ namespace IntegrationTestApp } public ICommand TrayIconCommand { get; } + public ICommand DockMenuCommand { get; } + + public void AddDockMenuItem(string header) + { + var dockMenu = NativeDock.GetMenu(this); + if (dockMenu is not null) + { + dockMenu.Items.Insert(0, new NativeMenuItem(header)); + } + } + + public int GetDockMenuItemCount() + { + var dockMenu = NativeDock.GetMenu(this); + return dockMenu?.Items.Count ?? 0; + } } } diff --git a/samples/IntegrationTestApp/Pages/DesktopPage.axaml b/samples/IntegrationTestApp/Pages/DesktopPage.axaml index a5495bd347..d7044df525 100644 --- a/samples/IntegrationTestApp/Pages/DesktopPage.axaml +++ b/samples/IntegrationTestApp/Pages/DesktopPage.axaml @@ -10,5 +10,11 @@