UI - Add a monochrome tray icon option

- New "Monochrome tray icon" toggle under General settings
- Shows a black or white icon matching the light or dark mode of the system tray
- On Windows the color follows the taskbar (Windows mode), which can differ from the app mode
- On Linux the color follows the system's light or dark mode
- On macOS the icon is a template icon, so the menu bar colors it itself
- Icons come in 16 to 256px, including the sizes used for high-DPI tray scaling

Based on the idea and work in #867 by ArthurGamerHD.
This commit is contained in:
RobertBeekman committed 2026-10-27 20:29:08 +01:00
1 parent 5ddd50b4a8
commit 7574ad4ad6
13 files changed
+157 -3

No files matched your search

+1 -1
View File
@@ -14,7 +14,7 @@
<TrayIcon.Icons>
<TrayIcons>
<TrayIcon Icon="avares://Artemis.UI/Assets/Images/Logo/application.ico" ToolTipText="Artemis" Command="{CompiledBinding OpenScreen}">
<TrayIcon Icon="{CompiledBinding TrayIcon}" ToolTipText="Artemis" Command="{CompiledBinding OpenScreen}">
<TrayIcon.Menu>
<NativeMenu>
<NativeMenuItem Header="Home" Command="{CompiledBinding OpenScreen}" CommandParameter="home" />
+1 -1
View File
@@ -14,7 +14,7 @@
<TrayIcon.Icons>
<TrayIcons>
<TrayIcon Icon="avares://Artemis.UI/Assets/Images/Logo/application.ico" ToolTipText="Artemis" Command="{CompiledBinding OpenScreen}">
<TrayIcon Icon="{CompiledBinding TrayIcon}" MacOSProperties.IsTemplateIcon="{CompiledBinding MonochromeTrayIcon}" ToolTipText="Artemis" Command="{CompiledBinding OpenScreen}">
<TrayIcon.Menu>
<NativeMenu>
<NativeMenuItem Header="Home" Command="{CompiledBinding OpenScreen}" CommandParameter="home" />
+1 -1
View File
@@ -14,7 +14,7 @@
<TrayIcon.Icons>
<TrayIcons>
<TrayIcon Icon="avares://Artemis.UI/Assets/Images/Logo/application.ico" ToolTipText="Artemis" Command="{CompiledBinding OpenScreen}">
<TrayIcon Icon="{CompiledBinding TrayIcon}" ToolTipText="Artemis" Command="{CompiledBinding OpenScreen}">
<TrayIcon.Menu>
<NativeMenu>
<NativeMenuItem Header="Home" Command="{CompiledBinding OpenScreen}" CommandParameter="home" />
@@ -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<InputProvider, WindowsInputProvider>(serviceKey: WindowsInputProvider.Id);
container.Register<IUpdateNotificationProvider, WindowsUpdateNotificationProvider>();
container.Register<IProtocolProvider, ProtocolProvider>();
container.Register<ITrayThemeProvider, WindowsTrayThemeProvider>(Reuse.Singleton);
}
}
@@ -0,0 +1,49 @@
using System;
using Artemis.UI.Services.Tray;
using Microsoft.Win32;
using Serilog;
namespace Artemis.UI.Windows.Providers;
/// <summary>
/// Provides the light or dark mode of the Windows taskbar, which can differ from the app mode Avalonia reports.
/// </summary>
public class WindowsTrayThemeProvider : ITrayThemeProvider
{
private const string PersonalizeKey = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
/// <summary>
/// Initializes a new instance of the <see cref="WindowsTrayThemeProvider" /> class.
/// </summary>
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");
}
}
/// <inheritdoc />
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;
}
}
/// <inheritdoc />
public event EventHandler? TrayThemeChanged;
private void SystemEventsOnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
{
if (e.Category == UserPreferenceCategory.General)
TrayThemeChanged?.Invoke(this, EventArgs.Empty);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

@@ -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<NodeScriptWindowViewModelBase, NodeScriptWindowViewModel>(Reuse.Singleton);
container.Register<IPropertyVmFactory, PropertyVmFactory>(Reuse.Singleton);
container.Register<IUpdateNotificationProvider, BasicUpdateNotificationProvider>();
container.Register<ITrayThemeProvider, BasicTrayThemeProvider>(Reuse.Singleton);
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IArtemisUIService>(), Reuse.Singleton);
}
@@ -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<RoutableScreen>, IMainWindowProv
private readonly IUpdateService _updateService;
private readonly IWindowService _windowService;
private readonly ObservableAsPropertyHelper<ViewModelBase?> _titleBarViewModel;
private readonly PluginSetting<bool> _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<RoutableScreen>, IMainWindowProv
IDebugService debugService,
IUpdateService updateService,
IWorkshopService workshopService,
ITrayThemeProvider trayThemeProvider,
SidebarViewModel sidebarViewModel,
DefaultTitleBarViewModel defaultTitleBarViewModel)
{
@@ -60,8 +68,17 @@ public class RootViewModel : RoutableHostScreen<RoutableScreen>, 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<RoutableScreen>, IMainWindowProv
public ReactiveCommand<Unit, Unit> Exit { get; }
public ViewModelBase? TitleBarViewModel => _titleBarViewModel.Value;
/// <summary>
/// Gets a value indicating whether the tray icon is black or white rather than colored.
/// </summary>
public bool MonochromeTrayIcon => _monochromeTrayIcon.Value;
/// <summary>
/// Gets the tray icon, in black or white to match the system tray when <see cref="MonochromeTrayIcon" /> is enabled.
/// </summary>
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<WindowSize?>? WindowSizeSetting { get; private set; }
public void GoBack()
@@ -188,6 +225,11 @@ public class RootViewModel : RoutableHostScreen<RoutableScreen>, 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")));
}
/// <inheritdoc />
public bool IsMainWindowOpen => _lifeTime.MainWindow != null;
@@ -70,6 +70,19 @@
</StackPanel>
</Grid>
<Border Classes="card-separator" IsVisible="{CompiledBinding IsWindows11}"/>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Monochrome tray icon</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Show a black or white icon in the system tray instead of the colored one, matching your light or dark mode.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding UIMonochromeTrayIcon.Value}" OnContent="Yes" OffContent="No" MinWidth="0" Margin="0 -10" />
</StackPanel>
</Grid>
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
@@ -159,6 +159,7 @@ public class GeneralTabViewModel : RoutableScreen
public PluginSetting<int> UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15);
public PluginSetting<bool> UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true);
public PluginSetting<bool> EnableMica => _settingsService.GetSetting("UI.EnableMica", true);
public PluginSetting<bool> UIMonochromeTrayIcon => _settingsService.GetSetting("UI.MonochromeTrayIcon", false);
public PluginSetting<bool> UICheckForUpdates => _settingsService.GetSetting("UI.Updating.AutoCheck", true);
public PluginSetting<bool> WorkshopShowNotifications => _settingsService.GetSetting("Workshop.ShowNotifications", true);
public PluginSetting<bool> UIAutoUpdate => _settingsService.GetSetting("UI.Updating.AutoInstall", true);
@@ -0,0 +1,26 @@
using System;
using Avalonia;
using Avalonia.Platform;
namespace Artemis.UI.Services.Tray;
/// <summary>
/// Provides the light or dark mode of the system tray based on the light or dark mode of the operating system.
/// </summary>
public class BasicTrayThemeProvider : ITrayThemeProvider
{
/// <summary>
/// Initializes a new instance of the <see cref="BasicTrayThemeProvider" /> class.
/// </summary>
public BasicTrayThemeProvider()
{
if (Application.Current?.PlatformSettings is { } platformSettings)
platformSettings.ColorValuesChanged += (_, _) => TrayThemeChanged?.Invoke(this, EventArgs.Empty);
}
/// <inheritdoc />
public bool IsTrayDark => Application.Current?.PlatformSettings?.GetColorValues().ThemeVariant != PlatformThemeVariant.Light;
/// <inheritdoc />
public event EventHandler? TrayThemeChanged;
}
@@ -0,0 +1,19 @@
using System;
namespace Artemis.UI.Services.Tray;
/// <summary>
/// Provides the light or dark mode of the system tray.
/// </summary>
public interface ITrayThemeProvider
{
/// <summary>
/// Gets a value indicating whether the system tray has a dark background.
/// </summary>
bool IsTrayDark { get; }
/// <summary>
/// Occurs when the background of the system tray may have changed, possibly on a background thread.
/// </summary>
event EventHandler? TrayThemeChanged;
}