Compare commits

..

22 Commits

Author SHA1 Message Date
GreemDev
ebc775aeb5 update compiling document 2025-11-11 12:54:34 -06:00
GreemDev
e24eb13e07 more partial ObservableProperties 2025-11-11 12:43:34 -06:00
GreemDev
c7572b5d30 chore: change BufferHolder.GetHandle() to a getter for the handle instead 2025-11-11 12:43:34 -06:00
GreemDev
2ea829f17b chore: change MultiRegionHandle GetHandles to an accessor property 2025-11-11 12:43:34 -06:00
GreemDev
9c00ffa4b1 fix conflicts 2025-11-11 12:43:34 -06:00
GreemDev
84f26f7276 language feature: field keyword & partial properties for observable properties in the UI
partial properties are from C# 13 but weren't usable for these properties
2025-11-11 12:43:34 -06:00
GreemDev
f2105d6040 chore: converted BufferHandle ToInt32 extension to an implicit int operator on BufferHandle directly. 2025-11-11 12:43:34 -06:00
GreemDev
1f3e4674b5 chore: Split SoftFloat into multiple partial class parts 2025-11-11 12:43:34 -06:00
GreemDev
342c811aca chore: Split SoftFallback into multiple partial class parts 2025-11-11 12:43:34 -06:00
GreemDev
83502494d9 some more extension members 2025-11-11 12:43:34 -06:00
GreemDev
64238e6ec3 language feature: Extension Members: More converted 2025-11-11 12:43:34 -06:00
GreemDev
36bd919c53 use extension members for StorageProviderExtensions 2025-11-11 12:43:34 -06:00
GreemDev
f1df537e76 language feature: Extension Members: HLE <-> UI enum converters 2025-11-11 12:43:34 -06:00
GreemDev
f9e71a5908 Parse UI enum directly 2025-11-11 12:43:34 -06:00
GreemDev
f20291ddf2 language feature: Extension Members: Misc enum extensions methods converted to properties 2025-11-11 12:43:34 -06:00
GreemDev
cc80621a17 language feature: Extension Members: Ryujinx.Graphics.GAL.Format 2025-11-11 12:43:34 -06:00
GreemDev
6a1dec9f91 language feature: Extension Members: OperandType 2025-11-11 12:43:34 -06:00
GreemDev
274ec74856 language feature: Extension Members: HLE 2025-11-11 12:43:34 -06:00
GreemDev
ac98ade572 language feature: Extension Members: Graphics related, enums 2025-11-11 12:43:34 -06:00
GreemDev
e23213d290 language feature: Extension Members: CPU-related, enums 2025-11-11 12:43:34 -06:00
GreemDev
6467720c5c Add .NET Runtime version in About window under Ryujinx version. 2025-11-11 12:43:34 -06:00
GreemDev
010eab44ba feature: Initial .NET 10 Support
Works as of .NET 10.0.0-preview.3.25171.5
2025-11-11 12:43:34 -06:00
40 changed files with 273 additions and 537 deletions

2
.gitignore vendored
View File

@@ -18,8 +18,6 @@ build/
[Oo]bj/
AppDir/
publish_appimage/
publish_ava/
publish_tmp_ava/
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
!packages/*/build/

Binary file not shown.

View File

@@ -10,8 +10,6 @@
<string>Ryujinx</string>
<key>CFBundleIconFile</key>
<string>Ryujinx.icns</string>
<key>CFBundleIconName</key>
<string>Ryujinx</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
@@ -42,7 +40,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.3</string>
<string>1.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

View File

@@ -25,7 +25,6 @@ cp "$PUBLISH_DIRECTORY"/*.dylib "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
cp Info.plist "$APP_BUNDLE_DIRECTORY/Contents"
cp Ryujinx.icns "$APP_BUNDLE_DIRECTORY/Contents/Resources/Ryujinx.icns"
cp updater.sh "$APP_BUNDLE_DIRECTORY/Contents/Resources/updater.sh"
cp Assets.car "$APP_BUNDLE_DIRECTORY/Contents/Resources/Assets.car"
cp -r "$PUBLISH_DIRECTORY/THIRDPARTY.md" "$APP_BUNDLE_DIRECTORY/Contents/Resources"
echo -n "APPL????" > "$APP_BUNDLE_DIRECTORY/Contents/PkgInfo"

View File

@@ -2275,12 +2275,12 @@
010018E011D92000,"Pokémon™ Shining Pearl",gpu;ldn-works,ingame,2024-08-28 13:26:35
010015F008C54000,"Pokémon™ HOME",Needs Update;crash;services,menus,2020-12-06 06:01:51
01001F5010DFA000,"Pokémon™ Legends: Arceus",gpu;Needs Update;ldn-works,ingame,2024-09-19 10:02:02
0100F43008C44000,"Pokémon™ Legends: Z-A",gpu;crash;ldn-works,ingame,2025-11-16 00:30:00
01005D100807A000,"Pokémon™ Quest",,playable,2022-02-22 16:12:32
0100A3D008C5C000,"Pokémon™ Scarlet",gpu;nvdec;ldn-works;amd-vendor-bug,ingame,2023-12-14 13:18:29
01008F6008C5E000,"Pokémon™ Violet",gpu;nvdec;ldn-works;amd-vendor-bug;mac-bug,ingame,2024-07-30 02:51:48
0100187003A36000,"Pokémon™: Lets Go, Eevee!",crash;nvdec;online-broken;ldn-broken,ingame,2024-06-01 15:03:04
010003F003A34000,"Pokémon™: Lets Go, Pikachu!",crash;nvdec;online-broken;ldn-broken,ingame,2024-03-15 07:55:41
0100F43008C44000,"Pokémon Legends: Z-A",gpu;crash;ldn-broken,ingame,2025-10-16 19:13:00
0100B3F000BE2000,"Pokkén Tournament™ DX",nvdec;ldn-works;opengl-backend-bug;LAN;amd-vendor-bug;intel-vendor-bug,playable,2024-07-18 23:11:08
010030D005AE6000,"Pokkén Tournament™ DX Demo",demo;opengl-backend-bug,playable,2022-08-10 12:03:19
0100A3500B4EC000,"Polandball: Can Into Space",,playable,2020-06-25 15:13:26
1 title_id game_name labels status last_updated
2275 010018E011D92000 Pokémon™ Shining Pearl gpu;ldn-works ingame 2024-08-28 13:26:35
2276 010015F008C54000 Pokémon™ HOME Needs Update;crash;services menus 2020-12-06 06:01:51
2277 01001F5010DFA000 Pokémon™ Legends: Arceus gpu;Needs Update;ldn-works ingame 2024-09-19 10:02:02
0100F43008C44000 Pokémon™ Legends: Z-A gpu;crash;ldn-works ingame 2025-11-16 00:30:00
2278 01005D100807A000 Pokémon™ Quest playable 2022-02-22 16:12:32
2279 0100A3D008C5C000 Pokémon™ Scarlet gpu;nvdec;ldn-works;amd-vendor-bug ingame 2023-12-14 13:18:29
2280 01008F6008C5E000 Pokémon™ Violet gpu;nvdec;ldn-works;amd-vendor-bug;mac-bug ingame 2024-07-30 02:51:48
2281 0100187003A36000 Pokémon™: Let’s Go, Eevee! crash;nvdec;online-broken;ldn-broken ingame 2024-06-01 15:03:04
2282 010003F003A34000 Pokémon™: Let’s Go, Pikachu! crash;nvdec;online-broken;ldn-broken ingame 2024-03-15 07:55:41
2283 0100F43008C44000 Pokémon Legends: Z-A gpu;crash;ldn-broken ingame 2025-10-16 19:13:00
2284 0100B3F000BE2000 Pokkén Tournament™ DX nvdec;ldn-works;opengl-backend-bug;LAN;amd-vendor-bug;intel-vendor-bug playable 2024-07-18 23:11:08
2285 010030D005AE6000 Pokkén Tournament™ DX Demo demo;opengl-backend-bug playable 2022-08-10 12:03:19
2286 0100A3500B4EC000 Polandball: Can Into Space playable 2020-06-25 15:13:26

View File

@@ -361,7 +361,10 @@ namespace ARMeilleure.Translation
IntervalTreeNode<TK, TV> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
tmp?.Parent = ParentOf(replacementNode);
if (tmp != null)
{
tmp.Parent = ParentOf(replacementNode);
}
if (ParentOf(replacementNode) == null)
{
@@ -579,7 +582,10 @@ namespace ARMeilleure.Translation
{
IntervalTreeNode<TK, TV> right = RightOf(node);
node.Right = LeftOf(right);
node.Right?.Parent = node;
if (node.Right != null)
{
node.Right.Parent = node;
}
IntervalTreeNode<TK, TV> nodeParent = ParentOf(node);
right.Parent = nodeParent;
@@ -609,7 +615,10 @@ namespace ARMeilleure.Translation
{
IntervalTreeNode<TK, TV> left = LeftOf(node);
node.Left = RightOf(left);
node.Left?.Parent = node;
if (node.Left != null)
{
node.Left.Parent = node;
}
IntervalTreeNode<TK, TV> nodeParent = ParentOf(node);
left.Parent = nodeParent;
@@ -658,7 +667,10 @@ namespace ARMeilleure.Translation
/// <param name="color">Color (Boolean)</param>
private static void SetColor(IntervalTreeNode<TK, TV> node, bool color)
{
node?.Color = color;
if (node != null)
{
node.Color = color;
}
}
/// <summary>

View File

@@ -92,7 +92,6 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
}
short[] outputBuffer = ArrayPool<short>.Shared.Rent((int)inputCount * SampleCount);
Array.Fill(outputBuffer, (short)0, 0, (int)inputCount * SampleCount);
for (int i = 0; i < bufferCount; i++)
{

View File

@@ -386,7 +386,10 @@ namespace Ryujinx.Common.Collections
IntervalTreeNode<TKey, TValue> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
tmp?.Parent = ParentOf(replacementNode);
if (tmp != null)
{
tmp.Parent = ParentOf(replacementNode);
}
if (ParentOf(replacementNode) == null)
{

View File

@@ -235,7 +235,10 @@ namespace Ryujinx.Common.Collections
parent = ParentOf(element);
color = ColorOf(element);
child?.Parent = parent;
if (child != null)
{
child.Parent = parent;
}
if (parent == null)
{
@@ -255,7 +258,8 @@ namespace Ryujinx.Common.Collections
element.Right = old.Right;
element.Parent = old.Parent;
element.Predecessor = old.Predecessor;
element.Predecessor?.Successor = element;
if (element.Predecessor != null)
element.Predecessor.Successor = element;
if (ParentOf(old) == null)
{
@@ -288,7 +292,10 @@ namespace Ryujinx.Common.Collections
parent = ParentOf(nodeToDelete);
color = ColorOf(nodeToDelete);
child?.Parent = parent;
if (child != null)
{
child.Parent = parent;
}
if (parent == null)
{
@@ -307,9 +314,11 @@ namespace Ryujinx.Common.Collections
{
RestoreBalanceAfterRemoval(child);
}
old.Successor?.Predecessor = old.Predecessor;
old.Predecessor?.Successor = old.Successor;
if (old.Successor != null)
old.Successor.Predecessor = old.Predecessor;
if (old.Predecessor != null)
old.Predecessor.Successor = old.Successor;
return old;
}

View File

@@ -250,7 +250,10 @@ namespace Ryujinx.Common.Collections
{
T right = RightOf(node);
node.Right = LeftOf(right);
node.Right?.Parent = node;
if (node.Right != null)
{
node.Right.Parent = node;
}
T nodeParent = ParentOf(node);
right.Parent = nodeParent;
@@ -278,7 +281,10 @@ namespace Ryujinx.Common.Collections
{
T left = LeftOf(node);
node.Left = RightOf(left);
node.Left?.Parent = node;
if (node.Left != null)
{
node.Left.Parent = node;
}
T nodeParent = ParentOf(node);
left.Parent = nodeParent;
@@ -323,7 +329,10 @@ namespace Ryujinx.Common.Collections
/// <param name="color">Color (Boolean)</param>
protected static void SetColor(T node, bool color)
{
node?.Color = color;
if (node != null)
{
node.Color = color;
}
}
/// <summary>

View File

@@ -328,7 +328,10 @@ namespace Ryujinx.Common.Collections
Node<TKey, TValue> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
tmp?.Parent = ParentOf(replacementNode);
if (tmp != null)
{
tmp.Parent = ParentOf(replacementNode);
}
if (ParentOf(replacementNode) == null)
{

View File

@@ -35,7 +35,6 @@ namespace Ryujinx.Common.Logging
ServiceBsd,
ServiceBtm,
ServiceCaps,
ServiceEctx,
ServiceFatal,
ServiceFriend,
ServiceFs,

View File

@@ -84,7 +84,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
for (int i = 0; i < count; i++)
{
ICounterEvent evt = _items[index + i].Event;
evt?.Invalid = true;
if (evt != null)
{
evt.Invalid = true;
}
}
_items.RemoveRange(index, count);

View File

@@ -26,8 +26,12 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// - Both branches are jumping to the same location.
// In this case, the branch on the current block can be removed,
// as the next block is going to jump to the same place anyway.
if (nextBlock == null)
{
return false;
}
if (nextBlock?.Operations.First?.Value is not Operation next)
if (nextBlock.Operations.First?.Value is not Operation next)
{
return false;
}

View File

@@ -891,7 +891,10 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
result = new NestedName(name, prev);
}
context?.FinishWithTemplateArguments = false;
if (context != null)
{
context.FinishWithTemplateArguments = false;
}
return result;
}
@@ -1071,7 +1074,10 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
return null;
}
context?.CtorDtorConversion = true;
if (context != null)
{
context.CtorDtorConversion = true;
}
return new ConversionOperatorType(type);
default:
@@ -1343,7 +1349,10 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
_position++;
context?.CtorDtorConversion = true;
if (context != null)
{
context.CtorDtorConversion = true;
}
if (isInherited && ParseName(context) == null)
{
@@ -1363,7 +1372,10 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
_position++;
context?.CtorDtorConversion = true;
if (context != null)
{
context.CtorDtorConversion = true;
}
return new CtorDtorNameType(prev, true);
}
@@ -2993,10 +3005,16 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
BaseNode result = null;
CvType cv = new(ParseCvQualifiers(), null);
context?.Cv = cv;
if (context != null)
{
context.Cv = cv;
}
SimpleReferenceType Ref = ParseRefQualifiers();
context?.Ref = Ref;
if (context != null)
{
context.Ref = Ref;
}
if (ConsumeIf("St"))
{
@@ -3042,7 +3060,10 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
}
result = new NameTypeWithTemplateArguments(result, templateArgument);
context?.FinishWithTemplateArguments = true;
if (context != null)
{
context.FinishWithTemplateArguments = true;
}
_substitutionList.Add(result);
continue;
@@ -3235,7 +3256,10 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
return null;
}
context?.FinishWithTemplateArguments = true;
if (context != null)
{
context.FinishWithTemplateArguments = true;
}
return new NameTypeWithTemplateArguments(substitution, templateArguments);
}
@@ -3255,7 +3279,10 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
return null;
}
context?.FinishWithTemplateArguments = true;
if (context != null)
{
context.FinishWithTemplateArguments = true;
}
return new NameTypeWithTemplateArguments(result, templateArguments);
}

View File

@@ -174,7 +174,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (previousThread != nextThread)
{
previousThread?.LastScheduledTime = PerformanceCounter.ElapsedTicks;
if (previousThread != null)
{
previousThread.LastScheduledTime = PerformanceCounter.ElapsedTicks;
}
_state.SelectedThread = nextThread;
_state.NeedsScheduling = true;

View File

@@ -1,32 +0,0 @@
using System;
using Ryujinx.Common.Logging;
using Ryujinx.Horizon.Common;
namespace Ryujinx.HLE.HOS.Services.Ectx
{
class IContextRegistrar : DisposableIpcService
{
public IContextRegistrar(ServiceCtx context) { }
[CommandCmif(0)] // 11.0.0+
// Complete(nn::Result result, buffer<bytes, 5> raw_context) -> (i32 context_descriptor)
public ResultCode Complete(ServiceCtx context)
{
Result result = new(context.RequestData.ReadInt32());
ulong rawContextPosition = context.Request.SendBuff[0].Position;
ulong rawContextSize = context.Request.SendBuff[0].Size;
byte[] rawContext = new byte[rawContextSize];
context.Memory.Read(rawContextPosition, rawContext);
context.ResponseData.Write(0); // TODO: return context_descriptor
Logger.Stub?.PrintStub(LogClass.ServiceEctx, $"Result: {result}, rawContext: {Convert.ToHexString(rawContext)}" );
return ResultCode.Success;
}
protected override void Dispose(bool isDisposing) { }
}
}

View File

@@ -4,14 +4,5 @@ namespace Ryujinx.HLE.HOS.Services.Ectx
class IWriterForApplication : IpcService
{
public IWriterForApplication(ServiceCtx context) { }
[CommandCmif(0)]
// CreateContextRegistrar() -> object<nn::err::context::IContextRegistrar>
public ResultCode CreateContextRegistrar(ServiceCtx context)
{
MakeObject(context, new IContextRegistrar(context));
return ResultCode.Success;
}
}
}

View File

@@ -1169,7 +1169,9 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
public override void DestroyAtExit()
{
_context?.Dispose();
if (_context != null) {
_context.Dispose();
}
}
}
}

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.Input.SDL3
private StandardControllerInputConfig _configuration;
private readonly SDL_GamepadButton[] _buttonsDriverMapping =
private static readonly SDL_GamepadButton[] _buttonsDriverMapping =
[
// Unbound, ignored.
SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
@@ -88,20 +88,6 @@ namespace Ryujinx.Input.SDL3
Features = GetFeaturesFlag();
_triggerThreshold = 0.0f;
// Face button mapping
SDL_GamepadButton[] faceButtons = _buttonsDriverMapping[1..5];
foreach (SDL_GamepadButton btn in faceButtons) {
int mapId = SDL_GetGamepadButtonLabel(_gamepadHandle, btn) switch {
SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_A or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_CROSS => 1,
SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_B or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_CIRCLE => 2,
SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_X or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_SQUARE => 3,
SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_Y or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE => 4,
_ => -1
};
if (mapId == -1) { continue; }
_buttonsDriverMapping[mapId] = btn;
}
// Enable motion tracking
if ((Features & GamepadFeaturesFlag.Motion) != 0)
{

View File

@@ -65,7 +65,7 @@ namespace Ryujinx.Input.SDL3
string strGuid = new(guidBytes);
return $"{strGuid[4..6]}{strGuid[6..8]}{strGuid[2..4]}{strGuid[0..2]}-{strGuid[10..12]}{strGuid[8..10]}-{strGuid[12..16]}-{strGuid[16..20]}-{strGuid[20..32]}";
return $"{strGuid[0..8]}-{strGuid[8..12]}-{strGuid[12..16]}-{strGuid[16..20]}-{strGuid[20..32]}";
}

View File

@@ -330,7 +330,6 @@ namespace Ryujinx.Input.HLE
_device.TamperMachine.UpdateInput(hleInputStates);
hleMotionStates.Clear();
_hleMotionStatesPool.Release(hleMotionStates);
}
}

View File

@@ -58,7 +58,10 @@ namespace Ryujinx.Memory.Tracking
{
foreach (RegionHandle handle in _handles)
{
handle?.RegisterAction((address, size) => action(handle.Address, handle.Size));
if (handle != null)
{
handle?.RegisterAction((address, size) => action(handle.Address, handle.Size));
}
}
}
@@ -66,7 +69,10 @@ namespace Ryujinx.Memory.Tracking
{
foreach (RegionHandle handle in _handles)
{
handle?.RegisterPreciseAction((address, size, write) => action(handle.Address, handle.Size, write));
if (handle != null)
{
handle?.RegisterPreciseAction((address, size, write) => action(handle.Address, handle.Size, write));
}
}
}

View File

@@ -233,8 +233,6 @@ namespace Ryujinx.Ava
{
Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location: {ConfigurationPath}");
ConfigurationFileFormat.RenameInvalidConfigFile(ConfigurationPath);
ConfigurationState.Instance.LoadDefault();
}
}

View File

@@ -501,12 +501,18 @@ namespace Ryujinx.Ava.Systems
private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs<bool> args)
{
Device?.Configuration.IgnoreMissingServices = args.NewValue;
if (Device != null)
{
Device.Configuration.IgnoreMissingServices = args.NewValue;
}
}
private void UpdateAspectRatioState(object sender, ReactiveEventArgs<AspectRatio> args)
{
Device?.Configuration.AspectRatio = args.NewValue;
if (Device != null)
{
Device.Configuration.AspectRatio = args.NewValue;
}
}
private void UpdateAntiAliasing(object sender, ReactiveEventArgs<AntiAliasing> e)

View File

@@ -6,9 +6,7 @@ using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE;
using System;
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.Ava.Systems.Configuration
{
@@ -512,43 +510,6 @@ namespace Ryujinx.Ava.Systems.Configuration
}
}
/// <summary>
/// Renames the configuration file when it is deemed invalid
/// </summary>
/// <param name="path">The path to the invalid JSON configuration file</param>
/// <returns>The path of the renamed invalid JSON configuration file, or null if the rename failed</returns>
public static string RenameInvalidConfigFile(string path)
{
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
{
return null;
}
try
{
string directory = Path.GetDirectoryName(path) ?? string.Empty;
string originalFileName = Path.GetFileName(path);
if (string.IsNullOrWhiteSpace(originalFileName))
{
return null;
}
string renamedFileName = $"{originalFileName}.{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.invalid";
string renamedPath = string.IsNullOrEmpty(directory) ? renamedFileName : Path.Combine(directory, renamedFileName);
File.Move(path, renamedPath, overwrite: false);
Logger.Warning?.PrintMsg(LogClass.Application, $"Invalid configuration renamed to: {renamedPath}");
return renamedPath;
}
catch (Exception ex)
{
Logger.Error?.PrintMsg(LogClass.Application, $"Failed to rename invalid configuration file \"{path}\": {ex}");
return null;
}
}
/// <summary>
/// Save a configuration file to disk
/// </summary>

View File

@@ -28,8 +28,6 @@ namespace Ryujinx.Ava.Systems.Configuration
{
RyuLogger.Warning?.Print(LogClass.Application, $"Unsupported configuration version {cff.Version}, loading default.");
ConfigurationFileFormat.RenameInvalidConfigFile(configurationFilePath);
LoadDefault();
return;
}

View File

@@ -88,10 +88,14 @@ namespace Ryujinx.Ava.Systems
{
if (showVersionUpToDate)
{
await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog(
UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
string.Empty,
_versionResponse.ReleaseUrlFormat.Format(currentVersion));
string.Empty);
if (userResult is UserResult.Ok)
{
OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion));
}
}
Logger.Info?.Print(LogClass.Application, "Up to date.");

View File

@@ -60,10 +60,14 @@ namespace Ryujinx.Ava.Systems
{
if (showVersionUpToDate)
{
await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog(
UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
string.Empty,
changelogUrl: _versionResponse.ReleaseUrlFormat.Format(currentVersion));
string.Empty);
if (userResult is UserResult.Ok)
{
OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion));
}
}
Logger.Info?.Print(LogClass.Application, "Up to date.");
@@ -102,18 +106,22 @@ namespace Ryujinx.Ava.Systems
Logger.Info?.Print(LogClass.Application, $"Version found: {newVersionString.Replace("", "->")}");
RequestUserToUpdate:
// Show a message asking the user if they want to update
UserResult shouldUpdate = await ContentDialogHelper.CreateUpdaterChoiceDialog(
LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage],
newVersionString,
ReleaseInformation.GetChangelogUrl(currentVersion, newVersion));
newVersionString);
switch (shouldUpdate)
{
case UserResult.Yes:
await UpdateRyujinx(_versionResponse.ArtifactUrl);
break;
// Secondary button maps to no, which in this case is the show changelog button.
case UserResult.No:
OpenHelper.OpenUrl(ReleaseInformation.GetChangelogUrl(currentVersion, newVersion));
goto RequestUserToUpdate;
default:
_running = false;
break;

View File

@@ -167,7 +167,10 @@ namespace Ryujinx.Ava.UI.Controls
private void Message_TextInput(object sender, TextInputEventArgs e)
{
_host?.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message);
if (_host != null)
{
_host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message);
}
}
private void Message_KeyUp(object sender, KeyEventArgs e)

View File

@@ -10,7 +10,6 @@ using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
using System;
using System.Threading;
@@ -103,25 +102,6 @@ namespace Ryujinx.Ava.UI.Helpers
return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction);
}
public async static Task<UserResult> ShowTextDialogWithButton(
string title,
string primaryText,
string secondaryText,
string primaryButton,
string secondaryButton,
string closeButton,
int iconSymbol,
string buttonText,
Action onClick,
UserResult primaryButtonResult = UserResult.Ok,
ManualResetEvent deferResetEvent = null,
TypedEventHandler<ContentDialog, ContentDialogButtonClickEventArgs> deferCloseAction = null)
{
Grid content = CreateTextDialogContentWithButton(primaryText, secondaryText, iconSymbol, buttonText, onClick);
return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction);
}
public static async Task<UserResult> ShowDeferredContentDialog(
Window window,
@@ -193,109 +173,43 @@ namespace Ryujinx.Ava.UI.Helpers
MinHeight = 80,
};
content.Children.Add(new SymbolIcon
SymbolIcon icon = new()
{
Symbol = (Symbol)symbol,
Margin = new Thickness(10),
FontSize = 40,
FlowDirection = FlowDirection.LeftToRight,
VerticalAlignment = VerticalAlignment.Center,
GridColumn = 0,
GridRow = 0,
GridRowSpan = 2
});
};
content.Children.Add(new TextBlock
Grid.SetColumn(icon, 0);
Grid.SetRowSpan(icon, 2);
Grid.SetRow(icon, 0);
TextBlock primaryLabel = new()
{
Text = primaryText,
Margin = new Thickness(5),
TextWrapping = TextWrapping.Wrap,
MaxWidth = 450,
GridColumn = 1,
GridRow = 0
});
};
content.Children.Add(new TextBlock
TextBlock secondaryLabel = new()
{
Text = secondaryText,
Margin = new Thickness(5),
TextWrapping = TextWrapping.Wrap,
MaxWidth = 450,
GridColumn = 1,
GridRow = 1
});
return content;
}
private static Grid CreateTextDialogContentWithButton(string primaryText, string secondaryText, int symbol, string buttonName, Action onClick)
{
Grid content = new()
{
RowDefinitions = [new(), new(), new(GridLength.Star), new()],
ColumnDefinitions = [new(GridLength.Auto), new()],
MinHeight = 80,
};
content.Children.Add(new SymbolIcon
{
Symbol = (Symbol)symbol,
Margin = new Thickness(10),
FontSize = 40,
FlowDirection = FlowDirection.LeftToRight,
VerticalAlignment = VerticalAlignment.Center,
GridColumn = 0,
GridRow = 0,
GridRowSpan = 2
});
Grid.SetColumn(primaryLabel, 1);
Grid.SetColumn(secondaryLabel, 1);
Grid.SetRow(primaryLabel, 0);
Grid.SetRow(secondaryLabel, 1);
StackPanel buttonContent = new()
{
Orientation = Orientation.Horizontal,
Spacing = 2
};
buttonContent.Children.Add(new TextBlock
{
Text = buttonName,
Margin = new Thickness(2)
});
buttonContent.Children.Add(new SymbolIcon
{
FlowDirection = FlowDirection.LeftToRight,
Symbol = Symbol.Open
});
content.Children.Add(new TextBlock
{
Text = primaryText,
Margin = new Thickness(5),
TextWrapping = TextWrapping.Wrap,
MaxWidth = 450,
GridColumn = 1,
GridRow = 0
});
content.Children.Add(new TextBlock
{
Text = secondaryText,
Margin = new Thickness(5),
TextWrapping = TextWrapping.Wrap,
MaxWidth = 450,
GridColumn = 1,
GridRow = 1
});
content.Children.Add(new Button
{
Content = buttonContent,
HorizontalAlignment = HorizontalAlignment.Center,
Command = Commands.Create(onClick),
GridRow = 2,
GridColumnSpan = 2,
});
content.Children.Add(icon);
content.Children.Add(primaryLabel);
content.Children.Add(secondaryLabel);
return content;
}
@@ -368,20 +282,15 @@ namespace Ryujinx.Ava.UI.Helpers
LocaleManager.Instance[LocaleKeys.InputDialogOk],
(int)Symbol.Important);
internal static async Task CreateUpdaterUpToDateInfoDialog(string primary, string secondaryText,
string changelogUrl)
{
await ShowTextDialogWithButton(
internal static async Task<UserResult> CreateUpdaterUpToDateInfoDialog(string primary, string secondaryText)
=> await ShowTextDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterTitle],
primary,
secondaryText,
string.Empty,
LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage],
string.Empty,
LocaleManager.Instance[LocaleKeys.InputDialogOk],
(int)Symbol.Important,
LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage],
() => OpenHelper.OpenUrl(changelogUrl));
}
(int)Symbol.Important);
internal static async Task CreateWarningDialog(string primary, string secondaryText)
=> await ShowTextDialog(
@@ -431,7 +340,7 @@ namespace Ryujinx.Ava.UI.Helpers
return response == UserResult.Yes;
}
internal static async Task<UserResult> CreateUpdaterChoiceDialog(string title, string primary, string secondaryText, string changelogUrl)
internal static async Task<UserResult> CreateUpdaterChoiceDialog(string title, string primary, string secondaryText)
{
if (_isChoiceDialogOpen)
{
@@ -440,16 +349,14 @@ namespace Ryujinx.Ava.UI.Helpers
_isChoiceDialogOpen = true;
UserResult response = await ShowTextDialogWithButton(
UserResult response = await ShowTextDialog(
title,
primary,
secondaryText,
LocaleManager.Instance[LocaleKeys.InputDialogYes],
string.Empty,
LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
(int)Symbol.Help,
LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage],
() => OpenHelper.OpenUrl(changelogUrl),
UserResult.Yes);
_isChoiceDialogOpen = false;

View File

@@ -1,40 +0,0 @@
using Avalonia.Controls;
namespace Ryujinx.Ava.UI.Helpers
{
public static class ControlExtensions
{
extension(Control ctrl)
{
public int GridRow
{
get => Grid.GetRow(ctrl);
set => Grid.SetRow(ctrl, value);
}
public int GridColumn
{
get => Grid.GetColumn(ctrl);
set => Grid.SetColumn(ctrl, value);
}
public int GridRowSpan
{
get => Grid.GetRowSpan(ctrl);
set => Grid.SetRowSpan(ctrl, value);
}
public int GridColumnSpan
{
get => Grid.GetColumnSpan(ctrl);
set => Grid.SetColumnSpan(ctrl, value);
}
public bool GridIsSharedSizeScope
{
get => Grid.GetIsSharedSizeScope(ctrl);
set => Grid.SetIsSharedSizeScope(ctrl, value);
}
}
}
}

View File

@@ -27,136 +27,82 @@ namespace Ryujinx.Ava.UI.Models.Input
public ControllerType ControllerType { get; set; }
public PlayerIndex PlayerIndex { get; set; }
[ObservableProperty]
public partial StickInputId LeftJoystick { get; set; }
[ObservableProperty] private StickInputId _leftJoystick;
[ObservableProperty] private bool _leftInvertStickX;
[ObservableProperty] private bool _leftInvertStickY;
[ObservableProperty] private bool _leftRotate90;
[ObservableProperty] private GamepadInputId _leftStickButton;
[ObservableProperty]
public partial bool LeftInvertStickX { get; set; }
[ObservableProperty] private StickInputId _rightJoystick;
[ObservableProperty] private bool _rightInvertStickX;
[ObservableProperty] private bool _rightInvertStickY;
[ObservableProperty] private bool _rightRotate90;
[ObservableProperty] private GamepadInputId _rightStickButton;
[ObservableProperty]
public partial bool LeftInvertStickY { get; set; }
[ObservableProperty] private GamepadInputId _dpadUp;
[ObservableProperty] private GamepadInputId _dpadDown;
[ObservableProperty] private GamepadInputId _dpadLeft;
[ObservableProperty] private GamepadInputId _dpadRight;
[ObservableProperty]
public partial bool LeftRotate90 { get; set; }
[ObservableProperty] private GamepadInputId _buttonMinus;
[ObservableProperty] private GamepadInputId _buttonPlus;
[ObservableProperty]
public partial GamepadInputId LeftStickButton { get; set; }
[ObservableProperty] private GamepadInputId _buttonA;
[ObservableProperty] private GamepadInputId _buttonB;
[ObservableProperty] private GamepadInputId _buttonX;
[ObservableProperty] private GamepadInputId _buttonY;
[ObservableProperty]
public partial StickInputId RightJoystick { get; set; }
[ObservableProperty] private GamepadInputId _buttonZl;
[ObservableProperty] private GamepadInputId _buttonZr;
[ObservableProperty]
public partial bool RightInvertStickX { get; set; }
[ObservableProperty] private GamepadInputId _buttonL;
[ObservableProperty] private GamepadInputId _buttonR;
[ObservableProperty]
public partial bool RightInvertStickY { get; set; }
[ObservableProperty] private GamepadInputId _leftButtonSl;
[ObservableProperty] private GamepadInputId _leftButtonSr;
[ObservableProperty]
public partial bool RightRotate90 { get; set; }
[ObservableProperty] private GamepadInputId _rightButtonSl;
[ObservableProperty] private GamepadInputId _rightButtonSr;
[ObservableProperty]
public partial GamepadInputId RightStickButton { get; set; }
[ObservableProperty] private float _deadzoneLeft;
[ObservableProperty] private float _deadzoneRight;
[ObservableProperty]
public partial GamepadInputId DpadUp { get; set; }
[ObservableProperty] private float _rangeLeft;
[ObservableProperty] private float _rangeRight;
[ObservableProperty]
public partial GamepadInputId DpadDown { get; set; }
[ObservableProperty] private float _triggerThreshold;
[ObservableProperty]
public partial GamepadInputId DpadLeft { get; set; }
[ObservableProperty] private bool _enableMotion;
[ObservableProperty]
public partial GamepadInputId DpadRight { get; set; }
[ObservableProperty] private bool _enableRumble;
[ObservableProperty]
public partial GamepadInputId ButtonMinus { get; set; }
[ObservableProperty] private bool _enableLedChanging;
[ObservableProperty]
public partial GamepadInputId ButtonPlus { get; set; }
[ObservableProperty]
public partial GamepadInputId ButtonA { get; set; }
[ObservableProperty]
public partial GamepadInputId ButtonB { get; set; }
[ObservableProperty]
public partial GamepadInputId ButtonX { get; set; }
[ObservableProperty]
public partial GamepadInputId ButtonY { get; set; }
[ObservableProperty]
public partial GamepadInputId ButtonZl { get; set; }
[ObservableProperty]
public partial GamepadInputId ButtonZr { get; set; }
[ObservableProperty]
public partial GamepadInputId ButtonL { get; set; }
[ObservableProperty]
public partial GamepadInputId ButtonR { get; set; }
[ObservableProperty]
public partial GamepadInputId LeftButtonSl { get; set; }
[ObservableProperty]
public partial GamepadInputId LeftButtonSr { get; set; }
[ObservableProperty]
public partial GamepadInputId RightButtonSl { get; set; }
[ObservableProperty]
public partial GamepadInputId RightButtonSr { get; set; }
[ObservableProperty]
public partial float DeadzoneLeft { get; set; }
[ObservableProperty]
public partial float DeadzoneRight { get; set; }
[ObservableProperty]
public partial float RangeLeft { get; set; }
[ObservableProperty]
public partial float RangeRight { get; set; }
[ObservableProperty]
public partial float TriggerThreshold { get; set; }
[ObservableProperty]
public partial bool EnableMotion { get; set; }
[ObservableProperty]
public partial bool EnableRumble { get; set; }
[ObservableProperty]
public partial bool EnableLedChanging { get; set; }
[ObservableProperty]
public partial Color LedColor { get; set; }
[ObservableProperty] private Color _ledColor;
public bool ShowLedColorPicker => !TurnOffLed && !UseRainbowLed;
private bool _turnOffLed;
public bool TurnOffLed
{
get;
get => _turnOffLed;
set
{
field = value;
_turnOffLed = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ShowLedColorPicker));
}
}
private bool _useRainbowLed;
public bool UseRainbowLed
{
get;
get => _useRainbowLed;
set
{
field = value;
_useRainbowLed = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ShowLedColorPicker));
}

View File

@@ -6,44 +6,31 @@ namespace Ryujinx.Ava.UI.Models.Input
{
public partial class HotkeyConfig : BaseModel
{
[ObservableProperty]
public partial Key ToggleVSyncMode { get; set; }
[ObservableProperty] private Key _toggleVSyncMode;
[ObservableProperty]
public partial Key Screenshot { get; set; }
[ObservableProperty] private Key _screenshot;
[ObservableProperty]
public partial Key ShowUI { get; set; }
[ObservableProperty] private Key _showUI;
[ObservableProperty]
public partial Key Pause { get; set; }
[ObservableProperty] private Key _pause;
[ObservableProperty]
public partial Key ToggleMute { get; set; }
[ObservableProperty] private Key _toggleMute;
[ObservableProperty]
public partial Key ResScaleUp { get; set; }
[ObservableProperty] private Key _resScaleUp;
[ObservableProperty]
public partial Key ResScaleDown { get; set; }
[ObservableProperty] private Key _resScaleDown;
[ObservableProperty]
public partial Key VolumeUp { get; set; }
[ObservableProperty] private Key _volumeUp;
[ObservableProperty]
public partial Key VolumeDown { get; set; }
[ObservableProperty] private Key _volumeDown;
[ObservableProperty]
public partial Key CustomVSyncIntervalIncrement { get; set; }
[ObservableProperty] private Key _customVSyncIntervalIncrement;
[ObservableProperty]
public partial Key CustomVSyncIntervalDecrement { get; set; }
[ObservableProperty] private Key _customVSyncIntervalDecrement;
[ObservableProperty]
public partial Key TurboMode { get; set; }
[ObservableProperty] private Key _turboMode;
[ObservableProperty]
public partial bool TurboModeWhileHeld { get; set; }
[ObservableProperty] private bool _turboModeWhileHeld;
public HotkeyConfig(KeyboardHotkeys config)
{

View File

@@ -12,89 +12,42 @@ namespace Ryujinx.Ava.UI.Models.Input
public ControllerType ControllerType { get; set; }
public PlayerIndex PlayerIndex { get; set; }
[ObservableProperty]
public partial Key LeftStickUp { get; set; }
[ObservableProperty] private Key _leftStickUp;
[ObservableProperty] private Key _leftStickDown;
[ObservableProperty] private Key _leftStickLeft;
[ObservableProperty] private Key _leftStickRight;
[ObservableProperty] private Key _leftStickButton;
[ObservableProperty]
public partial Key LeftStickDown { get; set; }
[ObservableProperty] private Key _rightStickUp;
[ObservableProperty] private Key _rightStickDown;
[ObservableProperty] private Key _rightStickLeft;
[ObservableProperty] private Key _rightStickRight;
[ObservableProperty] private Key _rightStickButton;
[ObservableProperty]
public partial Key LeftStickLeft { get; set; }
[ObservableProperty] private Key _dpadUp;
[ObservableProperty] private Key _dpadDown;
[ObservableProperty] private Key _dpadLeft;
[ObservableProperty] private Key _dpadRight;
[ObservableProperty]
public partial Key LeftStickRight { get; set; }
[ObservableProperty] private Key _buttonMinus;
[ObservableProperty] private Key _buttonPlus;
[ObservableProperty]
public partial Key LeftStickButton { get; set; }
[ObservableProperty] private Key _buttonA;
[ObservableProperty] private Key _buttonB;
[ObservableProperty] private Key _buttonX;
[ObservableProperty] private Key _buttonY;
[ObservableProperty]
public partial Key RightStickUp { get; set; }
[ObservableProperty] private Key _buttonL;
[ObservableProperty] private Key _buttonR;
[ObservableProperty]
public partial Key RightStickDown { get; set; }
[ObservableProperty] private Key _buttonZl;
[ObservableProperty] private Key _buttonZr;
[ObservableProperty]
public partial Key RightStickLeft { get; set; }
[ObservableProperty] private Key _leftButtonSl;
[ObservableProperty] private Key _leftButtonSr;
[ObservableProperty]
public partial Key RightStickRight { get; set; }
[ObservableProperty]
public partial Key RightStickButton { get; set; }
[ObservableProperty]
public partial Key DpadUp { get; set; }
[ObservableProperty]
public partial Key DpadDown { get; set; }
[ObservableProperty]
public partial Key DpadLeft { get; set; }
[ObservableProperty]
public partial Key DpadRight { get; set; }
[ObservableProperty]
public partial Key ButtonMinus { get; set; }
[ObservableProperty]
public partial Key ButtonPlus { get; set; }
[ObservableProperty]
public partial Key ButtonA { get; set; }
[ObservableProperty]
public partial Key ButtonB { get; set; }
[ObservableProperty]
public partial Key ButtonX { get; set; }
[ObservableProperty]
public partial Key ButtonY { get; set; }
[ObservableProperty]
public partial Key ButtonL { get; set; }
[ObservableProperty]
public partial Key ButtonR { get; set; }
[ObservableProperty]
public partial Key ButtonZl { get; set; }
[ObservableProperty]
public partial Key ButtonZr { get; set; }
[ObservableProperty]
public partial Key LeftButtonSl { get; set; }
[ObservableProperty]
public partial Key LeftButtonSr { get; set; }
[ObservableProperty]
public partial Key RightButtonSl { get; set; }
[ObservableProperty]
public partial Key RightButtonSr { get; set; }
[ObservableProperty] private Key _rightButtonSl;
[ObservableProperty] private Key _rightButtonSr;
public KeyboardInputConfig(InputConfig config)
{

View File

@@ -6,8 +6,8 @@ namespace Ryujinx.Ava.UI.Models
{
public partial class ModModel : BaseModel
{
[ObservableProperty]
public partial bool Enabled { get; set; }
[ObservableProperty] private bool _enabled;
public bool InSd { get; }
public string Path { get; }
public string Name { get; }

View File

@@ -15,7 +15,6 @@ namespace Ryujinx.Ava.UI.Models
public string Name { get; set; }
public byte[] Data { get; set; }
[ObservableProperty]
public partial SolidColorBrush BackgroundColor { get; set; } = new(Colors.White);
[ObservableProperty] private SolidColorBrush _backgroundColor = new(Colors.White);
}
}

View File

@@ -7,26 +7,24 @@ namespace Ryujinx.Ava.UI.Models
{
public partial class TempProfile : BaseModel
{
[ObservableProperty]
public partial byte[] Image { get; set; }
[ObservableProperty]
public partial string Name { get; set; } = string.Empty;
[ObservableProperty] private byte[] _image;
[ObservableProperty] private string _name = String.Empty;
private UserId _userId;
public static uint MaxProfileNameLength => 0x20;
public UserId UserId
{
get;
get => _userId;
set
{
field = value;
_userId = value;
OnPropertyChanged();
OnPropertyChanged(nameof(UserIdString));
}
}
public string UserIdString => UserId.ToString();
public string UserIdString => _userId.ToString();
public TempProfile(UserProfile profile)
{

View File

@@ -13,20 +13,11 @@ namespace Ryujinx.Ava.UI.Models
{
private readonly Profile _profile;
private readonly NavigationDialogHost _owner;
[ObservableProperty]
public partial byte[] Image { get; set; }
[ObservableProperty]
public partial string Name { get; set; }
[ObservableProperty]
public partial UserId UserId { get; set; }
[ObservableProperty]
public partial bool IsPointerOver { get; set; }
[ObservableProperty]
public partial IBrush BackgroundColor { get; set; }
[ObservableProperty] private byte[] _image;
[ObservableProperty] private string _name;
[ObservableProperty] private UserId _userId;
[ObservableProperty] private bool _isPointerOver;
[ObservableProperty] private IBrush _backgroundColor;
public UserProfile(Profile profile, NavigationDialogHost owner)
{
@@ -48,7 +39,7 @@ namespace Ryujinx.Ava.UI.Models
private void UpdateBackground()
{
Application currentApplication = Application.Current;
Application currentApplication = Avalonia.Application.Current;
currentApplication.Styles.TryGetResource("ControlFillColorSecondary", currentApplication.ActualThemeVariant, out object color);
if (color is not null)

View File

@@ -359,9 +359,8 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
[ObservableProperty]
public partial bool MatchSystemTime { get; set; }
[ObservableProperty] private bool _matchSystemTime;
public DateTimeOffset CurrentDate { get; set; }
public TimeSpan CurrentTime { get; set; }