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)
@ -12067,6 +12067,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": {
|
||||||
|
|||||||
24
src/Ryujinx/Assets/Icons/AppIcons/Bordered Ryugay.svg
Normal file
|
After Width: | Height: | Size: 890 KiB |
4
src/Ryujinx/Assets/Icons/AppIcons/Bordered Ryupride.svg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
src/Ryujinx/Assets/Icons/AppIcons/Ryubi.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
src/Ryujinx/Assets/Icons/AppIcons/Ryufluid.webp
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src/Ryujinx/Assets/Icons/AppIcons/Ryulesbian.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/Ryujinx/Assets/Icons/AppIcons/Ryupan.webp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/Ryujinx/Assets/Icons/AppIcons/Ryupride.webp
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
src/Ryujinx/Assets/Icons/AppIcons/Ryutrans.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
23
src/Ryujinx/Common/Models/ApplicationIcon.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -164,6 +164,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" />
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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>();
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
@ -16,7 +18,10 @@ using Ryujinx.Ava.Utilities;
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
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
|
||||||
{
|
{
|
||||||
@ -52,6 +57,9 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
Name = FormatTitle();
|
Name = FormatTitle();
|
||||||
|
|
||||||
|
RetrieveAvailableAppIcons();
|
||||||
|
RyujinxLogo.RefreshAppIconFromSettings();
|
||||||
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
|
||||||
if (OperatingSystem.IsMacOS())
|
if (OperatingSystem.IsMacOS())
|
||||||
@ -72,7 +80,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,5 +158,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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,12 +9,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;
|
||||||
@ -507,6 +509,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)
|
||||||
{
|
{
|
||||||
@ -632,6 +635,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);
|
||||||
@ -750,6 +754,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
|
||||||
{
|
{
|
||||||
@ -937,5 +942,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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||