diff --git a/WheelWizard.Test/Features/Dolphin/FeaturePathOwnershipTests.cs b/WheelWizard.Test/Features/Dolphin/FeaturePathOwnershipTests.cs index 3eda063f0..cc3637859 100644 --- a/WheelWizard.Test/Features/Dolphin/FeaturePathOwnershipTests.cs +++ b/WheelWizard.Test/Features/Dolphin/FeaturePathOwnershipTests.cs @@ -94,19 +94,19 @@ public void DistributionWithDolphin_UsesEffectiveLoadDirectory_WhileDownloadsFol private static (ISettingsManager, DolphinPaths) CreateDolphinPaths(MockFileSystem fs, string userFolder) { var settings = Substitute.For(); - settings.USER_FOLDER_PATH.Returns(new WhWzSetting(typeof(string), "UserFolderPath", userFolder)); - settings.DOLPHIN_LOCATION.Returns(new WhWzSetting(typeof(string), "DolphinLocation", "dolphin-emu")); + settings.USER_FOLDER_PATH.Returns(new WhWzSetting("UserFolderPath", userFolder)); + settings.DOLPHIN_LOCATION.Returns(new WhWzSetting("DolphinLocation", "dolphin-emu")); settings.LOAD_PATH.Returns( - new WhWzSetting(typeof(string), "LoadPath", "").SetValidation(value => + new WhWzSetting("LoadPath", "").SetValidation(value => value is string directory && !string.IsNullOrWhiteSpace(directory) && fs.Directory.Exists(directory) ) ); settings.NAND_ROOT_PATH.Returns( - new WhWzSetting(typeof(string), "NandRootPath", "").SetValidation(value => + new WhWzSetting("NandRootPath", "").SetValidation(value => value is string directory && !string.IsNullOrWhiteSpace(directory) && fs.Directory.Exists(directory) ) ); - settings.Get(Arg.Any()).Returns(call => (string)call.Arg().Get()); + settings.Get(Arg.Any>()).Returns(call => (string)call.Arg>().Get()); return (settings, new DolphinPaths(settings, new DolphinPathResolver(fs, Substitute.For()), fs)); } } diff --git a/WheelWizard.Test/Features/Launching/DolphinLaunchServiceTests.cs b/WheelWizard.Test/Features/Launching/DolphinLaunchServiceTests.cs index ac8d5a4b3..d9c7dbd4b 100644 --- a/WheelWizard.Test/Features/Launching/DolphinLaunchServiceTests.cs +++ b/WheelWizard.Test/Features/Launching/DolphinLaunchServiceTests.cs @@ -154,7 +154,7 @@ private sealed class Fixture public IDolphinVersionService Versions { get; } = Substitute.For(); public ILinuxDolphinInstaller Installer { get; } = Substitute.For(); public IDolphinLaunchPresentation Presentation { get; } = Substitute.For(); - public WhWzSetting GamePath { get; } = new(typeof(string), "GamePath", "/game.iso"); + public WhWzSetting GamePath { get; } = new("GamePath", "/game.iso"); public DolphinLaunchService Service { get; } public Fixture(bool windows = false, string command = "dolphin-emu") diff --git a/WheelWizard.Test/Features/Launching/LaunchPathTests.cs b/WheelWizard.Test/Features/Launching/LaunchPathTests.cs index bb10d20dc..584ef393f 100644 --- a/WheelWizard.Test/Features/Launching/LaunchPathTests.cs +++ b/WheelWizard.Test/Features/Launching/LaunchPathTests.cs @@ -94,8 +94,8 @@ public async Task BlockedDistributionPreflight_DoesNotKillPrepareOrWrite(bool be private static ISettingsManager CreateSettings() { var settings = Substitute.For(); - settings.GAME_LOCATION.Returns(new WhWzSetting(typeof(string), "GamePath", "/game.iso")); - settings.Get(Arg.Any()).Returns(call => (string)call.Arg().Get()); + settings.GAME_LOCATION.Returns(new WhWzSetting("GamePath", "/game.iso")); + settings.Get(Arg.Any>()).Returns(call => (string)call.Arg>().Get()); return settings; } } diff --git a/WheelWizard.Test/Features/Launching/RetroRewindLaunchServiceTests.cs b/WheelWizard.Test/Features/Launching/RetroRewindLaunchServiceTests.cs index e18842286..3d7492d40 100644 --- a/WheelWizard.Test/Features/Launching/RetroRewindLaunchServiceTests.cs +++ b/WheelWizard.Test/Features/Launching/RetroRewindLaunchServiceTests.cs @@ -122,11 +122,11 @@ public Fixture() { Fs.File.WriteAllText("/game.iso", "game"); var settings = Substitute.For(); - settings.GAME_LOCATION.Returns(new WhWzSetting(typeof(string), "Game", "/game.iso")); - settings.FORCE_WIIMOTE.Returns(new WhWzSetting(typeof(bool), "Force", true)); - settings.LAUNCH_WITH_DOLPHIN.Returns(new WhWzSetting(typeof(bool), "Dolphin", false)); - settings.Get(Arg.Any()).Returns(call => (string)call.Arg().Get()); - settings.Get(Arg.Any()).Returns(call => (bool)call.Arg().Get()); + settings.GAME_LOCATION.Returns(new WhWzSetting("Game", "/game.iso")); + settings.FORCE_WIIMOTE.Returns(new WhWzSetting("Force", true)); + settings.LAUNCH_WITH_DOLPHIN.Returns(new WhWzSetting("Dolphin", false)); + settings.Get(Arg.Any>()).Returns(call => (string)call.Arg>().Get()); + settings.Get(Arg.Any>()).Returns(call => (bool)call.Arg>().Get()); var paths = Substitute.For(); paths.PatchesFolderPath.Returns("/stable-patches"); paths.BetaPatchesFolderPath.Returns("/beta-patches"); diff --git a/WheelWizard.Test/Features/MiiRepositoryServiceTests.cs b/WheelWizard.Test/Features/MiiRepositoryServiceTests.cs index 0c909ca84..886851c35 100644 --- a/WheelWizard.Test/Features/MiiRepositoryServiceTests.cs +++ b/WheelWizard.Test/Features/MiiRepositoryServiceTests.cs @@ -26,9 +26,9 @@ public sealed class MiiRepositoryServiceTests public MiiRepositoryServiceTests() { _settings = SettingsTestUtils.CreateSettingsStub(Path.GetDirectoryName(_sourceNand)!); - var nandSetting = new WhWzSetting(typeof(string), "NandRoot", _sourceNand); - var copySetting = new WhWzSetting(typeof(bool), "CopyNand", false); - var useSetting = new WhWzSetting(typeof(bool), "UseDolphinData", true); + var nandSetting = new WhWzSetting("NandRoot", _sourceNand); + var copySetting = new WhWzSetting("CopyNand", false); + var useSetting = new WhWzSetting("UseDolphinData", true); _settings.NAND_ROOT_PATH.Returns(nandSetting); _settings.RECOMP_COPY_DOLPHIN_NAND.Returns(copySetting); _settings.RECOMP_USE_DOLPHIN_DATA.Returns(useSetting); diff --git a/WheelWizard.Test/Features/Settings/DolphinSettingsTests.cs b/WheelWizard.Test/Features/Settings/DolphinSettingsTests.cs index df96506b7..01fde950f 100644 --- a/WheelWizard.Test/Features/Settings/DolphinSettingsTests.cs +++ b/WheelWizard.Test/Features/Settings/DolphinSettingsTests.cs @@ -10,7 +10,7 @@ public class DolphinSettingTests [Fact] public void Constructor_Throws_WhenFileNameIsNotIni() { - var action = () => new DolphinSetting(typeof(string), ("Dolphin.cfg", "General", "NANDRootPath"), "value"); + var action = () => new DolphinSetting(("Dolphin.cfg", "General", "NANDRootPath"), "value"); Assert.Throws(action); } @@ -18,8 +18,7 @@ public void Constructor_Throws_WhenFileNameIsNotIni() [Fact] public void SetFromString_ParsesEnumAndFormatsAsIntegerString() { - var setting = new DolphinSetting( - typeof(DolphinShaderCompilationMode), + var setting = new DolphinSetting( ("GFX.ini", "Settings", "ShaderCompilationMode"), DolphinShaderCompilationMode.Default ); @@ -34,9 +33,7 @@ public void SetFromString_ParsesEnumAndFormatsAsIntegerString() [Fact] public void Set_ReturnsFalseAndKeepsOldValue_WhenValidationFails() { - var setting = new DolphinSetting(typeof(int), ("GFX.ini", "Settings", "InternalResolution"), 1).SetValidation(value => - (int)value! >= 0 - ); + var setting = new DolphinSetting(("GFX.ini", "Settings", "InternalResolution"), 1).SetValidation(value => value >= 0); setting.Set(2); var result = setting.Set(-1); @@ -46,11 +43,12 @@ public void Set_ReturnsFalseAndKeepsOldValue_WhenValidationFails() } [Fact] - public void SetFromString_Throws_WhenTypeIsUnsupported() + public void SetFromString_ParsesTypedNumbers() { - var setting = new DolphinSetting(typeof(decimal), ("GFX.ini", "Settings", "Price"), 1m); + var setting = new DolphinSetting(("GFX.ini", "Settings", "Price"), 1m); - Assert.Throws(() => setting.SetFromString("3.14")); + Assert.True(setting.SetFromString("3.14")); + Assert.Equal(3.14m, setting.Value); } } @@ -67,7 +65,7 @@ public void LoadSettings_ReadsExistingValue_FromIniFile() fileSystem.Directory.CreateDirectory(configFolderPath); fileSystem.File.WriteAllLines(iniPath, ["[General]", "NANDRootPath = /persisted"]); var manager = new DolphinSettingManager(fileSystem); - var setting = new DolphinSetting(typeof(string), ("Dolphin.ini", "General", "NANDRootPath"), "/default"); + var setting = new DolphinSetting(("Dolphin.ini", "General", "NANDRootPath"), "/default"); manager.RegisterSetting(setting); manager.LoadSettings(configFolderPath); @@ -85,7 +83,7 @@ public void LoadSettings_UsesDefaultWithoutWriting_WhenIniEntryIsMissing() fileSystem.Directory.CreateDirectory(configFolderPath); fileSystem.File.WriteAllLines(iniPath, ["[General]", "OtherSetting = 1"]); var manager = new DolphinSettingManager(fileSystem); - var setting = new DolphinSetting(typeof(string), ("Dolphin.ini", "General", "NANDRootPath"), "/default"); + var setting = new DolphinSetting(("Dolphin.ini", "General", "NANDRootPath"), "/default"); manager.RegisterSetting(setting); manager.LoadSettings(configFolderPath); @@ -105,7 +103,7 @@ public void SaveSettings_UpdatesExistingSettingLine_InIniFile() fileSystem.Directory.CreateDirectory(configFolderPath); fileSystem.File.WriteAllLines(iniPath, ["[General]", "NANDRootPath = /old"]); var manager = new DolphinSettingManager(fileSystem); - var setting = new DolphinSetting(typeof(string), ("Dolphin.ini", "General", "NANDRootPath"), "/default"); + var setting = new DolphinSetting(("Dolphin.ini", "General", "NANDRootPath"), "/default"); manager.RegisterSetting(setting); manager.LoadSettings(configFolderPath); @@ -127,7 +125,7 @@ public void ReloadSettings_ReReadsFile_AfterItChangesOnDisk() fileSystem.Directory.CreateDirectory(configFolderPath); fileSystem.File.WriteAllLines(iniPath, ["[General]", "NANDRootPath = /first"]); var manager = new DolphinSettingManager(fileSystem); - var setting = new DolphinSetting(typeof(string), ("Dolphin.ini", "General", "NANDRootPath"), "/default"); + var setting = new DolphinSetting(("Dolphin.ini", "General", "NANDRootPath"), "/default"); manager.RegisterSetting(setting); manager.LoadSettings(configFolderPath); diff --git a/WheelWizard.Test/Features/Settings/SettingsPersistencePathTests.cs b/WheelWizard.Test/Features/Settings/SettingsPersistencePathTests.cs index baf0611d1..2eb207c45 100644 --- a/WheelWizard.Test/Features/Settings/SettingsPersistencePathTests.cs +++ b/WheelWizard.Test/Features/Settings/SettingsPersistencePathTests.cs @@ -17,7 +17,7 @@ public void ApplicationSettings_SaveToRequestedDestination_WithoutChangingOrigin const string originalJson = "{\"Volume\":12}"; fs.File.WriteAllText(original, originalJson); var manager = new WhWzSettingManager(NullLogger.Instance, fs); - var setting = new WhWzSetting(typeof(int), "Volume", 5); + var setting = new WhWzSetting("Volume", 5); manager.RegisterSetting(setting); manager.LoadSettings(original); @@ -43,7 +43,7 @@ public void DolphinSettings_ReloadAndSave_UseNewUserDirectory() fs.File.WriteAllText(firstFile, originalIni); fs.File.WriteAllText(secondFile, "[General]\nNANDRootPath = /second-nand\nOther = keep\n"); var manager = new DolphinSettingManager(fs); - var setting = new DolphinSetting(typeof(string), ("Dolphin.ini", "General", "NANDRootPath"), ""); + var setting = new DolphinSetting(("Dolphin.ini", "General", "NANDRootPath"), ""); manager.RegisterSetting(setting); manager.LoadSettings(first); @@ -72,7 +72,7 @@ public void RecompSettings_ReloadSaveAndRemove_PreserveOtherFileAndUnrelatedCont ["# keep comment", "[paths]", "nand_root = \"second\"", "other = true", "[video]", "show_fps = false"] ); var manager = new RecompSettingManager(fs); - var setting = new RecompSetting(typeof(string), ("paths", "nand_root"), "", _ => { }); + var setting = new RecompSetting(("paths", "nand_root"), "", _ => { }); manager.RegisterSetting(setting); manager.LoadSettings(first); @@ -93,7 +93,7 @@ public void RecompSettings_DoNotCreateBackendOwnedFile_WhenMissing() var fs = new MockFileSystem(); var configPath = fs.Path.GetFullPath("/missing/Config.toml"); var manager = new RecompSettingManager(fs); - var setting = new RecompSetting(typeof(bool), ("video", "show_fps"), false, _ => { }); + var setting = new RecompSetting(("video", "show_fps"), false, _ => { }); manager.RegisterSetting(setting); manager.LoadSettings(configPath); diff --git a/WheelWizard.Test/Features/Settings/SettingsRecoveryTests.cs b/WheelWizard.Test/Features/Settings/SettingsRecoveryTests.cs index 3061ad73a..bcd54e20e 100644 --- a/WheelWizard.Test/Features/Settings/SettingsRecoveryTests.cs +++ b/WheelWizard.Test/Features/Settings/SettingsRecoveryTests.cs @@ -14,8 +14,8 @@ public void DolphinEditPreservesExternalValuesAndReloadDoesNotCarryPreviousProfi fs.Directory.CreateDirectory("/config"); fs.File.WriteAllText("/config/GFX.ini", "[Settings]\nShowFPS = False\nInternalResolution = 1\n"); var manager = new DolphinSettingManager(fs); - var fps = new DolphinSetting(typeof(bool), ("GFX.ini", "Settings", "ShowFPS"), false, s => manager.SaveSettings("/config", s)); - var resolution = new DolphinSetting(typeof(int), ("GFX.ini", "Settings", "InternalResolution"), 1); + var fps = new DolphinSetting(("GFX.ini", "Settings", "ShowFPS"), false, s => manager.SaveSettings("/config", s)); + var resolution = new DolphinSetting(("GFX.ini", "Settings", "InternalResolution"), 1); manager.RegisterSetting(fps); manager.RegisterSetting(resolution); manager.LoadSettings("/config"); @@ -39,7 +39,7 @@ public void RecompReloadMissingKeyUsesDefault() fs.Directory.CreateDirectory("/config"); fs.File.WriteAllText("/config/Config.toml", "[video]\nshow_fps = false\n"); var manager = new RecompSettingManager(fs); - var setting = new RecompSetting(typeof(bool), ("video", "show_fps"), true, s => manager.SaveSettings("/config/Config.toml", s)); + var setting = new RecompSetting(("video", "show_fps"), true, s => manager.SaveSettings("/config/Config.toml", s)); manager.RegisterSetting(setting); manager.LoadSettings("/config/Config.toml"); fs.File.WriteAllText("/config/Config.toml", "[video]\n"); @@ -54,8 +54,7 @@ public void RecompReloadMissingKeyUsesDefault() public void FailedSaveRollsBackAndCanBeRetried() { var fail = true; - var setting = new WhWzSetting( - typeof(bool), + var setting = new WhWzSetting( "Enabled", false, _ => @@ -84,7 +83,7 @@ public void JsonRetainsExistingKeysAndUnknownValuesAndPreservesCorruptFile() const string original = "{\"EnableAnimations\":false,\"FutureSetting\":{\"value\":2}}"; fs.File.WriteAllText(path, original); var manager = new WhWzSettingManager(NullLogger.Instance, fs); - var setting = new WhWzSetting(typeof(bool), "EnableAnimations", true, s => manager.SaveSettings(path, s)); + var setting = new WhWzSetting("EnableAnimations", true, s => manager.SaveSettings(path, s)); manager.RegisterSetting(setting); manager.LoadSettings(path); Assert.Equal(false, setting.Get()); @@ -102,8 +101,8 @@ public void JsonRetainsExistingKeysAndUnknownValuesAndPreservesCorruptFile() [Fact] public void VirtualSettingTracksDependenciesAfterSetterFailure() { - var source = new WhWzSetting(typeof(int), "source", 1); - using var derived = new VirtualSetting(typeof(int), _ => throw new IOException("save failed"), source.Get).SetDependencies(source); + var source = new WhWzSetting("source", 1); + using var derived = new VirtualSetting(_ => throw new IOException("save failed"), source.Get).SetDependencies(source); Assert.False(derived.Set(2)); source.Set(3); Assert.Equal(3, derived.Get()); diff --git a/WheelWizard.Test/Features/Settings/SettingsTests.cs b/WheelWizard.Test/Features/Settings/SettingsTests.cs index d207ca44a..2aa3245bf 100644 --- a/WheelWizard.Test/Features/Settings/SettingsTests.cs +++ b/WheelWizard.Test/Features/Settings/SettingsTests.cs @@ -21,6 +21,39 @@ public sealed class SettingsFeatureCollection; [Collection("SettingsFeature")] public class SettingsManagerTests { + [Fact] + public void RecommendedSettings_CanReenableAfterPartialDisableFailure() + { + using var manager = CreateManager(new MockFileSystem(), out _, out var dolphinManager, out _); + var children = dolphinManager + .ReceivedCalls() + .Where(call => call.GetMethodInfo().Name == nameof(IDolphinSettingManager.RegisterSetting)) + .Select(call => (IDolphinSetting)call.GetArguments()[0]!) + .ToDictionary(setting => setting.Name); + Assert.True(manager.Set(manager.RECOMMENDED_SETTINGS, true)); + var recommendedShaderMode = children["ShaderCompilationMode"].GetStringValue(); + var failure = new IOException("disk full"); + var fail = true; + dolphinManager + .When(m => m.SaveSettings(Arg.Any(), Arg.Is(s => s.Name == "MSAA"))) + .Do(_ => + { + if (fail) + throw failure; + }); + + Assert.False(manager.Set(manager.RECOMMENDED_SETTINGS, false)); + Assert.Same(failure, manager.RECOMMENDED_SETTINGS.SaveError); + Assert.NotEqual(recommendedShaderMode, children["ShaderCompilationMode"].GetStringValue()); + Assert.False(manager.Get(manager.RECOMMENDED_SETTINGS)); + + fail = false; + Assert.True(manager.Set(manager.RECOMMENDED_SETTINGS, true)); + Assert.Equal(recommendedShaderMode, children["ShaderCompilationMode"].GetStringValue()); + Assert.True(manager.Get(manager.RECOMMENDED_SETTINGS)); + Assert.Null(manager.RECOMMENDED_SETTINGS.SaveError); + } + [Theory] [InlineData("ShaderCompilationMode")] #if WINDOWS @@ -34,13 +67,13 @@ public void RecommendedSettings_PropagatesChildSaveFailureAndCanRetry(string fai var children = dolphinManager .ReceivedCalls() .Where(call => call.GetMethodInfo().Name == nameof(IDolphinSettingManager.RegisterSetting)) - .Select(call => (DolphinSetting)call.GetArguments()[0]!) + .Select(call => (IDolphinSetting)call.GetArguments()[0]!) .ToDictionary(setting => setting.Name); - children["SSAA"].Set(true, skipSave: true); + ((DolphinSetting)children["SSAA"]).Set(true, skipSave: true); var failure = new IOException("disk full"); var fail = true; dolphinManager - .When(m => m.SaveSettings(Arg.Any(), Arg.Is(s => s.Name == failedSetting))) + .When(m => m.SaveSettings(Arg.Any(), Arg.Is(s => s.Name == failedSetting))) .Do(_ => { if (fail) @@ -62,11 +95,22 @@ public void RecommendedSettings_PropagatesChildSaveFailureAndCanRetry(string fai } [Fact] - public void Get_Throws_WhenRequestedTypeDoesNotMatchSettingType() + public void Get_InfersTheTypeFromTheSetting() { var manager = CreateManager(new MockFileSystem(), out _, out _, out _); - Assert.Throws(() => manager.Get(manager.WW_LANGUAGE)); + string language = manager.Get(manager.WW_LANGUAGE); + Assert.Equal("en", language); + } + + [Fact] + public void SettingApiExposesOnlyItsDeclaredValueType() + { + var setter = typeof(Setting).GetMethod("Set")!; + Assert.Equal(typeof(bool), setter.GetParameters()[0].ParameterType); + Assert.Equal(typeof(bool), typeof(Setting).GetProperty("Value")!.PropertyType); + Assert.Null(typeof(Setting).GetMethod("Set")); + Assert.Null(typeof(Setting).GetMethod("Get")); } [Fact] @@ -221,7 +265,7 @@ public class SettingsSignalBusTests public void Publish_NotifiesActiveSubscribers() { var signalBus = SettingsTestUtils.CreateSettingsSignalBus(); - var setting = new WhWzSetting(typeof(int), "Volume", 10); + var setting = new WhWzSetting("Volume", 10); SettingChangedSignal? receivedSignal = null; using var _ = signalBus.Subscribe(signal => receivedSignal = signal); @@ -235,7 +279,7 @@ public void Publish_NotifiesActiveSubscribers() public void DisposeSubscription_StopsReceivingSignals() { var signalBus = SettingsTestUtils.CreateSettingsSignalBus(); - var setting = new WhWzSetting(typeof(int), "Volume", 10); + var setting = new WhWzSetting("Volume", 10); var receiveCount = 0; var subscription = signalBus.Subscribe(_ => receiveCount++); @@ -325,9 +369,9 @@ public void Initialize_SetsCurrentCulture_FromLanguageSetting() var originalLanguage = LocalizationProvider.Current.CurrentLanguage; var signalBus = SettingsTestUtils.CreateSettingsSignalBus(); var settingsManager = Substitute.For(); - var languageSetting = new WhWzSetting(typeof(string), "WW_Language", "fr"); + var languageSetting = new WhWzSetting("WW_Language", "fr"); settingsManager.WW_LANGUAGE.Returns(languageSetting); - settingsManager.Get(Arg.Any()).Returns(_ => (string)languageSetting.Get()); + settingsManager.Get(Arg.Any>()).Returns(_ => (string)languageSetting.Get()); var yamlLocalizationService = new EmbeddedYamlLocalizationService(); using var localizationService = new SettingsLocalizationService(settingsManager, signalBus, yamlLocalizationService); @@ -363,9 +407,9 @@ public void PublishLanguageSignal_UpdatesCulture_WhenLanguageChanges() var originalLanguage = LocalizationProvider.Current.CurrentLanguage; var signalBus = SettingsTestUtils.CreateSettingsSignalBus(); var settingsManager = Substitute.For(); - var languageSetting = new WhWzSetting(typeof(string), "WW_Language", "en"); + var languageSetting = new WhWzSetting("WW_Language", "en"); settingsManager.WW_LANGUAGE.Returns(languageSetting); - settingsManager.Get(Arg.Any()).Returns(_ => (string)languageSetting.Get()); + settingsManager.Get(Arg.Any>()).Returns(_ => (string)languageSetting.Get()); var yamlLocalizationService = new EmbeddedYamlLocalizationService(); using var localizationService = new SettingsLocalizationService(settingsManager, signalBus, yamlLocalizationService); @@ -478,17 +522,17 @@ public static string GetValidDolphinLocation(IFileSystem fileSystem) public static ISettingsManager CreateSettingsStub(string userFolderPath, string dolphinLocation = "dolphin-emu") { var settings = Substitute.For(); - var userFolderSetting = new WhWzSetting(typeof(string), "UserFolderPath", userFolderPath); - var dolphinLocationSetting = new WhWzSetting(typeof(string), "DolphinLocation", dolphinLocation); + var userFolderSetting = new WhWzSetting("UserFolderPath", userFolderPath); + var dolphinLocationSetting = new WhWzSetting("DolphinLocation", dolphinLocation); settings.USER_FOLDER_PATH.Returns(userFolderSetting); settings.DOLPHIN_LOCATION.Returns(dolphinLocationSetting); settings - .Get(Arg.Is(setting => ReferenceEquals(setting, userFolderSetting))) + .Get(Arg.Is>(setting => ReferenceEquals(setting, userFolderSetting))) .Returns(_ => (string)userFolderSetting.Get()); settings - .Get(Arg.Is(setting => ReferenceEquals(setting, dolphinLocationSetting))) + .Get(Arg.Is>(setting => ReferenceEquals(setting, dolphinLocationSetting))) .Returns(_ => (string)dolphinLocationSetting.Get()); return settings; diff --git a/WheelWizard.Test/Features/Settings/VirtualSettingsTests.cs b/WheelWizard.Test/Features/Settings/VirtualSettingsTests.cs index 475a1c7aa..a2ea05773 100644 --- a/WheelWizard.Test/Features/Settings/VirtualSettingsTests.cs +++ b/WheelWizard.Test/Features/Settings/VirtualSettingsTests.cs @@ -10,7 +10,7 @@ public class VirtualSettingTests public void Set_StoresValueAndInvokesSetter_WhenValueIsValid() { var backingValue = 1; - var setting = new VirtualSetting(typeof(int), value => backingValue = (int)value, () => backingValue); + var setting = new VirtualSetting(value => backingValue = (int)value, () => backingValue); var result = setting.Set(5); @@ -23,9 +23,7 @@ public void Set_StoresValueAndInvokesSetter_WhenValueIsValid() public void Set_ReturnsFalseAndKeepsOldValue_WhenValidationFails() { var backingValue = 2; - var setting = new VirtualSetting(typeof(int), value => backingValue = (int)value, () => backingValue).SetValidation(value => - (int)value! >= 0 - ); + var setting = new VirtualSetting(value => backingValue = (int)value, () => backingValue).SetValidation(value => value >= 0); var result = setting.Set(-1); @@ -37,8 +35,8 @@ public void Set_ReturnsFalseAndKeepsOldValue_WhenValidationFails() [Fact] public void SetDependencies_RecalculatesValue_WhenDependencySignalsChange() { - var dependency = new WhWzSetting(typeof(int), "Dependency", 1); - var setting = new VirtualSetting(typeof(int), _ => { }, () => (int)dependency.Get()).SetDependencies(dependency); + var dependency = new WhWzSetting("Dependency", 1); + var setting = new VirtualSetting(_ => { }, () => (int)dependency.Get()).SetDependencies(dependency); dependency.Set(7, skipSave: true); @@ -48,8 +46,8 @@ public void SetDependencies_RecalculatesValue_WhenDependencySignalsChange() [Fact] public void Dispose_StopsRecalculationWithoutAnyGlobalRuntime() { - var dependency = new WhWzSetting(typeof(int), "Dependency", 1); - var setting = new VirtualSetting(typeof(int), _ => { }, () => dependency.Get()).SetDependencies(dependency); + var dependency = new WhWzSetting("Dependency", 1); + var setting = new VirtualSetting(_ => { }, () => dependency.Get()).SetDependencies(dependency); setting.Dispose(); dependency.Set(7, skipSave: true); @@ -60,8 +58,8 @@ public void Dispose_StopsRecalculationWithoutAnyGlobalRuntime() [Fact] public void SetDependencies_Throws_WhenCalledTwice() { - var dependency = new WhWzSetting(typeof(int), "Dependency", 1); - var setting = new VirtualSetting(typeof(int), _ => { }, () => 1).SetDependencies(dependency); + var dependency = new WhWzSetting("Dependency", 1); + var setting = new VirtualSetting(_ => { }, () => 1).SetDependencies(dependency); Assert.Throws(() => setting.SetDependencies(dependency)); } diff --git a/WheelWizard.Test/Features/Settings/WhWzSettingsTests.cs b/WheelWizard.Test/Features/Settings/WhWzSettingsTests.cs index 16c91a58c..13d848f19 100644 --- a/WheelWizard.Test/Features/Settings/WhWzSettingsTests.cs +++ b/WheelWizard.Test/Features/Settings/WhWzSettingsTests.cs @@ -13,7 +13,7 @@ public class WhWzSettingTests public void Set_StoresValueAndCallsSaveAction_WhenValueIsValid() { var saveCalls = 0; - var setting = new WhWzSetting(typeof(int), "Volume", 10, _ => saveCalls++); + var setting = new WhWzSetting("Volume", 10, _ => saveCalls++); var result = setting.Set(20); @@ -25,7 +25,7 @@ public void Set_StoresValueAndCallsSaveAction_WhenValueIsValid() [Fact] public void Set_ReturnsFalseAndKeepsOldValue_WhenValidationFails() { - var setting = new WhWzSetting(typeof(int), "Volume", 10).SetValidation(value => (int)value! >= 0); + var setting = new WhWzSetting("Volume", 10).SetValidation(value => value >= 0); setting.Set(5); var result = setting.Set(-1); @@ -38,7 +38,7 @@ public void Set_ReturnsFalseAndKeepsOldValue_WhenValidationFails() public void Reset_AppliesDefaultValue_EvenIfDefaultDoesNotPassValidation() { var saveCalls = 0; - var setting = new WhWzSetting(typeof(int), "Threshold", 5, _ => saveCalls++).SetValidation(value => (int)value! >= 10); + var setting = new WhWzSetting("Threshold", 5, _ => saveCalls++).SetValidation(value => value >= 10); setting.Set(12); setting.Reset(); @@ -50,7 +50,7 @@ public void Reset_AppliesDefaultValue_EvenIfDefaultDoesNotPassValidation() [Fact] public void Set_NotifiesLaterHandlers_WhenAChangedHandlerThrows() { - var setting = new WhWzSetting(typeof(int), "Volume", 10); + var setting = new WhWzSetting("Volume", 10); Setting? received = null; setting.Changed += _ => throw new InvalidOperationException("Subscriber failed"); setting.Changed += changed => received = changed; @@ -64,7 +64,7 @@ public void Set_NotifiesLaterHandlers_WhenAChangedHandlerThrows() [Fact] public void Reset_RestoresValidation_WhenAChangedHandlerThrows() { - var setting = new WhWzSetting(typeof(int), "Threshold", 5).SetValidation(value => (int)value! >= 10); + var setting = new WhWzSetting("Threshold", 5).SetValidation(value => value >= 10); setting.Set(12); setting.Changed += _ => throw new InvalidOperationException("Subscriber failed"); @@ -80,8 +80,8 @@ public void Reset_RestoresValidation_WhenAChangedHandlerThrows() [InlineData(true)] public void Reset_RestoresForceSave_WhenSavingThrows(bool forceSave) { - var setting = new WhWzSetting(typeof(int), "Threshold", 5, _ => throw new IOException("Save failed")) - .SetValidation(value => (int)value! >= 10) + var setting = new WhWzSetting("Threshold", 5, _ => throw new IOException("Save failed")) + .SetValidation(value => value >= 10) .SetForceSave(forceSave); setting.Set(12, skipSave: true); @@ -95,8 +95,8 @@ public void Reset_RestoresForceSave_WhenSavingThrows(bool forceSave) [Fact] public void SetFromJson_ParsesEnumAndArrayValues() { - var enumSetting = new WhWzSetting(typeof(DayOfWeek), "Day", DayOfWeek.Monday); - var arraySetting = new WhWzSetting(typeof(string[]), "Names", Array.Empty()); + var enumSetting = new WhWzSetting("Day", DayOfWeek.Monday); + var arraySetting = new WhWzSetting("Names", Array.Empty()); using var enumDocument = JsonDocument.Parse("2"); using var arrayDocument = JsonDocument.Parse("[\"A\", \"B\"]"); @@ -110,12 +110,13 @@ public void SetFromJson_ParsesEnumAndArrayValues() } [Fact] - public void SetFromJson_Throws_WhenTypeIsUnsupported() + public void SetFromJson_ReadsTypedDecimalValues() { - var setting = new WhWzSetting(typeof(decimal), "Price", 1m); + var setting = new WhWzSetting("Price", 1m); using var document = JsonDocument.Parse("2"); - Assert.Throws(() => setting.SetFromJson(document.RootElement, skipSave: true)); + Assert.True(setting.SetFromJson(document.RootElement, skipSave: true)); + Assert.Equal(2m, setting.Value); } } @@ -128,8 +129,8 @@ public void LoadSettings_AppliesPersistedValues_ToRegisteredSettings() var fileSystem = new MockFileSystem(); var logger = Substitute.For>(); var manager = new WhWzSettingManager(logger, fileSystem); - var volume = new WhWzSetting(typeof(int), "Volume", 5).SetValidation(value => (int)value! >= 0); - var language = new WhWzSetting(typeof(string), "Language", "en"); + var volume = new WhWzSetting("Volume", 5).SetValidation(value => value >= 0); + var language = new WhWzSetting("Language", "en"); var configPath = fileSystem.Path.GetFullPath("/settings/config.json"); var configFolderPath = fileSystem.Path.GetDirectoryName(configPath)!; fileSystem.Directory.CreateDirectory(configFolderPath); @@ -149,7 +150,7 @@ public void LoadSettings_ResetsInvalidPersistedValues_ToDefaults() var fileSystem = new MockFileSystem(); var logger = Substitute.For>(); var manager = new WhWzSettingManager(logger, fileSystem); - var volume = new WhWzSetting(typeof(int), "Volume", 5).SetValidation(value => (int)value! >= 0); + var volume = new WhWzSetting("Volume", 5).SetValidation(value => value >= 0); var configPath = fileSystem.Path.GetFullPath("/settings/config.json"); var configFolderPath = fileSystem.Path.GetDirectoryName(configPath)!; fileSystem.Directory.CreateDirectory(configFolderPath); @@ -167,7 +168,7 @@ public void SaveSettings_PersistsRegisteredValues_AfterLoad() var fileSystem = new MockFileSystem(); var logger = Substitute.For>(); var manager = new WhWzSettingManager(logger, fileSystem); - var volume = new WhWzSetting(typeof(int), "Volume", 5); + var volume = new WhWzSetting("Volume", 5); var configPath = fileSystem.Path.GetFullPath("/settings/config.json"); manager.RegisterSetting(volume); @@ -185,8 +186,8 @@ public void RegisterSetting_IsIgnoredAfterLoad() var fileSystem = new MockFileSystem(); var logger = Substitute.For>(); var manager = new WhWzSettingManager(logger, fileSystem); - var registeredBeforeLoad = new WhWzSetting(typeof(int), "Volume", 1); - var ignoredAfterLoad = new WhWzSetting(typeof(string), "Future", "initial"); + var registeredBeforeLoad = new WhWzSetting("Volume", 1); + var ignoredAfterLoad = new WhWzSetting("Future", "initial"); var configPath = fileSystem.Path.GetFullPath("/settings/config.json"); manager.RegisterSetting(registeredBeforeLoad); diff --git a/WheelWizard.Test/Views/HomeDolphinLaunchTests.cs b/WheelWizard.Test/Views/HomeDolphinLaunchTests.cs index a053a9a79..1709e38b5 100644 --- a/WheelWizard.Test/Views/HomeDolphinLaunchTests.cs +++ b/WheelWizard.Test/Views/HomeDolphinLaunchTests.cs @@ -117,7 +117,7 @@ private sealed class Fixture public IDolphinVersionService Versions { get; } = Substitute.For(); public ILinuxDolphinInstaller Installer { get; } = Substitute.For(); public IDolphinLaunchPresentation Presentation { get; } = Substitute.For(); - public WhWzSetting GamePath { get; } = new(typeof(string), "GamePath", "/game.iso"); + public WhWzSetting GamePath { get; } = new("GamePath", "/game.iso"); public DolphinLaunchService Service { get; } public Fixture(bool windows = false, string command = "dolphin-emu") diff --git a/WheelWizard/Features/Settings/DolphinSettingManager.cs b/WheelWizard/Features/Settings/DolphinSettingManager.cs index 6aa0cdcb1..c8d7436cb 100644 --- a/WheelWizard/Features/Settings/DolphinSettingManager.cs +++ b/WheelWizard/Features/Settings/DolphinSettingManager.cs @@ -7,9 +7,9 @@ public class DolphinSettingManager(IFileSystem fileSystem) : IDolphinSettingMana { private readonly object _sync = new(); private bool _loaded; - private readonly List _settings = []; + private readonly List _settings = []; - public void RegisterSetting(DolphinSetting setting) + public void RegisterSetting(IDolphinSetting setting) { lock (_sync) { @@ -18,7 +18,7 @@ public void RegisterSetting(DolphinSetting setting) } } - public void SaveSettings(string configDirectory, DolphinSetting invokingSetting) + public void SaveSettings(string configDirectory, IDolphinSetting invokingSetting) { lock (_sync) { diff --git a/WheelWizard/Features/Settings/ISettingsServices.cs b/WheelWizard/Features/Settings/ISettingsServices.cs index f504dc595..4a2e84b2d 100644 --- a/WheelWizard/Features/Settings/ISettingsServices.cs +++ b/WheelWizard/Features/Settings/ISettingsServices.cs @@ -1,26 +1,27 @@ +using WheelWizard.Models.Enums; using WheelWizard.Settings.Types; namespace WheelWizard.Settings; public interface IWhWzSettingManager { - void RegisterSetting(WhWzSetting setting); - void SaveSettings(string configPath, WhWzSetting invokingSetting); + void RegisterSetting(IWhWzSetting setting); + void SaveSettings(string configPath, IWhWzSetting invokingSetting); void LoadSettings(string configPath); } public interface IDolphinSettingManager { - void RegisterSetting(DolphinSetting setting); - void SaveSettings(string configDirectory, DolphinSetting invokingSetting); + void RegisterSetting(IDolphinSetting setting); + void SaveSettings(string configDirectory, IDolphinSetting invokingSetting); void ReloadSettings(string configDirectory); void LoadSettings(string configDirectory); } public interface IRecompSettingManager { - void RegisterSetting(RecompSetting setting); - void SaveSettings(string configPath, RecompSetting invokingSetting); + void RegisterSetting(IRecompSetting setting); + void SaveSettings(string configPath, IRecompSetting invokingSetting); void ReloadSettings(string configPath); void LoadSettings(string configPath); @@ -33,45 +34,45 @@ public interface IRecompSettingManager public interface ISettingsProperties { - Setting USER_FOLDER_PATH { get; } - Setting DOLPHIN_LOCATION { get; } - Setting GAME_LOCATION { get; } - Setting FORCE_WIIMOTE { get; } - Setting LAUNCH_WITH_DOLPHIN { get; } - Setting LAUNCH_RR_ON_STARTUP { get; } - Setting ENABLE_RECOMP { get; } - Setting RECOMP_USE_DOLPHIN_DATA { get; } - Setting RECOMP_COPY_DOLPHIN_NAND { get; } - Setting PREFERS_MODS_ROW_VIEW { get; } - Setting USE_PATCHES_SYSTEM { get; } - Setting FOCUSED_USER { get; } - Setting ENABLE_ANIMATIONS { get; } - Setting TESTING_MODE_ENABLED { get; } - Setting SAVED_WINDOW_SCALE { get; } - Setting RR_REGION { get; } - Setting WW_LANGUAGE { get; } - Setting NAND_ROOT_PATH { get; } - Setting LOAD_PATH { get; } - Setting VSYNC { get; } - Setting INTERNAL_RESOLUTION { get; } - Setting SHOW_FPS { get; } - Setting GFX_BACKEND { get; } - Setting MACADDRESS { get; } - Setting WINDOW_SCALE { get; } - Setting RECOMMENDED_SETTINGS { get; } - Setting RECOMP_RESOLUTION_MULTIPLIER { get; } - Setting RECOMP_GRAPHICS_API { get; } - Setting RECOMP_SHOW_FPS { get; } - Setting RECOMP_PREVENT_STUTTERS { get; } - Setting RECOMP_NAND_ROOT { get; } + Setting USER_FOLDER_PATH { get; } + Setting DOLPHIN_LOCATION { get; } + Setting GAME_LOCATION { get; } + Setting FORCE_WIIMOTE { get; } + Setting LAUNCH_WITH_DOLPHIN { get; } + Setting LAUNCH_RR_ON_STARTUP { get; } + Setting ENABLE_RECOMP { get; } + Setting RECOMP_USE_DOLPHIN_DATA { get; } + Setting RECOMP_COPY_DOLPHIN_NAND { get; } + Setting PREFERS_MODS_ROW_VIEW { get; } + Setting USE_PATCHES_SYSTEM { get; } + Setting FOCUSED_USER { get; } + Setting ENABLE_ANIMATIONS { get; } + Setting TESTING_MODE_ENABLED { get; } + Setting SAVED_WINDOW_SCALE { get; } + Setting RR_REGION { get; } + Setting WW_LANGUAGE { get; } + Setting NAND_ROOT_PATH { get; } + Setting LOAD_PATH { get; } + Setting VSYNC { get; } + Setting INTERNAL_RESOLUTION { get; } + Setting SHOW_FPS { get; } + Setting GFX_BACKEND { get; } + Setting MACADDRESS { get; } + Setting WINDOW_SCALE { get; } + Setting RECOMMENDED_SETTINGS { get; } + Setting RECOMP_RESOLUTION_MULTIPLIER { get; } + Setting RECOMP_GRAPHICS_API { get; } + Setting RECOMP_SHOW_FPS { get; } + Setting RECOMP_PREVENT_STUTTERS { get; } + Setting RECOMP_NAND_ROOT { get; } } public interface ISettingsManager : ISettingsProperties { OperationResult ValidateCorePathSettings(); - T Get(Setting setting); - bool Set(Setting setting, T value, bool skipSave = false); + T Get(Setting setting); + bool Set(Setting setting, T value, bool skipSave = false); ExtensionConfigurationInfo CheckExtensionConfiguration(); bool PathsSetupCorrectly(); bool DolphinPathsSetupCorrectly(); diff --git a/WheelWizard/Features/Settings/RecompSettingManager.cs b/WheelWizard/Features/Settings/RecompSettingManager.cs index 418b249b4..5fed8732d 100644 --- a/WheelWizard/Features/Settings/RecompSettingManager.cs +++ b/WheelWizard/Features/Settings/RecompSettingManager.cs @@ -12,9 +12,9 @@ public class RecompSettingManager(IFileSystem fileSystem) : IRecompSettingManage private readonly object _syncRoot = new(); private readonly object _fileIoSync = new(); private bool _loaded; - private readonly List _settings = []; + private readonly List _settings = []; - public void RegisterSetting(RecompSetting setting) + public void RegisterSetting(IRecompSetting setting) { lock (_syncRoot) { @@ -25,7 +25,7 @@ public void RegisterSetting(RecompSetting setting) } } - public void SaveSettings(string configPath, RecompSetting invokingSetting) + public void SaveSettings(string configPath, IRecompSetting invokingSetting) { lock (_syncRoot) { @@ -80,7 +80,7 @@ public void RemoveTomlSetting(string configPath, string section, string settingT public void LoadSettings(string configPath) { - List settingsSnapshot; + List settingsSnapshot; if (_loaded) return; diff --git a/WheelWizard/Features/Settings/SettingsManager.cs b/WheelWizard/Features/Settings/SettingsManager.cs index d2461d796..a9924294e 100644 --- a/WheelWizard/Features/Settings/SettingsManager.cs +++ b/WheelWizard/Features/Settings/SettingsManager.cs @@ -27,10 +27,10 @@ public class SettingsManager : ISettingsManager, IDisposable private bool IsMissingExtensionForDolphin => _environment.IsMissingDolphinFlatpakExtension(_fileSystem); private bool IsMissingExtensionForRecomp => _environment.IsMissingRecompFlatpakExtension(_fileSystem); - private readonly Setting _dolphinCompilationMode; - private readonly Setting _dolphinCompileShadersAtStart; - private readonly Setting _dolphinSsaa; - private readonly Setting _dolphinMsaa; + private readonly Setting _dolphinCompilationMode; + private readonly Setting _dolphinCompileShadersAtStart; + private readonly Setting _dolphinSsaa; + private readonly Setting _dolphinMsaa; private bool _hasLoadedSettings; private double _internalScale = -1.0; @@ -79,7 +79,7 @@ IUnixCommandService commands // The Dolphin location setting is ignored with the Flatpak since it bundles a separate Dolphin return true; } - var pathOrCommand = value as string ?? string.Empty; + var pathOrCommand = value; if (string.IsNullOrWhiteSpace(pathOrCommand)) return IsRecompModeActive(); @@ -97,7 +97,7 @@ IUnixCommandService commands "", value => { - var userFolderPath = value as string ?? string.Empty; + var userFolderPath = value; if (string.IsNullOrWhiteSpace(userFolderPath)) return IsRecompModeActive(); if (!_fileSystem.Directory.Exists(userFolderPath)) @@ -207,36 +207,28 @@ IUnixCommandService commands } ); - GAME_LOCATION = RegisterWhWz("GameLocation", "", value => _fileSystem.File.Exists(value as string ?? string.Empty)); + GAME_LOCATION = RegisterWhWz("GameLocation", "", value => _fileSystem.File.Exists(value)); FORCE_WIIMOTE = RegisterWhWz("ForceWiimote", false); LAUNCH_WITH_DOLPHIN = RegisterWhWz("LaunchWithDolphin", false); LAUNCH_RR_ON_STARTUP = RegisterWhWz("LaunchRrOnStartup", false); PREFERS_MODS_ROW_VIEW = RegisterWhWz("PrefersModsRowView", true); USE_PATCHES_SYSTEM = RegisterWhWz("UsePatchesSystem", false); - FOCUSED_USER = RegisterWhWz("FavoriteUser", 0, value => (int)(value ?? -1) >= 0 && (int)(value ?? -1) < 4); + FOCUSED_USER = RegisterWhWz("FavoriteUser", 0, value => value >= 0 && value < 4); ENABLE_ANIMATIONS = RegisterWhWz("EnableAnimations", true); TESTING_MODE_ENABLED = RegisterWhWz("TestingModeEnabled", false); SAVED_WINDOW_SCALE = RegisterWhWz("WindowScale", 1.0, SettingValues.IsValidWindowScale); RR_REGION = RegisterWhWz("RR_Region", MarioKartWiiEnums.Regions.None); - WW_LANGUAGE = RegisterWhWz("WW_Language", "en", value => SettingValues.WhWzLanguages.ContainsKey((string)value!)); + WW_LANGUAGE = RegisterWhWz("WW_Language", "en", value => SettingValues.WhWzLanguages.ContainsKey(value)); #endregion #region Dolphin settings - NAND_ROOT_PATH = RegisterDolphin( - ("Dolphin.ini", "General", "NANDRootPath"), - "", - value => _fileSystem.Directory.Exists(value as string ?? string.Empty) - ); + NAND_ROOT_PATH = RegisterDolphin(("Dolphin.ini", "General", "NANDRootPath"), "", value => _fileSystem.Directory.Exists(value)); - LOAD_PATH = RegisterDolphin( - ("Dolphin.ini", "General", "LoadPath"), - "", - value => _fileSystem.Directory.Exists(value as string ?? string.Empty) - ); + LOAD_PATH = RegisterDolphin(("Dolphin.ini", "General", "LoadPath"), "", value => _fileSystem.Directory.Exists(value)); VSYNC = RegisterDolphin(("GFX.ini", "Hardware", "VSync"), false); - INTERNAL_RESOLUTION = RegisterDolphin(("GFX.ini", "Settings", "InternalResolution"), 1, value => (int)(value ?? -1) >= 0); + INTERNAL_RESOLUTION = RegisterDolphin(("GFX.ini", "Settings", "InternalResolution"), 1, value => value >= 0); SHOW_FPS = RegisterDolphin(("GFX.ini", "Settings", "ShowFPS"), false); GFX_BACKEND = RegisterDolphin(("Dolphin.ini", "Core", "GFXBackend"), SettingValues.GFXRenderers.Values.First()); @@ -247,7 +239,7 @@ IUnixCommandService commands _dolphinMsaa = RegisterDolphin( ("GFX.ini", "Settings", "MSAA"), "0x00000001", - value => (value?.ToString() ?? "") is "0x00000001" or "0x00000002" or "0x00000004" or "0x00000008" + value => (value) is "0x00000001" or "0x00000002" or "0x00000004" or "0x00000008" ); // Readonly settings @@ -268,34 +260,26 @@ IUnixCommandService commands #endregion #region Virtual settings - var windowScale = new VirtualSetting( - typeof(double), - value => _internalScale = (double)value!, + var windowScale = new VirtualSetting( + value => _internalScale = value, () => _internalScale == -1.0 ? SAVED_WINDOW_SCALE.Get() : _internalScale ); windowScale.SetValidation(SettingValues.IsValidWindowScale); WINDOW_SCALE = windowScale.SetDependencies(SAVED_WINDOW_SCALE); - RECOMMENDED_SETTINGS = new VirtualSetting( - typeof(bool), + RECOMMENDED_SETTINGS = new VirtualSetting( value => { - void SetDolphinSetting(Setting setting, object newValue) - { - if (!setting.Set(newValue)) - throw setting.SaveError ?? new IOException($"Failed to save Dolphin setting '{setting.Name}'."); - } - - var newValue = (bool)value!; - SetDolphinSetting( + var newValue = value; + SaveRecommended( _dolphinCompilationMode, newValue ? DolphinShaderCompilationMode.HybridUberShaders : DolphinShaderCompilationMode.Default ); #if WINDOWS - SetDolphinSetting(_dolphinCompileShadersAtStart, newValue); + SaveRecommended(_dolphinCompileShadersAtStart, newValue); #endif - SetDolphinSetting(_dolphinMsaa, newValue ? "0x00000002" : "0x00000001"); - SetDolphinSetting(_dolphinSsaa, false); + SaveRecommended(_dolphinMsaa, newValue ? "0x00000002" : "0x00000001"); + SaveRecommended(_dolphinSsaa, false); }, () => { @@ -317,58 +301,64 @@ void SetDolphinSetting(Setting setting, object newValue) } #endregion + private static void SaveRecommended(Setting setting, T value) + { + if (!setting.Set(value)) + throw setting.SaveError ?? new IOException($"Could not save {setting.Name}."); + } + #region Settings Properties - public Setting USER_FOLDER_PATH { get; } - public Setting DOLPHIN_LOCATION { get; } - public Setting GAME_LOCATION { get; } - public Setting FORCE_WIIMOTE { get; } - public Setting LAUNCH_WITH_DOLPHIN { get; } - public Setting LAUNCH_RR_ON_STARTUP { get; } - public Setting ENABLE_RECOMP { get; } - public Setting RECOMP_USE_DOLPHIN_DATA { get; } - public Setting RECOMP_COPY_DOLPHIN_NAND { get; } - public Setting PREFERS_MODS_ROW_VIEW { get; } - public Setting USE_PATCHES_SYSTEM { get; } - public Setting FOCUSED_USER { get; } - public Setting ENABLE_ANIMATIONS { get; } - public Setting TESTING_MODE_ENABLED { get; } - public Setting SAVED_WINDOW_SCALE { get; } - public Setting RR_REGION { get; } - public Setting WW_LANGUAGE { get; } - - public Setting NAND_ROOT_PATH { get; } - public Setting LOAD_PATH { get; } - public Setting VSYNC { get; } - public Setting INTERNAL_RESOLUTION { get; } - public Setting SHOW_FPS { get; } - public Setting GFX_BACKEND { get; } - public Setting MACADDRESS { get; } - public Setting WINDOW_SCALE { get; } - public Setting RECOMMENDED_SETTINGS { get; } - public Setting RECOMP_RESOLUTION_MULTIPLIER { get; } - public Setting RECOMP_GRAPHICS_API { get; } - public Setting RECOMP_SHOW_FPS { get; } - public Setting RECOMP_PREVENT_STUTTERS { get; } - public Setting RECOMP_NAND_ROOT { get; } + public Setting USER_FOLDER_PATH { get; } + public Setting DOLPHIN_LOCATION { get; } + public Setting GAME_LOCATION { get; } + public Setting FORCE_WIIMOTE { get; } + public Setting LAUNCH_WITH_DOLPHIN { get; } + public Setting LAUNCH_RR_ON_STARTUP { get; } + public Setting ENABLE_RECOMP { get; } + public Setting RECOMP_USE_DOLPHIN_DATA { get; } + public Setting RECOMP_COPY_DOLPHIN_NAND { get; } + public Setting PREFERS_MODS_ROW_VIEW { get; } + public Setting USE_PATCHES_SYSTEM { get; } + public Setting FOCUSED_USER { get; } + public Setting ENABLE_ANIMATIONS { get; } + public Setting TESTING_MODE_ENABLED { get; } + public Setting SAVED_WINDOW_SCALE { get; } + public Setting RR_REGION { get; } + public Setting WW_LANGUAGE { get; } + + public Setting NAND_ROOT_PATH { get; } + public Setting LOAD_PATH { get; } + public Setting VSYNC { get; } + public Setting INTERNAL_RESOLUTION { get; } + public Setting SHOW_FPS { get; } + public Setting GFX_BACKEND { get; } + public Setting MACADDRESS { get; } + public Setting WINDOW_SCALE { get; } + public Setting RECOMMENDED_SETTINGS { get; } + public Setting RECOMP_RESOLUTION_MULTIPLIER { get; } + public Setting RECOMP_GRAPHICS_API { get; } + public Setting RECOMP_SHOW_FPS { get; } + public Setting RECOMP_PREVENT_STUTTERS { get; } + public Setting RECOMP_NAND_ROOT { get; } #endregion #region Public API - public T Get(Setting setting) - { - // #todo: make setting keys generic so reading and writing the wrong value type fails at compile time. - var value = setting.Get(); - if (value is not T typedValue) - throw new InvalidOperationException($"Setting '{setting.Name}' does not match expected type '{typeof(T).Name}'."); - - return typedValue; - } + public T Get(Setting setting) => setting.Value; - public bool Set(Setting setting, T value, bool skipSave = false) + public bool Set(Setting setting, T value, bool skipSave = false) { if (value == null) throw new ArgumentNullException(nameof(value)); - return setting.Set(value, skipSave); + var previous = setting.Value; + if (!setting.Set(value, skipSave)) + return false; + if ( + !EqualityComparer.Default.Equals(previous, value) + && (ReferenceEquals(setting, USER_FOLDER_PATH) || ReferenceEquals(setting, DOLPHIN_LOCATION)) + ) + _dolphinSettingManager.ReloadSettings(_dolphinPaths.Resolve(DOLPHIN_LOCATION.Value, USER_FOLDER_PATH.Value).ConfigFolderPath); + return true; } public bool PathsSetupCorrectly() @@ -436,10 +426,9 @@ public void LoadSettings() #endregion #region Registration Helpers - private WhWzSetting RegisterWhWz(string name, T defaultValue, Func? validation = null) + private WhWzSetting RegisterWhWz(string name, T defaultValue, Func? validation = null) { - var setting = new WhWzSetting( - typeof(T), + var setting = new WhWzSetting( name, defaultValue!, setting => _whWzSettingManager.SaveSettings(_fileSystem.Path.Combine(_applicationData.DirectoryPath, "config.json"), setting) @@ -452,10 +441,9 @@ private WhWzSetting RegisterWhWz(string name, T defaultValue, Func((string, string, string) location, T defaultValue, Func? validation = null) + private DolphinSetting RegisterDolphin((string, string, string) location, T defaultValue, Func? validation = null) { - var setting = new DolphinSetting( - typeof(T), + var setting = new DolphinSetting( location, defaultValue!, setting => @@ -472,10 +460,9 @@ private DolphinSetting RegisterDolphin((string, string, string) location, T d return setting; } - private RecompSetting RegisterRecomp((string, string) location, T defaultValue, Func? validation = null) + private RecompSetting RegisterRecomp((string, string) location, T defaultValue, Func? validation = null) { - var setting = new RecompSetting( - typeof(T), + var setting = new RecompSetting( location, defaultValue!, setting => _recompSettingManager.SaveSettings(_recompPaths.ConfigFilePath, setting) diff --git a/WheelWizard/Features/Settings/Types/DolphinSetting.cs b/WheelWizard/Features/Settings/Types/DolphinSetting.cs index ace57169b..c6abcda45 100644 --- a/WheelWizard/Features/Settings/Types/DolphinSetting.cs +++ b/WheelWizard/Features/Settings/Types/DolphinSetting.cs @@ -2,89 +2,69 @@ namespace WheelWizard.Settings.Types; -public class DolphinSetting : Setting +public interface IDolphinSetting { - private readonly Action _saveAction; - - public string FileName { get; private set; } - public string Section { get; private set; } + string Name { get; } + string FileName { get; } + string Section { get; } + string GetStringValue(); + bool SetFromString(string value, bool skipSave = false); + void Reset(bool skipSave = false); +} - public DolphinSetting(Type type, (string, string, string) location, object defaultValue) - : this(type, location, defaultValue, _ => { }) { } +public class DolphinSetting : Setting, IDolphinSetting +{ + private readonly Action? _save; + public string FileName { get; } + public string Section { get; } - public DolphinSetting(Type type, (string, string, string) location, object defaultValue, Action saveAction) - : base(type, location.Item3, defaultValue) + public DolphinSetting((string File, string Section, string Key) location, T defaultValue, Action? saveAction = null) + : base(location.Key, defaultValue) { - _saveAction = saveAction ?? throw new ArgumentNullException(nameof(saveAction)); - FileName = location.Item1; - Section = location.Item2; - // name/key = location.Item3 - - // I rather not translate this message, makes it easier to check where a given error came from - if (!FileName.EndsWith(".ini")) - throw new ArgumentException( - $"FileName for dolphin setting '[{Section}]{Name}' must end with .ini (given file is '{FileName}')" - ); + if (!location.File.EndsWith(".ini", StringComparison.OrdinalIgnoreCase)) + throw new ArgumentException("Dolphin settings must use an .ini file."); + FileName = location.File; + Section = location.Section; + _save = saveAction; } - protected override bool SetInternal(object newValue, bool skipSave = false) + protected override void ApplyValue(bool skipSave) { - var oldValue = Value; - Value = newValue; - var newIsValid = SaveEvenIfNotValid || IsValid(); - if (newIsValid) - { - if (!skipSave) - _saveAction(this); - } - else - Value = oldValue; - - return newIsValid; + if (!skipSave) + _save?.Invoke(this); } - public override object Get() => Value; - - public override bool IsValid() => ValidationFunc == null || ValidationFunc(Value); + public string GetStringValue() => + typeof(T).IsEnum + ? Convert.ToInt32(Value, CultureInfo.InvariantCulture).ToString(CultureInfo.InvariantCulture) + : Convert.ToString(Value, CultureInfo.InvariantCulture) ?? string.Empty; - public new DolphinSetting SetValidation(Func validationFunc) + public bool SetFromString(string value, bool skipSave = false) { - base.SetValidation(validationFunc); - return this; + try + { + // Runtime conversion belongs at the untrusted file boundary, never at a setting call site. + if (typeof(T).IsEnum) + return int.TryParse(value, out var number) + && Enum.IsDefined(typeof(T), number) + && Set((T)Enum.ToObject(typeof(T), number), skipSave); + return Set((T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture), skipSave); + } + catch (Exception exception) when (exception is FormatException or OverflowException or InvalidCastException) + { + return false; + } } - public new DolphinSetting SetForceSave(bool saveEvenIfNotValid) + public new DolphinSetting SetValidation(Func validation) { - base.SetForceSave(saveEvenIfNotValid); + base.SetValidation(validation); return this; } - public string GetStringValue() + public new DolphinSetting SetForceSave(bool enabled) { - if (ValueType.IsEnum) - return ((int)Value).ToString(); - - return Convert.ToString(Value, CultureInfo.InvariantCulture) ?? "null"; - } - - public bool SetFromString(string newValue, bool skipSave = false) - { - // That these are the only types currently supported does not mean that these are all the Dolphin settings types - // feel free to add more types if you find them - return ValueType switch - { - { } t when t == typeof(string) => Set(newValue, skipSave), - { } t when t == typeof(int) => int.TryParse(newValue, CultureInfo.InvariantCulture, out var number) && Set(number, skipSave), - { } t when t == typeof(long) => long.TryParse(newValue, CultureInfo.InvariantCulture, out var number) && Set(number, skipSave), - { } t when t == typeof(float) => float.TryParse(newValue, CultureInfo.InvariantCulture, out var number) - && Set(number, skipSave), - { } t when t == typeof(double) => double.TryParse(newValue, CultureInfo.InvariantCulture, out var number) - && Set(number, skipSave), - { } t when t == typeof(bool) => bool.TryParse(newValue, out var flag) && Set(flag, skipSave), - { IsEnum: true } t => int.TryParse(newValue, out var number) - && Enum.IsDefined(t, number) - && Set(Enum.ToObject(t, number), skipSave), - _ => throw new InvalidOperationException($"Unsupported type: {ValueType.Name}"), - }; + base.SetForceSave(enabled); + return this; } } diff --git a/WheelWizard/Features/Settings/Types/RecompSetting.cs b/WheelWizard/Features/Settings/Types/RecompSetting.cs index f09c72b32..f5a0b3f27 100644 --- a/WheelWizard/Features/Settings/Types/RecompSetting.cs +++ b/WheelWizard/Features/Settings/Types/RecompSetting.cs @@ -8,42 +8,30 @@ namespace WheelWizard.Settings.Types; /// in-game settings bar. Values are formatted exactly the way the runtime's own writer formats /// them: booleans bare and lowercase, strings double-quoted, numbers invariant. /// -public class RecompSetting : Setting +public interface IRecompSetting { - private readonly Action _saveAction; - - public string Section { get; } + string Name { get; } + string Section { get; } + string GetStringValue(); + bool SetFromString(string value, bool skipSave = false); + void Reset(bool skipSave = false); +} - public RecompSetting(Type type, (string Section, string Key) location, object defaultValue, Action saveAction) - : base(type, location.Key, defaultValue) - { - _saveAction = saveAction ?? throw new ArgumentNullException(nameof(saveAction)); - Section = location.Section; - } +public class RecompSetting((string Section, string Key) location, T defaultValue, Action saveAction) + : Setting(location.Key, defaultValue), + IRecompSetting +{ + public string Section { get; } = location.Section; - protected override bool SetInternal(object newValue, bool skipSave = false) + protected override void ApplyValue(bool skipSave) { - var oldValue = Value; - Value = newValue; - var newIsValid = SaveEvenIfNotValid || IsValid(); - if (newIsValid) - { - if (!skipSave) - _saveAction(this); - } - else - Value = oldValue; - - return newIsValid; + if (!skipSave) + saveAction(this); } - public override object Get() => Value; - - public override bool IsValid() => ValidationFunc == null || ValidationFunc(Value); - - public new RecompSetting SetValidation(Func validationFunc) + public new RecompSetting SetValidation(Func validation) { - base.SetValidation(validationFunc); + base.SetValidation(validation); return this; } @@ -70,13 +58,14 @@ public string GetStringValue() => public bool SetFromString(string tomlValue, bool skipSave = false) { var literal = tomlValue.Trim(); - return ValueType switch + return typeof(T) switch { - { } t when t == typeof(string) => Set(Unquote(literal), skipSave), - { } t when t == typeof(bool) => bool.TryParse(literal, out var flag) && Set(flag, skipSave), + { } t when t == typeof(string) => Set((T)(object)Unquote(literal), skipSave), + { } t when t == typeof(bool) => bool.TryParse(literal, out var flag) && Set((T)(object)flag, skipSave), { } t when t == typeof(double) => double.TryParse(literal, NumberStyles.Float, CultureInfo.InvariantCulture, out var number) - && Set(number, skipSave), - _ => throw new InvalidOperationException($"Unsupported type: {ValueType.Name}"), + && double.IsFinite(number) + && Set((T)(object)number, skipSave), + _ => throw new InvalidOperationException($"Unsupported type: {typeof(T).Name}"), }; } diff --git a/WheelWizard/Features/Settings/Types/Setting.cs b/WheelWizard/Features/Settings/Types/Setting.cs index b5b39d0a6..281e3a40a 100644 --- a/WheelWizard/Features/Settings/Types/Setting.cs +++ b/WheelWizard/Features/Settings/Types/Setting.cs @@ -2,60 +2,76 @@ namespace WheelWizard.Settings.Types; -public abstract class Setting +// The non-generic surface is only for change notifications and heterogeneous registries. +public abstract class Setting(string name) { + public string Name { get; } = name; public event Action? Changed; + public Exception? SaveError { get; protected set; } + public abstract bool IsValid(); + public abstract void Reset(bool skipSave = false); - protected Setting(Type type, string name, object defaultValue) + protected void SignalChange() { - Name = name; - DefaultValue = defaultValue; - Value = defaultValue; - ValueType = type; + if (Changed is not { } handlers) + return; + foreach (Action handler in handlers.GetInvocationList()) + { + try + { + handler(this); + } + catch (Exception exception) + { + Trace.TraceError($"A subscriber threw while handling a change to setting '{Name}': {exception}"); + } + } } +} + +public abstract class Setting(string name, T defaultValue) : Setting(name) +{ + public T DefaultValue { get; } = defaultValue; + public T Value { get; protected set; } = defaultValue; + protected bool SaveEvenIfNotValid { get; private set; } + private Func? _validation; - public string Name { get; protected set; } - public object DefaultValue { get; protected set; } - protected object Value { get; set; } - protected Func? ValidationFunc { get; set; } - protected bool SaveEvenIfNotValid { get; set; } - public Type ValueType { get; protected set; } - public Exception? SaveError { get; private set; } + public T Get() => Value; - public bool Set(object newValue, bool skipSave = false) + public bool Set(T newValue, bool skipSave = false) { + ArgumentNullException.ThrowIfNull(newValue); SaveError = null; - if (newValue.GetType() != ValueType) - return false; - - if (Value.Equals(newValue)) + if (EqualityComparer.Default.Equals(Value, newValue)) return true; + if (!SaveEvenIfNotValid && _validation?.Invoke(newValue) == false) + return false; - var previousValue = Value; - bool succeeded; + var previous = Value; + Value = newValue; try { - succeeded = SetInternal(newValue, skipSave); + ApplyValue(skipSave); } catch (Exception exception) when (exception is IOException or UnauthorizedAccessException) { - Value = previousValue; + RestoreValueAfterSaveFailure(previous); SaveError = exception; return false; } - if (succeeded) - SignalChange(); - - return succeeded; + SignalChange(); + return true; } - protected abstract bool SetInternal(object newValue, bool skipSave = false); + protected abstract void ApplyValue(bool skipSave); - public abstract object Get(); + protected virtual void RestoreValueAfterSaveFailure(T previousValue) => Value = previousValue; - public void Reset(bool skipSave = false) + public override bool IsValid() => _validation?.Invoke(Value) ?? true; + + public override void Reset(bool skipSave = false) { - var s = SaveEvenIfNotValid; + var previous = SaveEvenIfNotValid; SaveEvenIfNotValid = true; try { @@ -63,41 +79,19 @@ public void Reset(bool skipSave = false) } finally { - SaveEvenIfNotValid = s; + SaveEvenIfNotValid = previous; } } - public abstract bool IsValid(); - - public Setting SetValidation(Func validationFunc) + public Setting SetValidation(Func validation) { - ValidationFunc = validationFunc; + _validation = validation; return this; } - public Setting SetForceSave(bool saveEvenIfNotValid) + public Setting SetForceSave(bool enabled) { - SaveEvenIfNotValid = saveEvenIfNotValid; + SaveEvenIfNotValid = enabled; return this; } - - protected void SignalChange() - { - var handlers = Changed; - if (handlers is null) - return; - - foreach (Action handler in handlers.GetInvocationList()) - { - try - { - handler(this); - } - catch (Exception exception) - { - // A subscriber failure must not interrupt the mutation or delivery to other subscribers. - Trace.TraceError($"A subscriber threw while handling a change to setting '{Name}': {exception}"); - } - } - } } diff --git a/WheelWizard/Features/Settings/Types/SettingConstants.cs b/WheelWizard/Features/Settings/Types/SettingConstants.cs index ebf3f6473..021d9a90e 100644 --- a/WheelWizard/Features/Settings/Types/SettingConstants.cs +++ b/WheelWizard/Features/Settings/Types/SettingConstants.cs @@ -21,9 +21,9 @@ public static class SettingValues public static readonly double[] WindowScales = [0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.8, 2]; - public static bool IsValidWindowScale(object? value) + public static bool IsValidWindowScale(double scale) { - return value is double scale && scale >= MinWindowScale && scale <= MaxWindowScale; + return scale >= MinWindowScale && scale <= MaxWindowScale; } public static readonly Dictionary GFXRenderers = new() //Display name, value diff --git a/WheelWizard/Features/Settings/Types/VirtualSetting.cs b/WheelWizard/Features/Settings/Types/VirtualSetting.cs index 638b81a76..a6e649b35 100644 --- a/WheelWizard/Features/Settings/Types/VirtualSetting.cs +++ b/WheelWizard/Features/Settings/Types/VirtualSetting.cs @@ -2,41 +2,27 @@ namespace WheelWizard.Settings.Types; -public class VirtualSetting : Setting, IDisposable +public class VirtualSetting : Setting, IDisposable { - private Setting[] _dependencies; - private readonly Action _setter; - private readonly Func _getter; + private Setting[] _dependencies = []; + private readonly Action _setter; + private readonly Func _getter; private bool _acceptsSignals = true; private bool _dependenciesAssigned; - public VirtualSetting(Type type, Action setter, Func getter) - : base(type, "virtual", getter()) + public VirtualSetting(Action setter, Func getter) + : base("virtual", getter()) { - _dependencies = []; _setter = setter; _getter = getter; } - protected override bool SetInternal(object newValue, bool skipSave = false) + protected override void ApplyValue(bool skipSave) { - // we don't use skipSave here since its a virtual setting, and so there is nothing to save _acceptsSignals = false; try { - var oldValue = Value; - Value = newValue; - var newIsValid = SaveEvenIfNotValid || IsValid(); - var succeeded = false; - if (newIsValid) - { - _setter(newValue); - succeeded = true; - } - else - Value = oldValue; - - return succeeded; + _setter(Value); } finally { @@ -44,13 +30,10 @@ protected override bool SetInternal(object newValue, bool skipSave = false) } } - public override object Get() => Value; - - // We dont have to constantly recalculate the value, since if they didn't change, the value is still the same - // and they only change when the dependencies change, or when the users sets a new value - public override bool IsValid() => ValidationFunc == null || ValidationFunc(Value); + // Earlier child saves may have succeeded, so the previous composite value can be stale. + protected override void RestoreValueAfterSaveFailure(T previousValue) => Recalculate(); - public VirtualSetting SetDependencies(params Setting[] dependencies) + public VirtualSetting SetDependencies(params Setting[] dependencies) { // I rather not translate this message, makes it easier to check where a given error came from if (_dependenciesAssigned) diff --git a/WheelWizard/Features/Settings/Types/WhWzSetting.cs b/WheelWizard/Features/Settings/Types/WhWzSetting.cs index 43a4c582e..4186f6448 100644 --- a/WheelWizard/Features/Settings/Types/WhWzSetting.cs +++ b/WheelWizard/Features/Settings/Types/WhWzSetting.cs @@ -2,74 +2,41 @@ namespace WheelWizard.Settings.Types; -public class WhWzSetting : Setting +public interface IWhWzSetting { - private readonly Action _saveAction; - - public WhWzSetting(Type type, string name, object defaultValue) - : this(type, name, defaultValue, _ => { }) { } - - public WhWzSetting(Type type, string name, object defaultValue, Action saveAction) - : base(type, name, defaultValue) - { - _saveAction = saveAction ?? throw new ArgumentNullException(nameof(saveAction)); - } + string Name { get; } + object? GetValue(); + bool SetFromJson(JsonElement value, bool skipSave = false); + void Reset(bool skipSave = false); +} - protected override bool SetInternal(object newValue, bool skipSave = false) +public class WhWzSetting(string name, T defaultValue, Action? saveAction = null) + : Setting(name, defaultValue), + IWhWzSetting +{ + protected override void ApplyValue(bool skipSave) { - var oldValue = Value; - Value = newValue; - var newIsValid = SaveEvenIfNotValid || IsValid(); - if (newIsValid) - { - if (!skipSave) - _saveAction(this); - } - else - Value = oldValue; - - return newIsValid; + if (!skipSave) + saveAction?.Invoke(this); } - public override object Get() => Value; + object? IWhWzSetting.GetValue() => Value; - public override bool IsValid() => ValidationFunc == null || ValidationFunc(Value); - - public new WhWzSetting SetValidation(Func validationFunc) + public bool SetFromJson(JsonElement value, bool skipSave = false) { - base.SetValidation(validationFunc); - return this; + var parsed = value.Deserialize(); + return parsed is not null && (!typeof(T).IsEnum || Enum.IsDefined(typeof(T), parsed)) && Set(parsed, skipSave); } - public new WhWzSetting SetForceSave(bool saveEvenIfNotValid) + public new WhWzSetting SetValidation(Func validation) { - base.SetForceSave(saveEvenIfNotValid); + base.SetValidation(validation); return this; } - public bool SetFromJson(JsonElement newValue, bool skipSave = false) - { - // Feel free to add more types if you find them - return ValueType switch - { - { } t when t == typeof(bool) => Set(newValue.GetBoolean(), skipSave), - { } t when t == typeof(int) => Set(newValue.GetInt32(), skipSave), - { } t when t == typeof(long) => Set(newValue.GetInt64(), skipSave), - { } t when t == typeof(float) => Set((float)newValue.GetDouble(), skipSave), - { } t when t == typeof(double) => Set(newValue.GetDouble(), skipSave), - { } t when t == typeof(string) => Set(newValue.GetString()!, skipSave), - { } t when t == typeof(DateTime) => Set(newValue.GetDateTime(), skipSave), - { IsEnum: true } t => Set(Enum.ToObject(t, newValue.GetInt32()), skipSave), - { IsArray: true } t => SetArray(newValue, t.GetElementType()!, skipSave), - _ => throw new InvalidOperationException($"Unsupported type: {ValueType.Name}"), - }; - } - - private bool SetArray(JsonElement value, Type elementType, bool skipSave = false) + public new WhWzSetting SetForceSave(bool enabled) { - var json = value.GetRawText().Trim('\0'); - var arrayType = Array.CreateInstance(elementType, 0).GetType(); - var array = (Array)JsonSerializer.Deserialize(json, arrayType)!; - return Set(array, skipSave); + base.SetForceSave(enabled); + return this; } } diff --git a/WheelWizard/Features/Settings/WhWzSettingManager.cs b/WheelWizard/Features/Settings/WhWzSettingManager.cs index 1b84198d1..8013ba2b2 100644 --- a/WheelWizard/Features/Settings/WhWzSettingManager.cs +++ b/WheelWizard/Features/Settings/WhWzSettingManager.cs @@ -10,10 +10,10 @@ public class WhWzSettingManager(ILogger logger, IFileSystem private readonly object _sync = new(); private bool _loaded; private Exception? _loadError; - private readonly Dictionary _settings = new(); + private readonly Dictionary _settings = new(); private readonly Dictionary _unknownSettings = new(); - public void RegisterSetting(WhWzSetting setting) + public void RegisterSetting(IWhWzSetting setting) { lock (_sync) { @@ -22,7 +22,7 @@ public void RegisterSetting(WhWzSetting setting) } } - public void SaveSettings(string configPath, WhWzSetting invokingSetting) + public void SaveSettings(string configPath, IWhWzSetting invokingSetting) { lock (_sync) { @@ -33,7 +33,7 @@ public void SaveSettings(string configPath, WhWzSetting invokingSetting) var values = _unknownSettings.ToDictionary(pair => pair.Key, pair => (object?)pair.Value); foreach (var (name, setting) in _settings) - values[name] = setting.Get(); + values[name] = setting.GetValue(); try { diff --git a/WheelWizard/Resources/Languages/cs.yml b/WheelWizard/Resources/Languages/cs.yml index 054493771..2b1fd3433 100644 --- a/WheelWizard/Resources/Languages/cs.yml +++ b/WheelWizard/Resources/Languages/cs.yml @@ -105,9 +105,11 @@ cs: action: play: "Hrát" play_offline: "Hrát Offline" + play_anyway: "Přesto hrát" install: "Nainstalovat" update: "Aktualizovat" import: "Importovat" + clone: "Klonovat" enable_all: "Zapnout Vše" disable_all: "Vypnout Vše" rename: "Přejmenovat" @@ -126,6 +128,7 @@ cs: revert: "Vrátit" maybe_later: "Možná Později" browse: "Prohlížet" + change: "Změnit" view_mod: "Zobrazit Mod" save_external_mii: "Přidat Mii do \"Moje Miiy“" view_mii: "Zobrazit Mii" @@ -134,6 +137,9 @@ cs: unfavorite: "Odebrat z Oblíbených" favorite: "Dát do Oblíbených" view_room: "Zobrazit Místnost" + recomp_nand_copy: "Vytvořit kopii" + recomp_nand_share: "Použít přímo" + recomp_install_offline: "Instalovat pouze offline" link: discord: "Mluvte s námi!" github: "Zdrojový kód" diff --git a/WheelWizard/Resources/Languages/de.yml b/WheelWizard/Resources/Languages/de.yml index b963aefb8..231f22a93 100644 --- a/WheelWizard/Resources/Languages/de.yml +++ b/WheelWizard/Resources/Languages/de.yml @@ -97,6 +97,7 @@ de: gender_male: "Männlich" state: loading: "Lädt..." + loading_leaderboard: "Lade Rangliste..." installing: "Installiert..." updating: "Aktualisieren..." config_not_finished: "Installation nicht abgeschlossen" @@ -113,11 +114,13 @@ de: installed: "Installiert" action: play: "Spielen" + open: "Öffnen" play_offline: "Offline spielen" play_anyway: "Trotzdem spielen" install: "Installieren" update: "Aktualisieren" import: "Importieren" + clone: "Klonen" convert_to_patches: "Zu Patches konvertieren" enable_all: "Alle aktivieren" disable_all: "Alle deaktivieren" @@ -127,7 +130,8 @@ de: edit: "Bearbeiten" cancel: "Beenden" save: "Speichern" - copy_friend_code: "FC kopieren" + copy_friend_code: "Freundescode kopieren" + add_friend: "Freund hinzufügen" export: "Exportieren" apply: "Anwenden" yes: "Ja" @@ -135,11 +139,15 @@ de: keep: "Behalten" ok: "Ok" revert: "Rückgängig" + retry: "Erneut versuchen" + refresh: "Neu laden" maybe_later: "Vielleicht später" browse: "Durchsuchen" + change: "Ändern" view_mod: "Mod ansehen" save_external_mii: "Mii zu \"Meine Miis\" hinzufügen" view_mii: "Mii ansehen" + view_profile: "Profil ansehen" duplicate: "Duplizieren" back: "Zurück" unfavorite: "Entfavorisieren" @@ -147,6 +155,7 @@ de: view_room: "Raum anzeigen" recomp_nand_copy: "Kopie erstellen" recomp_nand_share: "Direkt verwenden" + recomp_install_offline: "Nur offline installieren" link: discord: "Rede mit uns!" github: "Quellcode" @@ -194,6 +203,8 @@ de: modified_archive_member: "Bearbeitete Archiv-Inhalte" new_archive_member: "Neue Archiv-Inhalte" whole_file_override: "Gesamte Datei überschreiben" + top_3_podium: "Top 3 Podium" + live_rwfc: "Live von RWFC" hover: players_online: 0: "Zurzeit sind keine Spieler online" @@ -223,11 +234,14 @@ de: no_rooms_title: "Keine Räume gefunden." no_miis_title: "Noch keine Miis erstellt!" no_miis: "Die Mii-Daten können nicht von deinem System geladen werden. Stelle sicher, dass du dein Spiel mindestens einmal gestartet hast." + no_mii_name: "Unbekannter Spieler" no_mod_selected_title: "Keine Mod ausgewählt." no_mod_selected: "Wähle eine Mod aus der Liste, um mehr Details zu sehen." no_vr_history_title: "Kein R-Punkte Verlauf" no_vr_history: "Ein Freundescode wird benötigt, um den R-Punkte Verlauf zu laden." no_vr_history_range: "Noch kein R-Punkte Verlauf für diese Zeitspanne." + no_leaderboard_title: "Keine Ranglistendaten" + no_leaderboard: "Derzeit sind keine Spieler in der Rangliste verfügbar." time: days: 1: "1 Tag" @@ -248,6 +262,11 @@ de: lifetime: "Insgesamt" lifetime_history: "Gesamter Verlauf" past_n_days_with_range: "Vergangene {$1} Tage ({$2} bis {$3})" + placement: + first: "Champion" + second: "Zweiter Platz" + third: "Dritter Platz" + n: "{$1}." progress: estimated_time_remaining: "Geschätzte verbleibende Zeit:" downloading_mb: "Herunterladen mit {$1} MB" @@ -272,6 +291,7 @@ de: preparing_files_count: "{$1} Datei(en) vorbereiten" installing_recomp: "Installiere WiiCompiled" updating_recomp: "Aktualisiere WiiCompiled" + uninstalling_recomp: "Deinstalliere WiiCompiled" recomp_checking_release: "Prüfe die aktuelle Version von WiiCompiled" recomp_downloading_setup: "Den WiiCompiled Installer herunterladen" recomp_running_setup: "Den WiiCompiled Installer ausführen" @@ -286,9 +306,18 @@ de: recomp_use_dolphin_nand: title: "Sollen die Dolphin-Speicherdaten verwendet werden?" extra: "WiiCompiled kann mit den Speicherdaten, Miis und Freundescodes, die bereits in Dolphin genutzt werden, verwendet werden. Wenn nicht, werden diese Wii-Daten neu angelegt." + recomp_retro_wfc_unavailable: + title: "Retro WFC ist nicht erreichbar" + extra: "WiiCompiled benötigt die Retro-WFC-Server, um Online-Spiele einzurichten, doch diese reagieren momentan nicht. Du kannst jetzt ohne Online-Spiel installieren; WiiCompiled fügt es automatisch hinzu, sobald du das nächste Mal aktualisierst, nachdem die Server wieder verfügbar sind." recomp_nand_mode: title: "Sollen die Dolphin-Speicherdaten direkt verwendet werden?" extra: "Dolphin rät davon ab, andere Programme direkten Zugriff auf die Wii-Daten (NAND) zu geben, da es zu Datenverlusten kommen kann, wenn zwei Programme gleichzeitig darauf schreiben. Durch eine Kopie bleibt WiiCompiled vollständig getrennt, allerdings werden die in einem Programm erzielten Fortschritte nicht im anderen angezeigt." + recomp_share_dolphin_data: + title: "NAND-Daten mit Dolphin teilen?" + extra: "Beim Klonen erhält WiiCompiled die gleichen aktuellen Speicherdaten, Miis und Wii-Einstellungen, behält aber eine getrennte Kopie und ist sicherer. Bei direkter Freigabe schreiben Dolphin und WiiCompiled auf dasselbe NAND, was es beschädigen kann, wenn beide gleichzeitig laufen. Fahre nur fort, wenn du das Risiko verstehst." + recomp_overwrite_dolphin_clone: + title: "Den privaten Dolphin-Klon ersetzen?" + extra: "Dies ersetzt den aktuellen Klon durch die Daten von Dolphin. Speicherdaten, Miis und Wii-Einstellungen, die nur in WiiCompiled geändert wurden, gehen dabei verloren." launch_clear_mods_found: title: "Mods gefunden" extra: "Du bist dabei, das Spiel ohne Mods zu starten. Möchtest du deinen \"My Stuff\"-Ordner leeren?" @@ -297,7 +326,7 @@ de: extra: "Löschen ist permanent und kann nicht rückgängig gemacht werden" title_miis: "Bist du sicher, dass du {$1} Miis löschen möchtest?" apply_scale: - title: "Möchtest du die neue Skala anwenden?" + title: "Möchtest du die neue Skalierung anwenden?" extra: "Diese Änderung wird in {$1} zurückgesetzt, außer du wendest die Änderung an." move_data: title: "Wheel Wizard Daten verschieben?" @@ -357,6 +386,9 @@ de: title: "Das aktuelle Fenster wird sich schließen und ein neues Fenster mit den aktualisierten Spracheinstellungen wird sich öffnen." extra: "Neue Spracheinstellungen anwenden?" message_warning: + recomp_operation_cancelled: + title: "WiiCompiled-Vorgang abgebrochen" + extra: "Der Vorgang wurde vor Abschluss gestoppt. Du kannst ihn jederzeit erneut starten, wenn du bereit bist." not_find_dolphin: extra: "Der Dolphin Emulator konnte nicht gefunden werden. Bitte lege den Pfad in den Einstellungen fest." not_find_game: @@ -455,6 +487,7 @@ de: title: "Mod Download ist fehlgeschlagen." extra: "Während dem Download ist ein Fehler aufgetreten: {$1}" failed_vr_history: "Konnte R-Punkte Verlauf nicht laden." + failed_load_leaderboard: "Die Rangliste konnte nicht geladen werden." message_success: settings_saved: title: "Einstellungen erfolgreich gespeichert!" @@ -478,8 +511,19 @@ de: cant_save_mii: "Mii konnte nicht gespeichert werden." no_mii_export: "Es gab keine Miis zum Exportieren." no_mii_delete: "Es gab keine Miis zum Löschen." + mii_invalid: "Dieser Spieler hat keine gültigen Mii-Daten" + license_invalid: "Ungültige Lizenz ausgewählt." + license_invalid_self: "Wähle eine gültige Lizenz aus, bevor du Freunde hinzufügst." + friend_list_full: "Deine Freundesliste ist voll." + empty_fc: "Der Freundescode darf nicht leer sein." + invalid_fc: "Der Freundescode muss aus exakt 12 Ziffern bestehen." + nonexisting_fc: "Der Freundescode existiert nicht." + add_own_fc: "Du kannst nicht deinen eigenen Freundescode hinzufügen." + add_existing_friend: "Dieser Freund ist bereits in deiner Freundesliste." + no_active_room: "Konnte keinen aktiven Raum mit diesem Spieler finden." snackbar_success: mii_added: "Mii zu deinen Miis hinzugefügt" + friend_added: "{$1} wurde zu deiner Freundesliste hinzugefügt." copied_fc: "Freundescode wurde in die Zwischenablage kopiert" name_change: "Name erfolgreich zu '{$1}' geändert" saved_mii: "Mii '{$1}' wurde als Datei '{$2}' gespeichert" @@ -527,6 +571,10 @@ de: force_disable_wiimote: "Wiimote ausschalten erzwingen" launch_with_dolphin: "Starte das Spiel zusammen mit dem Dolphin-Fenster" launch_rr_on_startup: "Starte Retro Rewind beim Starten von Wheel Wizard" + retro_rewind_installation: "Retro Rewind-Installation" + save_folder: "Speicherdaten-Ordner" + installation_folder: "Installationsordner" + recomp_installation: "WiiCompiled-Installation" v_sync: "VSync" recommended: "Empfohlene Einstellungen" show_fps: "FPS anzeigen" @@ -534,6 +582,7 @@ de: window_scale: "Fenstergröße" open_config: "Konfigurationsordner öffnen" wheel_wizard_data_folder: "Wheel Wizard-Datenordner" + retro_rewind_folder: "Retro Rewind-Ordner" reset_data_folder: "Standard-Speicherort verwenden" dolphin_user_path: "Dolphin Benutzer-Ordner" mario_kart_game: "Mario Kart Wii Spiel-Datei" @@ -545,12 +594,15 @@ de: open_save_folder: "Speicherdatenordner öffnen" enable_recomp: "Aktiviere WiiCompiled (Beta)" recomp_use_dolphin_data: "Verwende Dolphin-Speicherdaten" + recomp_share_dolphin_data: "NAND-Daten mit Dolphin teilen" + recomp_clone_dolphin_data: "Dolphin-NAND klonen" recomp_resolution: "Auflösung" recomp_graphics_api: "Grafik-API" recomp_prevent_stutters: "Verhindere Ruckler" section: graphics: "Grafik" general: "Allgemein" + retro_rewind: "Retro Rewind" language: "Sprache" wii: "Wii-Einstellungen" resolution: "Auflösung" @@ -559,6 +611,7 @@ de: location_paths: "Dateispeicherort-Einstellungen" wh_wz_appearance: "Wheel Wizard Aussehen" installation: "Installation" + setup: "Einrichtung" recomp: "WiiCompiled (Beta)" recomp_dolphin_data: "Dolphin-Daten" recomp_video: "Video" @@ -567,16 +620,27 @@ de: location_warning: "Einstellungen sind deaktiviert, vervollständige erst die Dateispeicherorte." enable_recomp: "Verwende WiiCompiled, um das Spiel zu starten. Ist diese Option deaktiviert, wird Dolphin und Retro Rewind verwendet. Nur auf Windows." recomp_use_dolphin_data: "Verwende Dolphin's \"Wii\"-Ordner, damit WiiCompiled Speicherdaten, Miis und Wii-Einstellungen von Dolphin verwenden kann. Die Änderung wird beim nächsten Start von WiiCompiled übernommen." + recomp_share_dolphin_data: "WiiCompiled und Dolphin verwenden dieselben Speicherdaten, Miis und Wii-Einstellungen." + recomp_shared_nand_warning: "Starte Dolphin und WiiCompiled nicht gleichzeitig, während NAND-Daten geteilt werden." + recomp_clone_dolphin_data: "Kopiert Dolphins aktuelle Speicherdaten, Miis und Wii-Einstellungen in private WiiCompiled-Daten." recomp_graphics_api: "WiiCompiled funktioniert nur mit DirectX 12 und Vulkan zuverlässig, deswegen werden keine anderen Optionen angeboten." recomp_dolphin_data_not_found: "Setze zuerst einen existierenden Dolphin-Benutzerordner in den Wheel Wizard-Einstellungen. Der Ordner muss einen \"Wii\"-Ordner enthalten." recomp_prevent_stutters: "Während ein Shader noch kompiliert wird, werden die Objekte, die ihn benötigen, für einige Frames übersprungen, anstatt dass das gesamte Spiel ins Stocken gerät, bis er fertig ist. So kommt es zu einem winzigen Pop-in statt zu Rucklern." launch_with_dolphin: "Öffne Dolphins Hauptfenster zusammen mit dem Spiel." + launch_rr_on_startup: "Startet Retro Rewind automatisch, wenn Wheel Wizard gestartet wird" + retro_rewind_version: "Installierte Retro Rewind Version: {$1}" + installed_version: "Installierte Version: {$1}" force_disable_wiimote: "Diese Einstellung deaktiviert die Wiimote im Spiel, sie kann aber weiterhin für den Wii-Kanal verwendet werden." resolution_scale: "1x ist die native Auflösung" + renderer: "Wählt das Grafik-Backend aus, das Dolphin zum Rendern des Spiels verwendet." + v_sync: "Synchronisiert die Bildrate des Spiels mit deinem Bildschirm, um Screen Tearing zu verhindern." recommended: "Diese Einstellung wird bestimmte Dolphin-Einstellungen verändern, um Ruckeln und Lagspikes zu vermindern." 30_fps: "Diese Einstellung erzwingt, dass das Spiel mit 30 FPS läuft (Standard ist 60)." wheel_wizard_data_folder: "Wähle aus, wo Wheel Wizard seine Dateien speichert. Das Verschieben könnte eine Weile dauern." + retro_rewind_folder: "Öffnet den Ordner mit den installierten Retro Rewind-Dateien." end_with_exe: "Dateipfad muss mit .exe enden." + select_dolphin_linux: "Wähle eine Dolphin-Programmdatei aus oder gib einen Startbefehl ein" + select_dolphin_macos: "Wähle Dolphin.app aus" enable_animations: "Dies aktiviert die Animationen in der Wheel Wizard App." end_with_x: "Pfad kann enden mit:" wh_wz_language: "Dies ändert nur die Sprache innerhalb von Wheel Wizard. Um die Sprache innerhalb des Spiels anzupassen, musst du das in den Spieleinstellungen ändern." @@ -590,6 +654,9 @@ de: general: "Allgemein" video: "Video" status: + recomp_dolphin_clone_available: "Privater Klon: {$1}" + recomp_dolphin_clone_missing: "Es wurde noch kein privater Klon erstellt." + recomp_dolphin_data_cloned: "Dolphin-Daten für WiiCompiled geklont" recomp_dolphin_data_not_linked: "Nicht verknüpft. WiiCompiled wird seine privaten Wii-Daten nutzen." recomp_dolphin_data_linked: "Verwendet verknüpfte Dolphin-Daten von {$1}" recomp_dolphin_data_copied: "Verwendet eine Kopie der Dolphin-Daten von {$1}" @@ -617,5 +684,6 @@ de: czech: "Tschechisch" norwegian: "Norwegisch" chinese: "Chinesisch" + polish: "Polnisch" portuguese: "Portugisisch" - z_translators: "Weebo, kytronix, Gab, ToadetteHackFan, TheJanzap" + z_translators: "Weebo, kytronix, Gab, ToadetteHackFan, TheJanzap, Creeperkatze" diff --git a/WheelWizard/Resources/Languages/en.yml b/WheelWizard/Resources/Languages/en.yml index 3d910eb9b..0cfce8cd2 100644 --- a/WheelWizard/Resources/Languages/en.yml +++ b/WheelWizard/Resources/Languages/en.yml @@ -97,6 +97,7 @@ en: gender_male: "Male" state: loading: "Loading..." + loading_leaderboard: "Loading leaderboard..." installing: "Installing..." updating: "Updating..." config_not_finished: "Config Not Finished" @@ -132,12 +133,15 @@ en: cancel: "Cancel" save: "Save" copy_friend_code: "Copy Friend Code" + add_friend: "Add Friend" export: "Export" apply: "Apply" yes: "Yes" no: "No" keep: "Keep" ok: "Ok" + retry: "Retry" + refresh: "Refresh" revert: "Revert" maybe_later: "Maybe Later" browse: "Browse" @@ -145,6 +149,7 @@ en: view_mod: "View Mod" save_external_mii: "Add Mii to \"My Miis\"" view_mii: "View Mii" + view_profile: "View Profile" duplicate: "Duplicate" back: "Back" unfavorite: "Unfavorite" @@ -200,6 +205,8 @@ en: modified_archive_member: "Modified archive member" new_archive_member: "New archive member" whole_file_override: "Whole-file override" + top_3_podium: "Top 3 Podium" + live_rwfc: "Live from RWFC" hover: players_online: 0: "There are currently no players online" @@ -228,12 +235,15 @@ en: no_rooms: "You might not have internet connection, or no-one might be playing." no_rooms_title: "No rooms found" no_miis_title: "No Miis yet!" + no_mii_name: "Unknown Player" no_miis: "We can't read the Mii data from your system. Make sure you started the game at least once" no_mod_selected_title: "No mod selected" no_mod_selected: "Select a mod in the list to view its details" no_vr_history_title: "No VR history" no_vr_history: "Friend code is required to load VR history." no_vr_history_range: "No VR history found for this range yet." + no_leaderboard_title: "No leaderboard data" + no_leaderboard: "There are no ranked players available right now." time: days: 1: "1 day" @@ -254,6 +264,11 @@ en: lifetime: "Lifetime" lifetime_history: "Lifetime history" past_n_days_with_range: "Past {$1} days ({$2} to {$3})" + placement: + first: "Champion" + second: "2nd Place" + third: "3rd Place" + n: "#{$1}" progress: estimated_time_remaining: "Estimated time remaining:" downloading_mb: "downloading {$1} MB" @@ -474,6 +489,7 @@ en: title: "Mod download failed." extra: "An error occurred during download: {$1}" failed_vr_history: "Failed to load VR history" + failed_load_leaderboard: "Could not load leaderboard." message_success: settings_saved: title: "Settings saved successfully!" @@ -497,8 +513,19 @@ en: cant_save_mii: "Can't Save Mii" no_mii_export: "It seems there where no Miis to export" no_mii_delete: "It seems there where no Miis to delete" + mii_invalid: "This player has no valid Mii data." + license_invalid: "Invalid license selected." + license_invalid_self: "Select a valid license before adding friends." + friend_list_full: "Your friend list is full." + empty_fc: "Friend code cannot be empty." + invalid_fc: "Friend code must be exactly 12 digits." + nonexisting_fc: "Nonexisting friend code." + add_own_fc: "You cannot add your own friend code." + add_existing_friend: "This friend is already in your list." + no_active_room: "Could not find an active room for this player." snackbar_success: mii_added: "Mii has been added to your Miis" + friend_added: "Added {$1} to your friend list." copied_fc: "Copied friend code to clipboard" name_change: "Successfully changed name to '{$1}'" saved_mii: "Saved Mii '{$1}' to file '{$2}'" diff --git a/WheelWizard/Resources/Languages/es.yml b/WheelWizard/Resources/Languages/es.yml index 9370620e4..9af5cf3dc 100644 --- a/WheelWizard/Resources/Languages/es.yml +++ b/WheelWizard/Resources/Languages/es.yml @@ -4,7 +4,7 @@ es: settings: "Ajustes" mods: "My Stuff" patches: "Parches" - my_profiles: "Mis perfiles" + my_profiles: "Mis Perfiles" rooms: "Salas" friends: "Amigos" leaderboard: "Ranking" @@ -30,6 +30,10 @@ es: time_online: "Tiempo en línea" message: "Mensaje" is_online: "Está Online" + vr_history: "Historial de PC" + start_vr: "PC Inicial" + end_vr: "PC Final" + net_change: "Variación Neta" mii: gender_male: "Hombre" gender: "Género" @@ -39,7 +43,7 @@ es: nose_type: "Tipo de Nariz" horizontal_position: "Posición Horizontal (Izq/Der)" mouth_type: "Tipo de Boca" - hair_color: "Color del Pelo" + hair_color: "Color de Pelo" hair_type: "Tipo de Pelo" mirror_hair: "Voltear Pelo" glasses_type: "Tipo de Gafas" @@ -53,7 +57,7 @@ es: head_shape: "Forma de la Cabeza" facial_feature: "Rasgo Facial" irish_color: "Color de los Ojos" - space_between: "Espacio intermedio" + space_between: "Espacio Intermedio" rotation: "Rotación (Rotar Izquierda/Derecha)" eyebrow_type: "Tipo de Cejas" mustache_type: "Tipo de Bigote" @@ -63,7 +67,9 @@ es: default_name: "Nuevo Mii" status: "Estado" priority: "Prioridad" - sort_by: "Ordernar por" + sort_by: "Ordenar por" + match: "Partida" + enable_matches: "Partidas" total_games_won: "Partidas ganadas" total_games_played: "Partidas jugadas" vr_abbreviation: "PC" @@ -107,11 +113,13 @@ es: installed: "Instalado" action: play: "Jugar" + open: "Abrir" play_offline: "Jugar sin internet" play_anyway: "Jugar de todos modos" install: "Instalar" update: "Actualizar" import: "Importar" + clone: "Clonar" convert_to_patches: "Convertir a parches" enable_all: "Activar todo" disable_all: "Desactivar todo" @@ -131,6 +139,7 @@ es: revert: "Deshacer" maybe_later: "Quizás más tarde" browse: "Buscar" + change: "Cambiar" view_mod: "Ver Mod" save_external_mii: "Añadir Mii a \"Mis Miis\"" view_mii: "Ver Mii" @@ -141,6 +150,7 @@ es: view_room: "Ver sala" recomp_nand_copy: "Crear una copia" recomp_nand_share: "Usar directamente" + recomp_install_offline: "Instalar modo sin internet" link: discord: "¡Habla con nosotros!" github: "Código fuente" @@ -173,7 +183,7 @@ es: y {$2} thanks_translators: "Muchas gracias a todos los traductores:" wh_wz_translation_percentage: "La traducción de este idioma está a un {$1}%." - powered_gamebanana: "Impulsado por GameBanana" + powered_gamebanana: "Habilitado por GameBanana" language_translated_by: "Este idioma está traducido por: {$1}" brsar_entry: "{$1} entrada {$2}" brsar_entry_with_rwar: "{$1} entrada {$2} + RWAR" @@ -213,6 +223,9 @@ es: no_miis: "No podemos leer la información de Miis de tu sistema. Asegúrate que hayas lanzado el juego al menos una vez." no_mod_selected_title: "Ningún mod seleccionado" no_mod_selected: "Selecciona un mod de la lista para ver sus detalles" + no_vr_history_title: "Sin historial de PC" + no_vr_history: "Se requiere clave de amigo para cargar el historial de PC." + no_vr_history_range: "No se encontró historial de PC para este rango todavía." time: days: 1: "1 día" @@ -226,6 +239,13 @@ es: seconds: 1: "1 segundo" n: "{$1} segundos" + last_24_hours: "Últimas 24 horas" + last_7_days: "Últimos 7 días" + last_30_days: "Últimos 30 días" + last_60_days: "Últimos 60 días" + lifetime: "Desde siempre" + lifetime_history: "Historial completo" + past_n_days_with_range: "Pasados {$1} días ({$2} a {$3})" progress: estimated_time_remaining: "Tiempo restante estimado:" downloading_mb: "descargando {$1} MB" @@ -250,6 +270,7 @@ es: preparing_files_count: "Preparando {$1} archivo(s)" installing_recomp: "Instalando WiiCompiled" updating_recomp: "Actualizando WiiCompiled" + uninstalling_recomp: "Desinstalando WiiCompiled" recomp_checking_release: "Buscando la versión más reciente de WiiCompiled" recomp_downloading_setup: "Descargando el instalador de WiiCompiled" recomp_running_setup: "Ejecutando el instalador de WiiCompiled" @@ -297,9 +318,18 @@ es: recomp_use_dolphin_nand: title: "¿Usar tus datos de guardado de Dolphin?" extra: "WiiCompiled puede jugar con tus datos guardados, Miis y claves de amigo que ya hayas usado en Dolphin. En caso contrario, empezará de cero con sus propios datos de Wii." + recomp_retro_wfc_unavailable: + title: "No se ha podido contactar con Retro WFC." + extra: "WiiCompiled necesita los servidores de Retro WFC para preparar juegos en línea, y en estos momentos no responden. Puedes instalarlo ahora en modo sin internet; WiiCompiled los habilitará automáticamente la próxima vez que actualices una vez se restauren los servidores." recomp_nand_mode: title: "¿Copiar tus datos de Dolphin, o usarlos directamente?" extra: "Dolphin recomienda en contra de que otros programas usen sus datos de Wii (NAND) directamente, ya que dos programas escribiendo a la vez pueden dañar los datos. Por ello, una copia mantiene separado completamente a WiiCompiled, pero el progreso realizado en uno no aparecerá en el otro." + recomp_share_dolphin_data: + title: "¿Compartir datos NAND con Dolphin?" + extra: "Clonar otorga a WiiCompiled los datos de guardado, Miis y ajustes de Wii actuales, pero mantiene su copia separada y es más seguro. El compartir directamente permite que Dolphin y WiiCompiled escriban a la misma NAND, lo cual puede causar que se dañen los datos si ambos se ejecutan a la vez. Continúa solamente si comprendes el riesgo." + recomp_overwrite_dolphin_clone: + title: "¿Reemplazar el clonado privado de Dolphin?" + extra: "Esto reemplaza el clonado actual con los datos de Dolphin. Se perderán los guardados, Miis y ajustes de Wii que se hayan cambiado solamente en WiiCompiled." move_data: title: "¿Mover datos de Wheel Wizard?" extra: |- @@ -335,6 +365,9 @@ es: title: "Esto cerrará la ventana actual y abrirá una nueva con la nueva configuración de idioma." extra: "¿Deseas aplicar la nueva configuración de idioma?" message_warning: + recomp_operation_cancelled: + title: "Operación de WiiCompiled cancelada" + extra: "La operación fue interrumpida antes de terminar. Puedes reiniciarla cuando estés listo." not_find_dolphin: extra: "No se encontró el Emulador Dolphin. Por favor selecciona la ruta en ajustes." not_find_game: @@ -399,7 +432,7 @@ es: search_mod: "Buscar mods..." enter_mii_name: "Introducir nombre de Mii..." enter_text_here: "Introducir texto aquí..." - search_for_players: "Buscando jugadores..." + search_for_players: "Buscar jugadores..." enter_path: "Introducir la ruta deseada..." message_error: no_mod_folder: @@ -432,6 +465,7 @@ es: mod_download_fail: title: "Se produjo un error al descargar el mod." extra: "Se produjo un error durante la descarga: {$1}" + failed_vr_history: "Se produjo un error al cargar el historial de PC" message_success: settings_saved: title: "Se guardaron correctamente los ajustes." @@ -484,6 +518,9 @@ es: launch_with_dolphin: "Lanzar juego con la ventana de Dolphin" launch_rr_on_startup: "Lanzar Retro Rewind al iniciar" retro_rewind_installation: "Instalación de Retro Rewind" + save_folder: "Carpeta de Guardados" + installation_folder: "Carpeta de Instalación" + recomp_installation: "Instalación de WiiCompiled" v_sync: "VSync" recommended: "Ajustes recomendados" show_fps: "Mostrar FPS" @@ -491,6 +528,7 @@ es: window_scale: "Escala de la ventana" open_config: "Abrir carpeta de configuraciones" wheel_wizard_data_folder: "Carpeta de datos de Wheel Wizard" + retro_rewind_folder: "Carpeta de Retro Rewind" reset_data_folder: "Usar ubicación por defecto" dolphin_user_path: "Carpeta de Usuario de Dolphin" mario_kart_game: "Archivo del juego Mario Kart Wii" @@ -502,6 +540,8 @@ es: open_save_folder: "Abrir carpeta de archivos de juego guardados" enable_recomp: "Habilitar WiiCompiled (beta)" recomp_use_dolphin_data: "Compartir datos de guardado de Dolphin" + recomp_share_dolphin_data: "Compartir datos NAND con Dolphin" + recomp_clone_dolphin_data: "Clonar NAND de Dolphin" recomp_resolution: "Resolución" recomp_graphics_api: "API de Gráficos" recomp_prevent_stutters: "Prevenir caídas de FPS (stuttering)" @@ -517,6 +557,7 @@ es: location_paths: "Ajustes de Ubicación" wh_wz_appearance: "Apariencia de Wheel Wizard" installation: "Instalación" + setup: "Configuración" recomp: "WiiCompiled (beta)" recomp_dolphin_data: "Datos de Dolphin" recomp_video: "Vídeo" @@ -525,12 +566,16 @@ es: location_warning: "Los ajustes están desactivados. Completa antes los ajustes de ubicación." enable_recomp: "Utiliza WiiCompiled como lanzador de inicio. Desactiva esto para utilizar Dolphin y Retro Rewind en su lugar. Solo para Windows." recomp_use_dolphin_data: "Utiliza la carpeta Wii de Dolphin ya existente en su lugar para que WiiCompiled comparta guardados, Miis y ajustes de Wii. El cambio surtirá en efecto la próxima vez que se lance WiiCompiled." + recomp_share_dolphin_data: "WiiCompiled y Dolphin utilizan los mismos datos guardados, Miis y ajustes de Wii." + recomp_shared_nand_warning: "No ejecutes Dolphin y WiiCompiled a la vez mientras se compartan datos NAND." + recomp_clone_dolphin_data: "Copia los datos guardados, Miis y ajustes de Wii actuales de Dolphin a los datos privados de WiiCompiled." recomp_graphics_api: "Solo DirectX 12 y Vulkan ejecutan WiiCompiled de manera fiable. Por ende, no se ofrecen más opciones." recomp_dolphin_data_not_found: "Indica antes en los Ajustes de Ubicación una Carpeta de Usuario Dolphin que ya exista. Es necesario que contenga una carpeta Wii." recomp_prevent_stutters: "Mientras un sombreador se compila, los objetos que lo requieren son omitidos por unos pocos fotogramas, en vez de que se quede el juego estancado hasta que esté listo. Recibirás pequeñas apariciones repentinas en vez de caídas de FPS (stuttering)." launch_with_dolphin: "Se lanzará la ventana principal de Dolphin junto con el juego" launch_rr_on_startup: "Lanza automáticamente Retro Rewind cuando se inicia Wheel Wizard" retro_rewind_version: "Versión instalada: {$1}" + installed_version: "Versión instalada: {$1}" force_disable_wiimote: "Este ajuste desactiva el Wiimote en juego, pero lo activa para el Canal Wii" resolution_scale: "1x es la resolución nativa" renderer: "Selecciona el backend de gráficos que utiliza Dolphin para renderizar el juego." @@ -538,7 +583,10 @@ es: recommended: "Este ajuste cambiará ciertos ajustes de Dolphin para reducir caídas de FPS (stuttering) y picos de lag." 30_fps: "Este ajuste forzará al juego a ejecutarse a 30 FPS (por defecto es 60)" wheel_wizard_data_folder: "Indica dónde almacena Wheel Wizard sus archivos. Puede tardar un rato en moverlos." + retro_rewind_folder: "Abre la carpeta que contiene los archivos de Retro Rewind instalados." end_with_exe: "La ruta debe terminar en .exe" + select_dolphin_linux: "Selecciona el ejecutable Dolphin o introduce un comando de lanzamiento" + select_dolphin_macos: "Selecciona Dolphin.app" enable_animations: "Esto activa las animaciones en la aplicación Wheel Wizard" end_with_x: "Las rutas pueden acabar en:" wh_wz_language: "Cambia el idioma usado por Wheel Wizard, no el idioma del juego." @@ -599,6 +647,9 @@ es: unsupported_loose_override_extension: "{$1} utiliza una extensión de sobreescritura suelta sin soportar (.kcl, .kmp, .slt)." whole_file_baseline: "Este SZS se almacena como base. Exportando una sobreescritura completa." status: + recomp_dolphin_clone_available: "Clonado privado: {$1}" + recomp_dolphin_clone_missing: "No se ha creado ningún clonado privado." + recomp_dolphin_data_cloned: "Datos de Dolphin clonados para WiiCompiled" recomp_dolphin_data_not_linked: "Sin vincular. WiiCompiled utilizará sus datos de Wii privados." recomp_dolphin_data_linked: "Compartiendo datos de Dolphin desde {$1}" recomp_dolphin_data_copied: "Usando una copia de tus datos de Dolphin en {$1}" diff --git a/WheelWizard/Resources/Languages/fi.yml b/WheelWizard/Resources/Languages/fi.yml index e7dedb58b..f30f4c1e0 100644 --- a/WheelWizard/Resources/Languages/fi.yml +++ b/WheelWizard/Resources/Languages/fi.yml @@ -27,9 +27,13 @@ fi: races_played: "Kisoja pelattu" friend_code: "Kaverikoodi" player_count: "Pelaajia" - time_online: "Aika online-tilassa" + time_online: "Aika verkossa" message: "Viesti" - is_online: "Online-tilassa" + is_online: "Paikalla" + vr_history: "KP-historia" + start_vr: "KP alussa" + end_vr: "KP lopussa" + net_change: "Nettomuutos" mii: gender_male: "Mies" gender: "Sukupuoli" @@ -64,6 +68,8 @@ fi: status: "Status" priority: "Prioriteetti" sort_by: "Järjestä" + match: "Ottelu" + enable_matches: "Ottelua" total_games_won: "Kisoja voitettu yhteensä" total_games_played: "Kisoja pelattu yhteensä" vr_abbreviation: "KP" @@ -107,10 +113,13 @@ fi: installed: "Asennettu" action: play: "Pelaa" + open: "Avoin" play_offline: "Pelaa offline-tilassa" + play_anyway: "Pelaa silti" install: "Asenna" update: "Päivitä" import: "Tuo" + clone: "Kloonaa" convert_to_patches: "Muunna paikkauksiksi" enable_all: "Ota kaikki käyttöön" disable_all: "Poista kaikki" @@ -130,6 +139,7 @@ fi: revert: "Palauta" maybe_later: "Myöhemmin" browse: "Selaa" + change: "Muuta" view_mod: "Näytä modi" save_external_mii: "Lisää Mii omiin Mii-hahmoihin" view_mii: "Näytä Mii" @@ -138,6 +148,9 @@ fi: unfavorite: "Poista suosikeista" favorite: "Suosikki" view_room: "Näytä huone" + recomp_nand_copy: "Luo kopio" + recomp_nand_share: "Käytä suoraan" + recomp_install_offline: "Asenna vain offline-tilassa" link: discord: "Puhu meille!" github: "Lähdekoodi" @@ -180,19 +193,22 @@ fi: language_translated_by: "Tämän kielen käännökset tarjoaa: {$1}" external_reference: "Ulkoinen viite" external_reference_with_path: "Ulkoinen viite: {$1}" + modified_archive_member: "Muokattu arkiston jäsen" + new_archive_member: "Uusi arkiston jäsen" + whole_file_override: "Koko tiedoston ohitus" hover: players_online: - 0: "Ei pelaajia online-tilassa" - 1: "1 pelaaja on online-tilassa" - n: "{$1} pelaajaa on online-tilassa" + 0: "Ei pelaajia paikalla" + 1: "1 pelaaja on paikalla" + n: "{$1} pelaajaa on paikalla" rooms_online: 0: "Ei aktiivisia huoneita" 1: "1 aktiivinen huone" n: "{$1} aktiivista huonetta" friends_online: - 0: "Ei kavereita online-tilassa" - 1: "1 kaveri on online-tilassa" - n: "{$1} kaveria on online-tilassa" + 0: "Ei kavereita paikalla" + 1: "1 kaveri on paikalla" + n: "{$1} kaveria on paikalla" rooms_page_disclaimer: |- Huomioi, että tältä sivulta ei voi liittyä huoneeseen suoraan. Liittyäksesi tiettyyn huoneeseen, se tulee tehdä joko kaverin kautta tai toivoa että pääset siihen liittymällä verkkopeliin. @@ -211,6 +227,9 @@ fi: no_miis: "Mii-tietojen lukeminen järjestelmältäsi epäonnistui. Varmista, että olet käynnistänyt pelin ainakin kerran." no_mod_selected_title: "Ei modia valittuna" no_mod_selected: "Valitse modi listalta nähdäksesi sen tiedot" + no_vr_history_title: "Ei KP-historiaa" + no_vr_history: "Kaverikoodi vaaditaan KP-historian lataamiseen." + no_vr_history_range: "Tällä aikavälillä ei löydy KP-historiaa." time: days: 1: "1 pv" @@ -224,6 +243,13 @@ fi: seconds: 1: "1 s" n: "{$1} s" + last_24_hours: "Viimeiset 24 tuntia" + last_7_days: "Viimeiset 7 päivää" + last_30_days: "Viimeiset 30 päivää" + last_60_days: "Viimeiset 60 päivää" + lifetime: "Koko ajalta" + lifetime_history: "Koko ajan historia" + past_n_days_with_range: "Viimeiset {$1} päivää ({$2}–{$3})" progress: estimated_time_remaining: "Arvioitu kestoaika:" downloading_mb: "Ladataan {$1} Mt" @@ -236,6 +262,7 @@ fi: installing_rr: "Asennetaan Retro Rewindia" installing_rr_first_time: "Asennetaan Retro Rewindia ensimmäistä kertaa" installing_dolphin: "Asennetaan Dolphin-emulaattoria" + updating_dolphin: "Päivitetään Dolphin-emulaattoria" this_may_take_a_while: "Tässä voi kestää jonkin aikaa riippuen internet-yhteydestäsi." applying_converted_mod: "Sovelletaan muunnettu modi" combining_files: "Yhdistetään tiedostoja" @@ -245,7 +272,35 @@ fi: downloading_mod: "Ladataan {$1}" installing_mod: "Asennetaan modia" preparing_files_count: "Valmistellaan {$1} tiedostoa" + installing_recomp: "Asennetaan WiiCompiled" + updating_recomp: "Päivitetään WiiCompiled" + uninstalling_recomp: "Poistetaan WiiCompiled-asennusta" + recomp_checking_release: "Etsitään uusinta WiiCompiled-versiota" + recomp_downloading_setup: "Ladataan WiiCompiledin asennusohjelmaa" + recomp_running_setup: "Käynnistetään WiiCompiledin asennusohjelmaa" + recomp_finished: "WiiCompiled on valmis pelattavaksi" + recomp_copying_nand: "Kopioidaan Dolphin-dataa" question: + recomp_uninstall: + title: "Poistetaanko WiiCompiledin asennus?" + extra_shared: "Poistaa WiiCompiledin asennuksen. Tallennuksesi säilyy Dolphinin Wii-kansiossa, ja Retro Rewind pysyy asennettuna." + extra_private: "Poistaa WiiCompiledin asennuksen sekä sen yksityiset Wii-tallennustiedot. Retro Rewind pysyy asennettuna." + extra_copy: "Poistaa WiiCompiledin asennuksen sekä sen kopiot Dolphin-datastasi. Alkuperäinen Dolphin-data pysyy muuttumattomana, ja Retro Rewind pysyy asennettuna." + recomp_use_dolphin_nand: + title: "Käytetäänkö Dolphin-tallennustietoja?" + extra: "WiiCompiled voi käyttää tallennuksia, Miitä ja kaverikoodeja, jotka ovat jo käytössä Dolphinissa. Jos niitä ei ole, se aloittaa alusta omalla Wii-datallaan." + recomp_retro_wfc_unavailable: + title: "Retro WFC:hen ei saada yhteyttä" + extra: "WiiCompiled vaatii Retro WFC -palvelimia verkkopelaamista varten, ja ne eivät vastaa juuri nyt. Voit asentaa ilman verkkopeliä nyt; WiiCompiled lisää sen automaattisesti seuraavalla päivityskerralla, kun palvelimet ovat taas toiminnassa." + recomp_nand_mode: + title: "Kopioidaanko Dolphin-data vai käytetäänkö niitä suoraan?" + extra: "Dolphin suosittelee, etteivät muut ohjelmat käytä Wii-dataa (NAND) suoraan, koska kaksi samaan aikaan siihen kirjoittavaa ohjelmaa voi vioittaa sen. Kopio pitää WiiCompiledin täysin erillään, mutta toisessa tehdyt edistymiset eivät näy toisessa." + recomp_share_dolphin_data: + title: "Jaetaanko NAND-data Dolphinin kanssa?" + extra: "Kloonaus antaa WiiCompiledille samat nykyiset tallennukset, Miit ja Wii-asetukset, mutta pitää kopion erillään ja on turvallisempi. Suora jakaminen antaa Dolphinille ja WiiCompiledille mahdollisuuden kirjoittaa samaan NAND-muistiin, mikä voi vioittaa sitä, jos molemmat suorittavat samanaikaisesti. Jatka vain, jos ymmärrät riskin." + recomp_overwrite_dolphin_clone: + title: "Korvataanko yksityinen Dolphin-klooni?" + extra: "Korvaa nykyisen kloonin Dolphinin tiedoilla. Vain WiiCompiledissa muutetut tallennukset, Miit ja Wii-asetukset menetetään." launch_clear_mods_found: title: "Modeja löytyi" extra: |- @@ -267,8 +322,19 @@ fi: dolphin_found: title: "Dolphin-emulaattorin kansio löytyi" extra: |- - Jos et tiedä, mitä kaikki tarkoittaa, paina vain 'Kyllä' :) + Jos et tiedä, mitä kaikki tarkoittaa, paina vain 'Kyllä'. Dolphin-emulaattorin kansio löytyi. Haluatko käyttää tätä kansiota? + dolphin_outdated: + title: "Dolphin-emulaattorisi on vanhentunut" + extra: |- + Tässä Dolphin-versiossa ({$1}) on tunnettu tietoturvaongelma, jossa haitalliset pelitiedostot, modit tai huijaavat verkkopelaajat voivat tartuttaa tietokoneesi haittaohjelmilla. + + Päivitä versioon {$2} tai uudempaan varmuuden vuoksi. + dolphin_program_found: + title: "Dolphin-emulaattori löytyi" + extra: |- + Jos et tiedä, mitä kaikki tarkoittaa, paina vain 'Kyllä'. + Dolphin-emulaattori löytyi. Haluatko käyttää sitä? new_version_wh_wz: title: "Wheel Wizard -päivitys saatavilla" extra: "Wheel Wizardin versio {$1} on saatavilla (nykyinen versio: {$2}). Haluatko päivittää heti?" @@ -306,6 +372,9 @@ fi: title: "Tämä sulkee ikkunan ja avaa uuden valitulla kieliasetuksella." extra: "Haluatko ottaa uuden kieliasetuksen käyttöön?" message_warning: + recomp_operation_cancelled: + title: "WiiCompiledin toiminto peruttiin" + extra: "Toiminto pysäytettiin ennen sen valmistumista. Voit aloittaa sen uudelleen milloin tahansa." not_find_dolphin: extra: "Dolphin-emulaattoria ei löytynyt. Määritä sijaintipolku asetuksissa." not_find_game: @@ -322,6 +391,12 @@ fi: dolphin_not_found: title: "Dolphin-emulaattorin kansiota ei löytynyt" extra: "Dolphin-emulaattorin kansiota ei löytynyt automaattisesti. Yritä etsiä kansio mauaalisesti. Lisätietoa saat napsauttamalla 'Ohje'." + dolphin_version_unverified: + title: "Dolphin-emulaattorin versiota ei voitu vahvistaa" + extra: |- + Wheel Wizard ei voinut lukea määrittämääsi Dolphin-emulaattorin versiota, joten se ei voi tietää, onko siinä tietoturvakorjauksia. + + Tarkista, että käytät {$1}-versiota tai uudempaa. Vanhemmissa versioissa on tunnettu tietoturvaongelma, jossa haitalliset pelitiedostot, modit tai huijaavat verkkopelaajat voivat tartuttaa tietokoneesi haittaohjelmilla. dolphin_tool_selected: title: "Kelvoton Dolphinin käynnistystiedosto valittuna" extra: "Sinulla on DolphinTool.exe valittuna. Ole hyvä ja valitse Dolphin.exe (Dolphin-emulaattorin käynnistystiedosto) sen sijaan." @@ -397,6 +472,7 @@ fi: mod_download_fail: title: "Modin lataus epäonnistui." extra: "Ladattaessa tapahtui virhe: {$1}" + failed_vr_history: "KP-historian lataaminen epäonnistui" message_success: settings_saved: title: "Asetukset tallennettu!" @@ -430,6 +506,7 @@ fi: created_duplicate: "{$1}' kopioitu" created_duplicates_miis: "{$1}' kopioitua Miitä luotu" profile_set_primary: "Aseta profiili ensisijaiseksi" + dolphin_updated: "Dolphin-emulaattori on päivitetty" snackbar_error: mii_failure_deserialize: "Miin '{$1}' deserialisointi epäonnistui" mii_failure_update: "Miin '{$1}' päivittäminen epäonnistui" @@ -448,11 +525,18 @@ fi: incompatible_mod: message: "Tämä modi on yhteensopimaton muiden modien kanssa. Pelin käynnistäminen tämän modin kanssa voi aiheuttaa virheellistä toimintaa tai kaatumisia. Harkitse modin muuntamista paikkauksiksi napsauttamalla sitä hiiren oikealla painikkeella." title: "YHTEENSOPIMATON MODI" + warning: + brsar_external_count: "{$1} BRSAR-tiedostomerkintää ovat ulkoisia viittauksia, eikä niitä voida viedä." + brsar_file_id_external: "BRSAR-tiedostotunnus {$1} on ulkoinen tiedostoviittaus, eikä sitä voida viedä." option: wh_wz_language: "Wheel Wizardin kieli" force_disable_wiimote: "Pakota Wiimote pois käytöstä" launch_with_dolphin: "Käynnistä peli Dolphin-ikkunassa" launch_rr_on_startup: "Käynnistä Retro Rewind heti" + retro_rewind_installation: "Retro Rewind -asennus" + save_folder: "Tallennuskansio" + installation_folder: "Asennuskansio" + recomp_installation: "WiiCompiled -asennus" v_sync: "Pystytahdistus (V-Sync)" recommended: "Suositellut asetukset" show_fps: "Näytä kuvalaskuri" @@ -460,6 +544,7 @@ fi: window_scale: "Ikkunan koko" open_config: "Avaa asetuskansio" wheel_wizard_data_folder: "Wheel Wizardinin tietokansio" + retro_rewind_folder: "Retro Rewind -kansio" reset_data_folder: "Käytä oletussijaintia" dolphin_user_path: "Dolphin-käyttäjäkansio" mario_kart_game: "Mario Kart Wii -pelitiedosto" @@ -469,9 +554,17 @@ fi: reinstall: "Asenna RR uudelleen" open_game_folder: "Avaa pelikansio" open_save_folder: "Avaa tallennuskansio" + enable_recomp: "Käytä WiiCompiledia (beta)" + recomp_use_dolphin_data: "Jaa Dolphin-tallennustiedot" + recomp_share_dolphin_data: "Jaa NAND-data Dolphinin kanssa" + recomp_clone_dolphin_data: "Kloonaa Dolphinin NAND" + recomp_resolution: "Resoluutio" + recomp_graphics_api: "Grafiikan API" + recomp_prevent_stutters: "Estä pätkiminen" section: graphics: "Grafiikka" general: "Yleiset" + retro_rewind: "Retro Rewind" language: "Kieli" wii: "Wii-asetukset" resolution: "Resoluutio" @@ -480,27 +573,58 @@ fi: location_paths: "Sijaintiasetukset" wh_wz_appearance: "Wheel Wizardin ulkoasu" installation: "Asennus" + setup: "Kokoonpano" + recomp: "WiiCompiled (beta)" + recomp_dolphin_data: "Dolphin-data" + recomp_video: "Video" helper_text: topbar: location_warning: "Asetukset on lukittu, suorita sijaintipolkujen asetukset ensin" + enable_recomp: "Käyttää WiiCompiledia käynnistysohjelmana kotisivulla. Poista tämä käytöstä käyttääksesi Dolphinia ja Retro Rewindia. Vain Windowsille." + recomp_use_dolphin_data: "Käytä Dolphinin olemassa olevaa Wii-kansiota, jotta WiiCompiled jakaa tallennukset, Miit ja Wii-asetukset. Muutos tulee voimaan, kun WiiCompiled käynnistyy seuraavan kerran." + recomp_share_dolphin_data: "WiiCompiled ja Dolphin käyttävät samoja tallennuksia, Miitä ja Wii-asetuksia." + recomp_shared_nand_warning: "Älä käytä Dolphinia ja WiiCompiledia samaan aikaan, kun NAND-data jaetaan." + recomp_clone_dolphin_data: "Kopioi Dolphinin nykyiset tallennukset, Miit ja Wii-asetukset yksityiseksi WiiCompiled-dataksi." + recomp_graphics_api: "Vain DirectX 12 ja Vulkan suorittavat WiiCompiledia luotettavasti, joten muita vaihtoehtoja ei ole tarjolla." + recomp_dolphin_data_not_found: "Määritä ensin olemassa oleva Dolphin-käyttäjäkansio sijaintiasetuksissa. Sen on sisällettävä Wii-kansio." + recomp_prevent_stutters: "Varjostinta käännettäessä sitä tarvitsevat objektit ohitetaan muutaman kuvan ajan sen sijaan, että koko peli jumiutuisi, kunnes se on valmis. Vähentää pätkimistä grafiikan äkillisen ilmestymisen kustannuksella." launch_with_dolphin: "Käynnistää Dolphinin pääikkunan pelin mukana" launch_rr_on_startup: "Retro Rewind käynnistetään heti kun Wheel Wizard käynnistyy" + retro_rewind_version: "Asennettu versio: {$1}" + installed_version: "Asennettu versio: {$1}" force_disable_wiimote: "Poistaa Wiimoten käytöstä pelissä, mutta ottaa sen käyttöön Wii-valikossa" resolution_scale: "1x on alkuperäinen resoluutio" + renderer: "Valitsee grafiikkaohjelman, jota Dolphin käyttää pelin renderöintiin." + v_sync: "Synkronoi pelin kuvataajuuden näyttösi kanssa estääkseen kuvan pilkottumista." recommended: "Muuttaa joitakin Dolphinin asetuksia pätkimisen ja lagipiikkien vähentämiseksi" 30_fps: "Pakottaa pelin toimimaan 30 FPS -tilassa (oletus on 60)" wheel_wizard_data_folder: "Valitse Wheel Wizardin tiedostosijainti. Siirtäminen voi kestää hetken." + retro_rewind_folder: "Avaa kansion, joka sisältää asennetut Retro Rewind -tiedostot." end_with_exe: "Polun täytyy loppua päätteeseen .exe" + select_dolphin_linux: "Valitse Dolphin-ohjelmatiedosto tai anna käynnistyskomento" + select_dolphin_macos: "Valitse Dolphin.app" enable_animations: "Ottaa käyttöön animaatiot Wheel Wizard -sovelluksessa" end_with_x: "Polku voi loppua:" + wh_wz_language: "Muuttaa Wheel Wizardin käyttämää kieltä, mutta ei pelin sisäistä kieltä." optional: "Valinnainen" must_set_paths: "Nämä 3 polkua on määritettävä ennen kuin voit pelata Retro Rewindia" + must_set_game_path: "Sinun on valittava Mario Kart Wii -pelitiedostosi ennen kuin voit pelata WiiCompiledia" category: + dolphin: "Dolphin" about: "Tietoa" other: "Muut" general: "Yleiset" - video: "Grafiikka" + video: "Video" status: + recomp_dolphin_clone_available: "Yksityinen klooni: {$1}" + recomp_dolphin_clone_missing: "Yksityistä kloonia ei ole luotu." + recomp_dolphin_data_cloned: "Dolphin-data kloonattu WiiCompiledille" + recomp_dolphin_data_not_linked: "Ei linkitetty. WiiCompiled käyttää omaa yksityistä Wii-dataa." + recomp_dolphin_data_linked: "Jaetaan Dolphin-dataa kohteesta {$1}" + recomp_dolphin_data_copied: "Käytetään Dolphin-datasi kopiota kohteessa {$1}" + recomp_dolphin_data_not_found: "Dolphin-dataa ei löytynyt" + recomp_not_installed: "WiiCompiledia ei ole vielä asennettu. Asenna se ensin kotisivulta." + recomp_uninstalled: "WiiCompiledin asennus on poistettu" data_folder: default: "Käytetään oletussijaintia." custom: "Käytetään mukautettua sijaintia." @@ -522,5 +646,6 @@ fi: czech: "Tšekki" norwegian: "Norja" chinese: "Kiina" + polish: "Puola" portuguese: "Portugali" z_translators: "Clavey, Alokas" diff --git a/WheelWizard/Views/Layout.axaml.cs b/WheelWizard/Views/Layout.axaml.cs index dbfe085fa..5c11b8f1a 100644 --- a/WheelWizard/Views/Layout.axaml.cs +++ b/WheelWizard/Views/Layout.axaml.cs @@ -88,6 +88,11 @@ IModManager modManagerService SettingsSignalBus = settingsSignalBus; ModManagerService = modManagerService; InitializeComponent(); + + // Respects tiling window managers better if resizable. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + CanResize = true; + Navigation.PageChanged += Navigation_OnPageChanged; foreach (var button in SidePanelButtons.Children.OfType()) button.NavigationRequested += (_, pageType) => Navigation.NavigateTo(pageType); @@ -176,7 +181,7 @@ private void OnSettingChanged(Setting setting) // Note that this method will also be called whenever the setting changes if (setting == SettingsService.WINDOW_SCALE || setting == SettingsService.SAVED_WINDOW_SCALE) { - var scaleFactor = GetUsableWindowScale((double)setting.Get()); + var scaleFactor = GetUsableWindowScale(SettingsService.WINDOW_SCALE.Value); CompleteGrid.Resources["SettingsRowGap"] = 2d / scaleFactor; Height = WindowHeight * scaleFactor; Width = WindowWidth * scaleFactor; diff --git a/WheelWizard/Views/Pages/LeaderboardPage.axaml b/WheelWizard/Views/Pages/LeaderboardPage.axaml index 3f479fa05..5ce2673b0 100644 --- a/WheelWizard/Views/Pages/LeaderboardPage.axaml +++ b/WheelWizard/Views/Pages/LeaderboardPage.axaml @@ -78,7 +78,7 @@ VerticalAlignment="Bottom" /> @@ -92,7 +92,7 @@ Height="68" Foreground="{StaticResource Neutral700}" HorizontalAlignment="Center" /> - @@ -108,7 +108,7 @@ Height="56" Foreground="{StaticResource Danger500}" HorizontalAlignment="Center" /> - @@ -117,7 +117,7 @@ Width="310" TextAlignment="Center" HorizontalAlignment="Center" /> - - - - @@ -165,7 +165,7 @@ @@ -193,10 +193,10 @@ Click="CopyFriendCode_OnClick" /> - - @@ -224,10 +224,10 @@ Click="CopyFriendCode_OnClick" /> - - @@ -254,10 +254,10 @@ Click="CopyFriendCode_OnClick" /> - - @@ -288,10 +288,10 @@ Click="CopyFriendCode_OnClick" /> - - diff --git a/WheelWizard/Views/Pages/LeaderboardPage.axaml.cs b/WheelWizard/Views/Pages/LeaderboardPage.axaml.cs index 280478829..8897ff513 100644 --- a/WheelWizard/Views/Pages/LeaderboardPage.axaml.cs +++ b/WheelWizard/Views/Pages/LeaderboardPage.axaml.cs @@ -4,6 +4,7 @@ using Avalonia.Controls; using Avalonia.Input.Platform; using Avalonia.Interactivity; +using WheelWizard.Localization; using WheelWizard.Models; using WheelWizard.RrRooms; using WheelWizard.Settings; @@ -40,7 +41,7 @@ public sealed record LeaderboardPlayerItem public Mii? FirstMii => Mii; public bool HasBadges => HasBadge; public bool IsTopLeaderboardPlayer => true; - public string TopLabel => $"#{Rank}"; + public string TopLabel => t("placement.n", Rank); public bool IsOpenHost => false; } @@ -315,7 +316,7 @@ IReadOnlySet onlineProfileIds { Rank = rank, PlacementLabel = GetPlacementLabel(rank), - Name = string.IsNullOrWhiteSpace(entry.Name) ? "Unknown Player" : entry.Name, + Name = string.IsNullOrWhiteSpace(entry.Name) ? t("empty_content.no_mii_name") : entry.Name, FriendCode = friendCode, VrText = entry.Vr?.ToString("N0") ?? "--", Mii = DeserializeMii(entry.MiiData), @@ -343,10 +344,10 @@ private static int ResolveRank(RwfcLeaderboardEntry entry, int index) private static string GetPlacementLabel(int rank) => rank switch { - 1 => "Champion", - 2 => "2nd Place", - 3 => "3rd Place", - _ => $"#{rank}", + 1 => t("placement.first"), + 2 => t("placement.second"), + 3 => t("placement.third"), + _ => t("placement.n", rank), }; private static Mii? DeserializeMii(string? miiData) @@ -477,27 +478,27 @@ private async void AddFriend_OnClick(object sender, RoutedEventArgs e) if (player.FirstMii == null) { - ViewUtils.ShowSnackbar("This player has no valid Mii data.", ViewUtils.SnackbarType.Warning); + ViewUtils.ShowSnackbar(t("snackbar_warning.mii_invalid"), ViewUtils.SnackbarType.Warning); return; } var focusedUserIndex = SettingsManager.Get(SettingsManager.FOCUSED_USER); if (focusedUserIndex is < 0 or > 3) { - ViewUtils.ShowSnackbar("Invalid license selected.", ViewUtils.SnackbarType.Warning); + ViewUtils.ShowSnackbar(t("snackbar_warning.license_invalid"), ViewUtils.SnackbarType.Warning); return; } var activeUserPid = FriendCode.FriendCodeToProfileId(GameDataService.ActiveUser.FriendCode); if (activeUserPid == 0) { - ViewUtils.ShowSnackbar("Select a valid license before adding friends.", ViewUtils.SnackbarType.Warning); + ViewUtils.ShowSnackbar(t("snackbar_warning.license_invalid_self"), ViewUtils.SnackbarType.Warning); return; } if (GameDataService.ActiveCurrentFriends.Count >= 30) { - ViewUtils.ShowSnackbar("Your friend list is full.", ViewUtils.SnackbarType.Warning); + ViewUtils.ShowSnackbar(t("snackbar_warning.friend_list_full"), ViewUtils.SnackbarType.Warning); return; } @@ -512,7 +513,7 @@ private async void AddFriend_OnClick(object sender, RoutedEventArgs e) var friendProfileId = FriendCode.FriendCodeToProfileId(normalizedFriendCode); if (activeUserPid == friendProfileId) { - ViewUtils.ShowSnackbar("You cannot add your own friend code.", ViewUtils.SnackbarType.Warning); + ViewUtils.ShowSnackbar(t("snackbar_warning.add_own_fc"), ViewUtils.SnackbarType.Warning); return; } @@ -524,7 +525,7 @@ private async void AddFriend_OnClick(object sender, RoutedEventArgs e) if (duplicateFriend) { - ViewUtils.ShowSnackbar("This friend is already in your list.", ViewUtils.SnackbarType.Warning); + ViewUtils.ShowSnackbar(t("snackbar_warning.add_existing_friend"), ViewUtils.SnackbarType.Warning); return; } @@ -548,7 +549,7 @@ private async void AddFriend_OnClick(object sender, RoutedEventArgs e) } ViewUtils.GetLayout().UpdateFriendCount(); - ViewUtils.ShowSnackbar($"Added {player.Name} to your friend list."); + ViewUtils.ShowSnackbar(t("snackbar_success.friend_added", player.Name)); } private void JoinRoom_OnClick(string friendCode) @@ -565,7 +566,7 @@ private void JoinRoom_OnClick(string friendCode) return; } - ViewUtils.ShowSnackbar("Could not find an active room for this player.", ViewUtils.SnackbarType.Warning); + ViewUtils.ShowSnackbar(t("snackbar_warning.no_active_room"), ViewUtils.SnackbarType.Warning); } private static LeaderboardPlayerItem? GetContextPlayer(object sender) @@ -578,16 +579,16 @@ private void JoinRoom_OnClick(string friendCode) private static OperationResult NormalizeFriendCode(string friendCode) { if (string.IsNullOrWhiteSpace(friendCode)) - return Fail("Friend code cannot be empty."); + return Fail(t("snackbar_warning.empty_fc")); var digits = new string(friendCode.Where(char.IsDigit).ToArray()); if (digits.Length != 12 || !ulong.TryParse(digits, out _)) - return Fail("Friend code must be exactly 12 digits."); + return Fail(t("snackbar_warning.invalid_fc")); var formatted = $"{digits[..4]}-{digits.Substring(4, 4)}-{digits.Substring(8, 4)}"; var profileId = FriendCode.FriendCodeToProfileId(formatted); if (profileId == 0) - return Fail("Invalid friend code."); + return Fail(t("snackbar_warning.nonexisting_fc")); return formatted; } diff --git a/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs b/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs index 29c84b83a..13748af66 100644 --- a/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs +++ b/WheelWizard/Views/Pages/Settings/OtherSettings.axaml.cs @@ -81,12 +81,12 @@ private void RefreshRetroRewindVersion() private void ClickLaunchRrOnStartup(object? sender, RoutedEventArgs e) { - SettingsService.Set(SettingsService.LAUNCH_RR_ON_STARTUP, LaunchRrOnStartup.IsChecked == true); + SettingsEditing.Set(SettingsService, SettingsService.LAUNCH_RR_ON_STARTUP, LaunchRrOnStartup.IsChecked == true); } private void ClickEnableRecomp(object? sender, RoutedEventArgs e) { - SettingsService.Set(SettingsService.ENABLE_RECOMP, EnableRecomp.IsChecked == true); + SettingsEditing.Set(SettingsService, SettingsService.ENABLE_RECOMP, EnableRecomp.IsChecked == true); } private async void Reinstall_RetroRewind(object sender, RoutedEventArgs e) diff --git a/WheelWizard/Views/Pages/Settings/RecompSettings.axaml.cs b/WheelWizard/Views/Pages/Settings/RecompSettings.axaml.cs index 3a62a7542..f2a50fe2e 100644 --- a/WheelWizard/Views/Pages/Settings/RecompSettings.axaml.cs +++ b/WheelWizard/Views/Pages/Settings/RecompSettings.axaml.cs @@ -160,7 +160,7 @@ private void Resolution_OnChanged(object? sender, SelectionChangedEventArgs e) if (_loading || index < 0 || index >= RecompVideoConfig.ResolutionMultipliers.Count) return; - SettingsService.Set(SettingsService.RECOMP_RESOLUTION_MULTIPLIER, RecompVideoConfig.ResolutionMultipliers[index]); + SettingsEditing.Set(SettingsService, SettingsService.RECOMP_RESOLUTION_MULTIPLIER, RecompVideoConfig.ResolutionMultipliers[index]); } private void GraphicsApi_OnChanged(object? sender, SelectionChangedEventArgs e) @@ -169,7 +169,7 @@ private void GraphicsApi_OnChanged(object? sender, SelectionChangedEventArgs e) if (_loading || index < 0 || index >= RecompVideoConfig.OfferedGraphicsApis.Count) return; - SettingsService.Set(SettingsService.RECOMP_GRAPHICS_API, RecompVideoConfig.OfferedGraphicsApis[index]); + SettingsEditing.Set(SettingsService, SettingsService.RECOMP_GRAPHICS_API, RecompVideoConfig.OfferedGraphicsApis[index]); } private void ShowFps_OnChanged(object? sender, RoutedEventArgs e) @@ -177,7 +177,7 @@ private void ShowFps_OnChanged(object? sender, RoutedEventArgs e) if (_loading) return; - SettingsService.Set(SettingsService.RECOMP_SHOW_FPS, ShowFps.IsChecked == true); + SettingsEditing.Set(SettingsService, SettingsService.RECOMP_SHOW_FPS, ShowFps.IsChecked == true); } private void PreventStutters_OnChanged(object? sender, RoutedEventArgs e) @@ -185,7 +185,7 @@ private void PreventStutters_OnChanged(object? sender, RoutedEventArgs e) if (_loading) return; - SettingsService.Set(SettingsService.RECOMP_PREVENT_STUTTERS, PreventStutters.IsChecked == true); + SettingsEditing.Set(SettingsService, SettingsService.RECOMP_PREVENT_STUTTERS, PreventStutters.IsChecked == true); } #endregion diff --git a/WheelWizard/Views/Pages/Settings/SettingsEditing.cs b/WheelWizard/Views/Pages/Settings/SettingsEditing.cs new file mode 100644 index 000000000..7d4243949 --- /dev/null +++ b/WheelWizard/Views/Pages/Settings/SettingsEditing.cs @@ -0,0 +1,19 @@ +using WheelWizard.Settings; +using WheelWizard.Settings.Types; +using WheelWizard.Shared.MessageTranslations; + +namespace WheelWizard.Views.Pages.Settings; + +internal static class SettingsEditing +{ + public static bool Set(ISettingsManager settings, Setting setting, T value) + { + if (settings.Set(setting, value)) + return true; + if (setting.SaveError is { } error) + MessageTranslationHelper.ShowMessage(Fail(error)); + else + MessageTranslationHelper.ShowMessage(MessageTranslation.Warning_InvalidPathSettings); + return false; + } +} diff --git a/WheelWizard/Views/Pages/Settings/VideoSettings.axaml.cs b/WheelWizard/Views/Pages/Settings/VideoSettings.axaml.cs index 7343b8796..bde8b0d9d 100644 --- a/WheelWizard/Views/Pages/Settings/VideoSettings.axaml.cs +++ b/WheelWizard/Views/Pages/Settings/VideoSettings.axaml.cs @@ -51,12 +51,12 @@ public VideoSettings(ISettingsManager settingsService) private void ClickForceWiimote(object? sender, RoutedEventArgs e) { - SettingsService.Set(SettingsService.FORCE_WIIMOTE, DisableForce.IsChecked == true); + SettingsEditing.Set(SettingsService, SettingsService.FORCE_WIIMOTE, DisableForce.IsChecked == true); } private void ClickLaunchWithDolphinWindow(object? sender, RoutedEventArgs e) { - SettingsService.Set(SettingsService.LAUNCH_WITH_DOLPHIN, LaunchWithDolphin.IsChecked == true); + SettingsEditing.Set(SettingsService, SettingsService.LAUNCH_WITH_DOLPHIN, LaunchWithDolphin.IsChecked == true); } private void LoadSettings() @@ -91,17 +91,17 @@ private void ForceLoadSettings() private void ResolutionDropdown_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) { if (ResolutionDropdown.SelectedIndex >= 0) - SettingsService.Set(SettingsService.INTERNAL_RESOLUTION, ResolutionDropdown.SelectedIndex + 1); + SettingsEditing.Set(SettingsService, SettingsService.INTERNAL_RESOLUTION, ResolutionDropdown.SelectedIndex + 1); } private void VSync_OnClick(object? sender, RoutedEventArgs e) { - SettingsService.Set(SettingsService.VSYNC, VSyncButton.IsChecked == true); + SettingsEditing.Set(SettingsService, SettingsService.VSYNC, VSyncButton.IsChecked == true); } private void Recommended_OnClick(object? sender, RoutedEventArgs e) { - if (!SettingsService.Set(SettingsService.RECOMMENDED_SETTINGS, RecommendedButton.IsChecked == true)) + if (!SettingsEditing.Set(SettingsService, SettingsService.RECOMMENDED_SETTINGS, RecommendedButton.IsChecked == true)) { RecommendedButton.IsCheckedChanged -= Recommended_OnClick; try @@ -117,7 +117,7 @@ private void Recommended_OnClick(object? sender, RoutedEventArgs e) private void ShowFPS_OnClick(object? sender, RoutedEventArgs e) { - SettingsService.Set(SettingsService.SHOW_FPS, ShowFPSButton.IsChecked == true); + SettingsEditing.Set(SettingsService, SettingsService.SHOW_FPS, ShowFPSButton.IsChecked == true); } private void RendererDropdown_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) @@ -128,7 +128,7 @@ private void RendererDropdown_OnSelectionChanged(object? sender, SelectionChange if (SettingValues.GFXRenderers.TryGetValue(selectedDisplayName, out var actualValue)) { - SettingsService.Set(SettingsService.GFX_BACKEND, actualValue); + SettingsEditing.Set(SettingsService, SettingsService.GFX_BACKEND, actualValue); } else { diff --git a/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs b/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs index 604359ff3..fffa0a47d 100644 --- a/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs +++ b/WheelWizard/Views/Pages/Settings/WhWzSettings.axaml.cs @@ -350,22 +350,17 @@ private async void DolphinUserPathBrowse_OnClick(object sender, RoutedEventArgs } } - private async Task ApplyLocationSettingAsync(Setting setting, string path) + private async Task ApplyLocationSettingAsync(Setting setting, string path) { - var normalizedPath = - setting == SettingsService.USER_FOLDER_PATH ? path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) : path; - var previousPath = (string)setting.Get(); + var normalizedPath = setting == SettingsService.USER_FOLDER_PATH ? Path.TrimEndingDirectorySeparator(path) : path; - if (!setting.Set(normalizedPath)) + if (!SettingsEditing.Set(SettingsService, setting, normalizedPath)) { - await MessageTranslationHelper.AwaitMessageAsync(MessageTranslation.Warning_InvalidPathSettings); UpdateLocationRows(); return false; } UpdateLocationRows(); - if (!string.Equals(previousPath, normalizedPath, StringComparison.Ordinal) && SettingsService.PathsSetupCorrectly()) - DolphinSettingsService.ReloadSettings(DolphinPaths.ConfigFolderPath); await MessageTranslationHelper.AwaitMessageAsync(MessageTranslation.Success_PathSettingsSaved); return true; @@ -739,7 +734,7 @@ private async void WindowScaleDropdown_OnSelectionChanged(object sender, Selecti WindowScaleDropdown.Items.Add(selectedItemText); WindowScaleDropdown.SelectedItem = selectedItemText; - if (!SettingsService.WINDOW_SCALE.Set(scale)) + if (!SettingsEditing.Set(SettingsService, SettingsService.WINDOW_SCALE, scale)) { WindowScaleDropdown.SelectedItem = ScaleToString((double)SettingsService.WINDOW_SCALE.Get()); _editingScale = false; @@ -769,7 +764,7 @@ private async void WindowScaleDropdown_OnSelectionChanged(object sender, Selecti var yesNoAnswer = await yesNoWindow.AwaitAnswer(); if (yesNoAnswer) - SettingsService.SAVED_WINDOW_SCALE.Set(SettingsService.WINDOW_SCALE.Get()); + SettingsEditing.Set(SettingsService, SettingsService.SAVED_WINDOW_SCALE, SettingsService.WINDOW_SCALE.Value); else { SettingsService.WINDOW_SCALE.Set(SettingsService.SAVED_WINDOW_SCALE.Get()); @@ -838,7 +833,7 @@ private async void WhWzLanguageDropdown_OnSelectionChanged(object? sender, Selec return; // We only want to change the setting if we really apply this change } - if (SettingsService.WW_LANGUAGE.Set(selectedLanguage.Key)) + if (SettingsEditing.Set(SettingsService, SettingsService.WW_LANGUAGE, selectedLanguage.Key)) { LocalizationService.ApplyCurrentLanguage(); RefreshLanguageDropdown(); @@ -848,5 +843,5 @@ private async void WhWzLanguageDropdown_OnSelectionChanged(object? sender, Selec } private void EnableAnimations_OnClick(object sender, RoutedEventArgs e) => - SettingsService.ENABLE_ANIMATIONS.Set(EnableAnimations.IsChecked == true); + SettingsEditing.Set(SettingsService, SettingsService.ENABLE_ANIMATIONS, EnableAnimations.IsChecked == true); } diff --git a/WheelWizard/Views/Patterns/LeaderboardPodiumCard.axaml b/WheelWizard/Views/Patterns/LeaderboardPodiumCard.axaml index 7bc72aa26..9b462117f 100644 --- a/WheelWizard/Views/Patterns/LeaderboardPodiumCard.axaml +++ b/WheelWizard/Views/Patterns/LeaderboardPodiumCard.axaml @@ -82,7 +82,7 @@ BorderBrush="#4FFFFFFF" Background="#22FFFFFF" VerticalAlignment="Center"> - diff --git a/WheelWizard/Views/Patterns/LeaderboardPodiumCard.axaml.cs b/WheelWizard/Views/Patterns/LeaderboardPodiumCard.axaml.cs index 9bc7d7da9..aa1aa5264 100644 --- a/WheelWizard/Views/Patterns/LeaderboardPodiumCard.axaml.cs +++ b/WheelWizard/Views/Patterns/LeaderboardPodiumCard.axaml.cs @@ -7,14 +7,34 @@ namespace WheelWizard.Views.Patterns; public class LeaderboardPodiumCard : TemplatedControl { + static LeaderboardPodiumCard() + { + RankProperty.Changed.AddClassHandler( + (card, _) => + { + card.RankLabel = t("placement.n", card.Rank); + } + ); + } + public static readonly StyledProperty RankProperty = AvaloniaProperty.Register(nameof(Rank)); + public static readonly StyledProperty RankLabelProperty = AvaloniaProperty.Register( + nameof(RankLabel) + ); + public int Rank { get => GetValue(RankProperty); set => SetValue(RankProperty, value); } + public string RankLabel + { + get => GetValue(RankLabelProperty); + private set => SetValue(RankLabelProperty, value); + } + public static readonly StyledProperty PlacementLabelProperty = AvaloniaProperty.Register( nameof(PlacementLabel), string.Empty diff --git a/build-everything.bat b/build-everything.bat deleted file mode 100644 index b01a303f9..000000000 --- a/build-everything.bat +++ /dev/null @@ -1,12 +0,0 @@ -@echo off -echo Building for Windows... -call build-win.bat - -echo Building for Linux... -call build-linux.bat - -echo Building for Linux ARM64... -call build-linux-arm64.bat - -echo All builds completed! -pause diff --git a/build-linux-arm64.bat b/build-linux-arm64.bat deleted file mode 100644 index 7b2025d1b..000000000 --- a/build-linux-arm64.bat +++ /dev/null @@ -1 +0,0 @@ -dotnet publish -r linux-arm64 -c Release /p:UseAppHost=true /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true /p:EnableCompressionInSingleFile=true --self-contained true diff --git a/build-linux-arm64.sh b/build-linux-arm64.sh deleted file mode 100644 index ea8137bf7..000000000 --- a/build-linux-arm64.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -dotnet publish -r linux-arm64 -c Release /p:UseAppHost=true /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true /p:EnableCompressionInSingleFile=true --self-contained true diff --git a/build-linux.bat b/build-linux.bat deleted file mode 100644 index 2216967f5..000000000 --- a/build-linux.bat +++ /dev/null @@ -1 +0,0 @@ -dotnet publish -r linux-x64 -c Release /p:UseAppHost=true /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true /p:EnableCompressionInSingleFile=true --self-contained true diff --git a/build-linux.sh b/build-linux.sh deleted file mode 100644 index 05329dc26..000000000 --- a/build-linux.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -dotnet publish -r linux-x64 -c Release /p:UseAppHost=true /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true /p:EnableCompressionInSingleFile=true --self-contained true diff --git a/build-mac.sh b/build-mac.sh deleted file mode 100755 index a755f9733..000000000 --- a/build-mac.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -# Local macOS build. Produces a .app bundle via macos/release-macos.sh. -# https://avaloniaui.net/blog/the-definitive-guide-to-building-and-deploying-avalonia-applications-for-macos -set -euo pipefail -exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/macos/release-macos.sh" "$@" diff --git a/build-win.bat b/build-win.bat deleted file mode 100644 index ce89502df..000000000 --- a/build-win.bat +++ /dev/null @@ -1 +0,0 @@ -dotnet publish -r win-x64 -c Release /p:UseAppHost=true /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true /p:EnableCompressionInSingleFile=true --self-contained true