Compare commits

...

14 Commits

Author SHA1 Message Date
VewDev
a486cc0694 Merge branch 'allow-change-icon' into 'master'
feat: add ability to change app icon

See merge request [ryubing/ryujinx!128](https://git.ryujinx.app/ryubing/ryujinx/-/merge_requests/128)
2026-02-19 11:24:36 -06:00
BowedCascade
d1205dc95d Fix backslash key not mappable in controller settings (ryubing/ryujinx!265)
See merge request ryubing/ryujinx!265
2026-02-18 18:13:15 -06:00
Awesomeangotti
6f95172bb6 Compatability Data Update (ryubing/ryujinx!264)
See merge request ryubing/ryujinx!264
2026-02-17 19:24:01 -06:00
Princess Piplup
8208d43d9e compatiblity/2026-02-17 (ryubing/ryujinx!263)
See merge request ryubing/ryujinx!263
2026-02-17 18:57:50 -06:00
VewDev
1ce1b6f5f2 fix: reload titlebar icon when changing icons
Reload the title bar icon when a new icon is selected in the new "new Ryubing UI" mode.
2025-10-18 09:08:26 +02:00
VewDev
2c727c57bd fix: update translation for app icon instructions 2025-09-04 16:16:25 +02:00
VewDev
e8764a8910 feat: add explanatory tooltip about top left icon reload 2025-09-02 16:56:47 +02:00
VewDev
0bdd4ad091 ui: show icon preview next to name during icon selection 2025-09-02 12:22:33 +02:00
VewDev
8e2f8f4413 feat: implement SVG to PNG conversion for app icon rendering 2025-09-01 15:07:35 +02:00
VewDev
362fbf08a2 refactor: completely overhaul app icon management for cleaner workflow 2025-09-01 14:24:48 +02:00
VewDev
ab4567d0a2 feat: add Bordered Ryugay icon and rename old Ryugay icons to Ryupride 2025-09-01 11:49:16 +02:00
VewDev
1c6f312a27 feat: add new Ryubi app icon 2025-08-29 20:43:05 +02:00
VewDev
8ee675cd62 feat: add fallback to Classic Ryugay for app icon 2025-08-29 20:40:46 +02:00
VewDev
af0f8e2720 feat: add ability to change app icon
Add ability to change application icon to any image file inside the src\Ryujinx\Assets\Icons\AppIcons directory. The app should automatically load any resource in that folder as a selectable icon.
2025-08-29 13:56:28 +02:00
22 changed files with 348 additions and 13 deletions

View File

@@ -12025,6 +12025,56 @@
"zh_TW": "淺色" "zh_TW": "淺色"
} }
}, },
{
"ID": "SettingsTabGeneralIcon",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Application icon:",
"es_ES": "Icono de aplicación:",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "SettingsTabGeneralIconTooltip",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "An app restart may be required for the app icon to display properly across Ryujinx.",
"es_ES": "Podría ser necesario reiniciar la aplicación para que el icono se muestre correctamente en todo Ryujinx.",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{ {
"ID": "ControllerSettingsConfigureGeneral", "ID": "ControllerSettingsConfigureGeneral",
"Translations": { "Translations": {

View File

@@ -2050,7 +2050,9 @@
010003C00B868000,"Ninjin: Clash of Carrots",online-broken,playable,2024-07-10 05:12:26 010003C00B868000,"Ninjin: Clash of Carrots",online-broken,playable,2024-07-10 05:12:26
0100746010E4C000,"NinNinDays",,playable,2022-11-20 15:17:29 0100746010E4C000,"NinNinDays",,playable,2022-11-20 15:17:29
0100C9A00ECE6000,"Nintendo 64™ Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07 0100C9A00ECE6000,"Nintendo 64™ Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
010057D00ECE4000,"Nintendo 64™ Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
0100e0601c632000,"Nintendo 64™ Nintendo Switch Online: MATURE 17+",,ingame,2025-02-03 22:27:00 0100e0601c632000,"Nintendo 64™ Nintendo Switch Online: MATURE 17+",,ingame,2025-02-03 22:27:00
010037A0170D2000,"NINTENDO 64™ Nintendo Switch Online 18+",,ingame,2025-02-03 22:27:00
0100D870045B6000,"Nintendo Entertainment System™ - Nintendo Switch Online",online,playable,2022-07-01 15:45:06 0100D870045B6000,"Nintendo Entertainment System™ - Nintendo Switch Online",online,playable,2022-07-01 15:45:06
0100C4B0034B2000,"Nintendo Labo Toy-Con 01 Variety Kit",gpu,ingame,2022-08-07 12:56:07 0100C4B0034B2000,"Nintendo Labo Toy-Con 01 Variety Kit",gpu,ingame,2022-08-07 12:56:07
01001E9003502000,"Nintendo Labo Toy-Con 03 Vehicle Kit",services;crash,menus,2022-08-03 17:20:11 01001E9003502000,"Nintendo Labo Toy-Con 03 Vehicle Kit",services;crash,menus,2022-08-03 17:20:11
@@ -2638,6 +2640,7 @@
0100B16009C10000,"SINNER: Sacrifice for Redemption",nvdec;UE4;vulkan-backend-bug,playable,2022-08-12 20:37:33 0100B16009C10000,"SINNER: Sacrifice for Redemption",nvdec;UE4;vulkan-backend-bug,playable,2022-08-12 20:37:33
0100E9201410E000,"Sir Lovelot",,playable,2021-04-05 16:21:46 0100E9201410E000,"Sir Lovelot",,playable,2021-04-05 16:21:46
0100134011E32000,"Skate City",,playable,2022-11-04 11:37:39 0100134011E32000,"Skate City",,playable,2022-11-04 11:37:39
0100a8501b66e000,"Skateboard Drifting with Maxwell Cat: The Game Simulator",,playable,2026-02-17 19:05:00
0100B2F008BD8000,"Skee-Ball",,playable,2020-11-16 04:44:07 0100B2F008BD8000,"Skee-Ball",,playable,2020-11-16 04:44:07
01001A900F862000,"Skelattack",,playable,2021-06-09 15:26:26 01001A900F862000,"Skelattack",,playable,2021-06-09 15:26:26
01008E700F952000,"Skelittle: A Giant Party!",,playable,2021-06-09 19:08:34 01008E700F952000,"Skelittle: A Giant Party!",,playable,2021-06-09 19:08:34
@@ -3307,6 +3310,7 @@
0100AFA011068000,"Voxel Pirates",,playable,2022-09-28 22:55:02 0100AFA011068000,"Voxel Pirates",,playable,2022-09-28 22:55:02
0100BFB00D1F4000,"Voxel Sword",,playable,2022-08-30 14:57:27 0100BFB00D1F4000,"Voxel Sword",,playable,2022-08-30 14:57:27
01004E90028A2000,"Vroom in the night sky",Needs Update;vulkan-backend-bug,playable,2023-02-20 02:32:29 01004E90028A2000,"Vroom in the night sky",Needs Update;vulkan-backend-bug,playable,2023-02-20 02:32:29
0100BFC01D976000,"Virtual Boy Nintendo Classics",services,nothing,2026-02-17 11:26:59
0100C7C00AE6C000,"VSR: Void Space Racing",,playable,2021-01-27 14:08:59 0100C7C00AE6C000,"VSR: Void Space Racing",,playable,2021-01-27 14:08:59
0100B130119D0000,"Waifu Uncovered",crash,ingame,2023-02-27 01:17:46 0100B130119D0000,"Waifu Uncovered",crash,ingame,2023-02-27 01:17:46
0100E29010A4A000,"Wanba Warriors",,playable,2020-10-04 17:56:22 0100E29010A4A000,"Wanba Warriors",,playable,2020-10-04 17:56:22
1 title_id game_name labels status last_updated
2050 010003C00B868000 Ninjin: Clash of Carrots online-broken playable 2024-07-10 05:12:26
2051 0100746010E4C000 NinNinDays playable 2022-11-20 15:17:29
2052 0100C9A00ECE6000 Nintendo 64™ – Nintendo Switch Online gpu;vulkan ingame 2024-04-23 20:21:07
2053 010057D00ECE4000 Nintendo 64™ – Nintendo Switch Online gpu;vulkan ingame 2024-04-23 20:21:07
2054 0100e0601c632000 Nintendo 64™ – Nintendo Switch Online: MATURE 17+ ingame 2025-02-03 22:27:00
2055 010037A0170D2000 NINTENDO 64™ – Nintendo Switch Online 18+ ingame 2025-02-03 22:27:00
2056 0100D870045B6000 Nintendo Entertainment System™ - Nintendo Switch Online online playable 2022-07-01 15:45:06
2057 0100C4B0034B2000 Nintendo Labo Toy-Con 01 Variety Kit gpu ingame 2022-08-07 12:56:07
2058 01001E9003502000 Nintendo Labo Toy-Con 03 Vehicle Kit services;crash menus 2022-08-03 17:20:11
2640 0100B16009C10000 SINNER: Sacrifice for Redemption nvdec;UE4;vulkan-backend-bug playable 2022-08-12 20:37:33
2641 0100E9201410E000 Sir Lovelot playable 2021-04-05 16:21:46
2642 0100134011E32000 Skate City playable 2022-11-04 11:37:39
2643 0100a8501b66e000 Skateboard Drifting with Maxwell Cat: The Game Simulator playable 2026-02-17 19:05:00
2644 0100B2F008BD8000 Skee-Ball playable 2020-11-16 04:44:07
2645 01001A900F862000 Skelattack playable 2021-06-09 15:26:26
2646 01008E700F952000 Skelittle: A Giant Party! playable 2021-06-09 19:08:34
3310 0100AFA011068000 Voxel Pirates playable 2022-09-28 22:55:02
3311 0100BFB00D1F4000 Voxel Sword playable 2022-08-30 14:57:27
3312 01004E90028A2000 Vroom in the night sky Needs Update;vulkan-backend-bug playable 2023-02-20 02:32:29
3313 0100BFC01D976000 Virtual Boy – Nintendo Classics services nothing 2026-02-17 11:26:59
3314 0100C7C00AE6C000 VSR: Void Space Racing playable 2021-01-27 14:08:59
3315 0100B130119D0000 Waifu Uncovered crash ingame 2023-02-27 01:17:46
3316 0100E29010A4A000 Wanba Warriors playable 2020-10-04 17:56:22

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 890 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,23 @@
using Avalonia.Media.Imaging;
using Ryujinx.Ava.UI.Controls;
namespace Ryujinx.Ava.Common.Models
{
public class ApplicationIcon
{
public string Name { get; set; }
public string Filename { get; set; }
public string FullPath
{
get => $"Ryujinx/Assets/Icons/AppIcons/{Filename}";
}
public Bitmap Icon
{
get
{
return RyujinxLogo.GetBitmapForLogo(this);
}
}
}
}

View File

@@ -141,7 +141,7 @@ namespace Ryujinx.Ava.Input
AvaKey.OemComma, AvaKey.OemComma,
AvaKey.OemPeriod, AvaKey.OemPeriod,
AvaKey.OemQuestion, AvaKey.OemQuestion,
AvaKey.OemBackslash, AvaKey.OemPipe,
// NOTE: invalid // NOTE: invalid
AvaKey.None AvaKey.None

View File

@@ -166,6 +166,7 @@
<EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" /> <EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConRight.svg" /> <EmbeddedResource Include="Assets\Icons\Controller_JoyConRight.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_ProCon.svg" /> <EmbeddedResource Include="Assets\Icons\Controller_ProCon.svg" />
<EmbeddedResource Include="Assets\Icons\AppIcons\*" />
<EmbeddedResource Include="Assets\UIImages\Icon_NCA.png" /> <EmbeddedResource Include="Assets\UIImages\Icon_NCA.png" />
<EmbeddedResource Include="Assets\UIImages\Icon_NRO.png" /> <EmbeddedResource Include="Assets\UIImages\Icon_NRO.png" />
<EmbeddedResource Include="Assets\UIImages\Icon_NSO.png" /> <EmbeddedResource Include="Assets\UIImages\Icon_NSO.png" />

View File

@@ -356,6 +356,11 @@ namespace Ryujinx.Ava.Systems.Configuration
/// </summary> /// </summary>
public string BaseStyle { get; set; } public string BaseStyle { get; set; }
/// <summary>
/// The name of the currently selected window icon
/// </summary>
public string SelectedWindowIcon { get; set; }
/// <summary> /// <summary>
/// Chooses the view mode of the game list // Not Used /// Chooses the view mode of the game list // Not Used
/// </summary> /// </summary>

View File

@@ -134,6 +134,7 @@ namespace Ryujinx.Ava.Systems.Configuration
UI.ShownFileTypes.NSO.Value = shouldLoadFromFile ? cff.ShownFileTypes.NSO : UI.ShownFileTypes.NSO.Value; UI.ShownFileTypes.NSO.Value = shouldLoadFromFile ? cff.ShownFileTypes.NSO : UI.ShownFileTypes.NSO.Value;
UI.LanguageCode.Value = shouldLoadFromFile ? cff.LanguageCode : UI.LanguageCode.Value; UI.LanguageCode.Value = shouldLoadFromFile ? cff.LanguageCode : UI.LanguageCode.Value;
UI.BaseStyle.Value = shouldLoadFromFile ? cff.BaseStyle : UI.BaseStyle.Value; UI.BaseStyle.Value = shouldLoadFromFile ? cff.BaseStyle : UI.BaseStyle.Value;
UI.SelectedWindowIcon.Value = shouldLoadFromFile ? cff.SelectedWindowIcon : UI.SelectedWindowIcon.Value;
UI.GameListViewMode.Value = shouldLoadFromFile ? cff.GameListViewMode : UI.GameListViewMode.Value; UI.GameListViewMode.Value = shouldLoadFromFile ? cff.GameListViewMode : UI.GameListViewMode.Value;
UI.ShowNames.Value = shouldLoadFromFile ? cff.ShowNames : UI.ShowNames.Value; UI.ShowNames.Value = shouldLoadFromFile ? cff.ShowNames : UI.ShowNames.Value;
UI.IsAscendingOrder.Value = shouldLoadFromFile ? cff.IsAscendingOrder : UI.IsAscendingOrder.Value; UI.IsAscendingOrder.Value = shouldLoadFromFile ? cff.IsAscendingOrder : UI.IsAscendingOrder.Value;

View File

@@ -151,6 +151,11 @@ namespace Ryujinx.Ava.Systems.Configuration
/// </summary> /// </summary>
public ReactiveObject<string> BaseStyle { get; private set; } public ReactiveObject<string> BaseStyle { get; private set; }
/// <summary>
/// The currently selected window icon.
/// </summary>
public ReactiveObject<string> SelectedWindowIcon { get; private set; }
/// <summary> /// <summary>
/// Start games in fullscreen mode /// Start games in fullscreen mode
/// </summary> /// </summary>
@@ -200,6 +205,7 @@ namespace Ryujinx.Ava.Systems.Configuration
ShownFileTypes = new ShownFileTypeSettings(); ShownFileTypes = new ShownFileTypeSettings();
WindowStartup = new WindowStartupSettings(); WindowStartup = new WindowStartupSettings();
BaseStyle = new ReactiveObject<string>(); BaseStyle = new ReactiveObject<string>();
SelectedWindowIcon = new ReactiveObject<string>();
StartFullscreen = new ReactiveObject<bool>(); StartFullscreen = new ReactiveObject<bool>();
StartNoUI = new ReactiveObject<bool>(); StartNoUI = new ReactiveObject<bool>();
GameListViewMode = new ReactiveObject<int>(); GameListViewMode = new ReactiveObject<int>();

View File

@@ -126,6 +126,7 @@ namespace Ryujinx.Ava.Systems.Configuration
}, },
LanguageCode = UI.LanguageCode, LanguageCode = UI.LanguageCode,
BaseStyle = UI.BaseStyle, BaseStyle = UI.BaseStyle,
SelectedWindowIcon = UI.SelectedWindowIcon,
GameListViewMode = UI.GameListViewMode, GameListViewMode = UI.GameListViewMode,
ShowNames = UI.ShowNames, ShowNames = UI.ShowNames,
GridSize = UI.GridSize, GridSize = UI.GridSize,

View File

@@ -1,28 +1,146 @@
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 SkiaSharp;
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;
CurrentLogoBitmap.Event += CurrentLogoBitmapChanged_Event;
}
private void CurrentLogoBitmapChanged_Event(object _, ReactiveEventArgs<Bitmap> e)
{
Source = e.NewValue;
}
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)
{
Bitmap logoBitmap = GetBitmapForLogo(selectedIcon);
if (logoBitmap != null)
{
CurrentLogoBitmap.Value = logoBitmap;
}
}
}
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);
public static Bitmap GetBitmapForLogo(ApplicationIcon icon)
{
Stream activeIconStream = EmbeddedResources.GetStream(icon.FullPath);
if (activeIconStream == null)
return null;
// SVG files need to be converted to an image first
if (icon.FullPath.EndsWith(".svg", StringComparison.OrdinalIgnoreCase))
{
Stream pngStream = ConvertSvgToPng(activeIconStream);
return new Bitmap(pngStream);
}
else
{
return new Bitmap(activeIconStream);
}
}
private static Stream ConvertSvgToPng(Stream svgStream)
{
int width = 256;
int height = 256;
// Load SVG
var svg = new SKSvg();
svg.Load(svgStream);
// Determine size
var picture = svg.Picture;
if (picture == null)
throw new InvalidOperationException("Invalid SVG data");
var picWidth = width > 0 ? width : (int)svg.Picture.CullRect.Width;
var picHeight = height > 0 ? height : (int)svg.Picture.CullRect.Height;
// Create bitmap
using var bitmap = new SKBitmap(picWidth, picHeight);
using var canvas = new SKCanvas(bitmap);
canvas.Clear(SKColors.Transparent);
// Scale to fit
float scaleX = (float)picWidth / svg.Picture.CullRect.Width;
float scaleY = (float)picHeight / svg.Picture.CullRect.Height;
canvas.Scale(scaleX, scaleY);
canvas.DrawPicture(svg.Picture);
canvas.Flush();
// Encode PNG into memory stream
var outputStream = new MemoryStream();
using (var image = SKImage.FromBitmap(bitmap))
using (var data = image.Encode(SKEncodedImageFormat.Png, 100))
{
data.SaveTo(outputStream);
}
outputStream.Position = 0;
return outputStream;
} }
} }
} }

View File

@@ -8,7 +8,9 @@ using Avalonia.Threading;
using FluentAvalonia.UI.Windowing; using FluentAvalonia.UI.Windowing;
using Gommon; using Gommon;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
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;
@@ -17,7 +19,10 @@ using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.RenderDocApi; using Ryujinx.Graphics.RenderDocApi;
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq;
namespace Ryujinx.Ava namespace Ryujinx.Ava
{ {
@@ -53,6 +58,9 @@ namespace Ryujinx.Ava
{ {
Name = FormatTitle(); Name = FormatTitle();
RetrieveAvailableAppIcons();
RyujinxLogo.RefreshAppIconFromSettings();
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
if (OperatingSystem.IsMacOS()) if (OperatingSystem.IsMacOS())
@@ -75,7 +83,6 @@ namespace Ryujinx.Ava
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
ApplyConfiguredTheme(ConfigurationState.Instance.UI.BaseStyle); ApplyConfiguredTheme(ConfigurationState.Instance.UI.BaseStyle);
ConfigurationState.Instance.UI.BaseStyle.Event += ThemeChanged_Event; ConfigurationState.Instance.UI.BaseStyle.Event += ThemeChanged_Event;
} }
} }
@@ -154,5 +161,27 @@ namespace Ryujinx.Ava
{ {
await AboutView.Show(); await AboutView.Show();
} }
public static List<ApplicationIcon> AvailableApplicationIcons { get; set; } = [];
private static void RetrieveAvailableAppIcons()
{
AvailableApplicationIcons.Clear();
string resourceAssemblyPrefix = "Ryujinx.Assets.Icons.AppIcons.";
IEnumerable<string> availableAppIconResources = EmbeddedResources
.GetAllAvailableResources("Ryujinx/Assets")
.Where(x => x.StartsWith(resourceAssemblyPrefix));
foreach (string resource in availableAppIconResources)
{
string filename = resource.Remove(0, resourceAssemblyPrefix.Length);
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filename);
AvailableApplicationIcons.Add(new ApplicationIcon()
{
Name = fileNameWithoutExtension,
Filename = filename
});
}
}
} }
} }

View File

@@ -10,12 +10,14 @@ using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Audio.Backends.SDL3; using Ryujinx.Audio.Backends.SDL3;
using Ryujinx.Audio.Backends.SoundIo; using Ryujinx.Audio.Backends.SoundIo;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Common.Models;
using Ryujinx.Ava.Systems.Configuration; using Ryujinx.Ava.Systems.Configuration;
using Ryujinx.Ava.Systems.Configuration.System; using Ryujinx.Ava.Systems.Configuration.System;
using Ryujinx.Ava.Systems.Configuration.UI; using Ryujinx.Ava.Systems.Configuration.UI;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.GraphicsDriver;
@@ -509,6 +511,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Task.Run(CheckSoundBackends); Task.Run(CheckSoundBackends);
Task.Run(PopulateNetworkInterfaces); Task.Run(PopulateNetworkInterfaces);
ApplicationIcons = new(RyujinxApp.AvailableApplicationIcons);
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
@@ -636,6 +639,7 @@ namespace Ryujinx.Ava.UI.ViewModels
HideCursor = (int)config.HideCursor.Value; HideCursor = (int)config.HideCursor.Value;
UpdateCheckerType = (int)config.UpdateCheckerType.Value; UpdateCheckerType = (int)config.UpdateCheckerType.Value;
FocusLostActionType = (int)config.FocusLostActionType.Value; FocusLostActionType = (int)config.FocusLostActionType.Value;
AppIconSelectedIndex = _appIcons.ToList().FindIndex(x => x.Name == config.UI.SelectedWindowIcon.Value);
GameDirectories.Clear(); GameDirectories.Clear();
GameDirectories.AddRange(config.UI.GameDirs.Value); GameDirectories.AddRange(config.UI.GameDirs.Value);
@@ -754,6 +758,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.FocusLostActionType.Value = (FocusLostType)FocusLostActionType; config.FocusLostActionType.Value = (FocusLostType)FocusLostActionType;
config.UI.GameDirs.Value = [.. GameDirectories]; config.UI.GameDirs.Value = [.. GameDirectories];
config.UI.AutoloadDirs.Value = [.. AutoloadDirectories]; config.UI.AutoloadDirs.Value = [.. AutoloadDirectories];
config.UI.SelectedWindowIcon.Value = _appIcons[_appIconSelectedIndex].Name;
config.UI.BaseStyle.Value = BaseStyleIndex switch config.UI.BaseStyle.Value = BaseStyleIndex switch
{ {
@@ -941,5 +946,27 @@ namespace Ryujinx.Ava.UI.ViewModels
RevertIfNotSaved(IsCustomConfig, IsGameRunning); RevertIfNotSaved(IsCustomConfig, IsGameRunning);
CloseWindow?.Invoke(); CloseWindow?.Invoke();
} }
private AvaloniaList<ApplicationIcon> _appIcons = [];
public AvaloniaList<ApplicationIcon> ApplicationIcons
{
get => _appIcons;
set
{
_appIcons = value;
OnPropertyChanged();
}
}
private int _appIconSelectedIndex;
public int AppIconSelectedIndex
{
get => _appIconSelectedIndex;
set
{
_appIconSelectedIndex = value;
OnPropertyChanged();
}
}
} }
} }

View File

@@ -140,6 +140,32 @@
Content="{ext:Locale SettingsTabGeneralThemeDark}" /> Content="{ext:Locale SettingsTabGeneralThemeDark}" />
</ComboBox> </ComboBox>
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" /> <TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
</StackPanel>
<StackPanel
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
Margin="0, 15, 0, 10"
ToolTip.Tip="{ext:Locale SettingsTabGeneralIconTooltip}"
Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralIcon}"
Width="150" />
<ComboBox
MinWidth="100"
HorizontalAlignment="Left"
ItemsSource="{Binding ApplicationIcons}"
SelectedIndex="{Binding AppIconSelectedIndex}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding Icon}" Width="24" Height="24" Margin="0,0,8,0" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>

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);
}
} }
} }