A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

289 lines
8.3 KiB

using System;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform;
using Avalonia.Dialogs;
using Avalonia.Input;
using Avalonia.Native.Interop;
using Avalonia.Native.Interop.Impl;
using Avalonia.Threading;
namespace Avalonia.Native
{
internal class AvaloniaNativeMenuExporter : ITopLevelNativeMenuExporter
{
private readonly IAvaloniaNativeFactory _factory;
private bool _resetQueued = true;
private bool _exported;
private readonly IAvnWindow _nativeWindow;
private NativeMenu _menu;
private __MicroComIAvnMenuProxy _nativeMenu;
private readonly IAvnTrayIcon _trayIcon;
private readonly IAvnApplicationCommands _applicationCommands;
public AvaloniaNativeMenuExporter(IAvnWindow nativeWindow, IAvaloniaNativeFactory factory)
{
_factory = factory;
_nativeWindow = nativeWindow;
_applicationCommands = _factory.CreateApplicationCommands();
DoLayoutReset();
}
public AvaloniaNativeMenuExporter(IAvaloniaNativeFactory factory)
{
_factory = factory;
_applicationCommands = _factory.CreateApplicationCommands();
DoLayoutReset();
}
public AvaloniaNativeMenuExporter(IAvnTrayIcon trayIcon, IAvaloniaNativeFactory factory)
{
_factory = factory;
_trayIcon = trayIcon;
_applicationCommands = _factory.CreateApplicationCommands();
DoLayoutReset();
}
public bool IsNativeMenuExported => _exported;
public event EventHandler OnIsNativeMenuExportedChanged { add { } remove { } }
public void SetNativeMenu(NativeMenu menu)
{
_menu = menu ?? new NativeMenu();
DoLayoutReset(true);
}
internal void UpdateIfNeeded()
{
if (_resetQueued)
{
DoLayoutReset();
}
}
private NativeMenu CreateDefaultAppMenu()
{
var result = new NativeMenu();
var aboutItem = new NativeMenuItem("About Avalonia");
aboutItem.Click += async (_, _) =>
{
var dialog = new AboutAvaloniaDialog();
if (Application.Current is
{ ApplicationLifetime: IClassicDesktopStyleApplicationLifetime { MainWindow: { } mainWindow } })
{
await dialog.ShowDialog(mainWindow);
}
};
result.Add(aboutItem);
return result;
}
private void PopulateStandardOSXMenuItems(NativeMenu appMenu)
{
appMenu.Add(new NativeMenuItemSeparator());
var servicesMenu = new NativeMenuItem("Services");
servicesMenu.Menu = new NativeMenu { [MacOSNativeMenuCommands.IsServicesSubmenuProperty] = true };
appMenu.Add(servicesMenu);
appMenu.Add(new NativeMenuItemSeparator());
var hideItem = new NativeMenuItem("Hide " + (Application.Current?.Name ?? "Application"))
{
Gesture = new KeyGesture(Key.H, KeyModifiers.Meta)
};
hideItem.Click += (_, _) =>
{
_applicationCommands.HideApp();
};
appMenu.Add(hideItem);
var hideOthersItem = new NativeMenuItem("Hide Others")
{
Gesture = new KeyGesture(Key.Q, KeyModifiers.Meta | KeyModifiers.Alt)
};
hideOthersItem.Click += (_, _) =>
{
_applicationCommands.HideOthers();
};
appMenu.Add(hideOthersItem);
var showAllItem = new NativeMenuItem("Show All");
showAllItem.Click += (_, _) =>
{
_applicationCommands.ShowAll();
};
appMenu.Add(showAllItem);
appMenu.Add(new NativeMenuItemSeparator());
var quitItem = new NativeMenuItem("Quit") { Gesture = new KeyGesture(Key.Q, KeyModifiers.Meta) };
quitItem.Click += (_, _) =>
{
if (Application.Current is { ApplicationLifetime: IClassicDesktopStyleApplicationLifetime lifetime })
{
lifetime.TryShutdown();
}
else if(Application.Current is {ApplicationLifetime: IControlledApplicationLifetime controlledLifetime})
{
controlledLifetime.Shutdown();
}
};
appMenu.Add(quitItem);
}
private void DoLayoutReset(bool forceUpdate = false)
{
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>() ?? new MacOSPlatformOptions();
if (macOpts.DisableNativeMenus)
{
return;
}
if (_resetQueued || forceUpdate)
{
_resetQueued = false;
if (_nativeWindow is null)
{
if (_trayIcon is null)
{
var appMenu = NativeMenu.GetMenu(Application.Current);
if (appMenu == null)
{
appMenu = CreateDefaultAppMenu();
NativeMenu.SetMenu(Application.Current, appMenu);
}
SetMenu(appMenu);
}
else if (_menu != null)
{
SetMenu(_trayIcon, _menu);
}
}
else
{
if (_menu != null)
{
SetMenu(_nativeWindow, _menu);
}
}
_exported = true;
}
}
internal void QueueReset()
{
if (_resetQueued)
return;
_resetQueued = true;
Dispatcher.UIThread.Post(() => DoLayoutReset(), DispatcherPriority.Background);
}
private void SetMenu(NativeMenu menu)
{
var menuItem = menu.Parent;
var appMenuHolder = menuItem?.Parent;
if (menuItem is null)
{
menuItem = new NativeMenuItem();
}
if (appMenuHolder is null)
{
appMenuHolder = new NativeMenu();
appMenuHolder.Add(menuItem);
}
menuItem.Menu = menu;
var setMenu = false;
if (_nativeMenu is null)
{
_nativeMenu = __MicroComIAvnMenuProxy.Create(_factory);
_nativeMenu.Initialize(this, appMenuHolder, "");
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>();
if (macOpts == null || !macOpts.DisableDefaultApplicationMenuItems)
{
PopulateStandardOSXMenuItems(menu);
}
setMenu = true;
}
_nativeMenu.Update(_factory, appMenuHolder);
if (setMenu)
{
_factory.SetAppMenu(_nativeMenu);
}
}
private void SetMenu(IAvnWindow avnWindow, NativeMenu menu)
{
var setMenu = false;
if (_nativeMenu is null)
{
_nativeMenu = __MicroComIAvnMenuProxy.Create(_factory);
_nativeMenu.Initialize(this, menu, "");
setMenu = true;
}
_nativeMenu.Update(_factory, menu);
if(setMenu)
{
avnWindow.SetMainMenu(_nativeMenu);
}
}
private void SetMenu(IAvnTrayIcon trayIcon, NativeMenu menu)
{
var setMenu = false;
if (_nativeMenu is null)
{
_nativeMenu = __MicroComIAvnMenuProxy.Create(_factory);
_nativeMenu.Initialize(this, menu, "");
setMenu = true;
}
_nativeMenu.Update(_factory, menu);
if(setMenu)
{
trayIcon.SetMenu(_nativeMenu);
}
}
}
}