From cdb99ce9749ad52d00cfd7c108cbbb319e77fe2f Mon Sep 17 00:00:00 2001 From: nhruo Date: Thu, 27 Apr 2023 15:02:27 +0300 Subject: [PATCH 01/19] wip --- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index 0730ed498..d9751d790 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -16,6 +16,8 @@ class OuiModToggler : OuiGenericMenu, OuiModOptions.ISubmenu { private List allMods; // list of currently blacklisted mods private HashSet blacklistedMods; + // list of currently favorited mods + private HashSet favoritedMods; // list of blacklisted mods when the menu was open private HashSet blacklistedModsOriginal; @@ -240,7 +242,8 @@ protected override void addOptionsToMenu(TextMenu menu) { // reset the mods list allMods = new List(); blacklistedMods = new HashSet(); - + favoritedMods = new HashSet(); + string[] files; bool headerInserted; @@ -325,6 +328,15 @@ private void addFileToMenu(TextMenu menu, string file) { } updateHighlightedMods(); + }).AltPressed(() => { + if (favoritedMods.Contains(file)) { + favoritedMods.Remove(file); + } else { + favoritedMods.Add(file); + } + + modToggles[file].Container.HighlightColor = Color.Pink; + })); allMods.Add(file); From d744c7c808d67381d2e48b811eda5061b9d6c37e Mon Sep 17 00:00:00 2001 From: nhruo Date: Thu, 27 Apr 2023 17:38:44 +0300 Subject: [PATCH 02/19] wip: added the ability to mark mods as fav --- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 92 ++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 6 deletions(-) diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index d9751d790..2dbd9b53a 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -18,6 +18,9 @@ class OuiModToggler : OuiGenericMenu, OuiModOptions.ISubmenu { private HashSet blacklistedMods; // list of currently favorited mods private HashSet favoritedMods; + // dictionary mapping between the dependence and the dependents + private Dictionary> favoritesDependenciesMods; + // list of blacklisted mods when the menu was open private HashSet blacklistedModsOriginal; @@ -27,6 +30,8 @@ class OuiModToggler : OuiGenericMenu, OuiModOptions.ISubmenu { private TextMenuExt.SubHeaderExt restartMessage2; private Dictionary modYamls; + + private Dictionary modToggles; private Task modLoadingTask; @@ -243,7 +248,8 @@ protected override void addOptionsToMenu(TextMenu menu) { allMods = new List(); blacklistedMods = new HashSet(); favoritedMods = new HashSet(); - + favoritesDependenciesMods = new Dictionary>(); + string[] files; bool headerInserted; @@ -330,13 +336,12 @@ private void addFileToMenu(TextMenu menu, string file) { updateHighlightedMods(); }).AltPressed(() => { if (favoritedMods.Contains(file)) { - favoritedMods.Remove(file); + removeFromFavorites(file); } else { - favoritedMods.Add(file); + addToFavorites(file); } - modToggles[file].Container.HighlightColor = Color.Pink; - + updateHighlightedMods(); })); allMods.Add(file); @@ -350,7 +355,15 @@ private void addFileToMenu(TextMenu menu, string file) { private void updateHighlightedMods() { // adjust the mods' color if they are required dependencies for other mods foreach (KeyValuePair toggle in modToggles) { - ((patch_TextMenu.patch_Option) (object) toggle.Value).UnselectedColor = modHasDependencies(toggle.Key) ? Color.Goldenrod : Color.White; + Color unselectedColor = Color.White; + if (favoritedMods.Contains(toggle.Key)) { + unselectedColor = Color.DeepPink; + } else if (favoritesDependenciesMods.ContainsKey(toggle.Key)) { + unselectedColor = Color.LightPink; + } else if (modHasDependencies(toggle.Key)) { + unselectedColor = Color.Goldenrod; + } + ((patch_TextMenu.patch_Option) (object) toggle.Value).UnselectedColor = unselectedColor; } // turn the warning text about restarting/overwriting blacklist.txt orange/red if something was changed (so pressing Back will trigger a restart). @@ -428,6 +441,36 @@ private void removeFromBlacklist(string file) { } } + private void addToFavorites(string file) { + favoritedMods.Add(file); + + // I guess we silently fail? + if (TryGetModDependenciesFileNames(file, out List dependenciesFileNames)) { + foreach (string dependenciesFileName in dependenciesFileNames) { + if (!favoritesDependenciesMods.ContainsKey(dependenciesFileName)) { + favoritesDependenciesMods[dependenciesFileName] = new HashSet(); + } + + favoritesDependenciesMods[dependenciesFileName].Add(file); + } + } + } + + private void removeFromFavorites(string file) { + favoritedMods.Remove(file); + + if (TryGetModDependenciesFileNames(file, out List dependenciesFileNames)) { + foreach (string dependenciesFileName in dependenciesFileNames) { + if (favoritesDependenciesMods.ContainsKey(dependenciesFileName)) { + favoritesDependenciesMods[dependenciesFileName].Remove(file); + if (favoritesDependenciesMods[dependenciesFileName].Count == 0) { + favoritesDependenciesMods.Remove(dependenciesFileName); + } + } + } + } + } + private void onBackPressed(Overworld overworld) { // "back" only works if the loading is done. if (modLoadingTask == null || modLoadingTask.IsCompleted || modLoadingTask.IsCanceled || modLoadingTask.IsFaulted) { @@ -454,6 +497,43 @@ private void onBackPressed(Overworld overworld) { } } + private bool TryGetModDependenciesFileNames(string modFilename, out List dependenciesFileNames) { + // TODO: This nested loop is taken from the removeFromBlacklist, this could be replaced by creating a HashMap between modName to EverestModule, + // Right now it seems like we are ?needlessly? iterating n^2 times. + + // iterate over all the dependencies + if (modYamls.TryGetValue(modFilename, out EverestModuleMetadata[] metadatas)) { + dependenciesFileNames = new List(); + + foreach (EverestModuleMetadata metadata in metadatas) { + // iterate over each loaded mode to ensure its present + foreach (string dependencyName in metadata.Dependencies.Select((dep) => dep.Name)) { + KeyValuePair? found = null; + foreach (KeyValuePair candidateMetadatas in modYamls) { + foreach (EverestModuleMetadata candidateMetadata in candidateMetadatas.Value) { + if (candidateMetadata.Name == dependencyName) { + // we found it! + if (found == null || found.Value.Value.Version < candidateMetadata.Version) { + found = new KeyValuePair(candidateMetadatas.Key, candidateMetadata); + } + } + } + } + + if (found != null) { + dependenciesFileNames.Add(found.Value.Key); + } + } + } + + return true; + } + + + dependenciesFileNames = null; + return false; + } + private bool modHasDependencies(string modFilename) { if (modYamls.TryGetValue(modFilename, out EverestModuleMetadata[] metadatas)) { // this mod has a yaml, check all of the metadata entries (99% of the time there is one only). From f369d47c359e39bec8077d948194983f0c43b989 Mon Sep 17 00:00:00 2001 From: nhruo Date: Thu, 27 Apr 2023 19:43:09 +0300 Subject: [PATCH 03/19] WIP: added basic favorite support --- Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs | 19 +++- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 113 ++++++++++++++----- 2 files changed, 102 insertions(+), 30 deletions(-) diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs index bf98cc23d..c2bbcf46d 100644 --- a/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs +++ b/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs @@ -34,6 +34,12 @@ public static class Loader { /// public static ReadOnlyCollection Blacklist => _Blacklist?.AsReadOnly(); + /// + /// The path to the Everest /Mods/favorites.txt file. + /// + public static string PathFavorites { get; internal set; } + internal static HashSet Favorites = new HashSet(); + /// /// The path to the Everest /Mods/temporaryblacklist.txt file. /// @@ -152,6 +158,15 @@ internal static void LoadAuto() { } } + PathFavorites = Path.Combine(PathMods, "favorites.txt"); + if (File.Exists(PathFavorites)) { + Favorites = new HashSet(File.ReadAllLines(PathFavorites).Select(l => (l.StartsWith("#") ? "" : l).Trim())); + } else { + using (StreamWriter writer = File.CreateText(PathFavorites)) { + writer.WriteLine("# This is the favorites list. Lines starting with # are ignored."); + } + } + Stopwatch watch = Stopwatch.StartNew(); enforceOptionalDependencies = true; @@ -560,10 +575,10 @@ public static void LoadModAssembly(EverestModuleMetadata meta, Assembly asm) { Logger.Log(LogLevel.Warn, "loader", $"Skipping type '{type.FullName}' likely depending on optional dependency: {e}"); } - if (mod != null) { + if (mod != null) { mod.Metadata = meta; mod.Register(); - } + } } // Warn if we didn't find a module, as that could indicate an oversight from the developer diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index 2dbd9b53a..c93035be2 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -16,16 +16,20 @@ class OuiModToggler : OuiGenericMenu, OuiModOptions.ISubmenu { private List allMods; // list of currently blacklisted mods private HashSet blacklistedMods; + // list of blacklisted mods when the menu was open + private HashSet blacklistedModsOriginal; + // list of currently favorited mods private HashSet favoritedMods; + // list of favorited mods when the menu was open + private HashSet favoritedModsOriginal; // dictionary mapping between the dependence and the dependents private Dictionary> favoritesDependenciesMods; - // list of blacklisted mods when the menu was open - private HashSet blacklistedModsOriginal; - private bool toggleDependencies = true; + private bool toggleFavorites = false; + private TextMenuExt.SubHeaderExt restartMessage1; private TextMenuExt.SubHeaderExt restartMessage2; @@ -224,6 +228,13 @@ protected override void addOptionsToMenu(TextMenu menu) { menu.Add(new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_DISABLEALL")).Pressed(() => { blacklistedMods.Clear(); foreach (KeyValuePair toggle in modToggles) { + bool isFavorite = favoritedMods.Contains(toggle.Key) || favoritesDependenciesMods.ContainsKey(toggle.Key); + if (!toggleFavorites && isFavorite) { + // TODO: I don't like the continue keywords but the logic statement we want is kinda odd so for now it will do + // toggleFavorites || (!toggleFavorites && !isFavorite) + continue; + } + toggle.Value.Index = 0; blacklistedMods.Add(toggle.Key); } @@ -234,13 +245,20 @@ protected override void addOptionsToMenu(TextMenu menu) { TextMenu.Item toggleDependenciesButton; menu.Add(toggleDependenciesButton = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS"), true) .Change(value => toggleDependencies = value)); - toggleDependenciesButton.AddDescription(menu, Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE2")); toggleDependenciesButton.AddDescription(menu, Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE1")); + // TODO: add localization support + menu.Add(toggleDependenciesButton = new TextMenu.OnOff("Toggle Favorites", toggleFavorites) + .Change(value => toggleFavorites = value)); + + + // "cancel" button to leave the screen without saving menu.Add(new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_CANCEL")).Pressed(() => { blacklistedMods = blacklistedModsOriginal; + favoritedMods = favoritedModsOriginal; + favoritesDependenciesMods = null; onBackPressed(Overworld); })); @@ -300,19 +318,17 @@ protected override void addOptionsToMenu(TextMenu menu) { // sort the mods list alphabetically, for output in the blacklist.txt file later. allMods.Sort((a, b) => a.ToLowerInvariant().CompareTo(b.ToLowerInvariant())); + + // clone the list to be able to check if the list changed when leaving the menu. + blacklistedModsOriginal = new HashSet(blacklistedMods); + favoritedModsOriginal = new HashSet(favoritedMods); - // adjust the mods' color if they are required dependencies for other mods - foreach (KeyValuePair toggle in modToggles) { - if (modHasDependencies(toggle.Key)) { - ((patch_TextMenu.patch_Option) (object) toggle.Value).UnselectedColor = Color.Goldenrod; - } - } + // set colors to mods listings + updateHighlightedMods(); // snap the menu so that it doesn't show a scroll up. menu.Y = menu.ScrollTargetY; - // clone the list to be able to check if the list changed when leaving the menu. - blacklistedModsOriginal = new HashSet(blacklistedMods); // loading is done! modLoadingTask = null; @@ -325,6 +341,7 @@ private void addFileToMenu(TextMenu menu, string file) { TextMenu.OnOff option; bool enabled = !Everest.Loader.Blacklist.Contains(file); + bool favorite = Everest.Loader.Favorites.Contains(file); menu.Add(option = (TextMenu.OnOff) new TextMenu.OnOff(file.Length > 40 ? file.Substring(0, 40) + "..." : file, enabled) .Change(b => { if (b) { @@ -348,6 +365,9 @@ private void addFileToMenu(TextMenu menu, string file) { if (!enabled) { blacklistedMods.Add(file); } + if (favorite) { + addToFavorites(file); + } modToggles[file] = option; } @@ -441,32 +461,53 @@ private void removeFromBlacklist(string file) { } } - private void addToFavorites(string file) { - favoritedMods.Add(file); + private void addToFavorites(string modFileName) { + favoritedMods.Add(modFileName); // I guess we silently fail? - if (TryGetModDependenciesFileNames(file, out List dependenciesFileNames)) { + if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { foreach (string dependenciesFileName in dependenciesFileNames) { - if (!favoritesDependenciesMods.ContainsKey(dependenciesFileName)) { - favoritesDependenciesMods[dependenciesFileName] = new HashSet(); - } + addToFavoritesDependencies(dependenciesFileName, modFileName); + } + } + } + + private void addToFavoritesDependencies(string modFileName, string dependentModeFileName) { + if (!favoritesDependenciesMods.ContainsKey(modFileName)) { + favoritesDependenciesMods[modFileName] = new HashSet(); + } + + favoritesDependenciesMods[modFileName].Add(dependentModeFileName); - favoritesDependenciesMods[dependenciesFileName].Add(file); + // I guess we silently fail? + if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { + foreach (string dependenciesFileName in dependenciesFileNames) { + addToFavoritesDependencies(dependenciesFileName, modFileName); } } } - private void removeFromFavorites(string file) { - favoritedMods.Remove(file); + private void removeFromFavorites(string modFileName) { + favoritedMods.Remove(modFileName); - if (TryGetModDependenciesFileNames(file, out List dependenciesFileNames)) { + if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { foreach (string dependenciesFileName in dependenciesFileNames) { - if (favoritesDependenciesMods.ContainsKey(dependenciesFileName)) { - favoritesDependenciesMods[dependenciesFileName].Remove(file); - if (favoritesDependenciesMods[dependenciesFileName].Count == 0) { - favoritesDependenciesMods.Remove(dependenciesFileName); - } - } + removeFromFavoritesDependencies(dependenciesFileName, modFileName); + } + } + } + + private void removeFromFavoritesDependencies(string modFileName, string dependentModeFileName) { + if (favoritesDependenciesMods.ContainsKey(modFileName)) { + favoritesDependenciesMods[modFileName].Remove(dependentModeFileName); + if (favoritesDependenciesMods[modFileName].Count == 0) { + favoritesDependenciesMods.Remove(modFileName); + } + } + + if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { + foreach (string dependenciesFileName in dependenciesFileNames) { + removeFromFavoritesDependencies(dependenciesFileName, modFileName); } } } @@ -474,6 +515,18 @@ private void removeFromFavorites(string file) { private void onBackPressed(Overworld overworld) { // "back" only works if the loading is done. if (modLoadingTask == null || modLoadingTask.IsCompleted || modLoadingTask.IsCanceled || modLoadingTask.IsFaulted) { + if (!favoritedModsOriginal.SetEquals(favoritedMods)) { + Everest.Loader.Favorites = favoritedMods; + using (StreamWriter writer = File.CreateText(Everest.Loader.PathFavorites)) { + // header + writer.WriteLine("# This is the favorites list. Lines starting with # are ignored."); + writer.WriteLine(""); + + foreach (string mod in favoritedMods) { + writer.WriteLine(mod); + } + } + } if (blacklistedModsOriginal.SetEquals(blacklistedMods)) { // nothing changed, go back to Mod Options overworld.Goto(); @@ -567,6 +620,10 @@ public override IEnumerator Leave(Oui next) { modToggles = null; modLoadingTask = null; toggleDependencies = true; + toggleFavorites = false; + favoritedMods = null; + favoritedModsOriginal = null; + favoritesDependenciesMods = null; } } } From ba157d7331e5aed399117cef64386c6f88396d7f Mon Sep 17 00:00:00 2001 From: nhruo Date: Thu, 27 Apr 2023 20:03:15 +0300 Subject: [PATCH 04/19] WIP: feat(OuiModToggler): Favorite system Added support for saving favorite list to disk --- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index c93035be2..58392bcbe 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -251,7 +251,8 @@ protected override void addOptionsToMenu(TextMenu menu) { // TODO: add localization support menu.Add(toggleDependenciesButton = new TextMenu.OnOff("Toggle Favorites", toggleFavorites) .Change(value => toggleFavorites = value)); - + // TODO: Add an better indecation for how to use the favorite system + toggleDependenciesButton.AddDescription(menu, "Press the 'Journal key' in order to add or remove items from the favorite list."); // "cancel" button to leave the screen without saving From b57390bb3795cfafaf8f64c24ec9b4c4ca1bca15 Mon Sep 17 00:00:00 2001 From: nhruo Date: Thu, 27 Apr 2023 20:19:56 +0300 Subject: [PATCH 05/19] fix(OuiModToggler): typo --- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index 58392bcbe..61dd524ea 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -473,12 +473,12 @@ private void addToFavorites(string modFileName) { } } - private void addToFavoritesDependencies(string modFileName, string dependentModeFileName) { + private void addToFavoritesDependencies(string modFileName, string dependentmodFileName) { if (!favoritesDependenciesMods.ContainsKey(modFileName)) { favoritesDependenciesMods[modFileName] = new HashSet(); } - favoritesDependenciesMods[modFileName].Add(dependentModeFileName); + favoritesDependenciesMods[modFileName].Add(dependentmodFileName); // I guess we silently fail? if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { @@ -498,9 +498,9 @@ private void removeFromFavorites(string modFileName) { } } - private void removeFromFavoritesDependencies(string modFileName, string dependentModeFileName) { + private void removeFromFavoritesDependencies(string modFileName, string dependentmodFileName) { if (favoritesDependenciesMods.ContainsKey(modFileName)) { - favoritesDependenciesMods[modFileName].Remove(dependentModeFileName); + favoritesDependenciesMods[modFileName].Remove(dependentmodFileName); if (favoritesDependenciesMods[modFileName].Count == 0) { favoritesDependenciesMods.Remove(modFileName); } @@ -560,7 +560,7 @@ private bool TryGetModDependenciesFileNames(string modFilename, out List dependenciesFileNames = new List(); foreach (EverestModuleMetadata metadata in metadatas) { - // iterate over each loaded mode to ensure its present + // iterate over each loaded mod to ensure its present foreach (string dependencyName in metadata.Dependencies.Select((dep) => dep.Name)) { KeyValuePair? found = null; foreach (KeyValuePair candidateMetadatas in modYamls) { From 9024286c71d42d144dec6adec6db9df779cd916e Mon Sep 17 00:00:00 2001 From: nhruo Date: Thu, 27 Apr 2023 20:31:08 +0300 Subject: [PATCH 06/19] fix(OuiModToggler): cyclic mod dependencies Now cyclic mod dependencies shouldn't loop forever on `addToFavoritesDependencies` --- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index 61dd524ea..de6e37f6d 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -319,7 +319,7 @@ protected override void addOptionsToMenu(TextMenu menu) { // sort the mods list alphabetically, for output in the blacklist.txt file later. allMods.Sort((a, b) => a.ToLowerInvariant().CompareTo(b.ToLowerInvariant())); - + // clone the list to be able to check if the list changed when leaving the menu. blacklistedModsOriginal = new HashSet(blacklistedMods); favoritedModsOriginal = new HashSet(favoritedMods); @@ -473,12 +473,17 @@ private void addToFavorites(string modFileName) { } } - private void addToFavoritesDependencies(string modFileName, string dependentmodFileName) { + private void addToFavoritesDependencies(string modFileName, string dependentModFileName) { if (!favoritesDependenciesMods.ContainsKey(modFileName)) { favoritesDependenciesMods[modFileName] = new HashSet(); } - favoritesDependenciesMods[modFileName].Add(dependentmodFileName); + // If we have a cyclical dependencies we want to stop after the first occurrence of a mod. + if (favoritesDependenciesMods[modFileName].Contains(dependentModFileName)) { + return; + } + + favoritesDependenciesMods[modFileName].Add(dependentModFileName); // I guess we silently fail? if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { @@ -498,17 +503,17 @@ private void removeFromFavorites(string modFileName) { } } - private void removeFromFavoritesDependencies(string modFileName, string dependentmodFileName) { + private void removeFromFavoritesDependencies(string modFileName, string dependentModFileName) { if (favoritesDependenciesMods.ContainsKey(modFileName)) { - favoritesDependenciesMods[modFileName].Remove(dependentmodFileName); + favoritesDependenciesMods[modFileName].Remove(dependentModFileName); if (favoritesDependenciesMods[modFileName].Count == 0) { favoritesDependenciesMods.Remove(modFileName); } - } - if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { - foreach (string dependenciesFileName in dependenciesFileNames) { - removeFromFavoritesDependencies(dependenciesFileName, modFileName); + if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { + foreach (string dependenciesFileName in dependenciesFileNames) { + removeFromFavoritesDependencies(dependenciesFileName, modFileName); + } } } } From 1dd97e738a494dd3ec7a2b74372e16dd93035174 Mon Sep 17 00:00:00 2001 From: nhruo Date: Fri, 28 Apr 2023 13:21:34 +0300 Subject: [PATCH 07/19] fix(favorite): fixed favorite dependencies Fixed issue with complex dependencies Now (should) support DAGs and cyclic graphs --- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 31 +++++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index de6e37f6d..5055ece84 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -23,7 +23,7 @@ class OuiModToggler : OuiGenericMenu, OuiModOptions.ISubmenu { private HashSet favoritedMods; // list of favorited mods when the menu was open private HashSet favoritedModsOriginal; - // dictionary mapping between the dependence and the dependents + // dictionary mapping between the dependencies and the dependents private Dictionary> favoritesDependenciesMods; private bool toggleDependencies = true; @@ -464,6 +464,7 @@ private void removeFromBlacklist(string file) { private void addToFavorites(string modFileName) { favoritedMods.Add(modFileName); + Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{modFileName} was added to favorites"); // I guess we silently fail? if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { @@ -478,41 +479,49 @@ private void addToFavoritesDependencies(string modFileName, string dependentModF favoritesDependenciesMods[modFileName] = new HashSet(); } - // If we have a cyclical dependencies we want to stop after the first occurrence of a mod. - if (favoritesDependenciesMods[modFileName].Contains(dependentModFileName)) { + // If we have a cyclical dependencies we want to stop after the first occurrence of a mod, or if somehow we reach ourself. + if (favoritesDependenciesMods[modFileName].Contains(dependentModFileName) || modFileName.Equals(dependentModFileName)) { return; } + // Add dependent mod favoritesDependenciesMods[modFileName].Add(dependentModFileName); + Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{modFileName} was added as a favorite dependency of {dependentModFileName}"); - // I guess we silently fail? + + // we want to add A as the favorite mod that is dependent on all of modFileName dependencies as well if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { - foreach (string dependenciesFileName in dependenciesFileNames) { - addToFavoritesDependencies(dependenciesFileName, modFileName); + foreach (string dependencyFileName in dependenciesFileNames) { + addToFavoritesDependencies(dependencyFileName, dependentModFileName); } } } private void removeFromFavorites(string modFileName) { favoritedMods.Remove(modFileName); + Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{modFileName} was removed from favorites"); if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { - foreach (string dependenciesFileName in dependenciesFileNames) { - removeFromFavoritesDependencies(dependenciesFileName, modFileName); + foreach (string dependencyFileName in dependenciesFileNames) { + removeFromFavoritesDependencies(dependencyFileName, modFileName); } } } private void removeFromFavoritesDependencies(string modFileName, string dependentModFileName) { - if (favoritesDependenciesMods.ContainsKey(modFileName)) { + if (favoritesDependenciesMods.ContainsKey(modFileName) && favoritesDependenciesMods[modFileName].Contains(dependentModFileName)) { + favoritesDependenciesMods[modFileName].Remove(dependentModFileName); + Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{modFileName} was removed from being a favorite dependency of {dependentModFileName}"); + if (favoritesDependenciesMods[modFileName].Count == 0) { favoritesDependenciesMods.Remove(modFileName); + Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{modFileName} is no longer a favorite dependency"); } if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { - foreach (string dependenciesFileName in dependenciesFileNames) { - removeFromFavoritesDependencies(dependenciesFileName, modFileName); + foreach (string dependencyFileName in dependenciesFileNames) { + removeFromFavoritesDependencies(dependencyFileName, dependentModFileName); } } } From 912cfd7f08cd7536eed8d2db981f9ef167bb1b91 Mon Sep 17 00:00:00 2001 From: nhruo Date: Fri, 28 Apr 2023 14:24:10 +0300 Subject: [PATCH 08/19] fix(OuiModToggler): Add separate menu item for the favorite system --- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index 5055ece84..3386ef3b0 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -248,11 +248,13 @@ protected override void addOptionsToMenu(TextMenu menu) { toggleDependenciesButton.AddDescription(menu, Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE2")); toggleDependenciesButton.AddDescription(menu, Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE1")); + TextMenu.Item toggleFavoritesButton; // TODO: add localization support - menu.Add(toggleDependenciesButton = new TextMenu.OnOff("Toggle Favorites", toggleFavorites) + menu.Add(toggleFavoritesButton = new TextMenu.OnOff("Toggle Favorites", toggleFavorites) .Change(value => toggleFavorites = value)); - // TODO: Add an better indecation for how to use the favorite system - toggleDependenciesButton.AddDescription(menu, "Press the 'Journal key' in order to add or remove items from the favorite list."); + // TODO: Add a better indication for how to use the favorite system + toggleFavoritesButton.AddDescription(menu, "Press the 'Journal key' in order to add or remove items from the favorite list."); + // "cancel" button to leave the screen without saving From 014b1c1cc42c2494cff8c3802d546fcf600a1de9 Mon Sep 17 00:00:00 2001 From: nhruo Date: Fri, 28 Apr 2023 15:38:36 +0300 Subject: [PATCH 09/19] fix(favorite): fix mode could depend on itself --- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index 3386ef3b0..26996c0f4 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -477,15 +477,16 @@ private void addToFavorites(string modFileName) { } private void addToFavoritesDependencies(string modFileName, string dependentModFileName) { - if (!favoritesDependenciesMods.ContainsKey(modFileName)) { - favoritesDependenciesMods[modFileName] = new HashSet(); - } - // If we have a cyclical dependencies we want to stop after the first occurrence of a mod, or if somehow we reach ourself. - if (favoritesDependenciesMods[modFileName].Contains(dependentModFileName) || modFileName.Equals(dependentModFileName)) { + if ((favoritesDependenciesMods.ContainsKey(modFileName) && favoritesDependenciesMods[modFileName].Contains(dependentModFileName)) + || modFileName.Equals(dependentModFileName)) { return; } + if (!favoritesDependenciesMods.ContainsKey(modFileName)) { + favoritesDependenciesMods[modFileName] = new HashSet(); + } + // Add dependent mod favoritesDependenciesMods[modFileName].Add(dependentModFileName); Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{modFileName} was added as a favorite dependency of {dependentModFileName}"); From 5d295f2e9864798b0bb56ece94e5c4071e2ce90d Mon Sep 17 00:00:00 2001 From: nhruo Date: Fri, 28 Apr 2023 23:00:15 +0300 Subject: [PATCH 10/19] WIP: feat(favorite): Added Keybindings to hints --- Celeste.Mod.mm/Content/Dialog/English.txt | 3 + Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 20 ++++-- Celeste.Mod.mm/Mod/UI/TextMenuExt.cs | 88 +++++++++++++++++++++++ 3 files changed, 107 insertions(+), 4 deletions(-) diff --git a/Celeste.Mod.mm/Content/Dialog/English.txt b/Celeste.Mod.mm/Content/Dialog/English.txt index 3a2e90318..785e08248 100755 --- a/Celeste.Mod.mm/Content/Dialog/English.txt +++ b/Celeste.Mod.mm/Content/Dialog/English.txt @@ -260,6 +260,9 @@ MODOPTIONS_MODTOGGLE_TOGGLEDEPS= Toggle Dependencies Automatically MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE1= When you enable a mod, all its dependencies will be enabled. MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE2= When you disable a mod, all mods that depend on it will be disabled. + MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES= Toggle Favorites + MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE1= Press + MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE2= to add or remove items from the favorite list. MODOPTIONS_MODTOGGLE_MESSAGE_1= If you enable or disable mods, your blacklist.txt will be replaced, MODOPTIONS_MODTOGGLE_MESSAGE_2= and Celeste will restart to apply changes. MODOPTIONS_MODTOGGLE_MESSAGE_3= Highlighted mods are used by other enabled mods as a dependency. diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index 26996c0f4..9aa8542c0 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -249,12 +249,24 @@ protected override void addOptionsToMenu(TextMenu menu) { toggleDependenciesButton.AddDescription(menu, Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE1")); TextMenu.Item toggleFavoritesButton; - // TODO: add localization support - menu.Add(toggleFavoritesButton = new TextMenu.OnOff("Toggle Favorites", toggleFavorites) + menu.Add(toggleFavoritesButton = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES"), toggleFavorites) .Change(value => toggleFavorites = value)); - // TODO: Add a better indication for how to use the favorite system - toggleFavoritesButton.AddDescription(menu, "Press the 'Journal key' in order to add or remove items from the favorite list."); + TextMenuExt.EaseInSubMenuWithInputs favoriteToolTip = new TextMenuExt.EaseInSubMenuWithInputs( + new object[] { Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE1") + " ", + Input.MenuJournal, " " + Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE2") }, + Input.GuiInputController(), false, menu) { TextColor = Color.Gray }; + + menu.Add(favoriteToolTip); + + toggleFavoritesButton.OnEnter += delegate { + // make the description appear. + favoriteToolTip.FadeVisible = true; + }; + toggleFavoritesButton.OnLeave += delegate { + // make the description disappear. + favoriteToolTip.FadeVisible = false; + }; // "cancel" button to leave the screen without saving diff --git a/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs b/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs index 753c4d8fb..e311432f0 100644 --- a/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs +++ b/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs @@ -1353,5 +1353,93 @@ public void Dispose() { } } + public class SubMenuWithInputs : TextMenu.Item { + public object[] Items { get; set; } + public bool ControllerMode { get; set; } + public Color TextColor { get; set; } = Color.Gray; + public Color ButtonColor { get; set; } = Color.White; + public float Alpha { get; set; } = 1f; + public float Scale { get; set; } = 0.6f; + + public SubMenuWithInputs(object[] items, bool controllerMode) { + this.Items = items; + this.ControllerMode = controllerMode; + } + + + public override float Height() { + return ActiveFont.LineHeight; + } + + public override void Render(Vector2 position, bool highlighted) { + Vector2 lineOffset = position; + Vector2 justify = new Vector2(0f, 0.5f); + float strokeAlpha = Alpha * Alpha * Alpha; + + for (int i = 0; i < this.Items.Count(); i++) { + if (this.Items[i] is string) { + ActiveFont.DrawOutline(this.Items[i] as string, lineOffset, justify, Vector2.One * Scale, TextColor * Alpha, 2f, Color.Black * strokeAlpha); + lineOffset.X += ActiveFont.Measure(this.Items[i] as string).X * Scale; + } else if (this.Items[i] is VirtualButton) { + VirtualButton virtualButton = this.Items[i] as VirtualButton; + MTexture buttonTexture = null; + + if (this.ControllerMode) { + buttonTexture = Input.GuiButton(virtualButton, Input.PrefixMode.Attached, "controls/keyboard/oemquestion"); + } else if (virtualButton.Binding.Keyboard.Count > 0) { + buttonTexture = Input.GuiKey(virtualButton.Binding.Keyboard[0], "controls/keyboard/oemquestion"); + } else { + buttonTexture = Input.GuiKey(Microsoft.Xna.Framework.Input.Keys.None, "controls/keyboard/oemquestion"); + } + + buttonTexture.DrawJustified(lineOffset, justify, ButtonColor * strokeAlpha, Scale); + lineOffset.X += (float) buttonTexture.Width * Scale; + } + } + } + } + + public class EaseInSubMenuWithInputs : SubMenuWithInputs { + + + + /// + /// Toggling this will make the header ease in/out. + /// + public bool FadeVisible { get; set; } = true; + + private float uneasedAlpha; + private TextMenu containingMenu; + + public EaseInSubMenuWithInputs(object[] items, bool controllerMode, bool initiallyVisible, TextMenu containingMenu) : base(items, controllerMode) { + this.containingMenu = containingMenu; + + FadeVisible = initiallyVisible; + Alpha = FadeVisible ? 1 : 0; + uneasedAlpha = Alpha; + + } + + // the fade has to take into account the item spacing as well, or the other options will abruptly shift up when Visible is switched to false. + public override float Height() => MathHelper.Lerp(-containingMenu.ItemSpacing, base.Height(), Alpha); + + public override void Update() { + base.Update(); + + // gradually make the sub-header fade in or out. (~333ms fade) + float targetAlpha = FadeVisible ? 1 : 0; + if (uneasedAlpha != targetAlpha) { + uneasedAlpha = Calc.Approach(uneasedAlpha, targetAlpha, Engine.RawDeltaTime * 3f); + + if (FadeVisible) + Alpha = Ease.SineOut(uneasedAlpha); + else + Alpha = Ease.SineIn(uneasedAlpha); + } + + Visible = (Alpha != 0); + } + } + } } From ac7a7737d3447baab9c95da87310807e737c2868 Mon Sep 17 00:00:00 2001 From: nhruo Date: Sat, 29 Apr 2023 11:12:40 +0300 Subject: [PATCH 11/19] refactor (SubMenuWithInputs) : now uses EaseInDecorator --- Celeste.Mod.mm/Content/Dialog/English.txt | 5 +- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 25 ++-- Celeste.Mod.mm/Mod/UI/TextMenuExt.cs | 160 +++++++++++++++++----- 3 files changed, 139 insertions(+), 51 deletions(-) diff --git a/Celeste.Mod.mm/Content/Dialog/English.txt b/Celeste.Mod.mm/Content/Dialog/English.txt index 785e08248..e9858e405 100755 --- a/Celeste.Mod.mm/Content/Dialog/English.txt +++ b/Celeste.Mod.mm/Content/Dialog/English.txt @@ -260,9 +260,8 @@ MODOPTIONS_MODTOGGLE_TOGGLEDEPS= Toggle Dependencies Automatically MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE1= When you enable a mod, all its dependencies will be enabled. MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE2= When you disable a mod, all mods that depend on it will be disabled. - MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES= Toggle Favorites - MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE1= Press - MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE2= to add or remove items from the favorite list. + MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES= Disable All ignores favorites + MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE= Press {0} to add or remove items from the favorite list. MODOPTIONS_MODTOGGLE_MESSAGE_1= If you enable or disable mods, your blacklist.txt will be replaced, MODOPTIONS_MODTOGGLE_MESSAGE_2= and Celeste will restart to apply changes. MODOPTIONS_MODTOGGLE_MESSAGE_3= Highlighted mods are used by other enabled mods as a dependency. diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index 9aa8542c0..6a763d8ea 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -28,7 +28,7 @@ class OuiModToggler : OuiGenericMenu, OuiModOptions.ISubmenu { private bool toggleDependencies = true; - private bool toggleFavorites = false; + private bool DisableAllIgnoresFavorites = true; private TextMenuExt.SubHeaderExt restartMessage1; private TextMenuExt.SubHeaderExt restartMessage2; @@ -229,9 +229,7 @@ protected override void addOptionsToMenu(TextMenu menu) { blacklistedMods.Clear(); foreach (KeyValuePair toggle in modToggles) { bool isFavorite = favoritedMods.Contains(toggle.Key) || favoritesDependenciesMods.ContainsKey(toggle.Key); - if (!toggleFavorites && isFavorite) { - // TODO: I don't like the continue keywords but the logic statement we want is kinda odd so for now it will do - // toggleFavorites || (!toggleFavorites && !isFavorite) + if (DisableAllIgnoresFavorites && isFavorite) { continue; } @@ -249,13 +247,18 @@ protected override void addOptionsToMenu(TextMenu menu) { toggleDependenciesButton.AddDescription(menu, Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE1")); TextMenu.Item toggleFavoritesButton; - menu.Add(toggleFavoritesButton = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES"), toggleFavorites) - .Change(value => toggleFavorites = value)); + menu.Add(toggleFavoritesButton = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES"), DisableAllIgnoresFavorites) + .Change(value => DisableAllIgnoresFavorites = value)); - TextMenuExt.EaseInSubMenuWithInputs favoriteToolTip = new TextMenuExt.EaseInSubMenuWithInputs( - new object[] { Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE1") + " ", - Input.MenuJournal, " " + Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE2") }, - Input.GuiInputController(), false, menu) { TextColor = Color.Gray }; + + + + TextMenuExt.EaseInDecorator favoriteToolTip = + new TextMenuExt.EaseInDecorator( + new TextMenuExt.SubMenuWithInputs( + string.Format(Dialog.Get("MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE"), "|"), '|', + new Monocle.VirtualButton[] { Input.MenuJournal }, () => Input.GuiInputController()) { TextColor = Color.Gray } + , false, menu); menu.Add(favoriteToolTip); @@ -650,7 +653,7 @@ public override IEnumerator Leave(Oui next) { modToggles = null; modLoadingTask = null; toggleDependencies = true; - toggleFavorites = false; + DisableAllIgnoresFavorites = false; favoritedMods = null; favoritedModsOriginal = null; favoritesDependenciesMods = null; diff --git a/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs b/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs index e311432f0..8b8e01327 100644 --- a/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs +++ b/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs @@ -1353,43 +1353,61 @@ public void Dispose() { } } - public class SubMenuWithInputs : TextMenu.Item { + public class SubMenuWithInputs : TextMenu.Item, IItemExt { public object[] Items { get; set; } - public bool ControllerMode { get; set; } + private Func ControllerModeCallback; public Color TextColor { get; set; } = Color.Gray; public Color ButtonColor { get; set; } = Color.White; + public Color StrokeColor { get; set; } = Color.White; public float Alpha { get; set; } = 1f; public float Scale { get; set; } = 0.6f; + public string Icon { get; set; } + public float? IconWidth { get; set; } + public bool IconOutline { get; set; } + public Vector2 Offset { get; set; } - public SubMenuWithInputs(object[] items, bool controllerMode) { - this.Items = items; - this.ControllerMode = controllerMode; + public override float Height() { + return ActiveFont.LineHeight; } + public SubMenuWithInputs(string text, char separator, VirtualButton[] buttons, Func controllerModeCallback) { + this.ControllerModeCallback = controllerModeCallback; - public override float Height() { - return ActiveFont.LineHeight; + string[] parts = text.Split(separator); + Items = new object[parts.Length * 2 - 1]; + + for (int index = 0; index < Items.Length; index++) { + if (index % 2 == 0) { + // add text + Items[index] = parts[index / 2]; + } else { + // add VirtualButton + Items[index] = buttons[index / 2]; + } + } } + public override void Render(Vector2 position, bool highlighted) { Vector2 lineOffset = position; Vector2 justify = new Vector2(0f, 0.5f); float strokeAlpha = Alpha * Alpha * Alpha; - for (int i = 0; i < this.Items.Count(); i++) { - if (this.Items[i] is string) { - ActiveFont.DrawOutline(this.Items[i] as string, lineOffset, justify, Vector2.One * Scale, TextColor * Alpha, 2f, Color.Black * strokeAlpha); - lineOffset.X += ActiveFont.Measure(this.Items[i] as string).X * Scale; - } else if (this.Items[i] is VirtualButton) { - VirtualButton virtualButton = this.Items[i] as VirtualButton; + + foreach (object item in Items) { + if (item is string) { + ActiveFont.DrawOutline(item as string, lineOffset, justify, Vector2.One * Scale, TextColor * Alpha, 2f, Color.Black * strokeAlpha); + lineOffset.X += ActiveFont.Measure(item as string).X * Scale; + } else if (item is VirtualButton) { + VirtualButton virtualButton = item as VirtualButton; MTexture buttonTexture = null; - if (this.ControllerMode) { - buttonTexture = Input.GuiButton(virtualButton, Input.PrefixMode.Attached, "controls/keyboard/oemquestion"); + if (this.ControllerModeCallback()) { + buttonTexture = Input.GuiButton(virtualButton, Input.PrefixMode.Attached); } else if (virtualButton.Binding.Keyboard.Count > 0) { - buttonTexture = Input.GuiKey(virtualButton.Binding.Keyboard[0], "controls/keyboard/oemquestion"); + buttonTexture = Input.GuiKey(virtualButton.Binding.Keyboard[0]); } else { - buttonTexture = Input.GuiKey(Microsoft.Xna.Framework.Input.Keys.None, "controls/keyboard/oemquestion"); + buttonTexture = Input.GuiKey(Microsoft.Xna.Framework.Input.Keys.None); } buttonTexture.DrawJustified(lineOffset, justify, ButtonColor * strokeAlpha, Scale); @@ -1399,47 +1417,115 @@ public override void Render(Vector2 position, bool highlighted) { } } - public class EaseInSubMenuWithInputs : SubMenuWithInputs { - - - - /// - /// Toggling this will make the header ease in/out. - /// + // this is full of boilerplate code might be replaceable with DispatchProxy once we upgrade .NET + // https://learn.microsoft.com/en-us/dotnet/api/system.reflection.dispatchproxy?view=net-7.0 + public class EaseInDecorator : TextMenu.Item, IItemExt where T : TextMenu.Item, IItemExt { + private T Inner; public bool FadeVisible { get; set; } = true; - private float uneasedAlpha; - private TextMenu containingMenu; + private float UneasedAlpha; + private TextMenu ContainingMenu; - public EaseInSubMenuWithInputs(object[] items, bool controllerMode, bool initiallyVisible, TextMenu containingMenu) : base(items, controllerMode) { - this.containingMenu = containingMenu; + public EaseInDecorator(T inner, bool initiallyVisible, TextMenu containingMenu) { + this.Inner = inner; + this.ContainingMenu = containingMenu; FadeVisible = initiallyVisible; Alpha = FadeVisible ? 1 : 0; - uneasedAlpha = Alpha; + UneasedAlpha = Alpha; + } + + public Color TextColor { get => Inner.TextColor; set => Inner.TextColor = value; } + public string Icon { get => Inner.Icon; set => Inner.Icon = value; } + public float? IconWidth { get => Inner.IconWidth; set => Inner.IconWidth = value; } + public bool IconOutline { get => Inner.IconOutline; set => Inner.IconOutline = value; } + public Vector2 Offset { get => Inner.Offset; set => Inner.Offset = value; } + public float Alpha { get => Inner.Alpha; set => Inner.Alpha = value; } + new public bool Selectable { get => Inner.Selectable; set => Inner.Selectable = value; } + new public bool Visible { get => Inner.Visible; set => Inner.Visible = value; } + new public bool Disabled { get => Inner.Disabled; set => Inner.Disabled = value; } + new public bool IncludeWidthInMeasurement { get => Inner.IncludeWidthInMeasurement; set => Inner.IncludeWidthInMeasurement = value; } + new public bool AboveAll { get => Inner.AboveAll; set => Inner.AboveAll = value; } + new public TextMenu Container { get => Inner.Container; set => Inner.Container = value; } + new public Wiggler SelectWiggler { get => Inner.SelectWiggler; set => Inner.SelectWiggler = value; } + new public Wiggler ValueWiggler { get => Inner.ValueWiggler; set => Inner.ValueWiggler = value; } + new public Action OnEnter { get => Inner.OnEnter; set => Inner.OnEnter = value; } + new public Action OnLeave { get => Inner.OnLeave; set => Inner.OnLeave = value; } + new public Action OnPressed { get => Inner.OnPressed; set => Inner.OnPressed = value; } + new public Action OnAltPressed { get => Inner.OnAltPressed; set => Inner.OnAltPressed = value; } + new public Action OnUpdate { get => Inner.OnUpdate; set => Inner.OnUpdate = value; } + + + new public float Width { get => Inner.Width; } + + new public bool Hoverable { get => Inner.Hoverable; } + + + public override void Added() { + Inner.Added(); } - // the fade has to take into account the item spacing as well, or the other options will abruptly shift up when Visible is switched to false. - public override float Height() => MathHelper.Lerp(-containingMenu.ItemSpacing, base.Height(), Alpha); + public new TextMenu.Item AltPressed(Action onPressed) { + return Inner.AltPressed(onPressed); + } + + public override void ConfirmPressed() { + Inner.ConfirmPressed(); + } + + public new TextMenu.Item Enter(Action onEnter) { + return Inner.Enter(onEnter); + } + + public override float Height() { + return MathHelper.Lerp(-ContainingMenu.ItemSpacing, Inner.Height(), Inner.Alpha); + } + + public new TextMenu.Item Leave(Action onLeave) { + return Inner.Leave(onLeave); + } + + public override void LeftPressed() { + Inner.LeftPressed(); + } + + public override float LeftWidth() { + return Inner.LeftWidth(); + } + + public new TextMenu.Item Pressed(Action onPressed) { + return Inner.Pressed(onPressed); + } + + public override void Render(Vector2 position, bool highlighted) { + Inner.Render(position, highlighted); + } + + public override void RightPressed() { + Inner.RightPressed(); + } + + public override float RightWidth() { + return Inner.RightWidth(); + } public override void Update() { - base.Update(); + Inner.Update(); // gradually make the sub-header fade in or out. (~333ms fade) float targetAlpha = FadeVisible ? 1 : 0; - if (uneasedAlpha != targetAlpha) { - uneasedAlpha = Calc.Approach(uneasedAlpha, targetAlpha, Engine.RawDeltaTime * 3f); + if (UneasedAlpha != targetAlpha) { + UneasedAlpha = Calc.Approach(UneasedAlpha, targetAlpha, Engine.RawDeltaTime * 3f); if (FadeVisible) - Alpha = Ease.SineOut(uneasedAlpha); + Alpha = Ease.SineOut(UneasedAlpha); else - Alpha = Ease.SineIn(uneasedAlpha); + Alpha = Ease.SineIn(UneasedAlpha); } Visible = (Alpha != 0); } } - } } From 72cce6d9087ccb7b2778a44169b1b04def661e64 Mon Sep 17 00:00:00 2001 From: nhruo Date: Sat, 29 Apr 2023 20:58:55 +0300 Subject: [PATCH 12/19] formating --- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 13 ++- Celeste.Mod.mm/Mod/UI/TextMenuExt.cs | 106 ++++++++++++------------- 2 files changed, 58 insertions(+), 61 deletions(-) diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index 6a763d8ea..d2efe8f49 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -28,7 +28,7 @@ class OuiModToggler : OuiGenericMenu, OuiModOptions.ISubmenu { private bool toggleDependencies = true; - private bool DisableAllIgnoresFavorites = true; + private bool disableAllIgnoresFavorites = true; private TextMenuExt.SubHeaderExt restartMessage1; private TextMenuExt.SubHeaderExt restartMessage2; @@ -229,7 +229,7 @@ protected override void addOptionsToMenu(TextMenu menu) { blacklistedMods.Clear(); foreach (KeyValuePair toggle in modToggles) { bool isFavorite = favoritedMods.Contains(toggle.Key) || favoritesDependenciesMods.ContainsKey(toggle.Key); - if (DisableAllIgnoresFavorites && isFavorite) { + if (disableAllIgnoresFavorites && isFavorite) { continue; } @@ -247,11 +247,8 @@ protected override void addOptionsToMenu(TextMenu menu) { toggleDependenciesButton.AddDescription(menu, Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE1")); TextMenu.Item toggleFavoritesButton; - menu.Add(toggleFavoritesButton = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES"), DisableAllIgnoresFavorites) - .Change(value => DisableAllIgnoresFavorites = value)); - - - + menu.Add(toggleFavoritesButton = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES"), disableAllIgnoresFavorites) + .Change(value => disableAllIgnoresFavorites = value)); TextMenuExt.EaseInDecorator favoriteToolTip = new TextMenuExt.EaseInDecorator( @@ -653,7 +650,7 @@ public override IEnumerator Leave(Oui next) { modToggles = null; modLoadingTask = null; toggleDependencies = true; - DisableAllIgnoresFavorites = false; + disableAllIgnoresFavorites = false; favoritedMods = null; favoritedModsOriginal = null; favoritesDependenciesMods = null; diff --git a/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs b/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs index 8b8e01327..9772f2def 100644 --- a/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs +++ b/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs @@ -1355,7 +1355,6 @@ public void Dispose() { public class SubMenuWithInputs : TextMenu.Item, IItemExt { public object[] Items { get; set; } - private Func ControllerModeCallback; public Color TextColor { get; set; } = Color.Gray; public Color ButtonColor { get; set; } = Color.White; public Color StrokeColor { get; set; } = Color.White; @@ -1366,12 +1365,10 @@ public class SubMenuWithInputs : TextMenu.Item, IItemExt { public bool IconOutline { get; set; } public Vector2 Offset { get; set; } - public override float Height() { - return ActiveFont.LineHeight; - } + private Func controllerModeCallback; public SubMenuWithInputs(string text, char separator, VirtualButton[] buttons, Func controllerModeCallback) { - this.ControllerModeCallback = controllerModeCallback; + this.controllerModeCallback = controllerModeCallback; string[] parts = text.Split(separator); Items = new object[parts.Length * 2 - 1]; @@ -1387,6 +1384,9 @@ public SubMenuWithInputs(string text, char separator, VirtualButton[] buttons, F } } + public override float Height() { + return ActiveFont.LineHeight; + } public override void Render(Vector2 position, bool highlighted) { Vector2 lineOffset = position; @@ -1402,7 +1402,7 @@ public override void Render(Vector2 position, bool highlighted) { VirtualButton virtualButton = item as VirtualButton; MTexture buttonTexture = null; - if (this.ControllerModeCallback()) { + if (controllerModeCallback()) { buttonTexture = Input.GuiButton(virtualButton, Input.PrefixMode.Attached); } else if (virtualButton.Binding.Keyboard.Count > 0) { buttonTexture = Input.GuiKey(virtualButton.Binding.Keyboard[0]); @@ -1411,7 +1411,7 @@ public override void Render(Vector2 position, bool highlighted) { } buttonTexture.DrawJustified(lineOffset, justify, ButtonColor * strokeAlpha, Scale); - lineOffset.X += (float) buttonTexture.Width * Scale; + lineOffset.X += buttonTexture.Width * Scale; } } } @@ -1420,111 +1420,111 @@ public override void Render(Vector2 position, bool highlighted) { // this is full of boilerplate code might be replaceable with DispatchProxy once we upgrade .NET // https://learn.microsoft.com/en-us/dotnet/api/system.reflection.dispatchproxy?view=net-7.0 public class EaseInDecorator : TextMenu.Item, IItemExt where T : TextMenu.Item, IItemExt { - private T Inner; - public bool FadeVisible { get; set; } = true; + private T inner; + private float uneasedAlpha; + private TextMenu containingMenu; - private float UneasedAlpha; - private TextMenu ContainingMenu; + public bool FadeVisible { get; set; } = true; public EaseInDecorator(T inner, bool initiallyVisible, TextMenu containingMenu) { - this.Inner = inner; - this.ContainingMenu = containingMenu; + this.inner = inner; + this.containingMenu = containingMenu; FadeVisible = initiallyVisible; Alpha = FadeVisible ? 1 : 0; - UneasedAlpha = Alpha; + uneasedAlpha = Alpha; } - public Color TextColor { get => Inner.TextColor; set => Inner.TextColor = value; } - public string Icon { get => Inner.Icon; set => Inner.Icon = value; } - public float? IconWidth { get => Inner.IconWidth; set => Inner.IconWidth = value; } - public bool IconOutline { get => Inner.IconOutline; set => Inner.IconOutline = value; } - public Vector2 Offset { get => Inner.Offset; set => Inner.Offset = value; } - public float Alpha { get => Inner.Alpha; set => Inner.Alpha = value; } - new public bool Selectable { get => Inner.Selectable; set => Inner.Selectable = value; } - new public bool Visible { get => Inner.Visible; set => Inner.Visible = value; } + public Color TextColor { get => inner.TextColor; set => inner.TextColor = value; } + public string Icon { get => inner.Icon; set => inner.Icon = value; } + public float? IconWidth { get => inner.IconWidth; set => inner.IconWidth = value; } + public bool IconOutline { get => inner.IconOutline; set => inner.IconOutline = value; } + public Vector2 Offset { get => inner.Offset; set => inner.Offset = value; } + public float Alpha { get => inner.Alpha; set => inner.Alpha = value; } + new public bool Selectable { get => inner.Selectable; set => inner.Selectable = value; } + new public bool Visible { get => inner.Visible; set => inner.Visible = value; } - new public bool Disabled { get => Inner.Disabled; set => Inner.Disabled = value; } - new public bool IncludeWidthInMeasurement { get => Inner.IncludeWidthInMeasurement; set => Inner.IncludeWidthInMeasurement = value; } - new public bool AboveAll { get => Inner.AboveAll; set => Inner.AboveAll = value; } - new public TextMenu Container { get => Inner.Container; set => Inner.Container = value; } - new public Wiggler SelectWiggler { get => Inner.SelectWiggler; set => Inner.SelectWiggler = value; } - new public Wiggler ValueWiggler { get => Inner.ValueWiggler; set => Inner.ValueWiggler = value; } - new public Action OnEnter { get => Inner.OnEnter; set => Inner.OnEnter = value; } - new public Action OnLeave { get => Inner.OnLeave; set => Inner.OnLeave = value; } - new public Action OnPressed { get => Inner.OnPressed; set => Inner.OnPressed = value; } - new public Action OnAltPressed { get => Inner.OnAltPressed; set => Inner.OnAltPressed = value; } - new public Action OnUpdate { get => Inner.OnUpdate; set => Inner.OnUpdate = value; } + new public bool Disabled { get => inner.Disabled; set => inner.Disabled = value; } + new public bool IncludeWidthInMeasurement { get => inner.IncludeWidthInMeasurement; set => inner.IncludeWidthInMeasurement = value; } + new public bool AboveAll { get => inner.AboveAll; set => inner.AboveAll = value; } + new public TextMenu Container { get => inner.Container; set => inner.Container = value; } + new public Wiggler SelectWiggler { get => inner.SelectWiggler; set => inner.SelectWiggler = value; } + new public Wiggler ValueWiggler { get => inner.ValueWiggler; set => inner.ValueWiggler = value; } + new public Action OnEnter { get => inner.OnEnter; set => inner.OnEnter = value; } + new public Action OnLeave { get => inner.OnLeave; set => inner.OnLeave = value; } + new public Action OnPressed { get => inner.OnPressed; set => inner.OnPressed = value; } + new public Action OnAltPressed { get => inner.OnAltPressed; set => inner.OnAltPressed = value; } + new public Action OnUpdate { get => inner.OnUpdate; set => inner.OnUpdate = value; } - new public float Width { get => Inner.Width; } + new public float Width => inner.Width; - new public bool Hoverable { get => Inner.Hoverable; } + new public bool Hoverable => inner.Hoverable; public override void Added() { - Inner.Added(); + inner.Added(); } public new TextMenu.Item AltPressed(Action onPressed) { - return Inner.AltPressed(onPressed); + return inner.AltPressed(onPressed); } public override void ConfirmPressed() { - Inner.ConfirmPressed(); + inner.ConfirmPressed(); } public new TextMenu.Item Enter(Action onEnter) { - return Inner.Enter(onEnter); + return inner.Enter(onEnter); } public override float Height() { - return MathHelper.Lerp(-ContainingMenu.ItemSpacing, Inner.Height(), Inner.Alpha); + return MathHelper.Lerp(-containingMenu.ItemSpacing, inner.Height(), inner.Alpha); } public new TextMenu.Item Leave(Action onLeave) { - return Inner.Leave(onLeave); + return inner.Leave(onLeave); } public override void LeftPressed() { - Inner.LeftPressed(); + inner.LeftPressed(); } public override float LeftWidth() { - return Inner.LeftWidth(); + return inner.LeftWidth(); } public new TextMenu.Item Pressed(Action onPressed) { - return Inner.Pressed(onPressed); + return inner.Pressed(onPressed); } public override void Render(Vector2 position, bool highlighted) { - Inner.Render(position, highlighted); + inner.Render(position, highlighted); } public override void RightPressed() { - Inner.RightPressed(); + inner.RightPressed(); } public override float RightWidth() { - return Inner.RightWidth(); + return inner.RightWidth(); } public override void Update() { - Inner.Update(); + inner.Update(); // gradually make the sub-header fade in or out. (~333ms fade) float targetAlpha = FadeVisible ? 1 : 0; - if (UneasedAlpha != targetAlpha) { - UneasedAlpha = Calc.Approach(UneasedAlpha, targetAlpha, Engine.RawDeltaTime * 3f); + if (uneasedAlpha != targetAlpha) { + uneasedAlpha = Calc.Approach(uneasedAlpha, targetAlpha, Engine.RawDeltaTime * 3f); if (FadeVisible) - Alpha = Ease.SineOut(UneasedAlpha); + Alpha = Ease.SineOut(uneasedAlpha); else - Alpha = Ease.SineIn(UneasedAlpha); + Alpha = Ease.SineIn(uneasedAlpha); } - Visible = (Alpha != 0); + Visible = Alpha != 0; } } } From d278e4291e1c8afcc47aff07c679f1801ee422d1 Mon Sep 17 00:00:00 2001 From: Mayrom Rabinovich Date: Sun, 4 Jun 2023 16:36:24 +0300 Subject: [PATCH 13/19] improved dependencies search --- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 72 ++++++++++++-------------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index d2efe8f49..354adf9c0 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -33,8 +33,12 @@ class OuiModToggler : OuiGenericMenu, OuiModOptions.ISubmenu { private TextMenuExt.SubHeaderExt restartMessage1; private TextMenuExt.SubHeaderExt restartMessage2; + // maps each filename to Everest modules private Dictionary modYamls; + // maps each mode name to its Everest module + private Dictionary modNames; + private Dictionary modToggles; private Task modLoadingTask; @@ -198,6 +202,7 @@ protected override void addOptionsToMenu(TextMenu menu) { MainThreadHelper.Do(() => { modToggles = new Dictionary(); + modNames = buildModNames(modYamls); // remove the "loading..." message menu.Remove(loading); @@ -387,6 +392,25 @@ private void addFileToMenu(TextMenu menu, string file) { modToggles[file] = option; } + private Dictionary buildModNames(Dictionary modYamls) { + Dictionary modNames = new(); + + foreach (KeyValuePair pair in modYamls) { + foreach (EverestModuleMetadata currentModule in pair.Value) { + if (modNames.TryGetValue(currentModule.Name, out EverestModuleMetadata previousModule)) { + if (previousModule.Version < currentModule.Version) { + modNames[currentModule.Name] = currentModule; + } + } else { + modNames[currentModule.Name] = currentModule; + } + } + } + + + return modNames; + } + private void updateHighlightedMods() { // adjust the mods' color if they are required dependencies for other mods foreach (KeyValuePair toggle in modToggles) { @@ -450,28 +474,11 @@ private void removeFromBlacklist(string file) { blacklistedMods.Remove(file); Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{file} was removed from the blacklist"); - if (toggleDependencies && modYamls.TryGetValue(file, out EverestModuleMetadata[] metadatas)) { + if (toggleDependencies && TryGetModDependenciesFileNames(file, out List dependencies)) { // we should remove all of the mod's dependencies from the blacklist. - foreach (EverestModuleMetadata metadata in metadatas) { - foreach (string dependency in metadata.Dependencies.Select(dep => dep.Name)) { - // we want to go through all the other mods' info to found the one we want. - KeyValuePair? found = null; - foreach (KeyValuePair candidateMetadatas in modYamls) { - foreach (EverestModuleMetadata candidateMetadata in candidateMetadatas.Value) { - if (candidateMetadata.Name == dependency) { - // we found it! - if (found == null || found.Value.Value.Version < candidateMetadata.Version) { - found = new KeyValuePair(candidateMetadatas.Key, candidateMetadata); - } - } - } - } - if (found.HasValue) { - // we found where the dependency is: activate it. - removeFromBlacklist(found.Value.Key); - modToggles[found.Value.Key].Index = 1; - } - } + foreach (string modFileName in dependencies) { + removeFromBlacklist(modFileName); + modToggles[modFileName].Index = 1; } } } @@ -549,7 +556,7 @@ private void onBackPressed(Overworld overworld) { Everest.Loader.Favorites = favoritedMods; using (StreamWriter writer = File.CreateText(Everest.Loader.PathFavorites)) { // header - writer.WriteLine("# This is the favorites list. Lines starting with # are ignored."); + writer.WriteLine("# This is the favorite list. Lines starting with # are ignored."); writer.WriteLine(""); foreach (string mod in favoritedMods) { @@ -581,30 +588,15 @@ private void onBackPressed(Overworld overworld) { } private bool TryGetModDependenciesFileNames(string modFilename, out List dependenciesFileNames) { - // TODO: This nested loop is taken from the removeFromBlacklist, this could be replaced by creating a HashMap between modName to EverestModule, - // Right now it seems like we are ?needlessly? iterating n^2 times. - - // iterate over all the dependencies if (modYamls.TryGetValue(modFilename, out EverestModuleMetadata[] metadatas)) { dependenciesFileNames = new List(); + // iterate over all everest modules under file foreach (EverestModuleMetadata metadata in metadatas) { // iterate over each loaded mod to ensure its present foreach (string dependencyName in metadata.Dependencies.Select((dep) => dep.Name)) { - KeyValuePair? found = null; - foreach (KeyValuePair candidateMetadatas in modYamls) { - foreach (EverestModuleMetadata candidateMetadata in candidateMetadatas.Value) { - if (candidateMetadata.Name == dependencyName) { - // we found it! - if (found == null || found.Value.Value.Version < candidateMetadata.Version) { - found = new KeyValuePair(candidateMetadatas.Key, candidateMetadata); - } - } - } - } - - if (found != null) { - dependenciesFileNames.Add(found.Value.Key); + if (modNames.TryGetValue(dependencyName, out EverestModuleMetadata dependency)) { + dependenciesFileNames.Add(Path.GetFileName(dependency.PathArchive ?? dependency.PathDirectory)); } } } From 1df64958055d644965d094970bd948b08b53eed3 Mon Sep 17 00:00:00 2001 From: Mayrom Rabinovich Date: Tue, 6 Jun 2023 12:57:31 +0300 Subject: [PATCH 14/19] removed EaseInDecorator --- Celeste.Mod.mm/Content/Dialog/English.txt | 4 +- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 43 +++++---- Celeste.Mod.mm/Mod/UI/TextMenuExt.cs | 105 ++++------------------ 3 files changed, 40 insertions(+), 112 deletions(-) diff --git a/Celeste.Mod.mm/Content/Dialog/English.txt b/Celeste.Mod.mm/Content/Dialog/English.txt index f3f79122e..6385af548 100755 --- a/Celeste.Mod.mm/Content/Dialog/English.txt +++ b/Celeste.Mod.mm/Content/Dialog/English.txt @@ -263,8 +263,8 @@ MODOPTIONS_MODTOGGLE_TOGGLEDEPS= Toggle Dependencies Automatically MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE1= When you enable a mod, all its dependencies will be enabled. MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE2= When you disable a mod, all mods that depend on it will be disabled. - MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES= Disable All ignores favorites - MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE= Press {0} to add or remove items from the favorite list. + MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES= 'Disable All' ignores favorites + MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE= Press {0} to add or remove mods from the favorite list. MODOPTIONS_MODTOGGLE_MESSAGE_1= If you enable or disable mods, your blacklist.txt will be replaced, MODOPTIONS_MODTOGGLE_MESSAGE_2= and Celeste will restart to apply changes. MODOPTIONS_MODTOGGLE_MESSAGE_3= Highlighted mods are used by other enabled mods as a dependency. diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index 354adf9c0..17324890f 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -27,7 +27,6 @@ class OuiModToggler : OuiGenericMenu, OuiModOptions.ISubmenu { private Dictionary> favoritesDependenciesMods; private bool toggleDependencies = true; - private bool disableAllIgnoresFavorites = true; private TextMenuExt.SubHeaderExt restartMessage1; @@ -35,10 +34,8 @@ class OuiModToggler : OuiGenericMenu, OuiModOptions.ISubmenu { // maps each filename to Everest modules private Dictionary modYamls; - - // maps each mode name to its Everest module - private Dictionary modNames; - + // maps each mod name to its newest Everest module + private Dictionary modFilenameByModName; private Dictionary modToggles; private Task modLoadingTask; @@ -202,7 +199,7 @@ protected override void addOptionsToMenu(TextMenu menu) { MainThreadHelper.Do(() => { modToggles = new Dictionary(); - modNames = buildModNames(modYamls); + modFilenameByModName = buildModNames(modYamls); // remove the "loading..." message menu.Remove(loading); @@ -248,6 +245,7 @@ protected override void addOptionsToMenu(TextMenu menu) { TextMenu.Item toggleDependenciesButton; menu.Add(toggleDependenciesButton = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS"), true) .Change(value => toggleDependencies = value)); + toggleDependenciesButton.AddDescription(menu, Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE2")); toggleDependenciesButton.AddDescription(menu, Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE1")); @@ -255,12 +253,12 @@ protected override void addOptionsToMenu(TextMenu menu) { menu.Add(toggleFavoritesButton = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES"), disableAllIgnoresFavorites) .Change(value => disableAllIgnoresFavorites = value)); - TextMenuExt.EaseInDecorator favoriteToolTip = - new TextMenuExt.EaseInDecorator( - new TextMenuExt.SubMenuWithInputs( - string.Format(Dialog.Get("MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE"), "|"), '|', - new Monocle.VirtualButton[] { Input.MenuJournal }, () => Input.GuiInputController()) { TextColor = Color.Gray } - , false, menu); + TextMenuExt.EaseInSubMenuWithInputs favoriteToolTip = new TextMenuExt.EaseInSubMenuWithInputs( + string.Format(Dialog.Get("MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE"), "|"), + '|', + new Monocle.VirtualButton[] { Input.MenuJournal }, + false + ) {TextColor = Color.Gray}; menu.Add(favoriteToolTip); @@ -392,23 +390,25 @@ private void addFileToMenu(TextMenu menu, string file) { modToggles[file] = option; } - private Dictionary buildModNames(Dictionary modYamls) { - Dictionary modNames = new(); + private Dictionary buildModNames(Dictionary modYamls) { + Dictionary everestModulesByModName = new(); foreach (KeyValuePair pair in modYamls) { foreach (EverestModuleMetadata currentModule in pair.Value) { - if (modNames.TryGetValue(currentModule.Name, out EverestModuleMetadata previousModule)) { + if (everestModulesByModName.TryGetValue(currentModule.Name, out EverestModuleMetadata previousModule)) { if (previousModule.Version < currentModule.Version) { - modNames[currentModule.Name] = currentModule; + everestModulesByModName[currentModule.Name] = currentModule; } } else { - modNames[currentModule.Name] = currentModule; + everestModulesByModName[currentModule.Name] = currentModule; } } } - return modNames; + return everestModulesByModName + .Select(tuple => new KeyValuePair(tuple.Key, Path.GetFileName(tuple.Value.PathArchive ?? tuple.Value.PathDirectory))) + .ToDictionary(x=> x.Key, x=>x.Value); } private void updateHighlightedMods() { @@ -487,7 +487,6 @@ private void addToFavorites(string modFileName) { favoritedMods.Add(modFileName); Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{modFileName} was added to favorites"); - // I guess we silently fail? if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { foreach (string dependenciesFileName in dependenciesFileNames) { addToFavoritesDependencies(dependenciesFileName, modFileName); @@ -511,7 +510,7 @@ private void addToFavoritesDependencies(string modFileName, string dependentModF Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{modFileName} was added as a favorite dependency of {dependentModFileName}"); - // we want to add A as the favorite mod that is dependent on all of modFileName dependencies as well + // we want to walk the dependence graph and add all the sub-dependencies as dependencies of the original dependentModFileName if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { foreach (string dependencyFileName in dependenciesFileNames) { addToFavoritesDependencies(dependencyFileName, dependentModFileName); @@ -595,8 +594,8 @@ private bool TryGetModDependenciesFileNames(string modFilename, out List foreach (EverestModuleMetadata metadata in metadatas) { // iterate over each loaded mod to ensure its present foreach (string dependencyName in metadata.Dependencies.Select((dep) => dep.Name)) { - if (modNames.TryGetValue(dependencyName, out EverestModuleMetadata dependency)) { - dependenciesFileNames.Add(Path.GetFileName(dependency.PathArchive ?? dependency.PathDirectory)); + if (modFilenameByModName.TryGetValue(dependencyName, out string dependencyFileName)) { + dependenciesFileNames.Add(dependencyFileName); } } } diff --git a/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs b/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs index 9772f2def..23a36a312 100644 --- a/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs +++ b/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs @@ -1365,10 +1365,7 @@ public class SubMenuWithInputs : TextMenu.Item, IItemExt { public bool IconOutline { get; set; } public Vector2 Offset { get; set; } - private Func controllerModeCallback; - - public SubMenuWithInputs(string text, char separator, VirtualButton[] buttons, Func controllerModeCallback) { - this.controllerModeCallback = controllerModeCallback; + public SubMenuWithInputs(string text, char separator, VirtualButton[] buttons) { string[] parts = text.Split(separator); Items = new object[parts.Length * 2 - 1]; @@ -1402,7 +1399,7 @@ public override void Render(Vector2 position, bool highlighted) { VirtualButton virtualButton = item as VirtualButton; MTexture buttonTexture = null; - if (controllerModeCallback()) { + if (Input.GuiInputController()) { buttonTexture = Input.GuiButton(virtualButton, Input.PrefixMode.Attached); } else if (virtualButton.Binding.Keyboard.Count > 0) { buttonTexture = Input.GuiKey(virtualButton.Binding.Keyboard[0]); @@ -1417,101 +1414,33 @@ public override void Render(Vector2 position, bool highlighted) { } } - // this is full of boilerplate code might be replaceable with DispatchProxy once we upgrade .NET - // https://learn.microsoft.com/en-us/dotnet/api/system.reflection.dispatchproxy?view=net-7.0 - public class EaseInDecorator : TextMenu.Item, IItemExt where T : TextMenu.Item, IItemExt { - private T inner; - private float uneasedAlpha; - private TextMenu containingMenu; - + // TODO: this was copy pasted from EaseInSubHeaderExt, find a way to abstract away the EaseIn behavior + public class EaseInSubMenuWithInputs : SubMenuWithInputs { public bool FadeVisible { get; set; } = true; + private float uneasedAlpha; - public EaseInDecorator(T inner, bool initiallyVisible, TextMenu containingMenu) { - this.inner = inner; - this.containingMenu = containingMenu; - + public EaseInSubMenuWithInputs( + string text, + char separator, + VirtualButton[] buttons, + bool initiallyVisible + ) : base(text, separator, buttons) { FadeVisible = initiallyVisible; Alpha = FadeVisible ? 1 : 0; uneasedAlpha = Alpha; } - public Color TextColor { get => inner.TextColor; set => inner.TextColor = value; } - public string Icon { get => inner.Icon; set => inner.Icon = value; } - public float? IconWidth { get => inner.IconWidth; set => inner.IconWidth = value; } - public bool IconOutline { get => inner.IconOutline; set => inner.IconOutline = value; } - public Vector2 Offset { get => inner.Offset; set => inner.Offset = value; } - public float Alpha { get => inner.Alpha; set => inner.Alpha = value; } - new public bool Selectable { get => inner.Selectable; set => inner.Selectable = value; } - new public bool Visible { get => inner.Visible; set => inner.Visible = value; } - - new public bool Disabled { get => inner.Disabled; set => inner.Disabled = value; } - new public bool IncludeWidthInMeasurement { get => inner.IncludeWidthInMeasurement; set => inner.IncludeWidthInMeasurement = value; } - new public bool AboveAll { get => inner.AboveAll; set => inner.AboveAll = value; } - new public TextMenu Container { get => inner.Container; set => inner.Container = value; } - new public Wiggler SelectWiggler { get => inner.SelectWiggler; set => inner.SelectWiggler = value; } - new public Wiggler ValueWiggler { get => inner.ValueWiggler; set => inner.ValueWiggler = value; } - new public Action OnEnter { get => inner.OnEnter; set => inner.OnEnter = value; } - new public Action OnLeave { get => inner.OnLeave; set => inner.OnLeave = value; } - new public Action OnPressed { get => inner.OnPressed; set => inner.OnPressed = value; } - new public Action OnAltPressed { get => inner.OnAltPressed; set => inner.OnAltPressed = value; } - new public Action OnUpdate { get => inner.OnUpdate; set => inner.OnUpdate = value; } - - - new public float Width => inner.Width; - - new public bool Hoverable => inner.Hoverable; - - - public override void Added() { - inner.Added(); - } - - public new TextMenu.Item AltPressed(Action onPressed) { - return inner.AltPressed(onPressed); - } - - public override void ConfirmPressed() { - inner.ConfirmPressed(); - } - - public new TextMenu.Item Enter(Action onEnter) { - return inner.Enter(onEnter); - } - public override float Height() { - return MathHelper.Lerp(-containingMenu.ItemSpacing, inner.Height(), inner.Alpha); - } - - public new TextMenu.Item Leave(Action onLeave) { - return inner.Leave(onLeave); - } - - public override void LeftPressed() { - inner.LeftPressed(); - } - - public override float LeftWidth() { - return inner.LeftWidth(); - } - - public new TextMenu.Item Pressed(Action onPressed) { - return inner.Pressed(onPressed); - } - - public override void Render(Vector2 position, bool highlighted) { - inner.Render(position, highlighted); - } - - public override void RightPressed() { - inner.RightPressed(); - } + if (Container != null) { + return MathHelper.Lerp(-Container.ItemSpacing, base.Height(), Alpha); + } else { + return base.Height(); + } - public override float RightWidth() { - return inner.RightWidth(); } public override void Update() { - inner.Update(); + base.Update(); // gradually make the sub-header fade in or out. (~333ms fade) float targetAlpha = FadeVisible ? 1 : 0; From a1e70064a61baa744fd216629073e203dc80d053 Mon Sep 17 00:00:00 2001 From: nhruo Date: Tue, 1 Aug 2023 22:37:38 +0300 Subject: [PATCH 15/19] Small refactor - renamed Dialog entries - remove redundant dict lookups - renamed vars --- Celeste.Mod.mm/Content/Dialog/English.txt | 34 +++++----- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 75 ++++++++++++----------- Celeste.Mod.mm/Mod/UI/TextMenuExt.cs | 7 +-- 3 files changed, 58 insertions(+), 58 deletions(-) diff --git a/Celeste.Mod.mm/Content/Dialog/English.txt b/Celeste.Mod.mm/Content/Dialog/English.txt index 6385af548..43c0ce48f 100755 --- a/Celeste.Mod.mm/Content/Dialog/English.txt +++ b/Celeste.Mod.mm/Content/Dialog/English.txt @@ -258,23 +258,23 @@ OOBE_SETTINGS_OK= OK # Mod Toggle Menu - MODOPTIONS_MODTOGGLE= TOGGLE MODS - MODOPTIONS_MODTOGGLE_LOADING= Loading mod information... - MODOPTIONS_MODTOGGLE_TOGGLEDEPS= Toggle Dependencies Automatically - MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE1= When you enable a mod, all its dependencies will be enabled. - MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE2= When you disable a mod, all mods that depend on it will be disabled. - MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES= 'Disable All' ignores favorites - MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE= Press {0} to add or remove mods from the favorite list. - MODOPTIONS_MODTOGGLE_MESSAGE_1= If you enable or disable mods, your blacklist.txt will be replaced, - MODOPTIONS_MODTOGGLE_MESSAGE_2= and Celeste will restart to apply changes. - MODOPTIONS_MODTOGGLE_MESSAGE_3= Highlighted mods are used by other enabled mods as a dependency. - MODOPTIONS_MODTOGGLE_WHITELISTWARN= Disable your whitelist for these settings to be applied properly. - MODOPTIONS_MODTOGGLE_ENABLEALL= Enable All - MODOPTIONS_MODTOGGLE_DISABLEALL= Disable All - MODOPTIONS_MODTOGGLE_CANCEL= Cancel - MODOPTIONS_MODTOGGLE_ZIPS= Zip Files - MODOPTIONS_MODTOGGLE_DIRECTORIES= Directories - MODOPTIONS_MODTOGGLE_BINS= Map Bin Files + MODOPTIONS_MODTOGGLE= TOGGLE MODS + MODOPTIONS_MODTOGGLE_LOADING= Loading mod information... + MODOPTIONS_MODTOGGLE_TOGGLEDEPS= Toggle Dependencies Automatically + MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE1= When you enable a mod, all its dependencies will be enabled. + MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE2= When you disable a mod, all mods that depend on it will be disabled. + MODOPTIONS_MODTOGGLE_PROTECTFAVORITES= Protect Favorites + MODOPTIONS_MODTOGGLE_PROTECTFAVORITES_MESSAGE= Press {0} to add or remove mods from the favorite list. + MODOPTIONS_MODTOGGLE_MESSAGE_1= If you enable or disable mods, your blacklist.txt will be replaced, + MODOPTIONS_MODTOGGLE_MESSAGE_2= and Celeste will restart to apply changes. + MODOPTIONS_MODTOGGLE_MESSAGE_3= Highlighted mods are used by other enabled mods as a dependency. + MODOPTIONS_MODTOGGLE_WHITELISTWARN= Disable your whitelist for these settings to be applied properly. + MODOPTIONS_MODTOGGLE_ENABLEALL= Enable All + MODOPTIONS_MODTOGGLE_DISABLEALL= Disable All + MODOPTIONS_MODTOGGLE_CANCEL= Cancel + MODOPTIONS_MODTOGGLE_ZIPS= Zip Files + MODOPTIONS_MODTOGGLE_DIRECTORIES= Directories + MODOPTIONS_MODTOGGLE_BINS= Map Bin Files # Asset Reload Helper ASSETRELOADHELPER_RELOADINGMAP= Reloading map diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index 17324890f..a3360350c 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -19,23 +19,24 @@ class OuiModToggler : OuiGenericMenu, OuiModOptions.ISubmenu { // list of blacklisted mods when the menu was open private HashSet blacklistedModsOriginal; - // list of currently favorited mods + // list of currently favorite mods private HashSet favoritedMods; - // list of favorited mods when the menu was open + // list of favorite mods when the menu was open private HashSet favoritedModsOriginal; // dictionary mapping between the dependencies and the dependents - private Dictionary> favoritesDependenciesMods; + private Dictionary> favoriteDependencies; private bool toggleDependencies = true; - private bool disableAllIgnoresFavorites = true; + + private bool protectFavorites = true; private TextMenuExt.SubHeaderExt restartMessage1; private TextMenuExt.SubHeaderExt restartMessage2; - // maps each filename to Everest modules + // maps each filename to its Everest modules private Dictionary modYamls; // maps each mod name to its newest Everest module - private Dictionary modFilenameByModName; + private Dictionary modFilename; private Dictionary modToggles; private Task modLoadingTask; @@ -199,7 +200,7 @@ protected override void addOptionsToMenu(TextMenu menu) { MainThreadHelper.Do(() => { modToggles = new Dictionary(); - modFilenameByModName = buildModNames(modYamls); + modFilename = BuildModFilenameDictionary(modYamls); // remove the "loading..." message menu.Remove(loading); @@ -230,8 +231,8 @@ protected override void addOptionsToMenu(TextMenu menu) { menu.Add(new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_DISABLEALL")).Pressed(() => { blacklistedMods.Clear(); foreach (KeyValuePair toggle in modToggles) { - bool isFavorite = favoritedMods.Contains(toggle.Key) || favoritesDependenciesMods.ContainsKey(toggle.Key); - if (disableAllIgnoresFavorites && isFavorite) { + bool isFavoriteOrDependent = favoritedMods.Contains(toggle.Key) || favoriteDependencies.ContainsKey(toggle.Key); + if (protectFavorites && isFavoriteOrDependent) { continue; } @@ -249,24 +250,24 @@ protected override void addOptionsToMenu(TextMenu menu) { toggleDependenciesButton.AddDescription(menu, Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE2")); toggleDependenciesButton.AddDescription(menu, Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE1")); - TextMenu.Item toggleFavoritesButton; - menu.Add(toggleFavoritesButton = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES"), disableAllIgnoresFavorites) - .Change(value => disableAllIgnoresFavorites = value)); + TextMenu.Item toggleProtectFavoritesButton; + menu.Add(toggleProtectFavoritesButton = new TextMenu.OnOff(Dialog.Clean("MODOPTIONS_MODTOGGLE_PROTECTFAVORITES"), protectFavorites) + .Change(value => protectFavorites = value)); TextMenuExt.EaseInSubMenuWithInputs favoriteToolTip = new TextMenuExt.EaseInSubMenuWithInputs( - string.Format(Dialog.Get("MODOPTIONS_MODTOGGLE_TOGGLEFAVORITES_MESSAGE"), "|"), + string.Format(Dialog.Get("MODOPTIONS_MODTOGGLE_PROTECTFAVORITES_MESSAGE"), '|'), '|', new Monocle.VirtualButton[] { Input.MenuJournal }, false - ) {TextColor = Color.Gray}; + ) { TextColor = Color.Gray }; menu.Add(favoriteToolTip); - toggleFavoritesButton.OnEnter += delegate { + toggleProtectFavoritesButton.OnEnter += () => { // make the description appear. favoriteToolTip.FadeVisible = true; }; - toggleFavoritesButton.OnLeave += delegate { + toggleProtectFavoritesButton.OnLeave += () => { // make the description disappear. favoriteToolTip.FadeVisible = false; }; @@ -276,7 +277,7 @@ protected override void addOptionsToMenu(TextMenu menu) { menu.Add(new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_CANCEL")).Pressed(() => { blacklistedMods = blacklistedModsOriginal; favoritedMods = favoritedModsOriginal; - favoritesDependenciesMods = null; + favoriteDependencies = null; onBackPressed(Overworld); })); @@ -284,7 +285,7 @@ protected override void addOptionsToMenu(TextMenu menu) { allMods = new List(); blacklistedMods = new HashSet(); favoritedMods = new HashSet(); - favoritesDependenciesMods = new Dictionary>(); + favoriteDependencies = new Dictionary>(); string[] files; bool headerInserted; @@ -384,13 +385,14 @@ private void addFileToMenu(TextMenu menu, string file) { blacklistedMods.Add(file); } if (favorite) { + // because we don't store the dependencies of favorite mods we want to call addToFavorites to walk the dependencies graph addToFavorites(file); } modToggles[file] = option; } - private Dictionary buildModNames(Dictionary modYamls) { + private Dictionary BuildModFilenameDictionary(Dictionary modYamls) { Dictionary everestModulesByModName = new(); foreach (KeyValuePair pair in modYamls) { @@ -407,8 +409,7 @@ private Dictionary buildModNames(Dictionary new KeyValuePair(tuple.Key, Path.GetFileName(tuple.Value.PathArchive ?? tuple.Value.PathDirectory))) - .ToDictionary(x=> x.Key, x=>x.Value); + .ToDictionary(dictEntry => dictEntry.Key, dictEntry => Path.GetFileName(dictEntry.Value.PathArchive ?? dictEntry.Value.PathDirectory)); } private void updateHighlightedMods() { @@ -417,7 +418,7 @@ private void updateHighlightedMods() { Color unselectedColor = Color.White; if (favoritedMods.Contains(toggle.Key)) { unselectedColor = Color.DeepPink; - } else if (favoritesDependenciesMods.ContainsKey(toggle.Key)) { + } else if (favoriteDependencies.ContainsKey(toggle.Key)) { unselectedColor = Color.LightPink; } else if (modHasDependencies(toggle.Key)) { unselectedColor = Color.Goldenrod; @@ -495,18 +496,19 @@ private void addToFavorites(string modFileName) { } private void addToFavoritesDependencies(string modFileName, string dependentModFileName) { - // If we have a cyclical dependencies we want to stop after the first occurrence of a mod, or if somehow we reach ourself. - if ((favoritesDependenciesMods.ContainsKey(modFileName) && favoritesDependenciesMods[modFileName].Contains(dependentModFileName)) - || modFileName.Equals(dependentModFileName)) { + bool existsInFavoriteDependencies = favoriteDependencies.TryGetValue(modFileName, out HashSet dependents); + + // If we have a cyclical dependencies we want to stop after the first occurrence of a mod, or if somehow a mod reached itself. + if ((existsInFavoriteDependencies && dependents.Contains(dependentModFileName)) || modFileName == dependentModFileName) { return; } - if (!favoritesDependenciesMods.ContainsKey(modFileName)) { - favoritesDependenciesMods[modFileName] = new HashSet(); + if (!existsInFavoriteDependencies) { + dependents = favoriteDependencies[modFileName] = new HashSet(); } // Add dependent mod - favoritesDependenciesMods[modFileName].Add(dependentModFileName); + dependents.Add(dependentModFileName); Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{modFileName} was added as a favorite dependency of {dependentModFileName}"); @@ -530,13 +532,13 @@ private void removeFromFavorites(string modFileName) { } private void removeFromFavoritesDependencies(string modFileName, string dependentModFileName) { - if (favoritesDependenciesMods.ContainsKey(modFileName) && favoritesDependenciesMods[modFileName].Contains(dependentModFileName)) { + if (favoriteDependencies.TryGetValue(modFileName, out HashSet dependents) && dependents.Contains(dependentModFileName)) { - favoritesDependenciesMods[modFileName].Remove(dependentModFileName); + dependents.Remove(dependentModFileName); Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{modFileName} was removed from being a favorite dependency of {dependentModFileName}"); - if (favoritesDependenciesMods[modFileName].Count == 0) { - favoritesDependenciesMods.Remove(modFileName); + if (dependents.Count == 0) { + favoriteDependencies.Remove(modFileName); Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{modFileName} is no longer a favorite dependency"); } @@ -590,11 +592,9 @@ private bool TryGetModDependenciesFileNames(string modFilename, out List if (modYamls.TryGetValue(modFilename, out EverestModuleMetadata[] metadatas)) { dependenciesFileNames = new List(); - // iterate over all everest modules under file foreach (EverestModuleMetadata metadata in metadatas) { - // iterate over each loaded mod to ensure its present foreach (string dependencyName in metadata.Dependencies.Select((dep) => dep.Name)) { - if (modFilenameByModName.TryGetValue(dependencyName, out string dependencyFileName)) { + if (this.modFilename.TryGetValue(dependencyName, out string dependencyFileName)) { dependenciesFileNames.Add(dependencyFileName); } } @@ -638,13 +638,14 @@ public override IEnumerator Leave(Oui next) { restartMessage1 = null; restartMessage2 = null; modYamls = null; + modFilename = null; modToggles = null; modLoadingTask = null; toggleDependencies = true; - disableAllIgnoresFavorites = false; + protectFavorites = false; favoritedMods = null; favoritedModsOriginal = null; - favoritesDependenciesMods = null; + favoriteDependencies = null; } } } diff --git a/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs b/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs index 23a36a312..28d80230f 100644 --- a/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs +++ b/Celeste.Mod.mm/Mod/UI/TextMenuExt.cs @@ -1354,7 +1354,6 @@ public void Dispose() { } public class SubMenuWithInputs : TextMenu.Item, IItemExt { - public object[] Items { get; set; } public Color TextColor { get; set; } = Color.Gray; public Color ButtonColor { get; set; } = Color.White; public Color StrokeColor { get; set; } = Color.White; @@ -1364,6 +1363,7 @@ public class SubMenuWithInputs : TextMenu.Item, IItemExt { public float? IconWidth { get; set; } public bool IconOutline { get; set; } public Vector2 Offset { get; set; } + private readonly object[] Items; public SubMenuWithInputs(string text, char separator, VirtualButton[] buttons) { @@ -1387,7 +1387,7 @@ public override float Height() { public override void Render(Vector2 position, bool highlighted) { Vector2 lineOffset = position; - Vector2 justify = new Vector2(0f, 0.5f); + Vector2 justify = new(0f, 0.5f); float strokeAlpha = Alpha * Alpha * Alpha; @@ -1397,7 +1397,7 @@ public override void Render(Vector2 position, bool highlighted) { lineOffset.X += ActiveFont.Measure(item as string).X * Scale; } else if (item is VirtualButton) { VirtualButton virtualButton = item as VirtualButton; - MTexture buttonTexture = null; + MTexture buttonTexture; if (Input.GuiInputController()) { buttonTexture = Input.GuiButton(virtualButton, Input.PrefixMode.Attached); @@ -1436,7 +1436,6 @@ public override float Height() { } else { return base.Height(); } - } public override void Update() { From 47237fada3013342c56f071391f1be9defe9141f Mon Sep 17 00:00:00 2001 From: nhruo Date: Fri, 4 Aug 2023 19:22:14 +0300 Subject: [PATCH 16/19] Added sound and wiggle to favorite select --- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 42 +++++++++++++++----------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index a3360350c..539323424 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -357,28 +357,34 @@ protected override void addOptionsToMenu(TextMenu menu) { } private void addFileToMenu(TextMenu menu, string file) { - TextMenu.OnOff option; - bool enabled = !Everest.Loader.Blacklist.Contains(file); bool favorite = Everest.Loader.Favorites.Contains(file); - menu.Add(option = (TextMenu.OnOff) new TextMenu.OnOff(file.Length > 40 ? file.Substring(0, 40) + "..." : file, enabled) - .Change(b => { - if (b) { - removeFromBlacklist(file); - } else { - addToBlacklist(file); - } - updateHighlightedMods(); - }).AltPressed(() => { - if (favoritedMods.Contains(file)) { - removeFromFavorites(file); - } else { - addToFavorites(file); - } + TextMenu.OnOff option = new TextMenu.OnOff(file.Length > 40 ? file.Substring(0, 40) + "..." : file, enabled); - updateHighlightedMods(); - })); + option.Change(b => { + if (b) { + removeFromBlacklist(file); + } else { + addToBlacklist(file); + } + + updateHighlightedMods(); + }).AltPressed(() => { + if (!favoritedMods.Contains(file)) { + Audio.Play(SFX.ui_main_button_toggle_on); + addToFavorites(file); + } else { + Audio.Play(SFX.ui_main_button_toggle_off); + removeFromFavorites(file); + } + + option.SelectWiggler.Start(); + + updateHighlightedMods(); + }); + + menu.Add(option); allMods.Add(file); if (!enabled) { From dc31ed3c077e3f38f5f558079315f70a4079abb6 Mon Sep 17 00:00:00 2001 From: Mayrom Rabinovich Date: Tue, 5 Sep 2023 16:30:39 +0300 Subject: [PATCH 17/19] changes I forgot to commit --- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 55 +++++++++++++------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index 539323424..07975bfe3 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -19,12 +19,12 @@ class OuiModToggler : OuiGenericMenu, OuiModOptions.ISubmenu { // list of blacklisted mods when the menu was open private HashSet blacklistedModsOriginal; - // list of currently favorite mods - private HashSet favoritedMods; + // list of favorite mods + private HashSet favoriteMods; // list of favorite mods when the menu was open - private HashSet favoritedModsOriginal; - // dictionary mapping between the dependencies and the dependents - private Dictionary> favoriteDependencies; + private HashSet favoriteModsOriginal; + // maps each dependency to all its dependents + private Dictionary> favoriteModDependencies; private bool toggleDependencies = true; @@ -231,8 +231,8 @@ protected override void addOptionsToMenu(TextMenu menu) { menu.Add(new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_DISABLEALL")).Pressed(() => { blacklistedMods.Clear(); foreach (KeyValuePair toggle in modToggles) { - bool isFavoriteOrDependent = favoritedMods.Contains(toggle.Key) || favoriteDependencies.ContainsKey(toggle.Key); - if (protectFavorites && isFavoriteOrDependent) { + bool isFavoriteDependency = favoriteMods.Contains(toggle.Key) || favoriteModDependencies.ContainsKey(toggle.Key); + if (protectFavorites && isFavoriteDependency) { continue; } @@ -276,16 +276,15 @@ protected override void addOptionsToMenu(TextMenu menu) { // "cancel" button to leave the screen without saving menu.Add(new TextMenu.Button(Dialog.Clean("MODOPTIONS_MODTOGGLE_CANCEL")).Pressed(() => { blacklistedMods = blacklistedModsOriginal; - favoritedMods = favoritedModsOriginal; - favoriteDependencies = null; + favoriteMods = favoriteModsOriginal; onBackPressed(Overworld); })); // reset the mods list allMods = new List(); blacklistedMods = new HashSet(); - favoritedMods = new HashSet(); - favoriteDependencies = new Dictionary>(); + favoriteMods = new HashSet(); + favoriteModDependencies = new Dictionary>(); string[] files; bool headerInserted; @@ -340,7 +339,7 @@ protected override void addOptionsToMenu(TextMenu menu) { // clone the list to be able to check if the list changed when leaving the menu. blacklistedModsOriginal = new HashSet(blacklistedMods); - favoritedModsOriginal = new HashSet(favoritedMods); + favoriteModsOriginal = new HashSet(favoriteMods); // set colors to mods listings updateHighlightedMods(); @@ -360,7 +359,7 @@ private void addFileToMenu(TextMenu menu, string file) { bool enabled = !Everest.Loader.Blacklist.Contains(file); bool favorite = Everest.Loader.Favorites.Contains(file); - TextMenu.OnOff option = new TextMenu.OnOff(file.Length > 40 ? file.Substring(0, 40) + "..." : file, enabled); + TextMenu.OnOff option = new(file.Length > 40 ? file.Substring(0, 40) + "..." : file, enabled); option.Change(b => { if (b) { @@ -371,7 +370,7 @@ private void addFileToMenu(TextMenu menu, string file) { updateHighlightedMods(); }).AltPressed(() => { - if (!favoritedMods.Contains(file)) { + if (!favoriteMods.Contains(file)) { Audio.Play(SFX.ui_main_button_toggle_on); addToFavorites(file); } else { @@ -422,9 +421,9 @@ private void updateHighlightedMods() { // adjust the mods' color if they are required dependencies for other mods foreach (KeyValuePair toggle in modToggles) { Color unselectedColor = Color.White; - if (favoritedMods.Contains(toggle.Key)) { + if (favoriteMods.Contains(toggle.Key)) { unselectedColor = Color.DeepPink; - } else if (favoriteDependencies.ContainsKey(toggle.Key)) { + } else if (favoriteModDependencies.ContainsKey(toggle.Key)) { unselectedColor = Color.LightPink; } else if (modHasDependencies(toggle.Key)) { unselectedColor = Color.Goldenrod; @@ -491,7 +490,7 @@ private void removeFromBlacklist(string file) { } private void addToFavorites(string modFileName) { - favoritedMods.Add(modFileName); + favoriteMods.Add(modFileName); Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{modFileName} was added to favorites"); if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { @@ -502,7 +501,7 @@ private void addToFavorites(string modFileName) { } private void addToFavoritesDependencies(string modFileName, string dependentModFileName) { - bool existsInFavoriteDependencies = favoriteDependencies.TryGetValue(modFileName, out HashSet dependents); + bool existsInFavoriteDependencies = favoriteModDependencies.TryGetValue(modFileName, out HashSet dependents); // If we have a cyclical dependencies we want to stop after the first occurrence of a mod, or if somehow a mod reached itself. if ((existsInFavoriteDependencies && dependents.Contains(dependentModFileName)) || modFileName == dependentModFileName) { @@ -510,7 +509,7 @@ private void addToFavoritesDependencies(string modFileName, string dependentModF } if (!existsInFavoriteDependencies) { - dependents = favoriteDependencies[modFileName] = new HashSet(); + dependents = favoriteModDependencies[modFileName] = new HashSet(); } // Add dependent mod @@ -527,7 +526,7 @@ private void addToFavoritesDependencies(string modFileName, string dependentModF } private void removeFromFavorites(string modFileName) { - favoritedMods.Remove(modFileName); + favoriteMods.Remove(modFileName); Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{modFileName} was removed from favorites"); if (TryGetModDependenciesFileNames(modFileName, out List dependenciesFileNames)) { @@ -538,13 +537,13 @@ private void removeFromFavorites(string modFileName) { } private void removeFromFavoritesDependencies(string modFileName, string dependentModFileName) { - if (favoriteDependencies.TryGetValue(modFileName, out HashSet dependents) && dependents.Contains(dependentModFileName)) { + if (favoriteModDependencies.TryGetValue(modFileName, out HashSet dependents) && dependents.Contains(dependentModFileName)) { dependents.Remove(dependentModFileName); Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{modFileName} was removed from being a favorite dependency of {dependentModFileName}"); if (dependents.Count == 0) { - favoriteDependencies.Remove(modFileName); + favoriteModDependencies.Remove(modFileName); Logger.Log(LogLevel.Verbose, "OuiModToggler", $"{modFileName} is no longer a favorite dependency"); } @@ -559,14 +558,14 @@ private void removeFromFavoritesDependencies(string modFileName, string dependen private void onBackPressed(Overworld overworld) { // "back" only works if the loading is done. if (modLoadingTask == null || modLoadingTask.IsCompleted || modLoadingTask.IsCanceled || modLoadingTask.IsFaulted) { - if (!favoritedModsOriginal.SetEquals(favoritedMods)) { - Everest.Loader.Favorites = favoritedMods; + if (!favoriteModsOriginal.SetEquals(favoriteMods)) { + Everest.Loader.Favorites = favoriteMods; using (StreamWriter writer = File.CreateText(Everest.Loader.PathFavorites)) { // header writer.WriteLine("# This is the favorite list. Lines starting with # are ignored."); writer.WriteLine(""); - foreach (string mod in favoritedMods) { + foreach (string mod in favoriteMods) { writer.WriteLine(mod); } } @@ -649,9 +648,9 @@ public override IEnumerator Leave(Oui next) { modLoadingTask = null; toggleDependencies = true; protectFavorites = false; - favoritedMods = null; - favoritedModsOriginal = null; - favoriteDependencies = null; + favoriteMods = null; + favoriteModsOriginal = null; + favoriteModDependencies = null; } } } From 9796afac946f9ff3dca35cba9759d7da74643b94 Mon Sep 17 00:00:00 2001 From: nhruo Date: Fri, 3 Nov 2023 14:37:52 +0200 Subject: [PATCH 18/19] dialog change --- Celeste.Mod.mm/Content/Dialog/English.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Celeste.Mod.mm/Content/Dialog/English.txt b/Celeste.Mod.mm/Content/Dialog/English.txt index 43c0ce48f..a283ab733 100755 --- a/Celeste.Mod.mm/Content/Dialog/English.txt +++ b/Celeste.Mod.mm/Content/Dialog/English.txt @@ -264,7 +264,7 @@ MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE1= When you enable a mod, all its dependencies will be enabled. MODOPTIONS_MODTOGGLE_TOGGLEDEPS_MESSAGE2= When you disable a mod, all mods that depend on it will be disabled. MODOPTIONS_MODTOGGLE_PROTECTFAVORITES= Protect Favorites - MODOPTIONS_MODTOGGLE_PROTECTFAVORITES_MESSAGE= Press {0} to add or remove mods from the favorite list. + MODOPTIONS_MODTOGGLE_PROTECTFAVORITES_MESSAGE= Press {0} to add or remove mods from your favorite list. MODOPTIONS_MODTOGGLE_MESSAGE_1= If you enable or disable mods, your blacklist.txt will be replaced, MODOPTIONS_MODTOGGLE_MESSAGE_2= and Celeste will restart to apply changes. MODOPTIONS_MODTOGGLE_MESSAGE_3= Highlighted mods are used by other enabled mods as a dependency. From 61274f30aa3b3b8e32d3f4b8f716f76a8dfc910b Mon Sep 17 00:00:00 2001 From: Maddie <52103563+maddie480@users.noreply.github.com> Date: Mon, 6 Nov 2023 21:54:20 +0100 Subject: [PATCH 19/19] Update Celeste.Mod.mm/Mod/UI/OuiModToggler.cs --- Celeste.Mod.mm/Mod/UI/OuiModToggler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs index 07975bfe3..fe91f6542 100755 --- a/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs +++ b/Celeste.Mod.mm/Mod/UI/OuiModToggler.cs @@ -647,7 +647,7 @@ public override IEnumerator Leave(Oui next) { modToggles = null; modLoadingTask = null; toggleDependencies = true; - protectFavorites = false; + protectFavorites = true; favoriteMods = null; favoriteModsOriginal = null; favoriteModDependencies = null;