Răsfoiți Sursa

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
yell0wsuit 1 an în urmă
părinte
comite
2b6cc4b353

+ 32 - 2
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();
             }
         }
+
+        /// <summary>
+        /// Converts a PlatformThemeVariant value to the corresponding ThemeVariant value.
+        /// </summary>
+        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;
+        }
     }
 }

+ 1 - 0
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",

+ 14 - 0
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);
+        }
+    }
+}

+ 34 - 16
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;
+            UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value);
+            Dispatcher.UIThread.InvokeAsync(DownloadPatronsJson);
 
-            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")));
-            }
+            ThemeManager.ThemeChanged += ThemeManager_ThemeChanged;
+        }
 
-            Dispatcher.UIThread.InvokeAsync(DownloadPatronsJson);
+        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()

+ 14 - 2
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;

+ 3 - 0
src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml

@@ -65,6 +65,9 @@
                         <ComboBox SelectedIndex="{Binding BaseStyleIndex}"
                                   HorizontalContentAlignment="Left"
                                   MinWidth="100">
+                            <ComboBoxItem>
+                                <TextBlock Text="{locale:Locale SettingsTabGeneralThemeAuto}" />
+                            </ComboBoxItem>
                             <ComboBoxItem>
                                 <TextBlock Text="{locale:Locale SettingsTabGeneralThemeLight}" />
                             </ComboBoxItem>

+ 29 - 0
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
             }
         }
 
+        /// <summary>
+        /// Event handler for detecting OS theme change when using "Follow OS theme" option
+        /// </summary>
+        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)
+            {
+                /// <summary>
+                /// Unsubscribe to the ColorValuesChanged event
+                /// </summary>
+                PlatformSettings.ColorValuesChanged -= OnPlatformColorValuesChanged;
+            }
+        }
+
         protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
             base.OnApplyTemplate(e);
@@ -390,6 +414,11 @@ namespace Ryujinx.Ava.UI.Windows
 
             Initialize();
 
+            /// <summary>
+            /// Subscribe to the ColorValuesChanged event
+            /// </summary>
+            PlatformSettings.ColorValuesChanged += OnPlatformColorValuesChanged;
+
             ViewModel.Initialize(
                 ContentManager,
                 StorageProvider,