mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-12-16 16:26:36 +00:00
Compare commits
35 Commits
37dea12aa9
...
8fd9a707fc
Author | SHA1 | Date | |
---|---|---|---|
|
8fd9a707fc | ||
|
5dbba07e33 | ||
|
d86249cb0a | ||
|
04d68ca616 | ||
|
43feea8cd5 | ||
|
3b60e8f590 | ||
|
a3b50fb28d | ||
|
95e78f6ee1 | ||
|
3145934efc | ||
|
8ffe522f53 | ||
|
47c491271a | ||
|
9b6241985f | ||
|
65ec957c4c | ||
|
2d399b6d26 | ||
|
2e48ef62fa | ||
|
ddbdd0246a | ||
|
378cbf129d | ||
|
66205aa3a3 | ||
|
7f61ac3ab8 | ||
|
ae97783459 | ||
|
7e19054de1 | ||
|
388597b4e6 | ||
|
2d73107dc0 | ||
|
2281b3b59e | ||
|
c628cd7af5 | ||
|
286aebf70f | ||
|
1ad9b27ed6 | ||
|
9167833f0a | ||
|
b3262302fc | ||
|
7821d4581a | ||
|
417b4caa98 | ||
|
fd33ebb42d | ||
|
ea80d922a6 | ||
|
c141b248a8 | ||
|
f195198608 |
@ -1,6 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
public class KeyboardHotkeys
|
||||
public class KeyboardHotkeys : IEquatable<KeyboardHotkeys>
|
||||
{
|
||||
public Key ToggleVsync { get; set; }
|
||||
public Key Screenshot { get; set; }
|
||||
@ -11,5 +13,65 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||
public Key ResScaleDown { get; set; }
|
||||
public Key VolumeUp { get; set; }
|
||||
public Key VolumeDown { get; set; }
|
||||
|
||||
public bool Equals(KeyboardHotkeys other)
|
||||
{
|
||||
if (other == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return ToggleVsync == other.ToggleVsync &&
|
||||
Screenshot == other.Screenshot &&
|
||||
ShowUI == other.ShowUI &&
|
||||
Pause == other.Pause &&
|
||||
ToggleMute == other.ToggleMute &&
|
||||
ResScaleUp == other.ResScaleUp &&
|
||||
ResScaleDown == other.ResScaleDown &&
|
||||
VolumeUp == other.VolumeUp &&
|
||||
VolumeDown == other.VolumeDown;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, obj))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj.GetType() != this.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals((KeyboardHotkeys)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = (int)ToggleVsync;
|
||||
hashCode = (hashCode * 397) ^ (int)Screenshot;
|
||||
hashCode = (hashCode * 397) ^ (int)ShowUI;
|
||||
hashCode = (hashCode * 397) ^ (int)Pause;
|
||||
hashCode = (hashCode * 397) ^ (int)ToggleMute;
|
||||
hashCode = (hashCode * 397) ^ (int)ResScaleUp;
|
||||
hashCode = (hashCode * 397) ^ (int)ResScaleDown;
|
||||
hashCode = (hashCode * 397) ^ (int)VolumeUp;
|
||||
hashCode = (hashCode * 397) ^ (int)VolumeDown;
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,6 +71,8 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
public readonly int GatherBiasPrecision;
|
||||
|
||||
public readonly ulong MaximumGpuMemory;
|
||||
|
||||
public Capabilities(
|
||||
TargetApi api,
|
||||
string vendorName,
|
||||
@ -131,7 +133,8 @@ namespace Ryujinx.Graphics.GAL
|
||||
int shaderSubgroupSize,
|
||||
int storageBufferOffsetAlignment,
|
||||
int textureBufferOffsetAlignment,
|
||||
int gatherBiasPrecision)
|
||||
int gatherBiasPrecision,
|
||||
ulong maximumGpuMemory)
|
||||
{
|
||||
Api = api;
|
||||
VendorName = vendorName;
|
||||
@ -193,6 +196,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
StorageBufferOffsetAlignment = storageBufferOffsetAlignment;
|
||||
TextureBufferOffsetAlignment = textureBufferOffsetAlignment;
|
||||
GatherBiasPrecision = gatherBiasPrecision;
|
||||
MaximumGpuMemory = maximumGpuMemory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@ -46,7 +47,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
private const int MinCountForDeletion = 32;
|
||||
private const int MaxCapacity = 2048;
|
||||
private const ulong MaxTextureSizeCapacity = 1024 * 1024 * 1024; // MB;
|
||||
private const ulong MinTextureSizeCapacity = 512 * 1024 * 1024;
|
||||
private const ulong MaxTextureSizeCapacity = 4UL * 1024 * 1024 * 1024;
|
||||
private const ulong DefaultTextureSizeCapacity = 1UL * 1024 * 1024 * 1024;
|
||||
private const float MemoryScaleFactor = 0.50f;
|
||||
private ulong _maxCacheMemoryUsage = 0;
|
||||
|
||||
private readonly LinkedList<Texture> _textures;
|
||||
private ulong _totalSize;
|
||||
@ -56,6 +61,25 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
private readonly Dictionary<TextureDescriptor, ShortTextureCacheEntry> _shortCacheLookup;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the cache, setting the maximum texture capacity for the specified GPU context.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the backend GPU has 0 memory capacity, the cache size defaults to `DefaultTextureSizeCapacity`.
|
||||
/// </remarks>
|
||||
/// <param name="context">The GPU context that the cache belongs to</param>
|
||||
public void Initialize(GpuContext context)
|
||||
{
|
||||
var cacheMemory = (ulong)(context.Capabilities.MaximumGpuMemory * MemoryScaleFactor);
|
||||
|
||||
_maxCacheMemoryUsage = Math.Clamp(cacheMemory, MinTextureSizeCapacity, MaxTextureSizeCapacity);
|
||||
|
||||
if (context.Capabilities.MaximumGpuMemory == 0)
|
||||
{
|
||||
_maxCacheMemoryUsage = DefaultTextureSizeCapacity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the automatic deletion cache.
|
||||
/// </summary>
|
||||
@ -85,7 +109,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
texture.CacheNode = _textures.AddLast(texture);
|
||||
|
||||
if (_textures.Count > MaxCapacity ||
|
||||
(_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion))
|
||||
(_totalSize > _maxCacheMemoryUsage && _textures.Count >= MinCountForDeletion))
|
||||
{
|
||||
RemoveLeastUsedTexture();
|
||||
}
|
||||
@ -110,7 +134,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_textures.AddLast(texture.CacheNode);
|
||||
}
|
||||
|
||||
if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion)
|
||||
if (_totalSize > _maxCacheMemoryUsage && _textures.Count >= MinCountForDeletion)
|
||||
{
|
||||
RemoveLeastUsedTexture();
|
||||
}
|
||||
|
@ -68,6 +68,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_cache = new AutoDeleteCache();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the cache, setting the maximum texture capacity for the specified GPU context.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
_cache.Initialize(_context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles marking of textures written to a memory region being (partially) remapped.
|
||||
/// </summary>
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
@ -64,6 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += VirtualRangeCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += CounterCache.MemoryUnmappedHandler;
|
||||
Physical.TextureCache.Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 7331;
|
||||
private const uint CodeGenVersion = 7353;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@ -202,7 +202,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
shaderSubgroupSize: Constants.MaxSubgroupSize,
|
||||
storageBufferOffsetAlignment: HwCapabilities.StorageBufferOffsetAlignment,
|
||||
textureBufferOffsetAlignment: HwCapabilities.TextureBufferOffsetAlignment,
|
||||
gatherBiasPrecision: intelWindows || amdWindows ? 8 : 0); // Precision is 8 for these vendors on Vulkan.
|
||||
gatherBiasPrecision: intelWindows || amdWindows ? 8 : 0, // Precision is 8 for these vendors on Vulkan.
|
||||
maximumGpuMemory: 0);
|
||||
}
|
||||
|
||||
public void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan<byte> data)
|
||||
|
@ -190,7 +190,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
if (stage == ShaderStage.Vertex)
|
||||
{
|
||||
InitializePositionOutput(context);
|
||||
InitializeVertexOutputs(context);
|
||||
}
|
||||
|
||||
UInt128 usedAttributes = context.TranslatorContext.AttributeUsage.NextInputAttributesComponents;
|
||||
@ -236,12 +236,20 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitializePositionOutput(EmitterContext context)
|
||||
private static void InitializeVertexOutputs(EmitterContext context)
|
||||
{
|
||||
for (int c = 0; c < 4; c++)
|
||||
{
|
||||
context.Store(StorageKind.Output, IoVariable.Position, null, Const(c), ConstF(c == 3 ? 1f : 0f));
|
||||
}
|
||||
|
||||
if (context.Program.ClipDistancesWritten != 0)
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
context.Store(StorageKind.Output, IoVariable.ClipDistance, null, Const(i), ConstF(0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitializeOutput(EmitterContext context, int location, bool perPatch)
|
||||
|
@ -781,7 +781,26 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
shaderSubgroupSize: (int)Capabilities.SubgroupSize,
|
||||
storageBufferOffsetAlignment: (int)limits.MinStorageBufferOffsetAlignment,
|
||||
textureBufferOffsetAlignment: (int)limits.MinTexelBufferOffsetAlignment,
|
||||
gatherBiasPrecision: IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0);
|
||||
gatherBiasPrecision: IsIntelWindows || IsAmdWindows ? (int)Capabilities.SubTexelPrecisionBits : 0,
|
||||
maximumGpuMemory: GetTotalGPUMemory());
|
||||
}
|
||||
|
||||
private ulong GetTotalGPUMemory()
|
||||
{
|
||||
ulong totalMemory = 0;
|
||||
|
||||
Api.GetPhysicalDeviceMemoryProperties(_physicalDevice.PhysicalDevice, out PhysicalDeviceMemoryProperties memoryProperties);
|
||||
|
||||
for (int i = 0; i < memoryProperties.MemoryHeapCount; i++)
|
||||
{
|
||||
var heap = memoryProperties.MemoryHeaps[i];
|
||||
if ((heap.Flags & MemoryHeapFlags.DeviceLocalBit) == MemoryHeapFlags.DeviceLocalBit)
|
||||
{
|
||||
totalMemory += heap.Size;
|
||||
}
|
||||
}
|
||||
|
||||
return totalMemory;
|
||||
}
|
||||
|
||||
public HardwareInfo GetHardwareInfo()
|
||||
@ -865,6 +884,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private void PrintGpuInformation()
|
||||
{
|
||||
Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
|
||||
Logger.Notice.Print(LogClass.Gpu, $"GPU Memory: {GetTotalGPUMemory() / (1024 * 1024)} MiB");
|
||||
}
|
||||
|
||||
public void Initialize(GraphicsDebugLevel logLevel)
|
||||
|
@ -53,6 +53,7 @@ namespace Ryujinx.SDL2.Common
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_SetHint(SDL_HINT_APP_NAME, "Ryujinx");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
|
@ -92,6 +92,7 @@
|
||||
"LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.",
|
||||
"LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.",
|
||||
"Settings": "Settings",
|
||||
"SettingsDirty": "Unsaved Changes",
|
||||
"SettingsTabGeneral": "User Interface",
|
||||
"SettingsTabGeneralGeneral": "General",
|
||||
"SettingsTabGeneralEnableDiscordRichPresence": "Enable Discord Rich Presence",
|
||||
@ -490,8 +491,8 @@
|
||||
"DialogUserProfileUnsavedChangesTitle": "Warning - Unsaved Changes",
|
||||
"DialogUserProfileUnsavedChangesMessage": "You have made changes to this user profile that have not been saved.",
|
||||
"DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "The current controller settings has been updated.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "Do you want to save?",
|
||||
"DialogSettingsUnsavedChangesMessage": "You have made changes to settings that have not been saved.",
|
||||
"DialogSettingsUnsavedChangesSubMessage": "Do you want to discard your changes?",
|
||||
"DialogLoadFileErrorMessage": "{0}. Errored File: {1}",
|
||||
"DialogModAlreadyExistsMessage": "Mod already exists",
|
||||
"DialogModInvalidMessage": "The specified directory does not contain a mod!",
|
||||
|
@ -28,7 +28,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
string closeButton,
|
||||
UserResult primaryButtonResult = UserResult.Ok,
|
||||
ManualResetEvent deferResetEvent = null,
|
||||
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
|
||||
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null,
|
||||
Window parent = null)
|
||||
{
|
||||
UserResult result = UserResult.None;
|
||||
|
||||
@ -62,7 +63,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
contentDialog.PrimaryButtonClick += deferCloseAction;
|
||||
}
|
||||
|
||||
await ShowAsync(contentDialog);
|
||||
await ShowAsync(contentDialog, parent);
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -77,11 +78,21 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
int iconSymbol,
|
||||
UserResult primaryButtonResult = UserResult.Ok,
|
||||
ManualResetEvent deferResetEvent = null,
|
||||
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
|
||||
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null,
|
||||
Window parent = null)
|
||||
{
|
||||
Grid content = CreateTextDialogContent(primaryText, secondaryText, iconSymbol);
|
||||
|
||||
return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction);
|
||||
return await ShowContentDialog(
|
||||
title,
|
||||
content,
|
||||
primaryButton,
|
||||
secondaryButton,
|
||||
closeButton,
|
||||
primaryButtonResult,
|
||||
deferResetEvent,
|
||||
deferCloseAction,
|
||||
parent);
|
||||
}
|
||||
|
||||
public async static Task<UserResult> ShowDeferredContentDialog(
|
||||
@ -94,7 +105,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
string closeButton,
|
||||
int iconSymbol,
|
||||
ManualResetEvent deferResetEvent,
|
||||
Func<Window, Task> doWhileDeferred = null)
|
||||
Func<Window, Task> doWhileDeferred = null,
|
||||
Window parent = null)
|
||||
{
|
||||
bool startedDeferring = false;
|
||||
|
||||
@ -108,7 +120,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
iconSymbol,
|
||||
primaryButton == LocaleManager.Instance[LocaleKeys.InputDialogYes] ? UserResult.Yes : UserResult.Ok,
|
||||
deferResetEvent,
|
||||
DeferClose);
|
||||
DeferClose,
|
||||
parent);
|
||||
|
||||
async void DeferClose(ContentDialog sender, ContentDialogButtonClickEventArgs args)
|
||||
{
|
||||
@ -199,7 +212,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
string secondaryText,
|
||||
string acceptButton,
|
||||
string closeButton,
|
||||
string title)
|
||||
string title,
|
||||
Window parent = null)
|
||||
{
|
||||
return await ShowTextDialog(
|
||||
title,
|
||||
@ -208,7 +222,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
acceptButton,
|
||||
"",
|
||||
closeButton,
|
||||
(int)Symbol.Important);
|
||||
(int)Symbol.Important,
|
||||
parent: parent);
|
||||
}
|
||||
|
||||
internal static async Task<UserResult> CreateConfirmationDialog(
|
||||
@ -217,7 +232,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
string acceptButtonText,
|
||||
string cancelButtonText,
|
||||
string title,
|
||||
UserResult primaryButtonResult = UserResult.Yes)
|
||||
UserResult primaryButtonResult = UserResult.Yes,
|
||||
Window parent = null)
|
||||
{
|
||||
return await ShowTextDialog(
|
||||
string.IsNullOrWhiteSpace(title) ? LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle] : title,
|
||||
@ -227,7 +243,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
"",
|
||||
cancelButtonText,
|
||||
(int)Symbol.Help,
|
||||
primaryButtonResult);
|
||||
primaryButtonResult,
|
||||
parent: parent);
|
||||
}
|
||||
|
||||
internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText)
|
||||
@ -268,7 +285,11 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
(int)Symbol.Dismiss);
|
||||
}
|
||||
|
||||
internal static async Task<bool> CreateChoiceDialog(string title, string primary, string secondaryText)
|
||||
internal static async Task<bool> CreateChoiceDialog(
|
||||
string title,
|
||||
string primary,
|
||||
string secondaryText,
|
||||
Window parent = null)
|
||||
{
|
||||
if (_isChoiceDialogOpen)
|
||||
{
|
||||
@ -285,7 +306,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
"",
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||
(int)Symbol.Help,
|
||||
UserResult.Yes);
|
||||
UserResult.Yes,
|
||||
parent: parent);
|
||||
|
||||
_isChoiceDialogOpen = false;
|
||||
|
||||
@ -308,69 +330,62 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
LocaleManager.Instance[LocaleKeys.DialogExitSubMessage]);
|
||||
}
|
||||
|
||||
public static async Task<ContentDialogResult> ShowAsync(ContentDialog contentDialog)
|
||||
public static async Task<ContentDialogResult> ShowAsync(ContentDialog contentDialog, Window parent = null)
|
||||
{
|
||||
ContentDialogResult result;
|
||||
bool isTopDialog = true;
|
||||
|
||||
Window parent = GetMainWindow();
|
||||
parent ??= GetMainWindow();
|
||||
|
||||
if (_contentDialogOverlayWindow != null)
|
||||
{
|
||||
isTopDialog = false;
|
||||
}
|
||||
|
||||
if (parent is MainWindow window)
|
||||
parent.Activate();
|
||||
|
||||
_contentDialogOverlayWindow = new ContentDialogOverlayWindow
|
||||
{
|
||||
parent.Activate();
|
||||
Height = parent.Bounds.Height,
|
||||
Width = parent.Bounds.Width,
|
||||
Position = parent.PointToScreen(new Point()),
|
||||
ShowInTaskbar = false,
|
||||
};
|
||||
|
||||
_contentDialogOverlayWindow = new ContentDialogOverlayWindow
|
||||
parent.PositionChanged += OverlayOnPositionChanged;
|
||||
|
||||
void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)
|
||||
{
|
||||
if (_contentDialogOverlayWindow is null)
|
||||
{
|
||||
Height = parent.Bounds.Height,
|
||||
Width = parent.Bounds.Width,
|
||||
Position = parent.PointToScreen(new Point()),
|
||||
ShowInTaskbar = false,
|
||||
};
|
||||
|
||||
parent.PositionChanged += OverlayOnPositionChanged;
|
||||
|
||||
void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)
|
||||
{
|
||||
if (_contentDialogOverlayWindow is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_contentDialogOverlayWindow.Position = parent.PointToScreen(new Point());
|
||||
return;
|
||||
}
|
||||
|
||||
_contentDialogOverlayWindow.ContentDialog = contentDialog;
|
||||
|
||||
bool opened = false;
|
||||
|
||||
_contentDialogOverlayWindow.Opened += OverlayOnActivated;
|
||||
|
||||
async void OverlayOnActivated(object sender, EventArgs e)
|
||||
{
|
||||
if (opened)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
opened = true;
|
||||
|
||||
_contentDialogOverlayWindow.Position = parent.PointToScreen(new Point());
|
||||
|
||||
result = await ShowDialog();
|
||||
}
|
||||
|
||||
result = await _contentDialogOverlayWindow.ShowDialog<ContentDialogResult>(parent);
|
||||
_contentDialogOverlayWindow.Position = parent.PointToScreen(new Point());
|
||||
}
|
||||
else
|
||||
|
||||
_contentDialogOverlayWindow.ContentDialog = contentDialog;
|
||||
|
||||
bool opened = false;
|
||||
|
||||
_contentDialogOverlayWindow.Opened += OverlayOnActivated;
|
||||
|
||||
async void OverlayOnActivated(object sender, EventArgs e)
|
||||
{
|
||||
if (opened)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
opened = true;
|
||||
|
||||
_contentDialogOverlayWindow.Position = parent.PointToScreen(new Point());
|
||||
|
||||
result = await ShowDialog();
|
||||
}
|
||||
|
||||
result = await _contentDialogOverlayWindow.ShowDialog<ContentDialogResult>(parent);
|
||||
|
||||
async Task<ContentDialogResult> ShowDialog()
|
||||
{
|
||||
if (_contentDialogOverlayWindow is not null)
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Avalonia.Svg.Skia;
|
||||
using Ryujinx.Ava.UI.Models.Input;
|
||||
using Ryujinx.Ava.UI.ViewModels.Settings;
|
||||
using Ryujinx.Ava.UI.Views.Input;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
@ -54,9 +55,9 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
}
|
||||
}
|
||||
|
||||
public readonly InputViewModel ParentModel;
|
||||
public readonly SettingsInputViewModel ParentModel;
|
||||
|
||||
public ControllerInputViewModel(InputViewModel model, GamepadInputConfig config)
|
||||
public ControllerInputViewModel(SettingsInputViewModel model, GamepadInputConfig config)
|
||||
{
|
||||
ParentModel = model;
|
||||
model.NotifyChangesEvent += OnParentModelChanged;
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Avalonia.Svg.Skia;
|
||||
using Ryujinx.Ava.UI.Models.Input;
|
||||
using Ryujinx.Ava.UI.ViewModels.Settings;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
{
|
||||
@ -53,9 +54,9 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
}
|
||||
}
|
||||
|
||||
public readonly InputViewModel ParentModel;
|
||||
public readonly SettingsInputViewModel ParentModel;
|
||||
|
||||
public KeyboardInputViewModel(InputViewModel model, KeyboardInputConfig config)
|
||||
public KeyboardInputViewModel(SettingsInputViewModel model, KeyboardInputConfig config)
|
||||
{
|
||||
ParentModel = model;
|
||||
model.NotifyChangesEvent += OnParentModelChanged;
|
||||
|
89
src/Ryujinx/UI/ViewModels/Settings/SettingsAudioViewModel.cs
Normal file
89
src/Ryujinx/UI/ViewModels/Settings/SettingsAudioViewModel.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Audio.Backends.OpenAL;
|
||||
using Ryujinx.Audio.Backends.SDL2;
|
||||
using Ryujinx.Audio.Backends.SoundIo;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels.Settings
|
||||
{
|
||||
public class SettingsAudioViewModel : BaseModel
|
||||
{
|
||||
public event Action DirtyEvent;
|
||||
|
||||
private int _audioBackend;
|
||||
public int AudioBackend
|
||||
{
|
||||
get => _audioBackend;
|
||||
set
|
||||
{
|
||||
_audioBackend = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private float _volume;
|
||||
public float Volume
|
||||
{
|
||||
get => _volume;
|
||||
set
|
||||
{
|
||||
_volume = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsOpenAlEnabled { get; set; }
|
||||
public bool IsSoundIoEnabled { get; set; }
|
||||
public bool IsSDL2Enabled { get; set; }
|
||||
|
||||
public SettingsAudioViewModel()
|
||||
{
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
Task.Run(CheckSoundBackends);
|
||||
|
||||
AudioBackend = (int)config.System.AudioBackend.Value;
|
||||
Volume = config.System.AudioVolume * 100;
|
||||
}
|
||||
|
||||
public async Task CheckSoundBackends()
|
||||
{
|
||||
IsOpenAlEnabled = OpenALHardwareDeviceDriver.IsSupported;
|
||||
IsSoundIoEnabled = SoundIoHardwareDeviceDriver.IsSupported;
|
||||
IsSDL2Enabled = SDL2HardwareDeviceDriver.IsSupported;
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
OnPropertyChanged(nameof(IsOpenAlEnabled));
|
||||
OnPropertyChanged(nameof(IsSoundIoEnabled));
|
||||
OnPropertyChanged(nameof(IsSDL2Enabled));
|
||||
});
|
||||
}
|
||||
|
||||
public bool CheckIfModified(ConfigurationState config)
|
||||
{
|
||||
bool isDirty = false;
|
||||
|
||||
isDirty |= config.System.AudioBackend.Value != (AudioBackend)AudioBackend;
|
||||
isDirty |= config.System.AudioVolume.Value != Volume / 100;
|
||||
|
||||
return isDirty;
|
||||
}
|
||||
|
||||
public void Save(ConfigurationState config)
|
||||
{
|
||||
AudioBackend audioBackend = (AudioBackend)AudioBackend;
|
||||
if (audioBackend != config.System.AudioBackend.Value)
|
||||
{
|
||||
config.System.AudioBackend.Value = audioBackend;
|
||||
|
||||
Logger.Info?.Print(LogClass.Application, $"AudioBackend toggled to: {audioBackend}");
|
||||
}
|
||||
|
||||
config.System.AudioVolume.Value = Volume / 100;
|
||||
}
|
||||
}
|
||||
}
|
74
src/Ryujinx/UI/ViewModels/Settings/SettingsCpuViewModel.cs
Normal file
74
src/Ryujinx/UI/ViewModels/Settings/SettingsCpuViewModel.cs
Normal file
@ -0,0 +1,74 @@
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels.Settings
|
||||
{
|
||||
public class SettingsCpuViewModel : BaseModel
|
||||
{
|
||||
public event Action DirtyEvent;
|
||||
|
||||
public bool IsHypervisorAvailable => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
|
||||
|
||||
private bool _enablePptc;
|
||||
public bool EnablePptc
|
||||
{
|
||||
get => _enablePptc;
|
||||
set
|
||||
{
|
||||
_enablePptc = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _useHypervisor;
|
||||
public bool UseHypervisor
|
||||
{
|
||||
get => _useHypervisor;
|
||||
set
|
||||
{
|
||||
_useHypervisor = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _memoryMode;
|
||||
public int MemoryMode
|
||||
{
|
||||
get => _memoryMode;
|
||||
set
|
||||
{
|
||||
_memoryMode = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsCpuViewModel()
|
||||
{
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
EnablePptc = config.System.EnablePtc;
|
||||
MemoryMode = (int)config.System.MemoryManagerMode.Value;
|
||||
UseHypervisor = config.System.UseHypervisor;
|
||||
}
|
||||
|
||||
public bool CheckIfModified(ConfigurationState config)
|
||||
{
|
||||
bool isDirty = false;
|
||||
|
||||
isDirty |= config.System.EnablePtc.Value != EnablePptc;
|
||||
isDirty |= config.System.MemoryManagerMode.Value != (MemoryManagerMode)MemoryMode;
|
||||
isDirty |= config.System.UseHypervisor.Value != UseHypervisor;
|
||||
|
||||
return isDirty;
|
||||
}
|
||||
|
||||
public void Save(ConfigurationState config)
|
||||
{
|
||||
config.System.EnablePtc.Value = EnablePptc;
|
||||
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
|
||||
config.System.UseHypervisor.Value = UseHypervisor;
|
||||
}
|
||||
}
|
||||
}
|
321
src/Ryujinx/UI/ViewModels/Settings/SettingsGraphicsViewModel.cs
Normal file
321
src/Ryujinx/UI/ViewModels/Settings/SettingsGraphicsViewModel.cs
Normal file
@ -0,0 +1,321 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.GraphicsDriver;
|
||||
using Ryujinx.Graphics.Vulkan;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels.Settings
|
||||
{
|
||||
public class SettingsGraphicsViewModel : BaseModel
|
||||
{
|
||||
public event Action DirtyEvent;
|
||||
|
||||
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
|
||||
public bool ColorSpacePassthroughAvailable => OperatingSystem.IsMacOS();
|
||||
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
|
||||
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
||||
public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr;
|
||||
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
|
||||
public string ScalingFilterLevelText => ScalingFilterLevel.ToString("0");
|
||||
private readonly List<string> _gpuIds = new();
|
||||
|
||||
private int _graphicsBackendIndex;
|
||||
public int GraphicsBackendIndex
|
||||
{
|
||||
get => _graphicsBackendIndex;
|
||||
set
|
||||
{
|
||||
_graphicsBackendIndex = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(IsVulkanSelected));
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _preferredGpuIndex;
|
||||
public int PreferredGpuIndex
|
||||
{
|
||||
get => _preferredGpuIndex;
|
||||
set
|
||||
{
|
||||
_preferredGpuIndex = value;
|
||||
OnPropertyChanged();
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool _isVulkanAvailable = true;
|
||||
public bool IsVulkanAvailable
|
||||
{
|
||||
get => _isVulkanAvailable;
|
||||
set
|
||||
{
|
||||
_isVulkanAvailable = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableShaderCache;
|
||||
public bool EnableShaderCache
|
||||
{
|
||||
get => _enableShaderCache;
|
||||
set
|
||||
{
|
||||
_enableShaderCache = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableTextureRecompression;
|
||||
public bool EnableTextureRecompression
|
||||
{
|
||||
get => _enableTextureRecompression;
|
||||
set
|
||||
{
|
||||
_enableTextureRecompression = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableMacroHLE;
|
||||
public bool EnableMacroHLE
|
||||
{
|
||||
get => _enableMacroHLE;
|
||||
set
|
||||
{
|
||||
_enableMacroHLE = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableColorSpacePassthrough;
|
||||
public bool EnableColorSpacePassthrough
|
||||
{
|
||||
get => _enableColorSpacePassthrough;
|
||||
set
|
||||
{
|
||||
_enableColorSpacePassthrough = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _resolutionScale;
|
||||
public int ResolutionScale
|
||||
{
|
||||
get => _resolutionScale;
|
||||
set
|
||||
{
|
||||
_resolutionScale = value;
|
||||
|
||||
OnPropertyChanged(nameof(CustomResolutionScale));
|
||||
OnPropertyChanged(nameof(IsCustomResolutionScaleActive));
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private float _customResolutionScale;
|
||||
public float CustomResolutionScale
|
||||
{
|
||||
get => _customResolutionScale;
|
||||
set
|
||||
{
|
||||
_customResolutionScale = MathF.Round(value, 1);
|
||||
OnPropertyChanged();
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _maxAnisotropy;
|
||||
public int MaxAnisotropy
|
||||
{
|
||||
get => _maxAnisotropy;
|
||||
set
|
||||
{
|
||||
_maxAnisotropy = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _aspectRatio;
|
||||
public int AspectRatio
|
||||
{
|
||||
get => _aspectRatio;
|
||||
set
|
||||
{
|
||||
_aspectRatio = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _graphicsBackendMultithreadingIndex;
|
||||
public int GraphicsBackendMultithreadingIndex
|
||||
{
|
||||
get => _graphicsBackendMultithreadingIndex;
|
||||
set
|
||||
{
|
||||
_graphicsBackendMultithreadingIndex = value;
|
||||
OnPropertyChanged();
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private string _shaderDumpPath;
|
||||
public string ShaderDumpPath
|
||||
{
|
||||
get => _shaderDumpPath;
|
||||
set
|
||||
{
|
||||
_shaderDumpPath = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _antiAliasingEffect;
|
||||
public int AntiAliasingEffect
|
||||
{
|
||||
get => _antiAliasingEffect;
|
||||
set
|
||||
{
|
||||
_antiAliasingEffect = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _scalingFilter;
|
||||
public int ScalingFilter
|
||||
{
|
||||
get => _scalingFilter;
|
||||
set
|
||||
{
|
||||
_scalingFilter = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(IsScalingFilterActive));
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _scalingFilterLevel;
|
||||
public int ScalingFilterLevel
|
||||
{
|
||||
get => _scalingFilterLevel;
|
||||
set
|
||||
{
|
||||
_scalingFilterLevel = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(ScalingFilterLevelText));
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsGraphicsViewModel()
|
||||
{
|
||||
AvailableGpus = new ObservableCollection<ComboBoxItem>();
|
||||
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
|
||||
// Physical devices are queried asynchronously hence the preferred index config value is loaded in LoadAvailableGpus().
|
||||
EnableShaderCache = config.Graphics.EnableShaderCache;
|
||||
EnableTextureRecompression = config.Graphics.EnableTextureRecompression;
|
||||
EnableMacroHLE = config.Graphics.EnableMacroHLE;
|
||||
EnableColorSpacePassthrough = config.Graphics.EnableColorSpacePassthrough;
|
||||
ResolutionScale = config.Graphics.ResScale == -1 ? 4 : config.Graphics.ResScale - 1;
|
||||
CustomResolutionScale = config.Graphics.ResScaleCustom;
|
||||
MaxAnisotropy = config.Graphics.MaxAnisotropy == -1 ? 0 : (int)(MathF.Log2(config.Graphics.MaxAnisotropy));
|
||||
AspectRatio = (int)config.Graphics.AspectRatio.Value;
|
||||
AntiAliasingEffect = (int)config.Graphics.AntiAliasing.Value;
|
||||
ScalingFilter = (int)config.Graphics.ScalingFilter.Value;
|
||||
ScalingFilterLevel = config.Graphics.ScalingFilterLevel.Value;
|
||||
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
|
||||
ShaderDumpPath = config.Graphics.ShadersDumpPath;
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
Task.Run(LoadAvailableGpus);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadAvailableGpus()
|
||||
{
|
||||
AvailableGpus.Clear();
|
||||
|
||||
var devices = VulkanRenderer.GetPhysicalDevices();
|
||||
|
||||
if (devices.Length == 0)
|
||||
{
|
||||
IsVulkanAvailable = false;
|
||||
GraphicsBackendIndex = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var device in devices)
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
_gpuIds.Add(device.Id);
|
||||
|
||||
AvailableGpus.Add(new ComboBoxItem { Content = $"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}" });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// GPU configuration needs to be loaded during the async method or it will always return 0.
|
||||
PreferredGpuIndex = _gpuIds.Contains(ConfigurationState.Instance.Graphics.PreferredGpu) ?
|
||||
_gpuIds.IndexOf(ConfigurationState.Instance.Graphics.PreferredGpu) : 0;
|
||||
}
|
||||
|
||||
public bool CheckIfModified(ConfigurationState config)
|
||||
{
|
||||
bool isDirty = false;
|
||||
|
||||
isDirty |= config.Graphics.GraphicsBackend.Value != (GraphicsBackend)GraphicsBackendIndex;
|
||||
isDirty |= config.Graphics.PreferredGpu.Value != _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
|
||||
isDirty |= config.Graphics.EnableShaderCache.Value != EnableShaderCache;
|
||||
isDirty |= config.Graphics.EnableTextureRecompression.Value != EnableTextureRecompression;
|
||||
isDirty |= config.Graphics.EnableMacroHLE.Value != EnableMacroHLE;
|
||||
isDirty |= config.Graphics.EnableColorSpacePassthrough.Value != EnableColorSpacePassthrough;
|
||||
isDirty |= config.Graphics.ResScale.Value != (ResolutionScale == 4 ? -1 : ResolutionScale + 1);
|
||||
isDirty |= config.Graphics.ResScaleCustom.Value != CustomResolutionScale;
|
||||
isDirty |= config.Graphics.MaxAnisotropy.Value != (MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy));
|
||||
isDirty |= config.Graphics.AspectRatio.Value != (AspectRatio)AspectRatio;
|
||||
isDirty |= config.Graphics.AntiAliasing.Value != (AntiAliasing)AntiAliasingEffect;
|
||||
isDirty |= config.Graphics.ScalingFilter.Value != (ScalingFilter)ScalingFilter;
|
||||
isDirty |= config.Graphics.ScalingFilterLevel.Value != ScalingFilterLevel;
|
||||
isDirty |= config.Graphics.BackendThreading.Value != (BackendThreading)GraphicsBackendMultithreadingIndex;
|
||||
isDirty |= config.Graphics.ShadersDumpPath.Value != ShaderDumpPath;
|
||||
|
||||
return isDirty;
|
||||
}
|
||||
|
||||
public void Save(ConfigurationState config)
|
||||
{
|
||||
config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex;
|
||||
config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
|
||||
config.Graphics.EnableShaderCache.Value = EnableShaderCache;
|
||||
config.Graphics.EnableTextureRecompression.Value = EnableTextureRecompression;
|
||||
config.Graphics.EnableMacroHLE.Value = EnableMacroHLE;
|
||||
config.Graphics.EnableColorSpacePassthrough.Value = EnableColorSpacePassthrough;
|
||||
config.Graphics.ResScale.Value = ResolutionScale == 4 ? -1 : ResolutionScale + 1;
|
||||
config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
|
||||
config.Graphics.MaxAnisotropy.Value = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
|
||||
config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio;
|
||||
config.Graphics.AntiAliasing.Value = (AntiAliasing)AntiAliasingEffect;
|
||||
config.Graphics.ScalingFilter.Value = (ScalingFilter)ScalingFilter;
|
||||
config.Graphics.ScalingFilterLevel.Value = ScalingFilterLevel;
|
||||
config.Graphics.BackendThreading.Value = (BackendThreading)GraphicsBackendMultithreadingIndex;
|
||||
config.Graphics.ShadersDumpPath.Value = ShaderDumpPath;
|
||||
|
||||
if (config.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
|
||||
{
|
||||
DriverUtilities.ToggleOGLThreading(GraphicsBackendMultithreadingIndex == (int)BackendThreading.Off);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using Ryujinx.Ava.UI.Models.Input;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels.Settings
|
||||
{
|
||||
public class SettingsHotkeysViewModel : BaseModel
|
||||
{
|
||||
public event Action DirtyEvent;
|
||||
|
||||
public HotkeyConfig KeyboardHotkey { get; set; }
|
||||
|
||||
public SettingsHotkeysViewModel()
|
||||
{
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
KeyboardHotkey = new HotkeyConfig(config.Hid.Hotkeys.Value);
|
||||
KeyboardHotkey.PropertyChanged += (_, _) => DirtyEvent?.Invoke();
|
||||
}
|
||||
|
||||
public bool CheckIfModified(ConfigurationState config)
|
||||
{
|
||||
bool isDirty = false;
|
||||
|
||||
isDirty |= !config.Hid.Hotkeys.Value.Equals(KeyboardHotkey.GetConfig());
|
||||
|
||||
return isDirty;
|
||||
}
|
||||
|
||||
public void Save(ConfigurationState config)
|
||||
{
|
||||
config.Hid.Hotkeys.Value = KeyboardHotkey.GetConfig();
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.Models.Input;
|
||||
using Ryujinx.Ava.UI.ViewModels.Input;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
@ -30,10 +31,12 @@ using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.Gamepad
|
||||
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
|
||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
namespace Ryujinx.Ava.UI.ViewModels.Settings
|
||||
{
|
||||
public class InputViewModel : BaseModel, IDisposable
|
||||
public class SettingsInputViewModel : BaseModel, IDisposable
|
||||
{
|
||||
public event Action DirtyEvent;
|
||||
|
||||
private const string Disabled = "disabled";
|
||||
private const string ProControllerResource = "Ryujinx.UI.Common/Resources/Controller_ProCon.svg";
|
||||
private const string JoyConPairResource = "Ryujinx.UI.Common/Resources/Controller_JoyConPair.svg";
|
||||
@ -54,6 +57,17 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
|
||||
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
private bool _isModified;
|
||||
public bool IsModified
|
||||
{
|
||||
get => _isModified;
|
||||
set
|
||||
{
|
||||
_isModified = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public IGamepadDriver AvaloniaKeyboardDriver { get; }
|
||||
public IGamepad SelectedGamepad { get; private set; }
|
||||
|
||||
@ -70,7 +84,39 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
public bool IsRight { get; set; }
|
||||
public bool IsLeft { get; set; }
|
||||
|
||||
public bool IsModified { get; set; }
|
||||
private bool _enableDockedMode;
|
||||
public bool EnableDockedMode
|
||||
{
|
||||
get => _enableDockedMode;
|
||||
set
|
||||
{
|
||||
_enableDockedMode = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableKeyboard;
|
||||
public bool EnableKeyboard
|
||||
{
|
||||
get => _enableKeyboard;
|
||||
set
|
||||
{
|
||||
_enableKeyboard = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableMouse;
|
||||
public bool EnableMouse
|
||||
{
|
||||
get => _enableMouse;
|
||||
set
|
||||
{
|
||||
_enableMouse = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public event Action NotifyChangesEvent;
|
||||
|
||||
public object ConfigViewModel
|
||||
@ -89,12 +135,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
get => _playerId;
|
||||
set
|
||||
{
|
||||
if (IsModified)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsModified = false;
|
||||
_playerId = value;
|
||||
|
||||
if (!Enum.IsDefined(typeof(PlayerIndex), _playerId))
|
||||
@ -231,7 +271,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
|
||||
public InputConfig Config { get; set; }
|
||||
|
||||
public InputViewModel(UserControl owner) : this()
|
||||
public SettingsInputViewModel(UserControl owner) : this()
|
||||
{
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
@ -254,7 +294,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
}
|
||||
}
|
||||
|
||||
public InputViewModel()
|
||||
public SettingsInputViewModel()
|
||||
{
|
||||
PlayerIndexes = new ObservableCollection<PlayerModel>();
|
||||
Controllers = new ObservableCollection<ControllerModel>();
|
||||
@ -288,6 +328,10 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
{
|
||||
ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig));
|
||||
}
|
||||
|
||||
EnableDockedMode = ConfigurationState.Instance.System.EnableDockedMode;
|
||||
EnableKeyboard = ConfigurationState.Instance.Hid.EnableKeyboard;
|
||||
EnableMouse = ConfigurationState.Instance.Hid.EnableMouse;
|
||||
}
|
||||
|
||||
public void LoadDevice()
|
||||
@ -740,37 +784,35 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1;
|
||||
|
||||
if (validFileName)
|
||||
{
|
||||
string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
|
||||
|
||||
InputConfig config = null;
|
||||
|
||||
if (IsKeyboard)
|
||||
{
|
||||
config = (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig();
|
||||
}
|
||||
else if (IsController)
|
||||
{
|
||||
config = (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
|
||||
}
|
||||
|
||||
config.ControllerType = Controllers[_controller].Type;
|
||||
|
||||
string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig);
|
||||
|
||||
await File.WriteAllTextAsync(path, jsonString);
|
||||
|
||||
LoadProfiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1;
|
||||
|
||||
if (validFileName)
|
||||
{
|
||||
string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
|
||||
|
||||
InputConfig config = null;
|
||||
|
||||
if (IsKeyboard)
|
||||
{
|
||||
config = (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig();
|
||||
}
|
||||
else if (IsController)
|
||||
{
|
||||
config = (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
|
||||
}
|
||||
|
||||
config.ControllerType = Controllers[_controller].Type;
|
||||
|
||||
string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig);
|
||||
|
||||
await File.WriteAllTextAsync(path, jsonString);
|
||||
|
||||
LoadProfiles();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]);
|
||||
}
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -801,19 +843,44 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
public bool CheckIfModified(ConfigurationState config)
|
||||
{
|
||||
IsModified = false;
|
||||
bool isDirty = false;
|
||||
|
||||
List<InputConfig> newConfig = new();
|
||||
isDirty |= IsModified;
|
||||
isDirty |= config.System.EnableDockedMode.Value != EnableDockedMode;
|
||||
isDirty |= config.Hid.EnableKeyboard.Value != EnableKeyboard;
|
||||
isDirty |= config.Hid.EnableMouse.Value != EnableMouse;
|
||||
|
||||
newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value);
|
||||
return isDirty;
|
||||
}
|
||||
|
||||
newConfig.Remove(newConfig.Find(x => x == null));
|
||||
public void Save(ConfigurationState config)
|
||||
{
|
||||
var newInputConfig = ConstructInputConfigList();
|
||||
|
||||
_mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newInputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
||||
|
||||
// Atomically replace and signal input change.
|
||||
// NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
|
||||
config.Hid.InputConfig.Value = newInputConfig;
|
||||
|
||||
config.System.EnableDockedMode.Value = EnableDockedMode;
|
||||
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
||||
config.Hid.EnableMouse.Value = EnableMouse;
|
||||
}
|
||||
|
||||
public List<InputConfig> ConstructInputConfigList()
|
||||
{
|
||||
List<InputConfig> newInputConfig = new();
|
||||
|
||||
newInputConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value);
|
||||
|
||||
newInputConfig.Remove(newInputConfig.Find(x => x == null));
|
||||
|
||||
if (Device == 0)
|
||||
{
|
||||
newConfig.Remove(newConfig.Find(x => x.PlayerIndex == this.PlayerId));
|
||||
newInputConfig.Remove(newInputConfig.Find(x => x.PlayerIndex == this.PlayerId));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -821,44 +888,33 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
|
||||
if (device.Type == DeviceType.Keyboard)
|
||||
{
|
||||
var inputConfig = (ConfigViewModel as KeyboardInputViewModel).Config;
|
||||
inputConfig.Id = device.Id;
|
||||
var keyboardConfig = (ConfigViewModel as KeyboardInputViewModel).Config;
|
||||
keyboardConfig.Id = device.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
var inputConfig = (ConfigViewModel as ControllerInputViewModel).Config;
|
||||
inputConfig.Id = device.Id.Split(" ")[0];
|
||||
var controllerConfig = (ConfigViewModel as ControllerInputViewModel).Config;
|
||||
controllerConfig.Id = device.Id.Split(" ")[0];
|
||||
}
|
||||
|
||||
var config = !IsController
|
||||
var inputConfig = !IsController
|
||||
? (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig()
|
||||
: (ConfigViewModel as ControllerInputViewModel).Config.GetConfig();
|
||||
config.ControllerType = Controllers[_controller].Type;
|
||||
config.PlayerIndex = _playerId;
|
||||
inputConfig.ControllerType = Controllers[_controller].Type;
|
||||
inputConfig.PlayerIndex = _playerId;
|
||||
|
||||
int i = newConfig.FindIndex(x => x.PlayerIndex == PlayerId);
|
||||
int i = newInputConfig.FindIndex(x => x.PlayerIndex == PlayerId);
|
||||
if (i == -1)
|
||||
{
|
||||
newConfig.Add(config);
|
||||
newInputConfig.Add(inputConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
newConfig[i] = config;
|
||||
newInputConfig[i] = inputConfig;
|
||||
}
|
||||
}
|
||||
|
||||
_mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
||||
|
||||
// Atomically replace and signal input change.
|
||||
// NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
|
||||
ConfigurationState.Instance.Hid.InputConfig.Value = newConfig;
|
||||
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
}
|
||||
|
||||
public void NotifyChange(string property)
|
||||
{
|
||||
OnPropertyChanged(property);
|
||||
return newInputConfig;
|
||||
}
|
||||
|
||||
public void NotifyChanges()
|
||||
@ -870,6 +926,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||
OnPropertyChanged(nameof(IsRight));
|
||||
OnPropertyChanged(nameof(IsLeft));
|
||||
NotifyChangesEvent?.Invoke();
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
|
||||
public void Dispose()
|
183
src/Ryujinx/UI/ViewModels/Settings/SettingsLoggingViewModel.cs
Normal file
183
src/Ryujinx/UI/ViewModels/Settings/SettingsLoggingViewModel.cs
Normal file
@ -0,0 +1,183 @@
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels.Settings
|
||||
{
|
||||
public class SettingsLoggingViewModel : BaseModel
|
||||
{
|
||||
public event Action DirtyEvent;
|
||||
|
||||
private bool _enableFileLog;
|
||||
public bool EnableFileLog
|
||||
{
|
||||
get => _enableFileLog;
|
||||
set
|
||||
{
|
||||
_enableFileLog = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableStub;
|
||||
public bool EnableStub
|
||||
{
|
||||
get => _enableStub;
|
||||
set
|
||||
{
|
||||
_enableStub = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableInfo;
|
||||
public bool EnableInfo
|
||||
{
|
||||
get => _enableInfo;
|
||||
set
|
||||
{
|
||||
_enableInfo = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableWarn;
|
||||
public bool EnableWarn
|
||||
{
|
||||
get => _enableWarn;
|
||||
set
|
||||
{
|
||||
_enableWarn = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableError;
|
||||
public bool EnableError
|
||||
{
|
||||
get => _enableError;
|
||||
set
|
||||
{
|
||||
_enableError = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableTrace;
|
||||
public bool EnableTrace
|
||||
{
|
||||
get => _enableTrace;
|
||||
set
|
||||
{
|
||||
_enableTrace = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableGuest;
|
||||
public bool EnableGuest
|
||||
{
|
||||
get => _enableGuest;
|
||||
set
|
||||
{
|
||||
_enableGuest = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableFsAccessLog;
|
||||
public bool EnableFsAccessLog
|
||||
{
|
||||
get => _enableFsAccessLog;
|
||||
set
|
||||
{
|
||||
_enableFsAccessLog = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableDebug;
|
||||
public bool EnableDebug
|
||||
{
|
||||
get => _enableDebug;
|
||||
set
|
||||
{
|
||||
_enableDebug = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _fsGlobalAccessLogMode;
|
||||
public int FsGlobalAccessLogMode
|
||||
{
|
||||
get => _fsGlobalAccessLogMode;
|
||||
set
|
||||
{
|
||||
_fsGlobalAccessLogMode = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _openglDebugLevel;
|
||||
public int OpenglDebugLevel
|
||||
{
|
||||
get => _openglDebugLevel;
|
||||
set
|
||||
{
|
||||
_openglDebugLevel = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsLoggingViewModel()
|
||||
{
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
EnableFileLog = config.Logger.EnableFileLog;
|
||||
EnableStub = config.Logger.EnableStub;
|
||||
EnableInfo = config.Logger.EnableInfo;
|
||||
EnableWarn = config.Logger.EnableWarn;
|
||||
EnableError = config.Logger.EnableError;
|
||||
EnableTrace = config.Logger.EnableTrace;
|
||||
EnableGuest = config.Logger.EnableGuest;
|
||||
EnableDebug = config.Logger.EnableDebug;
|
||||
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
||||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||
}
|
||||
|
||||
public bool CheckIfModified(ConfigurationState config)
|
||||
{
|
||||
bool isDirty = false;
|
||||
|
||||
isDirty |= config.Logger.EnableFileLog.Value != EnableFileLog;
|
||||
isDirty |= config.Logger.EnableStub.Value != EnableStub;
|
||||
isDirty |= config.Logger.EnableInfo.Value != EnableInfo;
|
||||
isDirty |= config.Logger.EnableWarn.Value != EnableWarn;
|
||||
isDirty |= config.Logger.EnableError.Value != EnableError;
|
||||
isDirty |= config.Logger.EnableTrace.Value != EnableTrace;
|
||||
isDirty |= config.Logger.EnableGuest.Value != EnableGuest;
|
||||
isDirty |= config.Logger.EnableDebug.Value != EnableDebug;
|
||||
isDirty |= config.Logger.EnableFsAccessLog.Value != EnableFsAccessLog;
|
||||
isDirty |= config.System.FsGlobalAccessLogMode.Value != FsGlobalAccessLogMode;
|
||||
isDirty |= config.Logger.GraphicsDebugLevel.Value != (GraphicsDebugLevel)OpenglDebugLevel;
|
||||
|
||||
return isDirty;
|
||||
}
|
||||
|
||||
public void Save(ConfigurationState config)
|
||||
{
|
||||
config.Logger.EnableFileLog.Value = EnableFileLog;
|
||||
config.Logger.EnableStub.Value = EnableStub;
|
||||
config.Logger.EnableInfo.Value = EnableInfo;
|
||||
config.Logger.EnableWarn.Value = EnableWarn;
|
||||
config.Logger.EnableError.Value = EnableError;
|
||||
config.Logger.EnableTrace.Value = EnableTrace;
|
||||
config.Logger.EnableGuest.Value = EnableGuest;
|
||||
config.Logger.EnableDebug.Value = EnableDebug;
|
||||
config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog;
|
||||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||
}
|
||||
}
|
||||
}
|
104
src/Ryujinx/UI/ViewModels/Settings/SettingsNetworkViewModel.cs
Normal file
104
src/Ryujinx/UI/ViewModels/Settings/SettingsNetworkViewModel.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Common.Configuration.Multiplayer;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels.Settings
|
||||
{
|
||||
public class SettingsNetworkViewModel : BaseModel
|
||||
{
|
||||
public event Action DirtyEvent;
|
||||
|
||||
private readonly Dictionary<string, string> _networkInterfaces = new();
|
||||
public AvaloniaList<string> NetworkInterfaceList
|
||||
{
|
||||
get => new(_networkInterfaces.Keys);
|
||||
}
|
||||
|
||||
private bool _enableInternetAccess;
|
||||
public bool EnableInternetAccess
|
||||
{
|
||||
get => _enableInternetAccess;
|
||||
set
|
||||
{
|
||||
_enableInternetAccess = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _networkInterfaceIndex;
|
||||
public int NetworkInterfaceIndex
|
||||
{
|
||||
get => _networkInterfaceIndex;
|
||||
set
|
||||
{
|
||||
_networkInterfaceIndex = value != -1 ? value : 0;
|
||||
OnPropertyChanged();
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _multiplayerModeIndex;
|
||||
public int MultiplayerModeIndex
|
||||
{
|
||||
get => _multiplayerModeIndex;
|
||||
set
|
||||
{
|
||||
_multiplayerModeIndex = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsNetworkViewModel()
|
||||
{
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
Task.Run(PopulateNetworkInterfaces);
|
||||
|
||||
// LAN interface index is loaded asynchronously in PopulateNetworkInterfaces()
|
||||
EnableInternetAccess = config.System.EnableInternetAccess;
|
||||
MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value;
|
||||
}
|
||||
|
||||
private async Task PopulateNetworkInterfaces()
|
||||
{
|
||||
_networkInterfaces.Clear();
|
||||
_networkInterfaces.Add(LocaleManager.Instance[LocaleKeys.NetworkInterfaceDefault], "0");
|
||||
|
||||
foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces())
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
_networkInterfaces.Add(networkInterface.Name, networkInterface.Id);
|
||||
});
|
||||
}
|
||||
|
||||
// Network interface index needs to be loaded during the async method, or it will always return 0.
|
||||
NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
|
||||
}
|
||||
|
||||
public bool CheckIfModified(ConfigurationState config)
|
||||
{
|
||||
bool isDirty = false;
|
||||
|
||||
isDirty |= config.System.EnableInternetAccess.Value != EnableInternetAccess;
|
||||
isDirty |= config.Multiplayer.LanInterfaceId.Value != _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
|
||||
isDirty |= config.Multiplayer.Mode.Value != (MultiplayerMode)MultiplayerModeIndex;
|
||||
|
||||
return isDirty;
|
||||
}
|
||||
|
||||
public void Save(ConfigurationState config)
|
||||
{
|
||||
config.System.EnableInternetAccess.Value = EnableInternetAccess;
|
||||
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
|
||||
config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex;
|
||||
}
|
||||
}
|
||||
}
|
228
src/Ryujinx/UI/ViewModels/Settings/SettingsSystemViewModel.cs
Normal file
228
src/Ryujinx/UI/ViewModels/Settings/SettingsSystemViewModel.cs
Normal file
@ -0,0 +1,228 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Threading;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using Ryujinx.UI.Common.Configuration.System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels.Settings
|
||||
{
|
||||
public class SettingsSystemViewModel : BaseModel
|
||||
{
|
||||
public event Action DirtyEvent;
|
||||
|
||||
private readonly List<string> _validTzRegions = new();
|
||||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private readonly ContentManager _contentManager;
|
||||
private TimeZoneContentManager _timeZoneContentManager;
|
||||
|
||||
private int _region;
|
||||
public int Region
|
||||
{
|
||||
get => _region;
|
||||
set
|
||||
{
|
||||
_region = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _language;
|
||||
public int Language
|
||||
{
|
||||
get => _language;
|
||||
set
|
||||
{
|
||||
_language = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private string _timeZone;
|
||||
public string TimeZone
|
||||
{
|
||||
get => _timeZone;
|
||||
set
|
||||
{
|
||||
_timeZone = value;
|
||||
OnPropertyChanged();
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private DateTimeOffset _currentDate;
|
||||
public DateTimeOffset CurrentDate
|
||||
{
|
||||
get => _currentDate;
|
||||
set
|
||||
{
|
||||
_currentDate = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private TimeSpan _currentTime;
|
||||
public TimeSpan CurrentTime
|
||||
{
|
||||
get => _currentTime;
|
||||
set
|
||||
{
|
||||
_currentTime = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableVsync;
|
||||
public bool EnableVsync
|
||||
{
|
||||
get => _enableVsync;
|
||||
set
|
||||
{
|
||||
_enableVsync = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableFsIntegrityChecks;
|
||||
public bool EnableFsIntegrityChecks
|
||||
{
|
||||
get => _enableFsIntegrityChecks;
|
||||
set
|
||||
{
|
||||
_enableFsIntegrityChecks = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _ignoreMissingServices;
|
||||
public bool IgnoreMissingServices
|
||||
{
|
||||
get => _ignoreMissingServices;
|
||||
set
|
||||
{
|
||||
_ignoreMissingServices = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _expandedDramSize;
|
||||
public bool ExpandDramSize
|
||||
{
|
||||
get => _expandedDramSize;
|
||||
set
|
||||
{
|
||||
_expandedDramSize = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
internal AvaloniaList<TimeZone> TimeZones { get; set; }
|
||||
|
||||
public SettingsSystemViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager)
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_contentManager = contentManager;
|
||||
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
TimeZones = new();
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
Task.Run(LoadTimeZones);
|
||||
}
|
||||
|
||||
Region = (int)config.System.Region.Value;
|
||||
Language = (int)config.System.Language.Value;
|
||||
TimeZone = config.System.TimeZone;
|
||||
|
||||
DateTime currentHostDateTime = DateTime.Now;
|
||||
TimeSpan systemDateTimeOffset = TimeSpan.FromSeconds(config.System.SystemTimeOffset);
|
||||
DateTime currentDateTime = currentHostDateTime.Add(systemDateTimeOffset);
|
||||
|
||||
CurrentDate = currentDateTime.Date;
|
||||
CurrentTime = currentDateTime.TimeOfDay;
|
||||
|
||||
EnableVsync = config.Graphics.EnableVsync;
|
||||
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
||||
ExpandDramSize = config.System.ExpandRam;
|
||||
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
||||
}
|
||||
|
||||
public async Task LoadTimeZones()
|
||||
{
|
||||
_timeZoneContentManager = new TimeZoneContentManager();
|
||||
|
||||
_timeZoneContentManager.InitializeInstance(_virtualFileSystem, _contentManager, IntegrityCheckLevel.None);
|
||||
|
||||
foreach ((int offset, string location, string abbr) in _timeZoneContentManager.ParseTzOffsets())
|
||||
{
|
||||
int hours = Math.DivRem(offset, 3600, out int seconds);
|
||||
int minutes = Math.Abs(seconds) / 60;
|
||||
|
||||
string abbr2 = abbr.StartsWith('+') || abbr.StartsWith('-') ? string.Empty : abbr;
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
TimeZones.Add(new TimeZone($"UTC{hours:+0#;-0#;+00}:{minutes:D2}", location, abbr2));
|
||||
|
||||
_validTzRegions.Add(location);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void ValidateAndSetTimeZone(string location)
|
||||
{
|
||||
if (_validTzRegions.Contains(location))
|
||||
{
|
||||
TimeZone = location;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool CheckIfModified(ConfigurationState config)
|
||||
{
|
||||
bool isDirty = false;
|
||||
|
||||
isDirty |= config.System.Region.Value != (Region)Region;
|
||||
isDirty |= config.System.Language.Value != (Language)Language;
|
||||
|
||||
if (_validTzRegions.Contains(TimeZone))
|
||||
{
|
||||
isDirty |= config.System.TimeZone.Value != TimeZone;
|
||||
}
|
||||
|
||||
// SystemTimeOffset will always have changed, so we don't check it here
|
||||
|
||||
isDirty |= config.Graphics.EnableVsync.Value != EnableVsync;
|
||||
isDirty |= config.System.EnableFsIntegrityChecks.Value != EnableFsIntegrityChecks;
|
||||
isDirty |= config.System.ExpandRam.Value != ExpandDramSize;
|
||||
isDirty |= config.System.IgnoreMissingServices.Value != IgnoreMissingServices;
|
||||
|
||||
return isDirty;
|
||||
}
|
||||
|
||||
public void Save(ConfigurationState config)
|
||||
{
|
||||
config.System.Region.Value = (Region)Region;
|
||||
config.System.Language.Value = (Language)Language;
|
||||
|
||||
if (_validTzRegions.Contains(TimeZone))
|
||||
{
|
||||
config.System.TimeZone.Value = TimeZone;
|
||||
}
|
||||
|
||||
config.System.SystemTimeOffset.Value = Convert.ToInt64((CurrentDate.ToUnixTimeSeconds() + CurrentTime.TotalSeconds) - DateTimeOffset.Now.ToUnixTimeSeconds());
|
||||
|
||||
config.Graphics.EnableVsync.Value = EnableVsync;
|
||||
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
||||
config.System.ExpandRam.Value = ExpandDramSize;
|
||||
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
|
||||
}
|
||||
}
|
||||
}
|
148
src/Ryujinx/UI/ViewModels/Settings/SettingsUIViewModel.cs
Normal file
148
src/Ryujinx/UI/ViewModels/Settings/SettingsUIViewModel.cs
Normal file
@ -0,0 +1,148 @@
|
||||
using Avalonia.Collections;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels.Settings
|
||||
{
|
||||
public class SettingsUIViewModel : BaseModel
|
||||
{
|
||||
public event Action DirtyEvent;
|
||||
|
||||
private bool _enableDiscordIntegration;
|
||||
public bool EnableDiscordIntegration
|
||||
{
|
||||
get => _enableDiscordIntegration;
|
||||
set
|
||||
{
|
||||
_enableDiscordIntegration = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _checkUpdatesOnStart;
|
||||
public bool CheckUpdatesOnStart
|
||||
{
|
||||
get => _checkUpdatesOnStart;
|
||||
set
|
||||
{
|
||||
_checkUpdatesOnStart = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _showConfirmExit;
|
||||
public bool ShowConfirmExit
|
||||
{
|
||||
get => _showConfirmExit;
|
||||
set
|
||||
{
|
||||
_showConfirmExit = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _rememberWindowState;
|
||||
public bool RememberWindowState
|
||||
{
|
||||
get => _rememberWindowState;
|
||||
set
|
||||
{
|
||||
_rememberWindowState = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _hideCursor;
|
||||
public int HideCursor
|
||||
{
|
||||
get => _hideCursor;
|
||||
set
|
||||
{
|
||||
_hideCursor = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private int _baseStyleIndex;
|
||||
public int BaseStyleIndex
|
||||
{
|
||||
get => _baseStyleIndex;
|
||||
set
|
||||
{
|
||||
_baseStyleIndex = value;
|
||||
DirtyEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public bool DirsChanged;
|
||||
|
||||
public AvaloniaList<string> GameDirectories { get; set; }
|
||||
|
||||
public SettingsUIViewModel()
|
||||
{
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
GameDirectories = new();
|
||||
|
||||
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
||||
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
|
||||
ShowConfirmExit = config.ShowConfirmExit;
|
||||
RememberWindowState = config.RememberWindowState;
|
||||
HideCursor = (int)config.HideCursor.Value;
|
||||
|
||||
GameDirectories.Clear();
|
||||
GameDirectories.AddRange(config.UI.GameDirs.Value);
|
||||
GameDirectories.CollectionChanged += (_, _) => DirtyEvent?.Invoke();
|
||||
|
||||
BaseStyleIndex = config.UI.BaseStyle.Value switch
|
||||
{
|
||||
"Auto" => 0,
|
||||
"Light" => 1,
|
||||
"Dark" => 2,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
public bool CheckIfModified(ConfigurationState config)
|
||||
{
|
||||
bool isDirty = false;
|
||||
|
||||
DirsChanged = !config.UI.GameDirs.Value.SequenceEqual(GameDirectories);
|
||||
|
||||
isDirty |= config.EnableDiscordIntegration.Value != EnableDiscordIntegration;
|
||||
isDirty |= config.CheckUpdatesOnStart.Value != CheckUpdatesOnStart;
|
||||
isDirty |= config.ShowConfirmExit.Value != ShowConfirmExit;
|
||||
isDirty |= config.RememberWindowState.Value != RememberWindowState;
|
||||
isDirty |= config.HideCursor.Value != (HideCursorMode)HideCursor;
|
||||
isDirty |= DirsChanged;
|
||||
isDirty |= config.UI.BaseStyle.Value != BaseStyleIndex switch
|
||||
{
|
||||
0 => "Auto",
|
||||
1 => "Light",
|
||||
2 => "Dark",
|
||||
_ => "Auto"
|
||||
};
|
||||
|
||||
return isDirty;
|
||||
}
|
||||
|
||||
public void Save(ConfigurationState config)
|
||||
{
|
||||
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
||||
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
|
||||
config.ShowConfirmExit.Value = ShowConfirmExit;
|
||||
config.RememberWindowState.Value = RememberWindowState;
|
||||
config.HideCursor.Value = (HideCursorMode)HideCursor;
|
||||
config.UI.GameDirs.Value = GameDirectories.ToList();
|
||||
config.UI.BaseStyle.Value = BaseStyleIndex switch
|
||||
{
|
||||
0 => "Auto",
|
||||
1 => "Light",
|
||||
2 => "Dark",
|
||||
_ => "Auto"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
118
src/Ryujinx/UI/ViewModels/Settings/SettingsViewModel.cs
Normal file
118
src/Ryujinx/UI/ViewModels/Settings/SettingsViewModel.cs
Normal file
@ -0,0 +1,118 @@
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels.Settings
|
||||
{
|
||||
public class SettingsViewModel : BaseModel
|
||||
{
|
||||
private bool _isModified;
|
||||
|
||||
public bool IsModified
|
||||
{
|
||||
get => _isModified;
|
||||
private set
|
||||
{
|
||||
DirtyEvent?.Invoke(value);
|
||||
_isModified = value;
|
||||
}
|
||||
}
|
||||
|
||||
public event Action CloseWindow;
|
||||
public event Action<bool> DirtyEvent;
|
||||
public event Action<bool> ToggleButtons;
|
||||
|
||||
public bool IsMacOS => OperatingSystem.IsMacOS();
|
||||
|
||||
private readonly SettingsAudioViewModel _audioViewModel;
|
||||
private readonly SettingsCpuViewModel _cpuViewModel;
|
||||
private readonly SettingsGraphicsViewModel _graphicsViewModel;
|
||||
private readonly SettingsHotkeysViewModel _hotkeysViewModel;
|
||||
private readonly SettingsInputViewModel _inputViewModel;
|
||||
private readonly SettingsLoggingViewModel _loggingViewModel;
|
||||
private readonly SettingsNetworkViewModel _networkViewModel;
|
||||
private readonly SettingsSystemViewModel _systemViewModel;
|
||||
private readonly SettingsUIViewModel _uiViewModel;
|
||||
|
||||
public SettingsViewModel(
|
||||
SettingsAudioViewModel audioViewModel,
|
||||
SettingsCpuViewModel cpuViewModel,
|
||||
SettingsGraphicsViewModel graphicsViewModel,
|
||||
SettingsHotkeysViewModel hotkeysViewModel,
|
||||
SettingsInputViewModel inputViewModel,
|
||||
SettingsLoggingViewModel loggingViewModel,
|
||||
SettingsNetworkViewModel networkViewModel,
|
||||
SettingsSystemViewModel systemViewModel,
|
||||
SettingsUIViewModel uiViewModel)
|
||||
{
|
||||
_audioViewModel = audioViewModel;
|
||||
_cpuViewModel = cpuViewModel;
|
||||
_graphicsViewModel = graphicsViewModel;
|
||||
_hotkeysViewModel = hotkeysViewModel;
|
||||
_inputViewModel = inputViewModel;
|
||||
_loggingViewModel = loggingViewModel;
|
||||
_networkViewModel = networkViewModel;
|
||||
_systemViewModel = systemViewModel;
|
||||
_uiViewModel = uiViewModel;
|
||||
|
||||
_audioViewModel.DirtyEvent += CheckIfModified;
|
||||
_cpuViewModel.DirtyEvent += CheckIfModified;
|
||||
_graphicsViewModel.DirtyEvent += CheckIfModified;
|
||||
_hotkeysViewModel.DirtyEvent += CheckIfModified;
|
||||
_inputViewModel.DirtyEvent += CheckIfModified;
|
||||
_loggingViewModel.DirtyEvent += CheckIfModified;
|
||||
_networkViewModel.DirtyEvent += CheckIfModified;
|
||||
_systemViewModel.DirtyEvent += CheckIfModified;
|
||||
_uiViewModel.DirtyEvent += CheckIfModified;
|
||||
}
|
||||
|
||||
public void CheckIfModified()
|
||||
{
|
||||
bool isDirty = false;
|
||||
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
isDirty |= _audioViewModel?.CheckIfModified(config) ?? false;
|
||||
isDirty |= _cpuViewModel?.CheckIfModified(config) ?? false;
|
||||
isDirty |= _graphicsViewModel?.CheckIfModified(config) ?? false;
|
||||
isDirty |= _hotkeysViewModel?.CheckIfModified(config) ?? false;
|
||||
isDirty |= _inputViewModel?.CheckIfModified(config) ?? false;
|
||||
isDirty |= _loggingViewModel?.CheckIfModified(config) ?? false;
|
||||
isDirty |= _networkViewModel?.CheckIfModified(config) ?? false;
|
||||
isDirty |= _systemViewModel?.CheckIfModified(config) ?? false;
|
||||
isDirty |= _uiViewModel?.CheckIfModified(config) ?? false;
|
||||
|
||||
IsModified = isDirty;
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
_audioViewModel?.Save(config);
|
||||
_cpuViewModel?.Save(config);
|
||||
_graphicsViewModel?.Save(config);
|
||||
_hotkeysViewModel?.Save(config);
|
||||
_inputViewModel?.Save(config);
|
||||
_loggingViewModel?.Save(config);
|
||||
_networkViewModel?.Save(config);
|
||||
_systemViewModel?.Save(config);
|
||||
_uiViewModel?.Save(config);
|
||||
|
||||
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
|
||||
MainWindow.UpdateGraphicsConfig();
|
||||
}
|
||||
|
||||
public void ApplyButton()
|
||||
{
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
public void OkButton()
|
||||
{
|
||||
SaveSettings();
|
||||
CloseWindow?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,615 +0,0 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using Ryujinx.Audio.Backends.OpenAL;
|
||||
using Ryujinx.Audio.Backends.SDL2;
|
||||
using Ryujinx.Audio.Backends.SoundIo;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models.Input;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Multiplayer;
|
||||
using Ryujinx.Common.GraphicsDriver;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Vulkan;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
using Ryujinx.UI.Common.Configuration.System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public class SettingsViewModel : BaseModel
|
||||
{
|
||||
private readonly VirtualFileSystem _virtualFileSystem;
|
||||
private readonly ContentManager _contentManager;
|
||||
private TimeZoneContentManager _timeZoneContentManager;
|
||||
|
||||
private readonly List<string> _validTzRegions;
|
||||
|
||||
private readonly Dictionary<string, string> _networkInterfaces;
|
||||
|
||||
private float _customResolutionScale;
|
||||
private int _resolutionScale;
|
||||
private int _graphicsBackendMultithreadingIndex;
|
||||
private float _volume;
|
||||
private bool _isVulkanAvailable = true;
|
||||
private bool _directoryChanged;
|
||||
private readonly List<string> _gpuIds = new();
|
||||
private int _graphicsBackendIndex;
|
||||
private int _scalingFilter;
|
||||
private int _scalingFilterLevel;
|
||||
|
||||
public event Action CloseWindow;
|
||||
public event Action SaveSettingsEvent;
|
||||
private int _networkInterfaceIndex;
|
||||
private int _multiplayerModeIndex;
|
||||
|
||||
public int ResolutionScale
|
||||
{
|
||||
get => _resolutionScale;
|
||||
set
|
||||
{
|
||||
_resolutionScale = value;
|
||||
|
||||
OnPropertyChanged(nameof(CustomResolutionScale));
|
||||
OnPropertyChanged(nameof(IsCustomResolutionScaleActive));
|
||||
}
|
||||
}
|
||||
|
||||
public int GraphicsBackendMultithreadingIndex
|
||||
{
|
||||
get => _graphicsBackendMultithreadingIndex;
|
||||
set
|
||||
{
|
||||
_graphicsBackendMultithreadingIndex = value;
|
||||
|
||||
if (_graphicsBackendMultithreadingIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage],
|
||||
"",
|
||||
"",
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
||||
LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle])
|
||||
);
|
||||
}
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public float CustomResolutionScale
|
||||
{
|
||||
get => _customResolutionScale;
|
||||
set
|
||||
{
|
||||
_customResolutionScale = MathF.Round(value, 1);
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsVulkanAvailable
|
||||
{
|
||||
get => _isVulkanAvailable;
|
||||
set
|
||||
{
|
||||
_isVulkanAvailable = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
|
||||
|
||||
public bool IsHypervisorAvailable => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
|
||||
|
||||
public bool DirectoryChanged
|
||||
{
|
||||
get => _directoryChanged;
|
||||
set
|
||||
{
|
||||
_directoryChanged = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsMacOS => OperatingSystem.IsMacOS();
|
||||
|
||||
public bool EnableDiscordIntegration { get; set; }
|
||||
public bool CheckUpdatesOnStart { get; set; }
|
||||
public bool ShowConfirmExit { get; set; }
|
||||
public bool RememberWindowState { get; set; }
|
||||
public int HideCursor { get; set; }
|
||||
public bool EnableDockedMode { get; set; }
|
||||
public bool EnableKeyboard { get; set; }
|
||||
public bool EnableMouse { get; set; }
|
||||
public bool EnableVsync { get; set; }
|
||||
public bool EnablePptc { get; set; }
|
||||
public bool EnableInternetAccess { get; set; }
|
||||
public bool EnableFsIntegrityChecks { get; set; }
|
||||
public bool IgnoreMissingServices { get; set; }
|
||||
public bool ExpandDramSize { get; set; }
|
||||
public bool EnableShaderCache { get; set; }
|
||||
public bool EnableTextureRecompression { get; set; }
|
||||
public bool EnableMacroHLE { get; set; }
|
||||
public bool EnableColorSpacePassthrough { get; set; }
|
||||
public bool ColorSpacePassthroughAvailable => IsMacOS;
|
||||
public bool EnableFileLog { get; set; }
|
||||
public bool EnableStub { get; set; }
|
||||
public bool EnableInfo { get; set; }
|
||||
public bool EnableWarn { get; set; }
|
||||
public bool EnableError { get; set; }
|
||||
public bool EnableTrace { get; set; }
|
||||
public bool EnableGuest { get; set; }
|
||||
public bool EnableFsAccessLog { get; set; }
|
||||
public bool EnableDebug { get; set; }
|
||||
public bool IsOpenAlEnabled { get; set; }
|
||||
public bool IsSoundIoEnabled { get; set; }
|
||||
public bool IsSDL2Enabled { get; set; }
|
||||
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
||||
public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr;
|
||||
|
||||
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
|
||||
public bool UseHypervisor { get; set; }
|
||||
|
||||
public string TimeZone { get; set; }
|
||||
public string ShaderDumpPath { get; set; }
|
||||
|
||||
public int Language { get; set; }
|
||||
public int Region { get; set; }
|
||||
public int FsGlobalAccessLogMode { get; set; }
|
||||
public int AudioBackend { get; set; }
|
||||
public int MaxAnisotropy { get; set; }
|
||||
public int AspectRatio { get; set; }
|
||||
public int AntiAliasingEffect { get; set; }
|
||||
public string ScalingFilterLevelText => ScalingFilterLevel.ToString("0");
|
||||
public int ScalingFilterLevel
|
||||
{
|
||||
get => _scalingFilterLevel;
|
||||
set
|
||||
{
|
||||
_scalingFilterLevel = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(ScalingFilterLevelText));
|
||||
}
|
||||
}
|
||||
public int OpenglDebugLevel { get; set; }
|
||||
public int MemoryMode { get; set; }
|
||||
public int BaseStyleIndex { get; set; }
|
||||
public int GraphicsBackendIndex
|
||||
{
|
||||
get => _graphicsBackendIndex;
|
||||
set
|
||||
{
|
||||
_graphicsBackendIndex = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(IsVulkanSelected));
|
||||
}
|
||||
}
|
||||
public int ScalingFilter
|
||||
{
|
||||
get => _scalingFilter;
|
||||
set
|
||||
{
|
||||
_scalingFilter = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(IsScalingFilterActive));
|
||||
}
|
||||
}
|
||||
|
||||
public int PreferredGpuIndex { get; set; }
|
||||
|
||||
public float Volume
|
||||
{
|
||||
get => _volume;
|
||||
set
|
||||
{
|
||||
_volume = value;
|
||||
|
||||
ConfigurationState.Instance.System.AudioVolume.Value = _volume / 100;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public DateTimeOffset CurrentDate { get; set; }
|
||||
public TimeSpan CurrentTime { get; set; }
|
||||
|
||||
internal AvaloniaList<TimeZone> TimeZones { get; set; }
|
||||
public AvaloniaList<string> GameDirectories { get; set; }
|
||||
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
|
||||
|
||||
public AvaloniaList<string> NetworkInterfaceList
|
||||
{
|
||||
get => new(_networkInterfaces.Keys);
|
||||
}
|
||||
|
||||
public HotkeyConfig KeyboardHotkey { get; set; }
|
||||
|
||||
public int NetworkInterfaceIndex
|
||||
{
|
||||
get => _networkInterfaceIndex;
|
||||
set
|
||||
{
|
||||
_networkInterfaceIndex = value != -1 ? value : 0;
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[_networkInterfaceIndex]];
|
||||
}
|
||||
}
|
||||
|
||||
public int MultiplayerModeIndex
|
||||
{
|
||||
get => _multiplayerModeIndex;
|
||||
set
|
||||
{
|
||||
_multiplayerModeIndex = value;
|
||||
ConfigurationState.Instance.Multiplayer.Mode.Value = (MultiplayerMode)_multiplayerModeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_contentManager = contentManager;
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
Task.Run(LoadTimeZones);
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsViewModel()
|
||||
{
|
||||
GameDirectories = new AvaloniaList<string>();
|
||||
TimeZones = new AvaloniaList<TimeZone>();
|
||||
AvailableGpus = new ObservableCollection<ComboBoxItem>();
|
||||
_validTzRegions = new List<string>();
|
||||
_networkInterfaces = new Dictionary<string, string>();
|
||||
|
||||
Task.Run(CheckSoundBackends);
|
||||
Task.Run(PopulateNetworkInterfaces);
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
Task.Run(LoadAvailableGpus);
|
||||
LoadCurrentConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CheckSoundBackends()
|
||||
{
|
||||
IsOpenAlEnabled = OpenALHardwareDeviceDriver.IsSupported;
|
||||
IsSoundIoEnabled = SoundIoHardwareDeviceDriver.IsSupported;
|
||||
IsSDL2Enabled = SDL2HardwareDeviceDriver.IsSupported;
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
OnPropertyChanged(nameof(IsOpenAlEnabled));
|
||||
OnPropertyChanged(nameof(IsSoundIoEnabled));
|
||||
OnPropertyChanged(nameof(IsSDL2Enabled));
|
||||
});
|
||||
}
|
||||
|
||||
private async Task LoadAvailableGpus()
|
||||
{
|
||||
AvailableGpus.Clear();
|
||||
|
||||
var devices = VulkanRenderer.GetPhysicalDevices();
|
||||
|
||||
if (devices.Length == 0)
|
||||
{
|
||||
IsVulkanAvailable = false;
|
||||
GraphicsBackendIndex = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var device in devices)
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
_gpuIds.Add(device.Id);
|
||||
|
||||
AvailableGpus.Add(new ComboBoxItem { Content = $"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}" });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// GPU configuration needs to be loaded during the async method or it will always return 0.
|
||||
PreferredGpuIndex = _gpuIds.Contains(ConfigurationState.Instance.Graphics.PreferredGpu) ?
|
||||
_gpuIds.IndexOf(ConfigurationState.Instance.Graphics.PreferredGpu) : 0;
|
||||
|
||||
Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(PreferredGpuIndex)));
|
||||
}
|
||||
|
||||
public async Task LoadTimeZones()
|
||||
{
|
||||
_timeZoneContentManager = new TimeZoneContentManager();
|
||||
|
||||
_timeZoneContentManager.InitializeInstance(_virtualFileSystem, _contentManager, IntegrityCheckLevel.None);
|
||||
|
||||
foreach ((int offset, string location, string abbr) in _timeZoneContentManager.ParseTzOffsets())
|
||||
{
|
||||
int hours = Math.DivRem(offset, 3600, out int seconds);
|
||||
int minutes = Math.Abs(seconds) / 60;
|
||||
|
||||
string abbr2 = abbr.StartsWith('+') || abbr.StartsWith('-') ? string.Empty : abbr;
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
TimeZones.Add(new TimeZone($"UTC{hours:+0#;-0#;+00}:{minutes:D2}", location, abbr2));
|
||||
|
||||
_validTzRegions.Add(location);
|
||||
});
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(TimeZone)));
|
||||
}
|
||||
|
||||
private async Task PopulateNetworkInterfaces()
|
||||
{
|
||||
_networkInterfaces.Clear();
|
||||
_networkInterfaces.Add(LocaleManager.Instance[LocaleKeys.NetworkInterfaceDefault], "0");
|
||||
|
||||
foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces())
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
_networkInterfaces.Add(networkInterface.Name, networkInterface.Id);
|
||||
});
|
||||
}
|
||||
|
||||
// Network interface index needs to be loaded during the async method or it will always return 0.
|
||||
NetworkInterfaceIndex = _networkInterfaces.Values.ToList().IndexOf(ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value);
|
||||
|
||||
Dispatcher.UIThread.Post(() => OnPropertyChanged(nameof(NetworkInterfaceIndex)));
|
||||
}
|
||||
|
||||
public void ValidateAndSetTimeZone(string location)
|
||||
{
|
||||
if (_validTzRegions.Contains(location))
|
||||
{
|
||||
TimeZone = location;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadCurrentConfiguration()
|
||||
{
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
// User Interface
|
||||
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
||||
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
|
||||
ShowConfirmExit = config.ShowConfirmExit;
|
||||
RememberWindowState = config.RememberWindowState;
|
||||
HideCursor = (int)config.HideCursor.Value;
|
||||
|
||||
GameDirectories.Clear();
|
||||
GameDirectories.AddRange(config.UI.GameDirs.Value);
|
||||
|
||||
BaseStyleIndex = config.UI.BaseStyle.Value switch
|
||||
{
|
||||
"Auto" => 0,
|
||||
"Light" => 1,
|
||||
"Dark" => 2,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
// Input
|
||||
EnableDockedMode = config.System.EnableDockedMode;
|
||||
EnableKeyboard = config.Hid.EnableKeyboard;
|
||||
EnableMouse = config.Hid.EnableMouse;
|
||||
|
||||
// Keyboard Hotkeys
|
||||
KeyboardHotkey = new HotkeyConfig(config.Hid.Hotkeys.Value);
|
||||
|
||||
// System
|
||||
Region = (int)config.System.Region.Value;
|
||||
Language = (int)config.System.Language.Value;
|
||||
TimeZone = config.System.TimeZone;
|
||||
|
||||
DateTime currentHostDateTime = DateTime.Now;
|
||||
TimeSpan systemDateTimeOffset = TimeSpan.FromSeconds(config.System.SystemTimeOffset);
|
||||
DateTime currentDateTime = currentHostDateTime.Add(systemDateTimeOffset);
|
||||
CurrentDate = currentDateTime.Date;
|
||||
CurrentTime = currentDateTime.TimeOfDay;
|
||||
|
||||
EnableVsync = config.Graphics.EnableVsync;
|
||||
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
||||
ExpandDramSize = config.System.ExpandRam;
|
||||
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
||||
|
||||
// CPU
|
||||
EnablePptc = config.System.EnablePtc;
|
||||
MemoryMode = (int)config.System.MemoryManagerMode.Value;
|
||||
UseHypervisor = config.System.UseHypervisor;
|
||||
|
||||
// Graphics
|
||||
GraphicsBackendIndex = (int)config.Graphics.GraphicsBackend.Value;
|
||||
// Physical devices are queried asynchronously hence the prefered index config value is loaded in LoadAvailableGpus().
|
||||
EnableShaderCache = config.Graphics.EnableShaderCache;
|
||||
EnableTextureRecompression = config.Graphics.EnableTextureRecompression;
|
||||
EnableMacroHLE = config.Graphics.EnableMacroHLE;
|
||||
EnableColorSpacePassthrough = config.Graphics.EnableColorSpacePassthrough;
|
||||
ResolutionScale = config.Graphics.ResScale == -1 ? 4 : config.Graphics.ResScale - 1;
|
||||
CustomResolutionScale = config.Graphics.ResScaleCustom;
|
||||
MaxAnisotropy = config.Graphics.MaxAnisotropy == -1 ? 0 : (int)(MathF.Log2(config.Graphics.MaxAnisotropy));
|
||||
AspectRatio = (int)config.Graphics.AspectRatio.Value;
|
||||
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
|
||||
ShaderDumpPath = config.Graphics.ShadersDumpPath;
|
||||
AntiAliasingEffect = (int)config.Graphics.AntiAliasing.Value;
|
||||
ScalingFilter = (int)config.Graphics.ScalingFilter.Value;
|
||||
ScalingFilterLevel = config.Graphics.ScalingFilterLevel.Value;
|
||||
|
||||
// Audio
|
||||
AudioBackend = (int)config.System.AudioBackend.Value;
|
||||
Volume = config.System.AudioVolume * 100;
|
||||
|
||||
// Network
|
||||
EnableInternetAccess = config.System.EnableInternetAccess;
|
||||
// LAN interface index is loaded asynchronously in PopulateNetworkInterfaces()
|
||||
|
||||
// Logging
|
||||
EnableFileLog = config.Logger.EnableFileLog;
|
||||
EnableStub = config.Logger.EnableStub;
|
||||
EnableInfo = config.Logger.EnableInfo;
|
||||
EnableWarn = config.Logger.EnableWarn;
|
||||
EnableError = config.Logger.EnableError;
|
||||
EnableTrace = config.Logger.EnableTrace;
|
||||
EnableGuest = config.Logger.EnableGuest;
|
||||
EnableDebug = config.Logger.EnableDebug;
|
||||
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
||||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||
|
||||
MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value;
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
ConfigurationState config = ConfigurationState.Instance;
|
||||
|
||||
// User Interface
|
||||
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
||||
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
|
||||
config.ShowConfirmExit.Value = ShowConfirmExit;
|
||||
config.RememberWindowState.Value = RememberWindowState;
|
||||
config.HideCursor.Value = (HideCursorMode)HideCursor;
|
||||
|
||||
if (_directoryChanged)
|
||||
{
|
||||
List<string> gameDirs = new(GameDirectories);
|
||||
config.UI.GameDirs.Value = gameDirs;
|
||||
}
|
||||
|
||||
config.UI.BaseStyle.Value = BaseStyleIndex switch
|
||||
{
|
||||
0 => "Auto",
|
||||
1 => "Light",
|
||||
2 => "Dark",
|
||||
_ => "Auto"
|
||||
};
|
||||
|
||||
// Input
|
||||
config.System.EnableDockedMode.Value = EnableDockedMode;
|
||||
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
||||
config.Hid.EnableMouse.Value = EnableMouse;
|
||||
|
||||
// Keyboard Hotkeys
|
||||
config.Hid.Hotkeys.Value = KeyboardHotkey.GetConfig();
|
||||
|
||||
// System
|
||||
config.System.Region.Value = (Region)Region;
|
||||
config.System.Language.Value = (Language)Language;
|
||||
|
||||
if (_validTzRegions.Contains(TimeZone))
|
||||
{
|
||||
config.System.TimeZone.Value = TimeZone;
|
||||
}
|
||||
|
||||
config.System.SystemTimeOffset.Value = Convert.ToInt64((CurrentDate.ToUnixTimeSeconds() + CurrentTime.TotalSeconds) - DateTimeOffset.Now.ToUnixTimeSeconds());
|
||||
config.Graphics.EnableVsync.Value = EnableVsync;
|
||||
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
||||
config.System.ExpandRam.Value = ExpandDramSize;
|
||||
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
|
||||
|
||||
// CPU
|
||||
config.System.EnablePtc.Value = EnablePptc;
|
||||
config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode;
|
||||
config.System.UseHypervisor.Value = UseHypervisor;
|
||||
|
||||
// Graphics
|
||||
config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex;
|
||||
config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
|
||||
config.Graphics.EnableShaderCache.Value = EnableShaderCache;
|
||||
config.Graphics.EnableTextureRecompression.Value = EnableTextureRecompression;
|
||||
config.Graphics.EnableMacroHLE.Value = EnableMacroHLE;
|
||||
config.Graphics.EnableColorSpacePassthrough.Value = EnableColorSpacePassthrough;
|
||||
config.Graphics.ResScale.Value = ResolutionScale == 4 ? -1 : ResolutionScale + 1;
|
||||
config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
|
||||
config.Graphics.MaxAnisotropy.Value = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
|
||||
config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio;
|
||||
config.Graphics.AntiAliasing.Value = (AntiAliasing)AntiAliasingEffect;
|
||||
config.Graphics.ScalingFilter.Value = (ScalingFilter)ScalingFilter;
|
||||
config.Graphics.ScalingFilterLevel.Value = ScalingFilterLevel;
|
||||
|
||||
if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
|
||||
{
|
||||
DriverUtilities.ToggleOGLThreading(GraphicsBackendMultithreadingIndex == (int)BackendThreading.Off);
|
||||
}
|
||||
|
||||
config.Graphics.BackendThreading.Value = (BackendThreading)GraphicsBackendMultithreadingIndex;
|
||||
config.Graphics.ShadersDumpPath.Value = ShaderDumpPath;
|
||||
|
||||
// Audio
|
||||
AudioBackend audioBackend = (AudioBackend)AudioBackend;
|
||||
if (audioBackend != config.System.AudioBackend.Value)
|
||||
{
|
||||
config.System.AudioBackend.Value = audioBackend;
|
||||
|
||||
Logger.Info?.Print(LogClass.Application, $"AudioBackend toggled to: {audioBackend}");
|
||||
}
|
||||
|
||||
config.System.AudioVolume.Value = Volume / 100;
|
||||
|
||||
// Network
|
||||
config.System.EnableInternetAccess.Value = EnableInternetAccess;
|
||||
|
||||
// Logging
|
||||
config.Logger.EnableFileLog.Value = EnableFileLog;
|
||||
config.Logger.EnableStub.Value = EnableStub;
|
||||
config.Logger.EnableInfo.Value = EnableInfo;
|
||||
config.Logger.EnableWarn.Value = EnableWarn;
|
||||
config.Logger.EnableError.Value = EnableError;
|
||||
config.Logger.EnableTrace.Value = EnableTrace;
|
||||
config.Logger.EnableGuest.Value = EnableGuest;
|
||||
config.Logger.EnableDebug.Value = EnableDebug;
|
||||
config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog;
|
||||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||
|
||||
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
|
||||
config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex;
|
||||
|
||||
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
|
||||
MainWindow.UpdateGraphicsConfig();
|
||||
|
||||
SaveSettingsEvent?.Invoke();
|
||||
|
||||
_directoryChanged = false;
|
||||
}
|
||||
|
||||
private static void RevertIfNotSaved()
|
||||
{
|
||||
Program.ReloadConfig();
|
||||
}
|
||||
|
||||
public void ApplyButton()
|
||||
{
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
public void OkButton()
|
||||
{
|
||||
SaveSettings();
|
||||
CloseWindow?.Invoke();
|
||||
}
|
||||
|
||||
public void CancelButton()
|
||||
{
|
||||
RevertIfNotSaved();
|
||||
CloseWindow?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,225 +0,0 @@
|
||||
<UserControl
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||
xmlns:views="clr-namespace:Ryujinx.Ava.UI.Views.Input"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
d:DesignHeight="800"
|
||||
d:DesignWidth="800"
|
||||
x:Class="Ryujinx.Ava.UI.Views.Input.InputView"
|
||||
x:DataType="viewModels:InputViewModel"
|
||||
x:CompileBindings="True"
|
||||
mc:Ignorable="d"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:InputViewModel />
|
||||
</Design.DataContext>
|
||||
<UserControl.Styles>
|
||||
<Style Selector="ToggleButton">
|
||||
<Setter Property="Width" Value="90" />
|
||||
<Setter Property="Height" Value="27" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<StackPanel
|
||||
Margin="0 0 0 5"
|
||||
Orientation="Vertical"
|
||||
Spacing="5">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Player Selection -->
|
||||
<Grid
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Margin="5,0,10,0"
|
||||
Width="90"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsPlayer}" />
|
||||
<ComboBox
|
||||
Grid.Column="1"
|
||||
Name="PlayerIndexBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
SelectionChanged="PlayerIndexBox_OnSelectionChanged"
|
||||
ItemsSource="{Binding PlayerIndexes}"
|
||||
SelectedIndex="{Binding PlayerId}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
<!-- Profile Selection -->
|
||||
<Grid
|
||||
Grid.Column="2"
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Margin="5,0,10,0"
|
||||
Width="90"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsProfile}" />
|
||||
<ui:FAComboBox
|
||||
Grid.Column="1"
|
||||
IsEditable="True"
|
||||
Name="ProfileBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
SelectedIndex="0"
|
||||
ItemsSource="{Binding ProfilesList}"
|
||||
Text="{Binding ProfileName, Mode=TwoWay}" />
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
MinWidth="0"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale ControllerSettingsLoadProfileToolTip}"
|
||||
Command="{Binding LoadProfile}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Upload"
|
||||
FontSize="15"
|
||||
Height="20" />
|
||||
</Button>
|
||||
<Button
|
||||
Grid.Column="3"
|
||||
MinWidth="0"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale ControllerSettingsSaveProfileToolTip}"
|
||||
Command="{Binding SaveProfile}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Save"
|
||||
FontSize="15"
|
||||
Height="20" />
|
||||
</Button>
|
||||
<Button
|
||||
Grid.Column="4"
|
||||
MinWidth="0"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale ControllerSettingsRemoveProfileToolTip}"
|
||||
Command="{Binding RemoveProfile}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Delete"
|
||||
FontSize="15"
|
||||
Height="20" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Separator />
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Input Device -->
|
||||
<Grid
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="5,0,10,0"
|
||||
Width="90"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsInputDevice}" />
|
||||
<ComboBox
|
||||
Grid.Column="1"
|
||||
Name="DeviceBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{Binding DeviceList}"
|
||||
SelectedIndex="{Binding Device}" />
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
MinWidth="0"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding LoadDevices}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Refresh"
|
||||
FontSize="15"
|
||||
Height="20"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
<!-- Controller Type -->
|
||||
<Grid
|
||||
Grid.Column="2"
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Margin="5,0,10,0"
|
||||
Width="90"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsControllerType}" />
|
||||
<ComboBox
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding Controllers}"
|
||||
SelectedIndex="{Binding Controller}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate DataType="models:ControllerModel">
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<ContentControl Content="{Binding ConfigViewModel}" IsVisible="{Binding ShowSettings}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="viewModels:ControllerInputViewModel">
|
||||
<views:ControllerInputView />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="viewModels:KeyboardInputViewModel">
|
||||
<views:KeyboardInputView />
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
</StackPanel>
|
||||
</UserControl>
|
@ -1,61 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.ViewModels.Input;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Input
|
||||
{
|
||||
public partial class InputView : UserControl
|
||||
{
|
||||
private bool _dialogOpen;
|
||||
private InputViewModel ViewModel { get; set; }
|
||||
|
||||
public InputView()
|
||||
{
|
||||
DataContext = ViewModel = new InputViewModel(this);
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void SaveCurrentProfile()
|
||||
{
|
||||
ViewModel.Save();
|
||||
}
|
||||
|
||||
private async void PlayerIndexBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (ViewModel.IsModified && !_dialogOpen)
|
||||
{
|
||||
_dialogOpen = true;
|
||||
|
||||
var result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogControllerSettingsModifiedConfirmSubMessage],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
||||
|
||||
if (result == UserResult.Yes)
|
||||
{
|
||||
ViewModel.Save();
|
||||
}
|
||||
|
||||
_dialogOpen = false;
|
||||
|
||||
ViewModel.IsModified = false;
|
||||
|
||||
if (e.AddedItems.Count > 0)
|
||||
{
|
||||
var player = (PlayerModel)e.AddedItems[0];
|
||||
ViewModel.PlayerId = player.Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ViewModel.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -672,4 +672,4 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
@ -2,6 +2,7 @@ using Avalonia.Controls;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.ViewModels.Input;
|
||||
using Ryujinx.Ava.UI.ViewModels.Settings;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Input
|
||||
|
@ -7,11 +7,11 @@
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Settings"
|
||||
mc:Ignorable="d"
|
||||
x:DataType="viewModels:SettingsViewModel">
|
||||
x:DataType="viewModels:SettingsAudioViewModel">
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
<viewModels:SettingsAudioViewModel />
|
||||
</Design.DataContext>
|
||||
<ScrollViewer
|
||||
Name="AudioPage"
|
||||
|
@ -1,11 +1,15 @@
|
||||
using Avalonia.Controls;
|
||||
using Ryujinx.Ava.UI.ViewModels.Settings;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Settings
|
||||
{
|
||||
public partial class SettingsAudioView : UserControl
|
||||
{
|
||||
public SettingsAudioViewModel ViewModel;
|
||||
|
||||
public SettingsAudioView()
|
||||
{
|
||||
DataContext = ViewModel = new SettingsAudioViewModel();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Settings
|
||||
{
|
||||
public partial class SettingsCPUView : UserControl
|
||||
{
|
||||
public SettingsCPUView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsCPUView"
|
||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsCpuView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Settings"
|
||||
mc:Ignorable="d"
|
||||
x:DataType="viewModels:SettingsViewModel">
|
||||
x:DataType="viewModels:SettingsCpuViewModel">
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
</Design.DataContext>
|
16
src/Ryujinx/UI/Views/Settings/SettingsCpuView.axaml.cs
Normal file
16
src/Ryujinx/UI/Views/Settings/SettingsCpuView.axaml.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using Avalonia.Controls;
|
||||
using Ryujinx.Ava.UI.ViewModels.Settings;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Settings
|
||||
{
|
||||
public partial class SettingsCpuView : UserControl
|
||||
{
|
||||
public SettingsCpuViewModel ViewModel;
|
||||
|
||||
public SettingsCpuView()
|
||||
{
|
||||
DataContext = ViewModel = new SettingsCpuViewModel();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
@ -7,12 +7,12 @@
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Settings"
|
||||
Design.Width="1000"
|
||||
mc:Ignorable="d"
|
||||
x:DataType="viewModels:SettingsViewModel">
|
||||
x:DataType="viewModels:SettingsGraphicsViewModel">
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
<viewModels:SettingsGraphicsViewModel />
|
||||
</Design.DataContext>
|
||||
<ScrollViewer
|
||||
Name="GraphicsPage"
|
||||
@ -268,6 +268,7 @@
|
||||
<ComboBox Width="350"
|
||||
HorizontalContentAlignment="Left"
|
||||
ToolTip.Tip="{locale:Locale GalThreadingTooltip}"
|
||||
SelectionChanged="GraphicsBackendMultithreadingIndex_OnSelectionChanged"
|
||||
SelectedIndex="{Binding GraphicsBackendMultithreadingIndex}">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale CommonAuto}" />
|
||||
|
@ -1,12 +1,40 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels.Settings;
|
||||
using Ryujinx.UI.Common.Configuration;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Settings
|
||||
{
|
||||
public partial class SettingsGraphicsView : UserControl
|
||||
{
|
||||
public SettingsGraphicsViewModel ViewModel;
|
||||
|
||||
public SettingsGraphicsView()
|
||||
{
|
||||
DataContext = ViewModel = new SettingsGraphicsViewModel();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void GraphicsBackendMultithreadingIndex_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (e.Source is not ComboBox comboBox)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (comboBox.SelectedIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage],
|
||||
"",
|
||||
"",
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
||||
LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle],
|
||||
parent: this.VisualRoot as Window)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,14 +5,14 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Settings"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
mc:Ignorable="d"
|
||||
x:DataType="viewModels:SettingsViewModel"
|
||||
x:DataType="viewModels:SettingsHotkeysViewModel"
|
||||
x:CompileBindings="True"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
<viewModels:SettingsHotkeysViewModel />
|
||||
</Design.DataContext>
|
||||
<UserControl.Resources>
|
||||
<helpers:KeyValueConverter x:Key="Key" />
|
||||
|
@ -5,7 +5,7 @@ using Avalonia.Interactivity;
|
||||
using Avalonia.LogicalTree;
|
||||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.UI.ViewModels.Settings;
|
||||
using Ryujinx.Input;
|
||||
using Ryujinx.Input.Assigner;
|
||||
using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||
@ -14,11 +14,15 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
||||
{
|
||||
public partial class SettingsHotkeysView : UserControl
|
||||
{
|
||||
public SettingsHotkeysViewModel ViewModel;
|
||||
|
||||
private ButtonKeyAssigner _currentAssigner;
|
||||
private readonly IGamepadDriver _avaloniaKeyboardDriver;
|
||||
|
||||
public SettingsHotkeysView()
|
||||
{
|
||||
DataContext = ViewModel = new SettingsHotkeysViewModel();
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
foreach (ILogical visual in SettingButtons.GetLogicalDescendants())
|
||||
@ -77,37 +81,36 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
||||
{
|
||||
if (e.ButtonValue.HasValue)
|
||||
{
|
||||
var viewModel = (DataContext) as SettingsViewModel;
|
||||
var buttonValue = e.ButtonValue.Value;
|
||||
|
||||
switch (button.Name)
|
||||
{
|
||||
case "ToggleVsync":
|
||||
viewModel.KeyboardHotkey.ToggleVsync = buttonValue.AsHidType<Key>();
|
||||
ViewModel.KeyboardHotkey.ToggleVsync = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "Screenshot":
|
||||
viewModel.KeyboardHotkey.Screenshot = buttonValue.AsHidType<Key>();
|
||||
ViewModel.KeyboardHotkey.Screenshot = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "ShowUI":
|
||||
viewModel.KeyboardHotkey.ShowUI = buttonValue.AsHidType<Key>();
|
||||
ViewModel.KeyboardHotkey.ShowUI = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "Pause":
|
||||
viewModel.KeyboardHotkey.Pause = buttonValue.AsHidType<Key>();
|
||||
ViewModel.KeyboardHotkey.Pause = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "ToggleMute":
|
||||
viewModel.KeyboardHotkey.ToggleMute = buttonValue.AsHidType<Key>();
|
||||
ViewModel.KeyboardHotkey.ToggleMute = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "ResScaleUp":
|
||||
viewModel.KeyboardHotkey.ResScaleUp = buttonValue.AsHidType<Key>();
|
||||
ViewModel.KeyboardHotkey.ResScaleUp = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "ResScaleDown":
|
||||
viewModel.KeyboardHotkey.ResScaleDown = buttonValue.AsHidType<Key>();
|
||||
ViewModel.KeyboardHotkey.ResScaleDown = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "VolumeUp":
|
||||
viewModel.KeyboardHotkey.VolumeUp = buttonValue.AsHidType<Key>();
|
||||
ViewModel.KeyboardHotkey.VolumeUp = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
case "VolumeDown":
|
||||
viewModel.KeyboardHotkey.VolumeDown = buttonValue.AsHidType<Key>();
|
||||
ViewModel.KeyboardHotkey.VolumeDown = buttonValue.AsHidType<Key>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,18 @@
|
||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsInputView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:views="clr-namespace:Ryujinx.Ava.UI.Views.Input"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:inputViewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Settings"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||
mc:Ignorable="d"
|
||||
x:DataType="viewModels:SettingsViewModel">
|
||||
x:DataType="viewModels:SettingsInputViewModel">
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
<viewModels:SettingsInputViewModel />
|
||||
</Design.DataContext>
|
||||
<ScrollViewer
|
||||
Name="InputPage"
|
||||
@ -27,41 +30,233 @@
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<views:InputView
|
||||
Grid.Row="0"
|
||||
Name="InputView" />
|
||||
<StackPanel
|
||||
Orientation="Vertical"
|
||||
Grid.Row="2">
|
||||
<Separator
|
||||
Margin="0 10"
|
||||
Height="1" />
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<CheckBox
|
||||
ToolTip.Tip="{locale:Locale DockModeToggleTooltip}"
|
||||
MinWidth="0"
|
||||
IsChecked="{Binding EnableDockedMode}">
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabInputEnableDockedMode}" />
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
ToolTip.Tip="{locale:Locale DirectKeyboardTooltip}"
|
||||
IsChecked="{Binding EnableKeyboard}">
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabInputDirectKeyboardAccess}" />
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
ToolTip.Tip="{locale:Locale DirectMouseTooltip}"
|
||||
IsChecked="{Binding EnableMouse}">
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabInputDirectMouseAccess}" />
|
||||
</CheckBox>
|
||||
Grid.Row="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<StackPanel
|
||||
Margin="0 0 0 5"
|
||||
Orientation="Vertical"
|
||||
Spacing="5">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Player Selection -->
|
||||
<Grid
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Margin="5,0,10,0"
|
||||
Width="90"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsPlayer}" />
|
||||
<ComboBox
|
||||
Grid.Column="1"
|
||||
Name="PlayerIndexBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{Binding PlayerIndexes}"
|
||||
SelectedIndex="{Binding PlayerId}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
<!-- Profile Selection -->
|
||||
<Grid
|
||||
Grid.Column="2"
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Margin="5,0,10,0"
|
||||
Width="90"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsProfile}" />
|
||||
<ui:FAComboBox
|
||||
Grid.Column="1"
|
||||
IsEditable="True"
|
||||
Name="ProfileBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
SelectedIndex="0"
|
||||
ItemsSource="{Binding ProfilesList}"
|
||||
Text="{Binding ProfileName, Mode=TwoWay}" />
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
MinWidth="0"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale ControllerSettingsLoadProfileToolTip}"
|
||||
Command="{Binding LoadProfile}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Upload"
|
||||
FontSize="15"
|
||||
Height="20" />
|
||||
</Button>
|
||||
<Button
|
||||
Grid.Column="3"
|
||||
MinWidth="0"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale ControllerSettingsSaveProfileToolTip}"
|
||||
Command="{Binding SaveProfile}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Save"
|
||||
FontSize="15"
|
||||
Height="20" />
|
||||
</Button>
|
||||
<Button
|
||||
Grid.Column="4"
|
||||
MinWidth="0"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale ControllerSettingsRemoveProfileToolTip}"
|
||||
Command="{Binding RemoveProfile}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Delete"
|
||||
FontSize="15"
|
||||
Height="20" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Separator />
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Input Device -->
|
||||
<Grid
|
||||
Grid.Column="0"
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Margin="5,0,10,0"
|
||||
Width="90"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsInputDevice}" />
|
||||
<ComboBox
|
||||
Grid.Column="1"
|
||||
Name="DeviceBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{Binding DeviceList}"
|
||||
SelectedIndex="{Binding Device}" />
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
MinWidth="0"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding LoadDevices}">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Refresh"
|
||||
FontSize="15"
|
||||
Height="20"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
<!-- Controller Type -->
|
||||
<Grid
|
||||
Grid.Column="2"
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Margin="5,0,10,0"
|
||||
Width="90"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsControllerType}" />
|
||||
<ComboBox
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
ItemsSource="{Binding Controllers}"
|
||||
SelectedIndex="{Binding Controller}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate DataType="models:ControllerModel">
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<ContentControl Content="{Binding ConfigViewModel}" IsVisible="{Binding ShowSettings}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="inputViewModels:ControllerInputViewModel">
|
||||
<views:ControllerInputView />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="inputViewModels:KeyboardInputViewModel">
|
||||
<views:KeyboardInputView />
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Orientation="Vertical"
|
||||
Grid.Row="2">
|
||||
<Separator
|
||||
Margin="0 10"
|
||||
Height="1" />
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<CheckBox
|
||||
ToolTip.Tip="{locale:Locale DockModeToggleTooltip}"
|
||||
MinWidth="0"
|
||||
IsChecked="{Binding EnableDockedMode}">
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabInputEnableDockedMode}" />
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
ToolTip.Tip="{locale:Locale DirectKeyboardTooltip}"
|
||||
IsChecked="{Binding EnableKeyboard}">
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabInputDirectKeyboardAccess}" />
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
ToolTip.Tip="{locale:Locale DirectMouseTooltip}"
|
||||
IsChecked="{Binding EnableMouse}">
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabInputDirectMouseAccess}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Panel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
@ -1,17 +1,22 @@
|
||||
using Avalonia.Controls;
|
||||
using Ryujinx.Ava.UI.ViewModels.Settings;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Settings
|
||||
{
|
||||
public partial class SettingsInputView : UserControl
|
||||
{
|
||||
public SettingsInputViewModel ViewModel;
|
||||
|
||||
public SettingsInputView()
|
||||
{
|
||||
DataContext = ViewModel = new SettingsInputViewModel(this);
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
InputView.Dispose();
|
||||
ViewModel.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,11 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Settings"
|
||||
mc:Ignorable="d"
|
||||
x:DataType="viewModels:SettingsViewModel">
|
||||
x:DataType="viewModels:SettingsLoggingViewModel">
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
<viewModels:SettingsLoggingViewModel />
|
||||
</Design.DataContext>
|
||||
<ScrollViewer
|
||||
Name="LoggingPage"
|
||||
@ -117,4 +117,4 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
@ -1,11 +1,15 @@
|
||||
using Avalonia.Controls;
|
||||
using Ryujinx.Ava.UI.ViewModels.Settings;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Settings
|
||||
{
|
||||
public partial class SettingsLoggingView : UserControl
|
||||
{
|
||||
public SettingsLoggingViewModel ViewModel;
|
||||
|
||||
public SettingsLoggingView()
|
||||
{
|
||||
DataContext = ViewModel = new SettingsLoggingViewModel();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,11 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Settings"
|
||||
mc:Ignorable="d"
|
||||
x:DataType="viewModels:SettingsViewModel">
|
||||
x:DataType="viewModels:SettingsNetworkViewModel">
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
<viewModels:SettingsNetworkViewModel />
|
||||
</Design.DataContext>
|
||||
<ScrollViewer
|
||||
Name="NetworkPage"
|
||||
|
@ -1,11 +1,15 @@
|
||||
using Avalonia.Controls;
|
||||
using Ryujinx.Ava.UI.ViewModels.Settings;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Settings
|
||||
{
|
||||
public partial class SettingsNetworkView : UserControl
|
||||
{
|
||||
public SettingsNetworkViewModel ViewModel;
|
||||
|
||||
public SettingsNetworkView()
|
||||
{
|
||||
DataContext = ViewModel = new SettingsNetworkViewModel();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
@ -5,15 +5,15 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Settings"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
mc:Ignorable="d"
|
||||
x:DataType="viewModels:SettingsViewModel">
|
||||
x:DataType="viewModels:SettingsSystemViewModel">
|
||||
<UserControl.Resources>
|
||||
<helpers:TimeZoneConverter x:Key="TimeZone" />
|
||||
</UserControl.Resources>
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
<viewModels:SettingsSystemViewModel />
|
||||
</Design.DataContext>
|
||||
<ScrollViewer
|
||||
Name="SystemPage"
|
||||
@ -154,7 +154,7 @@
|
||||
SelectionChanged="TimeZoneBox_OnSelectionChanged"
|
||||
Text="{Binding Path=TimeZone, Mode=OneWay}"
|
||||
TextChanged="TimeZoneBox_OnTextChanged"
|
||||
ToolTip.Tip="{locale:Locale TimezoneTooltip}"
|
||||
ToolTip.Tip="{locale:Locale TimezoneTooltip}"
|
||||
ValueMemberBinding="{Binding Mode=OneWay, Converter={StaticResource TimeZone}}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
@ -166,7 +166,7 @@
|
||||
ToolTip.Tip="{locale:Locale TimeTooltip}"
|
||||
Width="250"/>
|
||||
<DatePicker
|
||||
VerticalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
SelectedDate="{Binding CurrentDate}"
|
||||
ToolTip.Tip="{locale:Locale TimeTooltip}"
|
||||
Width="350" />
|
||||
@ -221,4 +221,4 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
@ -1,15 +1,17 @@
|
||||
using Avalonia.Controls;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.UI.ViewModels.Settings;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Settings
|
||||
{
|
||||
public partial class SettingsSystemView : UserControl
|
||||
{
|
||||
public SettingsViewModel ViewModel;
|
||||
public SettingsSystemViewModel ViewModel;
|
||||
|
||||
public SettingsSystemView()
|
||||
public SettingsSystemView(VirtualFileSystem virtualFileSystem, ContentManager contentManager)
|
||||
{
|
||||
DataContext = ViewModel = new SettingsSystemViewModel(virtualFileSystem, contentManager);
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
@ -5,11 +5,11 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Settings"
|
||||
mc:Ignorable="d"
|
||||
x:DataType="viewModels:SettingsViewModel">
|
||||
x:DataType="viewModels:SettingsUIViewModel">
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
<viewModels:SettingsUIViewModel />
|
||||
</Design.DataContext>
|
||||
<ScrollViewer
|
||||
Name="UiPage"
|
||||
|
@ -2,8 +2,7 @@ using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.VisualTree;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.UI.ViewModels.Settings;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -12,10 +11,11 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
||||
{
|
||||
public partial class SettingsUiView : UserControl
|
||||
{
|
||||
public SettingsViewModel ViewModel;
|
||||
public SettingsUIViewModel ViewModel;
|
||||
|
||||
public SettingsUiView()
|
||||
{
|
||||
DataContext = ViewModel = new SettingsUIViewModel();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
@ -26,7 +26,6 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
||||
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.GameDirectories.Contains(path))
|
||||
{
|
||||
ViewModel.GameDirectories.Add(path);
|
||||
ViewModel.DirectoryChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -40,7 +39,6 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
||||
if (result.Count > 0)
|
||||
{
|
||||
ViewModel.GameDirectories.Add(result[0].Path.LocalPath);
|
||||
ViewModel.DirectoryChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,7 +51,6 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
||||
foreach (string path in new List<string>(GameList.SelectedItems.Cast<string>()))
|
||||
{
|
||||
ViewModel.GameDirectories.Remove(path);
|
||||
ViewModel.DirectoryChanged = true;
|
||||
}
|
||||
|
||||
if (GameList.ItemCount > 0)
|
||||
|
@ -7,8 +7,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:settings="clr-namespace:Ryujinx.Ava.UI.Views.Settings"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Settings"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
Width="1100"
|
||||
Height="768"
|
||||
@ -32,17 +31,6 @@
|
||||
Grid.Row="1"
|
||||
IsVisible="False"
|
||||
KeyboardNavigation.IsTabStop="False"/>
|
||||
<Grid Name="Pages" IsVisible="False" Grid.Row="2">
|
||||
<settings:SettingsUiView Name="UiPage" />
|
||||
<settings:SettingsInputView Name="InputPage" />
|
||||
<settings:SettingsHotkeysView Name="HotkeysPage" />
|
||||
<settings:SettingsSystemView Name="SystemPage" />
|
||||
<settings:SettingsCPUView Name="CpuPage" />
|
||||
<settings:SettingsGraphicsView Name="GraphicsPage" />
|
||||
<settings:SettingsAudioView Name="AudioPage" />
|
||||
<settings:SettingsNetworkView Name="NetworkPage" />
|
||||
<settings:SettingsLoggingView Name="LoggingPage" />
|
||||
</Grid>
|
||||
<ui:NavigationView
|
||||
Grid.Row="1"
|
||||
IsSettingsVisible="False"
|
||||
@ -108,21 +96,24 @@
|
||||
</ui:NavigationView>
|
||||
<ReversibleStackPanel
|
||||
Grid.Row="2"
|
||||
Name="Buttons"
|
||||
Margin="10"
|
||||
Spacing="10"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
ReverseOrder="{Binding IsMacOS}">
|
||||
<Button
|
||||
HotKey="Enter"
|
||||
IsDefault="True"
|
||||
Classes="accent"
|
||||
Content="{locale:Locale SettingsButtonOk}"
|
||||
Command="{Binding OkButton}" />
|
||||
<Button
|
||||
HotKey="Escape"
|
||||
IsCancel="True"
|
||||
Content="{locale:Locale SettingsButtonCancel}"
|
||||
Command="{Binding CancelButton}" />
|
||||
Click="Cancel_OnClick" />
|
||||
<Button
|
||||
Name="Apply"
|
||||
IsEnabled="False"
|
||||
Content="{locale:Locale SettingsButtonApply}"
|
||||
Command="{Binding ApplyButton}" />
|
||||
</ReversibleStackPanel>
|
||||
|
@ -1,8 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using FluentAvalonia.Core;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels.Settings;
|
||||
using Ryujinx.Ava.UI.Views.Settings;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using System;
|
||||
|
||||
@ -10,44 +13,79 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
public partial class SettingsWindow : StyleableWindow
|
||||
{
|
||||
internal SettingsViewModel ViewModel { get; set; }
|
||||
private SettingsViewModel ViewModel { get; }
|
||||
|
||||
public readonly SettingsUiView UiPage;
|
||||
public readonly SettingsInputView InputPage;
|
||||
public readonly SettingsHotkeysView HotkeysPage;
|
||||
public readonly SettingsSystemView SystemPage;
|
||||
public readonly SettingsCpuView CpuPage;
|
||||
public readonly SettingsGraphicsView GraphicsPage;
|
||||
public readonly SettingsAudioView AudioPage;
|
||||
public readonly SettingsNetworkView NetworkPage;
|
||||
public readonly SettingsLoggingView LoggingPage;
|
||||
|
||||
public SettingsWindow(VirtualFileSystem virtualFileSystem, ContentManager contentManager)
|
||||
{
|
||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.Settings]}";
|
||||
Title = $"{LocaleManager.Instance[LocaleKeys.Settings]}";
|
||||
|
||||
AudioPage = new SettingsAudioView();
|
||||
CpuPage = new SettingsCpuView();
|
||||
GraphicsPage = new SettingsGraphicsView();
|
||||
HotkeysPage = new SettingsHotkeysView();
|
||||
InputPage = new SettingsInputView();
|
||||
LoggingPage = new SettingsLoggingView();
|
||||
NetworkPage = new SettingsNetworkView();
|
||||
SystemPage = new SettingsSystemView(virtualFileSystem, contentManager);
|
||||
UiPage = new SettingsUiView();
|
||||
|
||||
ViewModel = new SettingsViewModel(
|
||||
AudioPage.ViewModel,
|
||||
CpuPage.ViewModel,
|
||||
GraphicsPage.ViewModel,
|
||||
HotkeysPage.ViewModel,
|
||||
InputPage.ViewModel,
|
||||
LoggingPage.ViewModel,
|
||||
NetworkPage.ViewModel,
|
||||
SystemPage.ViewModel,
|
||||
UiPage.ViewModel);
|
||||
|
||||
ViewModel = new SettingsViewModel(virtualFileSystem, contentManager);
|
||||
DataContext = ViewModel;
|
||||
|
||||
ViewModel.CloseWindow += Close;
|
||||
ViewModel.SaveSettingsEvent += SaveSettings;
|
||||
ViewModel.DirtyEvent += UpdateDirtyTitle;
|
||||
ViewModel.ToggleButtons += ToggleButtons;
|
||||
|
||||
InitializeComponent();
|
||||
Load();
|
||||
}
|
||||
|
||||
public SettingsWindow()
|
||||
private void UpdateDirtyTitle(bool isDirty)
|
||||
{
|
||||
ViewModel = new SettingsViewModel();
|
||||
DataContext = ViewModel;
|
||||
|
||||
InitializeComponent();
|
||||
Load();
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
InputPage.InputView?.SaveCurrentProfile();
|
||||
|
||||
if (Owner is MainWindow window && ViewModel.DirectoryChanged)
|
||||
if (!IsInitialized)
|
||||
{
|
||||
window.LoadApplications();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDirty)
|
||||
{
|
||||
Title = $"{LocaleManager.Instance[LocaleKeys.Settings]} - {LocaleManager.Instance[LocaleKeys.SettingsDirty]}";
|
||||
Apply.IsEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Title = $"{LocaleManager.Instance[LocaleKeys.Settings]}";
|
||||
Apply.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleButtons(bool enable)
|
||||
{
|
||||
Buttons.IsEnabled = enable;
|
||||
}
|
||||
|
||||
private void Load()
|
||||
{
|
||||
Pages.Children.Clear();
|
||||
NavPanel.SelectionChanged += NavPanelOnSelectionChanged;
|
||||
NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0);
|
||||
}
|
||||
@ -59,7 +97,6 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
switch (navItem.Tag.ToString())
|
||||
{
|
||||
case "UiPage":
|
||||
UiPage.ViewModel = ViewModel;
|
||||
NavPanel.Content = UiPage;
|
||||
break;
|
||||
case "InputPage":
|
||||
@ -69,7 +106,6 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
NavPanel.Content = HotkeysPage;
|
||||
break;
|
||||
case "SystemPage":
|
||||
SystemPage.ViewModel = ViewModel;
|
||||
NavPanel.Content = SystemPage;
|
||||
break;
|
||||
case "CpuPage":
|
||||
@ -93,8 +129,34 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
}
|
||||
}
|
||||
|
||||
private async void Cancel_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ViewModel.IsModified)
|
||||
{
|
||||
var result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogSettingsUnsavedChangesMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogSettingsUnsavedChangesSubMessage],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxConfirm],
|
||||
parent: this);
|
||||
|
||||
if (result != UserResult.Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
protected override void OnClosing(WindowClosingEventArgs e)
|
||||
{
|
||||
if (Owner is MainWindow window && UiPage.ViewModel.DirsChanged)
|
||||
{
|
||||
window.LoadApplications();
|
||||
}
|
||||
|
||||
HotkeysPage.Dispose();
|
||||
InputPage.Dispose();
|
||||
base.OnClosing(e);
|
||||
|
Loading…
Reference in New Issue
Block a user