refactor: completely overhaul app icon management for cleaner workflow

This commit is contained in:
VewDev
2025-09-01 14:24:48 +02:00
parent ab4567d0a2
commit 362fbf08a2
3 changed files with 85 additions and 38 deletions

View File

@@ -1,28 +1,84 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Ryujinx.Ava.Common.Models;
using Ryujinx.Ava.Systems.Configuration; using Ryujinx.Ava.Systems.Configuration;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Common;
using System.Reflection; using Svg.Skia;
using System;
using System.IO;
using System.Linq;
namespace Ryujinx.Ava.UI.Controls namespace Ryujinx.Ava.UI.Controls
{ {
public class RyujinxLogo : Image public class RyujinxLogo : Image
{ {
// The UI specifically uses a thicker bordered variant of the icon to avoid crunching out the border at lower resolutions. public static ReactiveObject<Bitmap> CurrentLogoBitmap { get; private set; } = new();
// For an example of this, download canary 1.2.95, then open the settings menu, and look at the icon in the top-left.
// The border gets reduced to colored pixels in the 4 corners.
public static readonly Bitmap Bitmap =
new(Assembly.GetAssembly(typeof(MainWindowViewModel))!
.GetManifestResourceStream("Ryujinx.Assets.UIImages.Logo_Ryujinx_AntiAlias.png")!);
public RyujinxLogo() public RyujinxLogo()
{ {
Margin = new Thickness(7, 7, 7, 0); Margin = new Thickness(7, 7, 7, 0);
Height = 25; Height = 25;
Width = 25; Width = 25;
Source = Bitmap; Source = CurrentLogoBitmap.Value;
IsVisible = !ConfigurationState.Instance.ShowOldUI; IsVisible = !ConfigurationState.Instance.ShowOldUI;
ConfigurationState.Instance.UI.SelectedWindowIcon.Event += WindowIconChanged_Event;
} }
public static void RefreshAppIconFromSettings()
{
SetNewAppIcon(ConfigurationState.Instance.UI.SelectedWindowIcon.Value);
}
private static void SetNewAppIcon(string newIconName)
{
string defaultIconName = "Bordered Ryupride";
if (string.IsNullOrEmpty(newIconName))
{
SetDefaultAppIcon(defaultIconName);
}
ApplicationIcon selectedIcon = RyujinxApp.AvailableApplicationIcons.FirstOrDefault(x => x.Name == newIconName);
if (selectedIcon == null)
{
// Always try to fallback to "Bordered Ryupride" as a default
// If not found, fallback to first found icon
if (newIconName != defaultIconName)
{
SetDefaultAppIcon(defaultIconName);
return;
}
if (RyujinxApp.AvailableApplicationIcons.Count > 0)
{
SetDefaultAppIcon(RyujinxApp.AvailableApplicationIcons.First().Name);
return;
}
}
Stream activeIconStream = EmbeddedResources.GetStream(selectedIcon.FullPath);
if (activeIconStream != null)
{
// SVG files need to be converted to an image first
if (selectedIcon.FullPath.EndsWith(".svg", StringComparison.OrdinalIgnoreCase))
{
// TODO: Convert SVG to a bitmap
using SKSvg svg = new();
}
else
{
CurrentLogoBitmap.Value = new Bitmap(activeIconStream);
}
}
}
private static void SetDefaultAppIcon(string defaultIconName)
{
// Doing this triggers the WindowIconChanged_Event, which will then
// call SetNewAppIcon again
ConfigurationState.Instance.UI.SelectedWindowIcon.Value = defaultIconName;
}
private void WindowIconChanged_Event(object _, ReactiveEventArgs<string> rArgs) => SetNewAppIcon(rArgs.NewValue);
} }
} }

View File

@@ -2,7 +2,6 @@ using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Threading; using Avalonia.Threading;
@@ -11,6 +10,7 @@ using Gommon;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Common.Models; using Ryujinx.Ava.Common.Models;
using Ryujinx.Ava.Systems.Configuration; using Ryujinx.Ava.Systems.Configuration;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Views.Dialog; using Ryujinx.Ava.UI.Views.Dialog;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
@@ -58,6 +58,7 @@ namespace Ryujinx.Ava
Name = FormatTitle(); Name = FormatTitle();
RetrieveAvailableAppIcons(); RetrieveAvailableAppIcons();
RyujinxLogo.RefreshAppIconFromSettings();
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
@@ -80,9 +81,6 @@ namespace Ryujinx.Ava
{ {
ApplyConfiguredTheme(ConfigurationState.Instance.UI.BaseStyle); ApplyConfiguredTheme(ConfigurationState.Instance.UI.BaseStyle);
ConfigurationState.Instance.UI.BaseStyle.Event += ThemeChanged_Event; ConfigurationState.Instance.UI.BaseStyle.Event += ThemeChanged_Event;
UpdateAppIcon(ConfigurationState.Instance.UI.SelectedWindowIcon);
ConfigurationState.Instance.UI.SelectedWindowIcon.Event += WindowIconChanged_Event;
} }
} }
@@ -182,28 +180,5 @@ namespace Ryujinx.Ava
}); });
} }
} }
public static void UpdateAppIcon(string newIconName)
{
if (newIconName.IsNullOrEmpty())
{
newIconName = "Classic Ryugay";
}
ApplicationIcon selectedIcon = AvailableApplicationIcons.FirstOrDefault(x => x.Name == newIconName);
if (selectedIcon == null)
{
// Fallback to default icon if the selected one is not found
UpdateAppIcon("Classic Ryugay");
}
Stream activeIconStream = EmbeddedResources.GetStream(selectedIcon.FullPath);
if (activeIconStream != null)
{
MainWindow.Icon = new Bitmap(activeIconStream);
}
}
private void WindowIconChanged_Event(object _, ReactiveEventArgs<string> rArgs) => UpdateAppIcon(rArgs.NewValue);
} }
} }

View File

@@ -3,11 +3,13 @@ using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using FluentAvalonia.UI.Windowing; using FluentAvalonia.UI.Windowing;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Systems.Configuration; using Ryujinx.Ava.Systems.Configuration;
using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Controls;
using Ryujinx.Common;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Windows namespace Ryujinx.Ava.UI.Windows
@@ -39,7 +41,8 @@ namespace Ryujinx.Ava.UI.Windows
TitleBar.Height = titleBarHeight.Value; TitleBar.Height = titleBarHeight.Value;
} }
Icon = RyujinxLogo.Bitmap; Icon = RyujinxLogo.CurrentLogoBitmap.Value;
RyujinxLogo.CurrentLogoBitmap.Event += WindowIconChanged_Event;
} }
private void LocaleChanged() private void LocaleChanged()
@@ -53,6 +56,12 @@ namespace Ryujinx.Ava.UI.Windows
ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome | ExtendClientAreaChromeHints.OSXThickTitleBar; ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome | ExtendClientAreaChromeHints.OSXThickTitleBar;
} }
private void WindowIconChanged_Event(object _, ReactiveEventArgs<Bitmap> rArgs) => UpdateIcon(rArgs.NewValue);
private void UpdateIcon(Bitmap newIcon)
{
Icon = newIcon;
}
} }
public abstract class StyleableWindow : Window public abstract class StyleableWindow : Window
@@ -73,7 +82,8 @@ namespace Ryujinx.Ava.UI.Windows
LocaleManager.Instance.LocaleChanged += LocaleChanged; LocaleManager.Instance.LocaleChanged += LocaleChanged;
LocaleChanged(); LocaleChanged();
Icon = new WindowIcon(RyujinxLogo.Bitmap); Icon = new WindowIcon(RyujinxLogo.CurrentLogoBitmap.Value);
RyujinxLogo.CurrentLogoBitmap.Event += WindowIconChanged_Event;
} }
private void LocaleChanged() private void LocaleChanged()
@@ -87,5 +97,11 @@ namespace Ryujinx.Ava.UI.Windows
ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome | ExtendClientAreaChromeHints.OSXThickTitleBar; ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome | ExtendClientAreaChromeHints.OSXThickTitleBar;
} }
private void WindowIconChanged_Event(object _, ReactiveEventArgs<Bitmap> rArgs) => UpdateIcon(rArgs.NewValue);
private void UpdateIcon(Bitmap newIcon)
{
Icon = new WindowIcon(newIcon);
}
} }
} }