diff --git a/src/Artemis.UI.Linux/App.axaml b/src/Artemis.UI.Linux/App.axaml index d2109667a..94a041f7a 100644 --- a/src/Artemis.UI.Linux/App.axaml +++ b/src/Artemis.UI.Linux/App.axaml @@ -14,7 +14,7 @@ - + diff --git a/src/Artemis.UI.MacOS/App.axaml b/src/Artemis.UI.MacOS/App.axaml index 5a000915e..9ea5abac2 100644 --- a/src/Artemis.UI.MacOS/App.axaml +++ b/src/Artemis.UI.MacOS/App.axaml @@ -14,7 +14,7 @@ - + diff --git a/src/Artemis.UI.Windows/App.axaml b/src/Artemis.UI.Windows/App.axaml index 63ff0b8a1..f35f8ffde 100644 --- a/src/Artemis.UI.Windows/App.axaml +++ b/src/Artemis.UI.Windows/App.axaml @@ -14,7 +14,7 @@ - + diff --git a/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs b/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs index a2906d46c..2e8b4f424 100644 --- a/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs +++ b/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs @@ -1,5 +1,6 @@ using Artemis.Core.Providers; using Artemis.Core.Services; +using Artemis.UI.Services.Tray; using Artemis.UI.Services.Updating; using Artemis.UI.Shared.Providers; using Artemis.UI.Windows.Providers; @@ -25,5 +26,6 @@ public static class UIContainerExtensions container.Register(serviceKey: WindowsInputProvider.Id); container.Register(); container.Register(); + container.Register(Reuse.Singleton); } } \ No newline at end of file diff --git a/src/Artemis.UI.Windows/Providers/WindowsTrayThemeProvider.cs b/src/Artemis.UI.Windows/Providers/WindowsTrayThemeProvider.cs new file mode 100644 index 000000000..b525b3832 --- /dev/null +++ b/src/Artemis.UI.Windows/Providers/WindowsTrayThemeProvider.cs @@ -0,0 +1,49 @@ +using System; +using Artemis.UI.Services.Tray; +using Microsoft.Win32; +using Serilog; + +namespace Artemis.UI.Windows.Providers; + +/// +/// Provides the light or dark mode of the Windows taskbar, which can differ from the app mode Avalonia reports. +/// +public class WindowsTrayThemeProvider : ITrayThemeProvider +{ + private const string PersonalizeKey = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"; + + /// + /// Initializes a new instance of the class. + /// + public WindowsTrayThemeProvider(ILogger logger) + { + try + { + SystemEvents.UserPreferenceChanged += SystemEventsOnUserPreferenceChanged; + } + catch (Exception e) + { + logger.Warning(e, "Could not subscribe to system events, the tray icon won't follow changes to the taskbar color"); + } + } + + /// + public bool IsTrayDark + { + get + { + // Versions of Windows without the setting only have a dark taskbar + using RegistryKey? key = Registry.CurrentUser.OpenSubKey(PersonalizeKey); + return key?.GetValue("SystemUsesLightTheme") is not 1; + } + } + + /// + public event EventHandler? TrayThemeChanged; + + private void SystemEventsOnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) + { + if (e.Category == UserPreferenceCategory.General) + TrayThemeChanged?.Invoke(this, EventArgs.Empty); + } +} diff --git a/src/Artemis.UI/Assets/Images/Logo/application-monochrome-black.ico b/src/Artemis.UI/Assets/Images/Logo/application-monochrome-black.ico new file mode 100644 index 000000000..0d4c13e23 Binary files /dev/null and b/src/Artemis.UI/Assets/Images/Logo/application-monochrome-black.ico differ diff --git a/src/Artemis.UI/Assets/Images/Logo/application-monochrome-white.ico b/src/Artemis.UI/Assets/Images/Logo/application-monochrome-white.ico new file mode 100644 index 000000000..91399e5ed Binary files /dev/null and b/src/Artemis.UI/Assets/Images/Logo/application-monochrome-white.ico differ diff --git a/src/Artemis.UI/DryIoc/ContainerExtensions.cs b/src/Artemis.UI/DryIoc/ContainerExtensions.cs index bcf4a20b0..7871e46c8 100644 --- a/src/Artemis.UI/DryIoc/ContainerExtensions.cs +++ b/src/Artemis.UI/DryIoc/ContainerExtensions.cs @@ -4,6 +4,7 @@ using Artemis.UI.DryIoc.InstanceProviders; using Artemis.UI.Screens.Device.Layout.LayoutProviders; using Artemis.UI.Screens.VisualScripting; using Artemis.UI.Services.Interfaces; +using Artemis.UI.Services.Tray; using Artemis.UI.Services.Updating; using Artemis.UI.Shared; using Artemis.UI.Shared.Services.NodeEditor; @@ -33,6 +34,7 @@ public static class ContainerExtensions container.Register(Reuse.Singleton); container.Register(Reuse.Singleton); container.Register(); + container.Register(Reuse.Singleton); container.RegisterMany(thisAssembly, type => type.IsAssignableTo(), Reuse.Singleton); } diff --git a/src/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Artemis.UI/Screens/Root/RootViewModel.cs index 55c2051a2..f3e4b8f83 100644 --- a/src/Artemis.UI/Screens/Root/RootViewModel.cs +++ b/src/Artemis.UI/Screens/Root/RootViewModel.cs @@ -7,6 +7,7 @@ using Artemis.Core.Services; using Artemis.UI.Models; using Artemis.UI.Screens.Sidebar; using Artemis.UI.Services.Interfaces; +using Artemis.UI.Services.Tray; using Artemis.UI.Services.Updating; using Artemis.UI.Shared; using Artemis.UI.Shared.Routing; @@ -16,6 +17,7 @@ using Artemis.WebClient.Workshop.Services; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform; using Avalonia.Threading; using ReactiveUI; using Serilog; @@ -34,6 +36,11 @@ public class RootViewModel : RoutableHostScreen, IMainWindowProv private readonly IUpdateService _updateService; private readonly IWindowService _windowService; private readonly ObservableAsPropertyHelper _titleBarViewModel; + private readonly PluginSetting _monochromeTrayIcon; + private readonly ITrayThemeProvider _trayThemeProvider; + private WindowIcon? _coloredTrayIcon; + private WindowIcon? _blackTrayIcon; + private WindowIcon? _whiteTrayIcon; public RootViewModel(ILogger logger, IRouter router, @@ -45,6 +52,7 @@ public class RootViewModel : RoutableHostScreen, IMainWindowProv IDebugService debugService, IUpdateService updateService, IWorkshopService workshopService, + ITrayThemeProvider trayThemeProvider, SidebarViewModel sidebarViewModel, DefaultTitleBarViewModel defaultTitleBarViewModel) { @@ -60,8 +68,17 @@ public class RootViewModel : RoutableHostScreen, IMainWindowProv _debugService = debugService; _updateService = updateService; _defaultTitleBarViewModel = defaultTitleBarViewModel; + _trayThemeProvider = trayThemeProvider; _lifeTime = (IClassicDesktopStyleApplicationLifetime) Application.Current!.ApplicationLifetime!; + _monochromeTrayIcon = settingsService.GetSetting("UI.MonochromeTrayIcon", false); + _monochromeTrayIcon.SettingChanged += (_, _) => + { + this.RaisePropertyChanged(nameof(MonochromeTrayIcon)); + this.RaisePropertyChanged(nameof(TrayIcon)); + }; + _trayThemeProvider.TrayThemeChanged += (_, _) => Dispatcher.UIThread.Post(() => this.RaisePropertyChanged(nameof(TrayIcon))); + router.SetRoot(this); mainWindowService.ConfigureMainWindowProvider(this); @@ -117,6 +134,26 @@ public class RootViewModel : RoutableHostScreen, IMainWindowProv public ReactiveCommand Exit { get; } public ViewModelBase? TitleBarViewModel => _titleBarViewModel.Value; + + /// + /// Gets a value indicating whether the tray icon is black or white rather than colored. + /// + public bool MonochromeTrayIcon => _monochromeTrayIcon.Value; + + /// + /// Gets the tray icon, in black or white to match the system tray when is enabled. + /// + public WindowIcon TrayIcon + { + get + { + if (!_monochromeTrayIcon.Value) + return _coloredTrayIcon ??= LoadTrayIcon("application"); + return _trayThemeProvider.IsTrayDark + ? _whiteTrayIcon ??= LoadTrayIcon("application-monochrome-white") + : _blackTrayIcon ??= LoadTrayIcon("application-monochrome-black"); + } + } public static PluginSetting? WindowSizeSetting { get; private set; } public void GoBack() @@ -188,6 +225,11 @@ public class RootViewModel : RoutableHostScreen, IMainWindowProv #region Implementation of IMainWindowProvider + private static WindowIcon LoadTrayIcon(string name) + { + return new WindowIcon(AssetLoader.Open(new Uri($"avares://Artemis.UI/Assets/Images/Logo/{name}.ico"))); + } + /// public bool IsMainWindowOpen => _lifeTime.MainWindow != null; diff --git a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml index 537a950b2..ab96d77b3 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml @@ -70,6 +70,19 @@ + + + + Monochrome tray icon + + Show a black or white icon in the system tray instead of the colored one, matching your light or dark mode. + + + + + + + diff --git a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs index a22f8f5f1..61d4cfc14 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs @@ -159,6 +159,7 @@ public class GeneralTabViewModel : RoutableScreen public PluginSetting UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15); public PluginSetting UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true); public PluginSetting EnableMica => _settingsService.GetSetting("UI.EnableMica", true); + public PluginSetting UIMonochromeTrayIcon => _settingsService.GetSetting("UI.MonochromeTrayIcon", false); public PluginSetting UICheckForUpdates => _settingsService.GetSetting("UI.Updating.AutoCheck", true); public PluginSetting WorkshopShowNotifications => _settingsService.GetSetting("Workshop.ShowNotifications", true); public PluginSetting UIAutoUpdate => _settingsService.GetSetting("UI.Updating.AutoInstall", true); diff --git a/src/Artemis.UI/Services/Tray/BasicTrayThemeProvider.cs b/src/Artemis.UI/Services/Tray/BasicTrayThemeProvider.cs new file mode 100644 index 000000000..cdc3fbd11 --- /dev/null +++ b/src/Artemis.UI/Services/Tray/BasicTrayThemeProvider.cs @@ -0,0 +1,26 @@ +using System; +using Avalonia; +using Avalonia.Platform; + +namespace Artemis.UI.Services.Tray; + +/// +/// Provides the light or dark mode of the system tray based on the light or dark mode of the operating system. +/// +public class BasicTrayThemeProvider : ITrayThemeProvider +{ + /// + /// Initializes a new instance of the class. + /// + public BasicTrayThemeProvider() + { + if (Application.Current?.PlatformSettings is { } platformSettings) + platformSettings.ColorValuesChanged += (_, _) => TrayThemeChanged?.Invoke(this, EventArgs.Empty); + } + + /// + public bool IsTrayDark => Application.Current?.PlatformSettings?.GetColorValues().ThemeVariant != PlatformThemeVariant.Light; + + /// + public event EventHandler? TrayThemeChanged; +} diff --git a/src/Artemis.UI/Services/Tray/ITrayThemeProvider.cs b/src/Artemis.UI/Services/Tray/ITrayThemeProvider.cs new file mode 100644 index 000000000..a68338701 --- /dev/null +++ b/src/Artemis.UI/Services/Tray/ITrayThemeProvider.cs @@ -0,0 +1,19 @@ +using System; + +namespace Artemis.UI.Services.Tray; + +/// +/// Provides the light or dark mode of the system tray. +/// +public interface ITrayThemeProvider +{ + /// + /// Gets a value indicating whether the system tray has a dark background. + /// + bool IsTrayDark { get; } + + /// + /// Occurs when the background of the system tray may have changed, possibly on a background thread. + /// + event EventHandler? TrayThemeChanged; +}