From 2b6cc4b3536693d222b695295c9db4715ca3f570 Mon Sep 17 00:00:00 2001
From: yell0wsuit <5692900+yell0wsuit@users.noreply.github.com>
Date: Tue, 14 May 2024 22:00:03 +0700
Subject: [PATCH] Add the "Auto" theme option in setting (#6611)
* Add "Follow OS theme" option
* Update App.axaml.cs
* Add "Follow OS theme" option
* Update App.axaml.cs
* Remove `this`
* Remove annotation for nullable reference
* Change into switch expression to make it concise
* Change comments to XML docs
* Update en_US.json
* Fix icons in About dialog do not response to "auto" theme
The theme icons seemingly use Dark variant event when the OS theme is light. In addition, I added ThemeManager common to make it accessible for both App and AboutWindow
* Newline at the end
* newline moment
* Update ThemeManager.cs
* bait to switch to lf
* change to lf
* temp. revert
* Add back ThemeManager.cs common, pls pass the format check
* I found the mistake: should have put `ThemeManager.OnThemeChanged();` in try block
Finally solve the formatting check
* test formatting
* Update App.axaml.cs
* Ok i seem to forget to add version lol
* Fix info CA1816
---
src/Ryujinx/App.axaml.cs | 34 +++++++++++-
src/Ryujinx/Assets/Locales/en_US.json | 1 +
src/Ryujinx/Common/ThemeManager.cs | 14 +++++
.../UI/ViewModels/AboutWindowViewModel.cs | 52 +++++++++++++------
.../UI/ViewModels/SettingsViewModel.cs | 16 +++++-
.../UI/Views/Settings/SettingsUIView.axaml | 3 ++
src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 29 +++++++++++
7 files changed, 128 insertions(+), 21 deletions(-)
create mode 100644 src/Ryujinx/Common/ThemeManager.cs
diff --git a/src/Ryujinx/App.axaml.cs b/src/Ryujinx/App.axaml.cs
index 387a6dc1..24d8a70a 100644
--- a/src/Ryujinx/App.axaml.cs
+++ b/src/Ryujinx/App.axaml.cs
@@ -1,8 +1,10 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
+using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Threading;
+using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
@@ -84,7 +86,7 @@ namespace Ryujinx.Ava
ApplyConfiguredTheme();
}
- private void ApplyConfiguredTheme()
+ public void ApplyConfiguredTheme()
{
try
{
@@ -92,13 +94,18 @@ namespace Ryujinx.Ava
if (string.IsNullOrWhiteSpace(baseStyle))
{
- ConfigurationState.Instance.UI.BaseStyle.Value = "Dark";
+ ConfigurationState.Instance.UI.BaseStyle.Value = "Auto";
baseStyle = ConfigurationState.Instance.UI.BaseStyle;
}
+ ThemeVariant systemTheme = DetectSystemTheme();
+
+ ThemeManager.OnThemeChanged();
+
RequestedThemeVariant = baseStyle switch
{
+ "Auto" => systemTheme,
"Light" => ThemeVariant.Light,
"Dark" => ThemeVariant.Dark,
_ => ThemeVariant.Default,
@@ -111,5 +118,28 @@ namespace Ryujinx.Ava
ShowRestartDialog();
}
}
+
+ ///
+ /// Converts a PlatformThemeVariant value to the corresponding ThemeVariant value.
+ ///
+ public static ThemeVariant ConvertThemeVariant(PlatformThemeVariant platformThemeVariant) =>
+ platformThemeVariant switch
+ {
+ PlatformThemeVariant.Dark => ThemeVariant.Dark,
+ PlatformThemeVariant.Light => ThemeVariant.Light,
+ _ => ThemeVariant.Default,
+ };
+
+ public static ThemeVariant DetectSystemTheme()
+ {
+ if (Application.Current is App app)
+ {
+ var colorValues = app.PlatformSettings.GetColorValues();
+
+ return ConvertThemeVariant(colorValues.ThemeVariant);
+ }
+
+ return ThemeVariant.Default;
+ }
}
}
diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json
index 2299d9e6..8df0f96a 100644
--- a/src/Ryujinx/Assets/Locales/en_US.json
+++ b/src/Ryujinx/Assets/Locales/en_US.json
@@ -404,6 +404,7 @@
"GameListContextMenuToggleFavorite": "Toggle Favorite",
"GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game",
"SettingsTabGeneralTheme": "Theme:",
+ "SettingsTabGeneralThemeAuto": "Auto",
"SettingsTabGeneralThemeDark": "Dark",
"SettingsTabGeneralThemeLight": "Light",
"ControllerSettingsConfigureGeneral": "Configure",
diff --git a/src/Ryujinx/Common/ThemeManager.cs b/src/Ryujinx/Common/ThemeManager.cs
new file mode 100644
index 00000000..8c52c2a6
--- /dev/null
+++ b/src/Ryujinx/Common/ThemeManager.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Ryujinx.Ava.Common
+{
+ public static class ThemeManager
+ {
+ public static event EventHandler ThemeChanged;
+
+ public static void OnThemeChanged()
+ {
+ ThemeChanged?.Invoke(null, EventArgs.Empty);
+ }
+ }
+}
diff --git a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs
index 6020f40e..f8fd5b7d 100644
--- a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs
@@ -1,6 +1,8 @@
using Avalonia.Media.Imaging;
using Avalonia.Platform;
+using Avalonia.Styling;
using Avalonia.Threading;
+using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Utilities;
using Ryujinx.UI.Common.Configuration;
@@ -11,7 +13,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.ViewModels
{
- public class AboutWindowViewModel : BaseModel
+ public class AboutWindowViewModel : BaseModel, IDisposable
{
private Bitmap _githubLogo;
private Bitmap _discordLogo;
@@ -86,23 +88,39 @@ namespace Ryujinx.Ava.UI.ViewModels
public AboutWindowViewModel()
{
Version = Program.Version;
-
- if (ConfigurationState.Instance.UI.BaseStyle.Value == "Light")
- {
- GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_GitHub_Light.png?assembly=Ryujinx.UI.Common")));
- DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Discord_Light.png?assembly=Ryujinx.UI.Common")));
- PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Patreon_Light.png?assembly=Ryujinx.UI.Common")));
- TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Twitter_Light.png?assembly=Ryujinx.UI.Common")));
- }
- else
- {
- GithubLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_GitHub_Dark.png?assembly=Ryujinx.UI.Common")));
- DiscordLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Discord_Dark.png?assembly=Ryujinx.UI.Common")));
- PatreonLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Patreon_Dark.png?assembly=Ryujinx.UI.Common")));
- TwitterLogo = new Bitmap(AssetLoader.Open(new Uri("resm:Ryujinx.UI.Common.Resources.Logo_Twitter_Dark.png?assembly=Ryujinx.UI.Common")));
- }
-
+ UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value);
Dispatcher.UIThread.InvokeAsync(DownloadPatronsJson);
+
+ ThemeManager.ThemeChanged += ThemeManager_ThemeChanged;
+ }
+
+ private void ThemeManager_ThemeChanged(object sender, EventArgs e)
+ {
+ Dispatcher.UIThread.Post(() => UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value));
+ }
+
+ private void UpdateLogoTheme(string theme)
+ {
+ bool isDarkTheme = theme == "Dark" || (theme == "Auto" && App.DetectSystemTheme() == ThemeVariant.Dark);
+
+ string basePath = "resm:Ryujinx.UI.Common.Resources.";
+ string themeSuffix = isDarkTheme ? "Dark.png" : "Light.png";
+
+ GithubLogo = LoadBitmap($"{basePath}Logo_GitHub_{themeSuffix}?assembly=Ryujinx.UI.Common");
+ DiscordLogo = LoadBitmap($"{basePath}Logo_Discord_{themeSuffix}?assembly=Ryujinx.UI.Common");
+ PatreonLogo = LoadBitmap($"{basePath}Logo_Patreon_{themeSuffix}?assembly=Ryujinx.UI.Common");
+ TwitterLogo = LoadBitmap($"{basePath}Logo_Twitter_{themeSuffix}?assembly=Ryujinx.UI.Common");
+ }
+
+ private Bitmap LoadBitmap(string uri)
+ {
+ return new Bitmap(Avalonia.Platform.AssetLoader.Open(new Uri(uri)));
+ }
+
+ public void Dispose()
+ {
+ ThemeManager.ThemeChanged -= ThemeManager_ThemeChanged;
+ GC.SuppressFinalize(this);
}
private async Task DownloadPatronsJson()
diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
index 0f43d0f7..70e5fa5c 100644
--- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs
@@ -397,7 +397,13 @@ namespace Ryujinx.Ava.UI.ViewModels
GameDirectories.Clear();
GameDirectories.AddRange(config.UI.GameDirs.Value);
- BaseStyleIndex = config.UI.BaseStyle == "Light" ? 0 : 1;
+ BaseStyleIndex = config.UI.BaseStyle.Value switch
+ {
+ "Auto" => 0,
+ "Light" => 1,
+ "Dark" => 2,
+ _ => 0
+ };
// Input
EnableDockedMode = config.System.EnableDockedMode;
@@ -486,7 +492,13 @@ namespace Ryujinx.Ava.UI.ViewModels
config.UI.GameDirs.Value = gameDirs;
}
- config.UI.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark";
+ config.UI.BaseStyle.Value = BaseStyleIndex switch
+ {
+ 0 => "Auto",
+ 1 => "Light",
+ 2 => "Dark",
+ _ => "Auto"
+ };
// Input
config.System.EnableDockedMode.Value = EnableDockedMode;
diff --git a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml
index b60058fc..f9b9be44 100644
--- a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml
+++ b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml
@@ -65,6 +65,9 @@
+
+
+
diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs
index b1b7a485..7de8a49a 100644
--- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs
+++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs
@@ -2,6 +2,7 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
+using Avalonia.Platform;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common;
@@ -92,6 +93,29 @@ namespace Ryujinx.Ava.UI.Windows
}
}
+ ///
+ /// Event handler for detecting OS theme change when using "Follow OS theme" option
+ ///
+ private void OnPlatformColorValuesChanged(object sender, PlatformColorValues e)
+ {
+ if (Application.Current is App app)
+ {
+ app.ApplyConfiguredTheme();
+ }
+ }
+
+ protected override void OnClosed(EventArgs e)
+ {
+ base.OnClosed(e);
+ if (PlatformSettings != null)
+ {
+ ///
+ /// Unsubscribe to the ColorValuesChanged event
+ ///
+ PlatformSettings.ColorValuesChanged -= OnPlatformColorValuesChanged;
+ }
+ }
+
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
@@ -390,6 +414,11 @@ namespace Ryujinx.Ava.UI.Windows
Initialize();
+ ///
+ /// Subscribe to the ColorValuesChanged event
+ ///
+ PlatformSettings.ColorValuesChanged += OnPlatformColorValuesChanged;
+
ViewModel.Initialize(
ContentManager,
StorageProvider,