From 456623c6d478f115f93a8501fffbea7f37d62c00 Mon Sep 17 00:00:00 2001 From: Patchzy <64382339+patchzyy@users.noreply.github.com> Date: Wed, 15 Apr 2026 01:05:00 +0200 Subject: [PATCH 1/4] Add mod storage modes and patch support --- .../GameBanana/Domain/GameBananaModPreview.cs | 7 +- .../GameBanana/Domain/GameBananaTag.cs | 72 ++++++++++ .../Features/Settings/ISettingsServices.cs | 1 + .../Features/Settings/SettingsManager.cs | 2 + .../Launcher/Helpers/ModsLaunchHelper.cs | 133 +++++++++++++----- .../Services/Launcher/RrBetaLauncher.cs | 7 +- WheelWizard/Services/Launcher/RrLauncher.cs | 7 +- WheelWizard/Services/ModStorageSystem.cs | 61 ++++++++ WheelWizard/Services/PathManager.cs | 8 +- WheelWizard/Views/App.axaml.cs | 24 ++-- WheelWizard/Views/Layout.axaml | 2 +- WheelWizard/Views/Layout.axaml.cs | 11 ++ WheelWizard/Views/Pages/ModsPage.axaml | 13 +- WheelWizard/Views/Pages/ModsPage.axaml.cs | 42 ++++++ .../Views/Patterns/ModBrowserListItem.axaml | 24 +++- .../Patterns/ModBrowserListItem.axaml.cs | 10 ++ .../ModManagement/ModBrowserWindow.axaml | 12 +- .../ModManagement/ModBrowserWindow.axaml.cs | 86 +++++++++-- 18 files changed, 454 insertions(+), 68 deletions(-) create mode 100644 WheelWizard/Features/GameBanana/Domain/GameBananaTag.cs create mode 100644 WheelWizard/Services/ModStorageSystem.cs diff --git a/WheelWizard/Features/GameBanana/Domain/GameBananaModPreview.cs b/WheelWizard/Features/GameBanana/Domain/GameBananaModPreview.cs index b3d57774..61fb6dfd 100644 --- a/WheelWizard/Features/GameBanana/Domain/GameBananaModPreview.cs +++ b/WheelWizard/Features/GameBanana/Domain/GameBananaModPreview.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using WheelWizard.Services; namespace WheelWizard.GameBanana.Domain; @@ -16,7 +17,8 @@ public class GameBananaModPreview public required string Version { get; set; } [JsonPropertyName("_aTags")] - public required List Tags { get; set; } + [JsonConverter(typeof(GameBananaTagListJsonConverter))] + public required List Tags { get; set; } [JsonPropertyName("_sProfileUrl")] public required string ProfileUrl { get; set; } @@ -52,4 +54,7 @@ public class GameBananaModPreview /// [JsonPropertyName("_sModelName")] public required string ModelName { get; set; } + + [JsonIgnore] + public bool UsesPatches => ModStorageSystemHelper.UsesPatches(Tags); } diff --git a/WheelWizard/Features/GameBanana/Domain/GameBananaTag.cs b/WheelWizard/Features/GameBanana/Domain/GameBananaTag.cs new file mode 100644 index 00000000..81bf2dc2 --- /dev/null +++ b/WheelWizard/Features/GameBanana/Domain/GameBananaTag.cs @@ -0,0 +1,72 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace WheelWizard.GameBanana.Domain; + +public class GameBananaTag +{ + public string Title { get; set; } = string.Empty; + public string Value { get; set; } = string.Empty; +} + +public sealed class GameBananaTagListJsonConverter : JsonConverter> +{ + public override List Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + return []; + + if (reader.TokenType != JsonTokenType.StartArray) + return []; //maybe throw but now just do this + + var tags = new List(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + return tags; + + switch (reader.TokenType) + { + case JsonTokenType.String: + var tagText = reader.GetString() ?? string.Empty; + tags.Add(new() { Title = tagText, Value = tagText }); + break; + case JsonTokenType.StartObject: + using (var tagDocument = JsonDocument.ParseValue(ref reader)) + { + var root = tagDocument.RootElement; + tags.Add( + new() + { + Title = root.TryGetProperty("_sTitle", out var title) ? title.GetString() ?? string.Empty : string.Empty, + Value = root.TryGetProperty("_sValue", out var value) ? value.GetString() ?? string.Empty : string.Empty, + } + ); + } + break; + + default: + using (JsonDocument.ParseValue(ref reader)) { } + break; + } + } + + throw new JsonException("Unexpected end of GameBanana tag payload."); + } + + public override void Write(Utf8JsonWriter writer, List value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + + foreach (var tag in value) + { + writer.WriteStartObject(); + writer.WriteString("_sTitle", tag.Title); + writer.WriteString("_sValue", tag.Value); + writer.WriteEndObject(); + } + + writer.WriteEndArray(); + } +} diff --git a/WheelWizard/Features/Settings/ISettingsServices.cs b/WheelWizard/Features/Settings/ISettingsServices.cs index 8875fa8e..0f85dca3 100644 --- a/WheelWizard/Features/Settings/ISettingsServices.cs +++ b/WheelWizard/Features/Settings/ISettingsServices.cs @@ -26,6 +26,7 @@ public interface ISettingsProperties Setting LAUNCH_WITH_DOLPHIN { get; } Setting LAUNCH_RR_ON_STARTUP { 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; } diff --git a/WheelWizard/Features/Settings/SettingsManager.cs b/WheelWizard/Features/Settings/SettingsManager.cs index 1980ad23..d5c7a768 100644 --- a/WheelWizard/Features/Settings/SettingsManager.cs +++ b/WheelWizard/Features/Settings/SettingsManager.cs @@ -111,6 +111,7 @@ IFileSystem fileSystem 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); ENABLE_ANIMATIONS = RegisterWhWz("EnableAnimations", true); @@ -198,6 +199,7 @@ IFileSystem fileSystem public Setting LAUNCH_WITH_DOLPHIN { get; } public Setting LAUNCH_RR_ON_STARTUP { 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; } diff --git a/WheelWizard/Services/Launcher/Helpers/ModsLaunchHelper.cs b/WheelWizard/Services/Launcher/Helpers/ModsLaunchHelper.cs index 7cfd0ad5..a39f477a 100644 --- a/WheelWizard/Services/Launcher/Helpers/ModsLaunchHelper.cs +++ b/WheelWizard/Services/Launcher/Helpers/ModsLaunchHelper.cs @@ -1,5 +1,6 @@ using Avalonia.Threading; using WheelWizard.Helpers; +using WheelWizard.Models.Mods; using WheelWizard.Resources.Languages; using WheelWizard.Views.Popups.Generic; @@ -7,24 +8,80 @@ namespace WheelWizard.Services.Launcher.Helpers; public static class ModsLaunchHelper { - public static readonly string MyStuffFolderPath = PathManager.MyStuffFolderPath; public static readonly string ModsFolderPath = PathManager.ModsFolderPath; - public static readonly string[] AcceptedModExtensions = ["*.szs", "*.arc", "*.brstm", "*.brsar", "*.thp"]; - public static async Task PrepareModsForLaunch(string? myStuffFolderPath = null) + //todo: move this to like an actual static place somewhere that makes more sense. + public static readonly string[] AcceptedMyStuffExtensions = + [ + ".bcp", + ".szs", + ".bdof", + ".bfg", + ".blight", + ".bmg", + ".bmm", + ".brctr", + ".breff", + ".breft", + ".brfnt", + ".brlan", + ".brlyt", + ".brres", + ".brsar", + ".brstm", + ".bsp", + ".bti", + ".chr0", + ".clr0", + ".krm", + ".mdl0", + ".pat0", + ".rkc", + ".rkg", + ".scn0", + ".shp0", + ".srt0", + ".tex0", + ".thp", + ".tpl", + ".u8", + ".yaz0", + ".ast", + ".bdl", + ".bmd", + ".bco", + ".bol", + ".dat", + ".rarc", + ".ct-def", + ".le-def", + ".lex", + ".lfl", + ".lpar", + ".lta", + ".tplx", + ".wbz", + ".wlz", + ".wu8", + ".ybz", + ".ylz", + ]; + + public static async Task PrepareModsForLaunch(string targetFolderPath, string inactiveFolderPath, ModStorageSystem storageSystem) { - var resolvedMyStuffFolderPath = myStuffFolderPath ?? MyStuffFolderPath; + ClearInactiveFolder(inactiveFolderPath); + var mods = ModManager.Instance.Mods.Where(mod => mod.IsEnabled).ToArray(); if (mods.Length == 0) { - if (Directory.Exists(resolvedMyStuffFolderPath) && Directory.EnumerateFiles(resolvedMyStuffFolderPath).Any()) + if (Directory.Exists(targetFolderPath) && Directory.EnumerateFiles(targetFolderPath).Any()) { var modsFoundQuestion = new YesNoWindow() .SetButtonText(Common.Action_Delete, Common.Action_Keep) .SetMainText(Phrases.Question_LaunchClearModsFound_Title) - .SetExtraText(Phrases.Question_LaunchClearModsFound_Extra); + .SetExtraText(ModStorageSystemHelper.GetClearFolderPrompt(storageSystem)); if (await modsFoundQuestion.AwaitAnswer()) - Directory.Delete(resolvedMyStuffFolderPath, true); + Directory.Delete(targetFolderPath, true); return; } @@ -38,34 +95,23 @@ public static async Task PrepareModsForLaunch(string? myStuffFolderPath = null) { if (!mod.IsEnabled) continue; + var modFolder = Path.Combine(ModsFolderPath, mod.Title); - foreach (var extension in AcceptedModExtensions) - { - var files = Directory.GetFiles(modFolder, extension, SearchOption.AllDirectories); - foreach (var file in files) - { - var relativePath = Path.GetFileName(file); - // Since higher priority mods overwrite lower ones, we can overwrite entries in the dictionary - finalFiles[relativePath] = file; - } - } - } + if (!Directory.Exists(modFolder)) + continue; - // Get existing files in MyStuff - var existingFiles = new HashSet(StringComparer.OrdinalIgnoreCase); - if (Directory.Exists(resolvedMyStuffFolderPath)) - { - var files = Directory.GetFiles(resolvedMyStuffFolderPath, "*.*", SearchOption.TopDirectoryOnly); - foreach (var file in files) + foreach (var file in Directory.GetFiles(modFolder, "*", SearchOption.AllDirectories)) { + if (!ShouldCopyFile(mod, file, storageSystem)) + continue; + var relativePath = Path.GetFileName(file); - existingFiles.Add(relativePath); + // Since higher priority mods overwrite lower ones, we can overwrite entries in the dictionary + finalFiles[relativePath] = file; } } - else - { - Directory.CreateDirectory(resolvedMyStuffFolderPath); - } + + Directory.CreateDirectory(targetFolderPath); var totalFiles = finalFiles.Count; var progressWindow = new ProgressWindow(Phrases.Progress_InstallingMods).SetGoal( @@ -76,10 +122,10 @@ public static async Task PrepareModsForLaunch(string? myStuffFolderPath = null) await Task.Run(() => { var processedFiles = 0; - // Delete files in MyStuff that are not in finalFiles - if (Directory.Exists(resolvedMyStuffFolderPath)) + // Delete files in the active loose-mod folder that are not in finalFiles + if (Directory.Exists(targetFolderPath)) { - var files = Directory.GetFiles(resolvedMyStuffFolderPath, "*.*", SearchOption.TopDirectoryOnly); + var files = Directory.GetFiles(targetFolderPath, "*.*", SearchOption.TopDirectoryOnly); foreach (var file in files) { var relativePath = Path.GetFileName(file); @@ -94,7 +140,7 @@ await Task.Run(() => { var relativePath = kvp.Key; var sourceFile = kvp.Value; - var destinationFile = Path.Combine(resolvedMyStuffFolderPath, relativePath); + var destinationFile = Path.Combine(targetFolderPath, relativePath); processedFiles++; var progress = (int)((processedFiles) / (double)totalFiles * 100); @@ -131,4 +177,25 @@ await Task.Run(() => progressWindow.Close(); } + + private static void ClearInactiveFolder(string inactiveFolderPath) + { + if (!Directory.Exists(inactiveFolderPath)) + return; + + Directory.Delete(inactiveFolderPath, true); + } + + private static bool ShouldCopyFile(Mod mod, string filePath, ModStorageSystem storageSystem) + { + var modMetadataFile = Path.Combine(ModsFolderPath, mod.Title, $"{mod.Title}.ini"); + if (Path.GetFullPath(filePath).Equals(Path.GetFullPath(modMetadataFile), StringComparison.OrdinalIgnoreCase)) + return false; + + if (storageSystem == ModStorageSystem.Patches) + return true; + + var extension = Path.GetExtension(filePath); + return AcceptedMyStuffExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); + } } diff --git a/WheelWizard/Services/Launcher/RrBetaLauncher.cs b/WheelWizard/Services/Launcher/RrBetaLauncher.cs index c7c0066d..f6bbb07a 100644 --- a/WheelWizard/Services/Launcher/RrBetaLauncher.cs +++ b/WheelWizard/Services/Launcher/RrBetaLauncher.cs @@ -31,7 +31,12 @@ public async Task Launch() DolphinLaunchHelper.KillDolphin(); if (WiiMoteSettings.IsForceSettingsEnabled()) WiiMoteSettings.DisableVirtualWiiMote(); - await ModsLaunchHelper.PrepareModsForLaunch(PathManager.RrBetaMyStuffFolderPath); + var storageSystem = ModStorageSystemHelper.GetCurrent(_settingsManager); + await ModsLaunchHelper.PrepareModsForLaunch( + ModStorageSystemHelper.GetTargetFolderPath(storageSystem, isBeta: true), + ModStorageSystemHelper.GetInactiveFolderPath(storageSystem, isBeta: true), + storageSystem + ); if (!File.Exists(PathManager.GameFilePath)) { Dispatcher.UIThread.Post(() => diff --git a/WheelWizard/Services/Launcher/RrLauncher.cs b/WheelWizard/Services/Launcher/RrLauncher.cs index ec08959a..e8e26db6 100644 --- a/WheelWizard/Services/Launcher/RrLauncher.cs +++ b/WheelWizard/Services/Launcher/RrLauncher.cs @@ -32,7 +32,12 @@ public async Task Launch() DolphinLaunchHelper.KillDolphin(); if (WiiMoteSettings.IsForceSettingsEnabled()) WiiMoteSettings.DisableVirtualWiiMote(); - await ModsLaunchHelper.PrepareModsForLaunch(); + var storageSystem = ModStorageSystemHelper.GetCurrent(_settingsManager); + await ModsLaunchHelper.PrepareModsForLaunch( + ModStorageSystemHelper.GetTargetFolderPath(storageSystem), + ModStorageSystemHelper.GetInactiveFolderPath(storageSystem), + storageSystem + ); if (!File.Exists(PathManager.GameFilePath)) { Dispatcher.UIThread.Post(() => diff --git a/WheelWizard/Services/ModStorageSystem.cs b/WheelWizard/Services/ModStorageSystem.cs new file mode 100644 index 00000000..6bd7ff05 --- /dev/null +++ b/WheelWizard/Services/ModStorageSystem.cs @@ -0,0 +1,61 @@ +using WheelWizard.GameBanana.Domain; +using WheelWizard.Resources.Languages; +using WheelWizard.Settings; + +namespace WheelWizard.Services; + +public enum ModStorageSystem +{ + MyStuff, + Patches, +} + +public static class ModStorageSystemHelper +{ + public static ModStorageSystem GetCurrent(ISettingsManager settingsManager) + { + return settingsManager.Get(settingsManager.USE_PATCHES_SYSTEM) ? ModStorageSystem.Patches : ModStorageSystem.MyStuff; + } + + public static string GetDisplayName(ModStorageSystem storageSystem) => + storageSystem == ModStorageSystem.Patches ? "Patches" : Common.PageTitle_Mods; + + public static string GetClearFolderPrompt(ModStorageSystem storageSystem) + { + if (storageSystem == ModStorageSystem.MyStuff) + return Phrases.Question_LaunchClearModsFound_Extra; + + //todo: translation lol + return "You are about to launch the game without mods. Do you want to clear your Patches folder?"; + } + + public static string GetTargetFolderPath(ModStorageSystem storageSystem, bool isBeta = false) + { + if (storageSystem == ModStorageSystem.Patches) + return isBeta ? PathManager.RrBetaPatchesFolderPath : PathManager.PatchesFolderPath; + + return isBeta ? PathManager.RrBetaMyStuffFolderPath : PathManager.MyStuffFolderPath; + } + + public static string GetInactiveFolderPath(ModStorageSystem storageSystem, bool isBeta = false) + { + var inactiveStorageSystem = storageSystem == ModStorageSystem.Patches ? ModStorageSystem.MyStuff : ModStorageSystem.Patches; + return GetTargetFolderPath(inactiveStorageSystem, isBeta); + } + + public static bool UsesPatches(IEnumerable? tags) => tags?.Any(tag => IsPatchesTag(tag.Title)) == true; + + public static bool IsPatchesTag(string? tagTitle) + { + var normalizedTitle = tagTitle?.Trim(); + if (string.IsNullOrWhiteSpace(normalizedTitle)) + return false; + + var titleOnly = normalizedTitle.Split(':', 2)[0].Trim(); + return normalizedTitle is not null + && ( + titleOnly.Equals("patch", StringComparison.OrdinalIgnoreCase) + || titleOnly.Equals("patches", StringComparison.OrdinalIgnoreCase) + ); + } +} diff --git a/WheelWizard/Services/PathManager.cs b/WheelWizard/Services/PathManager.cs index b53ea83f..998bce82 100644 --- a/WheelWizard/Services/PathManager.cs +++ b/WheelWizard/Services/PathManager.cs @@ -500,15 +500,17 @@ private static DirectoryMoveContentsResult MoveWheelWizardAppdataContents(string #endregion - //In case it is unclear, the mods folder is a folder with mods that are desired to be installed (if enabled) - //When launching we want to move the mods from the Mods folder to the MyStuff folder since that is the folder the game uses - //Also remember that mods may not be in a subfolder, all mod files must be located in /MyStuff directly + // In case it is unclear, the mods folder is a folder with mods that are desired to be installed (if enabled). + // When launching we sync those mods into the active loose-mod runtime folder. + // The runtime folder is either MyStuff (legacy) or patches, and all files must be located there directly. // Helper paths for folders used across multiple files public static string MyStuffFolderPath => Path.Combine(RiivolutionWhWzFolderPath, "RetroRewind6", "MyStuff"); + public static string PatchesFolderPath => Path.Combine(RiivolutionWhWzFolderPath, "RetroRewind6", "Patches"); public static string RrBetaFolderPath => Path.Combine(RiivolutionWhWzFolderPath, "RRBeta"); public static string RrBetaMyStuffFolderPath => Path.Combine(RrBetaFolderPath, "MyStuff"); + public static string RrBetaPatchesFolderPath => Path.Combine(RrBetaFolderPath, "Patches"); public static string GetModDirectoryPath(string modName) => Path.Combine(ModsFolderPath, modName); diff --git a/WheelWizard/Views/App.axaml.cs b/WheelWizard/Views/App.axaml.cs index bd1c454a..f17de132 100644 --- a/WheelWizard/Views/App.axaml.cs +++ b/WheelWizard/Views/App.axaml.cs @@ -5,11 +5,11 @@ using Microsoft.Extensions.Logging; using WheelWizard.AutoUpdating; using WheelWizard.MiiRendering.Services; -using WheelWizard.Settings; -using WheelWizard.Services.Launcher; using WheelWizard.Services; +using WheelWizard.Services.Launcher; using WheelWizard.Services.LiveData; using WheelWizard.Services.UrlProtocol; +using WheelWizard.Settings; using WheelWizard.Views.Behaviors; using WheelWizard.Views.Popups.Generic; using WheelWizard.WheelWizardData; @@ -79,15 +79,19 @@ private static StartupLaunchTarget GetStartupLaunchTarget() for (var i = 1; i < args.Length; i++) { var argument = args[i]; - if (argument.Equals("--launch", StringComparison.OrdinalIgnoreCase) || argument.Equals("-l", StringComparison.OrdinalIgnoreCase)) + if ( + argument.Equals("--launch", StringComparison.OrdinalIgnoreCase) || argument.Equals("-l", StringComparison.OrdinalIgnoreCase) + ) { if (i + 1 >= args.Length) continue; var launchTarget = args[++i]; - if (launchTarget.Equals("rr", StringComparison.OrdinalIgnoreCase) || - launchTarget.Equals("retrorewind", StringComparison.OrdinalIgnoreCase) || - launchTarget.Equals("retro-rewind", StringComparison.OrdinalIgnoreCase)) + if ( + launchTarget.Equals("rr", StringComparison.OrdinalIgnoreCase) + || launchTarget.Equals("retrorewind", StringComparison.OrdinalIgnoreCase) + || launchTarget.Equals("retro-rewind", StringComparison.OrdinalIgnoreCase) + ) { return StartupLaunchTarget.RetroRewind; } @@ -99,9 +103,11 @@ private static StartupLaunchTarget GetStartupLaunchTarget() continue; var launchTargetFromEquals = argument["--launch=".Length..].Trim(); - if (launchTargetFromEquals.Equals("rr", StringComparison.OrdinalIgnoreCase) || - launchTargetFromEquals.Equals("retrorewind", StringComparison.OrdinalIgnoreCase) || - launchTargetFromEquals.Equals("retro-rewind", StringComparison.OrdinalIgnoreCase)) + if ( + launchTargetFromEquals.Equals("rr", StringComparison.OrdinalIgnoreCase) + || launchTargetFromEquals.Equals("retrorewind", StringComparison.OrdinalIgnoreCase) + || launchTargetFromEquals.Equals("retro-rewind", StringComparison.OrdinalIgnoreCase) + ) { return StartupLaunchTarget.RetroRewind; } diff --git a/WheelWizard/Views/Layout.axaml b/WheelWizard/Views/Layout.axaml index d2886b41..30d4f98f 100644 --- a/WheelWizard/Views/Layout.axaml +++ b/WheelWizard/Views/Layout.axaml @@ -127,7 +127,7 @@ Text="{x:Static lang:Common.PageTitle_MyProfiles}" x:Name="MyProfilesButton" /> + Text="{x:Static lang:Common.PageTitle_Mods}" x:Name="ModsButton" /> diff --git a/WheelWizard/Views/Layout.axaml.cs b/WheelWizard/Views/Layout.axaml.cs index 9c41e809..f01537b6 100644 --- a/WheelWizard/Views/Layout.axaml.cs +++ b/WheelWizard/Views/Layout.axaml.cs @@ -9,6 +9,7 @@ using WheelWizard.Branding; using WheelWizard.Helpers; using WheelWizard.Resources.Languages; +using WheelWizard.Services; using WheelWizard.Services.LiveData; using WheelWizard.Settings; using WheelWizard.Settings.Types; @@ -105,6 +106,7 @@ protected override void OnLoaded(RoutedEventArgs e) { Title = BrandingService.Branding.DisplayName; TitleLabel.Text = BrandingService.Branding.DisplayName; + UpdateModsButtonText(); NavigationManager.NavigateTo(); } @@ -136,6 +138,15 @@ private void OnSettingChanged(Setting setting) if (setting == SettingsService.TESTING_MODE_ENABLED) UpdateTestingButtonVisibility(); + + if (setting == SettingsService.USE_PATCHES_SYSTEM) + UpdateModsButtonText(); + } + + private void UpdateModsButtonText() + { + var storageSystem = ModStorageSystemHelper.GetCurrent(SettingsService); + ModsButton.Text = ModStorageSystemHelper.GetDisplayName(storageSystem); } public void NavigateToPage(UserControl page) diff --git a/WheelWizard/Views/Pages/ModsPage.axaml b/WheelWizard/Views/Pages/ModsPage.axaml index 2dfc4105..eb9dedb0 100644 --- a/WheelWizard/Views/Pages/ModsPage.axaml +++ b/WheelWizard/Views/Pages/ModsPage.axaml @@ -6,7 +6,7 @@ xmlns:components="clr-namespace:WheelWizard.Views.Components" xmlns:patterns="clr-namespace:WheelWizard.Views.Patterns" xmlns:pages="clr-namespace:WheelWizard.Views.Pages" - mc:Ignorable="d" d:DesignWidth="656" d:DesignHeight="876" + mc:Ignorable="d" d:DesignWidth="450" d:DesignHeight="876" x:Class="WheelWizard.Views.Pages.ModsPage" x:DataType="pages:ModsPage"> @@ -20,18 +20,21 @@ - - + Orientation="Vertical" x:Name="TopBarButtons" Spacing="3"> + + diff --git a/WheelWizard/Views/Pages/ModsPage.axaml.cs b/WheelWizard/Views/Pages/ModsPage.axaml.cs index a051c7b2..52fad886 100644 --- a/WheelWizard/Views/Pages/ModsPage.axaml.cs +++ b/WheelWizard/Views/Pages/ModsPage.axaml.cs @@ -21,9 +21,14 @@ public record ModListItem(Mod Mod, bool IsLowest, bool IsHighest); public partial class ModsPage : UserControlBase, INotifyPropertyChanged { + private IDisposable? _settingsSignalSubscription; + [Inject] private ISettingsManager SettingsService { get; set; } = null!; + [Inject] + private ISettingsSignalBus SettingsSignalBus { get; set; } = null!; + public ModManager ModManager => ModManager.Instance; public ObservableCollection Mods => @@ -35,6 +40,21 @@ public partial class ModsPage : UserControlBase, INotifyPropertyChanged )) ); + public bool UsePatches + { + get => SettingsService.Get(SettingsService.USE_PATCHES_SYSTEM); + set + { + if (UsePatches == value) + return; + + SettingsService.Set(SettingsService.USE_PATCHES_SYSTEM, value); + RefreshStorageMode(); + } + } + + public string StoragePageTitle => ModStorageSystemHelper.GetDisplayName(ModStorageSystemHelper.GetCurrent(SettingsService)); + private bool _hasMods; public bool HasMods @@ -70,8 +90,10 @@ public ModsPage() DataContext = this; Focusable = true; ModManager.PropertyChanged += OnModsChanged; + _settingsSignalSubscription = SettingsSignalBus.Subscribe(OnSettingSignal); ModManager.ReloadAsync(); SetModsViewVariant(); + RefreshStorageMode(); // Apply priority edits as soon as the user clicks anywhere outside the textbox. AddHandler(PointerPressedEvent, OnPagePointerPressed, RoutingStrategies.Tunnel, true); @@ -82,6 +104,14 @@ public ModsPage() PointerCaptureLost += OnDragPointerCaptureLost; } + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + ModManager.PropertyChanged -= OnModsChanged; + _settingsSignalSubscription?.Dispose(); + _settingsSignalSubscription = null; + base.OnDetachedFromVisualTree(e); + } + private void OnModsChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(ModManager.Mods)) @@ -101,6 +131,18 @@ private void OnModsChanged() UpdateEnableAllCheckboxState(); } + private void OnSettingSignal(SettingChangedSignal signal) + { + if (signal.Setting == SettingsService.USE_PATCHES_SYSTEM) + RefreshStorageMode(); + } + + private void RefreshStorageMode() + { + OnPropertyChanged(nameof(UsePatches)); + OnPropertyChanged(nameof(StoragePageTitle)); + } + private void UpdateEnableAllCheckboxState() { EnableAllCheckbox.IsChecked = !ModManager.Mods.Select(mod => mod.IsEnabled).Contains(false); diff --git a/WheelWizard/Views/Patterns/ModBrowserListItem.axaml b/WheelWizard/Views/Patterns/ModBrowserListItem.axaml index 1d8014e6..1151b2ab 100644 --- a/WheelWizard/Views/Patterns/ModBrowserListItem.axaml +++ b/WheelWizard/Views/Patterns/ModBrowserListItem.axaml @@ -9,8 +9,9 @@ ModAuthor="Me"/> + ModTitle="Some other mod with quite a long title" + UsesPatches="true" + ModAuthor="somebody else with also a long name and the name"/> + + + @@ -127,6 +137,16 @@ + + + +