diff --git a/Silksong.ModMenu/Directory.Build.props b/Silksong.ModMenu/Directory.Build.props
index 04fadb4..9078789 100644
--- a/Silksong.ModMenu/Directory.Build.props
+++ b/Silksong.ModMenu/Directory.Build.props
@@ -11,6 +11,6 @@
It should follow the format major.minor.patch (semantic versioning). If you publish your mod
as a library to NuGet, this version will also be used as the package version.
-->
- 0.4.6
+ 0.5.0
diff --git a/Silksong.ModMenu/Elements/LocalizedText.cs b/Silksong.ModMenu/Elements/LocalizedText.cs
index 1dae199..8a2db84 100644
--- a/Silksong.ModMenu/Elements/LocalizedText.cs
+++ b/Silksong.ModMenu/Elements/LocalizedText.cs
@@ -7,7 +7,7 @@ namespace Silksong.ModMenu.Elements;
///
public class LocalizedText
{
- private readonly TeamCherry.Localization.LocalisedString localisedString;
+ private readonly LocalisedString localisedString;
private readonly string rawText;
private LocalizedText(LocalisedString localisedString, string rawText)
@@ -24,6 +24,11 @@ private LocalizedText(LocalisedString localisedString, string rawText)
? rawText
: Language.Get(localisedString.Key, localisedString.Sheet);
+ ///
+ /// Get a canonical programmatic string for this LocalizedText that does not change when the language changes.
+ ///
+ public string Canonical => localisedString.IsEmpty ? rawText : localisedString.Key;
+
///
/// Returns true if this object has localization support.
///
@@ -49,6 +54,22 @@ private LocalizedText(LocalisedString localisedString, string rawText)
///
public static LocalizedText Raw(string rawText) => new(new(), rawText);
+ ///
+ public override int GetHashCode() =>
+ IsLocalized ? localisedString.GetHashCode() : rawText.GetHashCode();
+
+ ///
+ /// Equals. Ignores raw text if it does not apply.
+ ///
+ public override bool Equals(object obj) =>
+ obj is LocalizedText other
+ && IsLocalized == other.IsLocalized
+ && (
+ IsLocalized
+ ? localisedString.Equals(other.localisedString)
+ : rawText.Equals(other.rawText)
+ );
+
///
/// Implicit conversion for raw text to un-localized LocalizedText.
///
diff --git a/Silksong.ModMenu/Internal/MenuPrefabs.cs b/Silksong.ModMenu/Internal/MenuPrefabs.cs
index b43404c..546abf1 100644
--- a/Silksong.ModMenu/Internal/MenuPrefabs.cs
+++ b/Silksong.ModMenu/Internal/MenuPrefabs.cs
@@ -1,6 +1,7 @@
using MonoDetour;
using MonoDetour.DetourTypes;
using MonoDetour.HookGen;
+using Silksong.ModMenu.Elements;
using Silksong.UnityHelper.Extensions;
using UnityEngine;
using UnityEngine.EventSystems;
@@ -152,10 +153,10 @@ private MenuPrefabs(UIManager uiManager)
sliderChild.FindChild("MasterVolValue")!.name = "Value";
}
- internal GameObject NewCustomMenu(string title)
+ internal GameObject NewCustomMenu(LocalizedText title)
{
var obj = Object.Instantiate(menuTemplate);
- obj.name = $"ModMenuScreen-{title}";
+ obj.name = $"ModMenuScreen-{title.Canonical}";
obj.transform.SetParent(canvas.transform, false);
obj.transform.localPosition = new(0, 10, 0);
diff --git a/Silksong.ModMenu/Internal/TreeNode.cs b/Silksong.ModMenu/Internal/TreeNode.cs
new file mode 100644
index 0000000..ed78e96
--- /dev/null
+++ b/Silksong.ModMenu/Internal/TreeNode.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+
+namespace Silksong.ModMenu.Internal;
+
+///
+/// A recursive tree structure which supports lazy node creation and postfix traversal.
+///
+internal class TreeNode
+ where V : new()
+{
+ private readonly Dictionary> subtrees = [];
+
+ public V Value = new();
+
+ public IReadOnlyDictionary> Subtrees => subtrees;
+
+ public TreeNode this[IEnumerable keys]
+ {
+ get
+ {
+ TreeNode tree = this;
+ foreach (var key in keys)
+ {
+ if (tree.subtrees.TryGetValue(key, out var subtree))
+ tree = subtree;
+ else
+ {
+ subtree = new();
+ tree.subtrees[key] = subtree;
+ tree = subtree;
+ }
+ }
+ return tree;
+ }
+ }
+
+ private void ForEachPostfixRecursive(
+ List keys,
+ Action, TreeNode> action
+ )
+ {
+ foreach (var e in subtrees)
+ {
+ keys.Add(e.Key);
+ e.Value.ForEachPostfixRecursive(keys, action);
+ keys.RemoveAt(keys.Count - 1);
+ }
+ action(keys, this);
+ }
+
+ public void ForEachPostfix(Action, TreeNode> action) =>
+ ForEachPostfixRecursive([], action);
+}
diff --git a/Silksong.ModMenu/Plugin/ConfigEntryFactory.cs b/Silksong.ModMenu/Plugin/ConfigEntryFactory.cs
index 1276139..1e5cd52 100644
--- a/Silksong.ModMenu/Plugin/ConfigEntryFactory.cs
+++ b/Silksong.ModMenu/Plugin/ConfigEntryFactory.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
using BepInEx;
using BepInEx.Configuration;
using Silksong.ModMenu.Elements;
@@ -48,32 +49,133 @@ public static void AddDefaultGenerator(MenuElementGenerator generator) =>
///
public readonly List Generators = [.. defaultGenerators];
+ ///
+ /// If true, organize elements heirarchically by the period-delimeted names of their config definitions.
+ ///
+ public bool GenerateSubgroups = false;
+
+ ///
+ /// The minimum number of elements required to generate a subgroup. Has no effect if GenerateSubgroups is false.
+ ///
+ public int MinSubgroupSize = 2;
+
+ private static IEnumerable SubgroupsFromConfig(ConfigDefinition config) =>
+ config.Section.Split('.').Concat(config.Key.Split('.')).Select(LocalizedText.Raw);
+
+ ///
+ /// The hierarchical subgroup names to use for each config entry.
+ ///
+ protected virtual IEnumerable GetSubgroupNames(
+ ConfigEntryBase config,
+ MenuElement menuElement
+ )
+ {
+ var subgroups = config.Description.Tags.OfType().FirstOrDefault();
+ return subgroups?.Subgroups
+ ?? (GenerateSubgroups ? SubgroupsFromConfig(config.Definition) : []);
+ }
+
+ private static void FindFirstNonEmptyChild(
+ ref TreeNode tree,
+ List keys
+ )
+ {
+ while (tree.Value.TotalElements <= 1 && tree.Subtrees.Count == 1)
+ {
+ var (key, subtree) = tree.Subtrees.First();
+ keys.Add(key);
+ tree = subtree;
+ }
+ }
+
+ private List<(string, MenuElement)> BuildSubtreeElements(
+ LocalizedText menuName,
+ List subpageNames,
+ TreeNode tree
+ )
+ {
+ // Return elements directly if there is not enough of them.
+ if (tree.Value.TotalElements < MinSubgroupSize)
+ {
+ List<(string, MenuElement)> list = [];
+ tree.ForEachPostfix((keys, t) => list.AddRange(t.Value.Elements));
+ list.Sort((a, b) => a.Item1.CompareTo(b.Item1));
+ return list;
+ }
+
+ // Otherwise, build a sub-page and return a button that navigates to it.
+ var name = string.Join(".", subpageNames.Select(n => n.Canonical));
+ var screen = BuildSubtreeScreen(menuName, subpageNames, tree);
+ TextButton button = new(subpageNames.Last())
+ {
+ OnSubmit = () => MenuScreenNavigation.Show(screen),
+ };
+ return [(name, button)];
+ }
+
+ private AbstractMenuScreen BuildSubtreeScreen(
+ LocalizedText menuName,
+ List subpageNames,
+ TreeNode tree
+ )
+ {
+ List<(string path, MenuElement element)> elements = [.. tree.Value.Elements];
+
+ int origSize = subpageNames.Count;
+ foreach (var entry in tree.Subtrees)
+ {
+ var subtree = entry.Value;
+ FindFirstNonEmptyChild(ref subtree, subpageNames);
+ elements.AddRange(BuildSubtreeElements(menuName, subpageNames, subtree));
+ subpageNames.RemoveRange(origSize, subpageNames.Count - origSize);
+ }
+
+ PaginatedMenuScreenBuilder builder = new(subpageNames.LastOrDefault() ?? menuName);
+ builder.AddRange(elements.OrderBy(e => e.path).Select(e => e.element));
+ return builder.Build();
+ }
+
///
/// Generate a button for this plugin which opens a sub-menu for its ConfigFile.
///
public virtual bool GenerateEntryButton(
- string name,
+ LocalizedText name,
BaseUnityPlugin plugin,
[MaybeNullWhen(false)] out SelectableElement selectableElement
)
{
- List elements = [];
- foreach (var entry in plugin.Config)
+ TreeNode elementsTree = new();
+ foreach (var entry in plugin.Config.OrderBy(e => e.Key.Key))
{
if (GenerateMenuElement(entry.Value, out var element))
- elements.Add(element);
+ {
+ var subgroupNames = GetSubgroupNames(entry.Value, element);
+ elementsTree[subgroupNames].Value.Elements.Add((entry.Key.Key, element));
+ }
}
- if (elements.Count == 0)
+ // Count the total number of elements in each subtree.
+ // The number of elements counted for each subtree is 1 if it gets its own menu button, N otherwise.
+ elementsTree.ForEachPostfix(
+ (_, tree) =>
+ tree.Value.TotalElements =
+ tree.Value.Elements.Count
+ + tree.Subtrees.Values.Select(t =>
+ t.Value.TotalElements >= MinSubgroupSize ? 1 : t.Value.TotalElements
+ )
+ .Sum()
+ );
+ if (elementsTree.Value.TotalElements == 0)
{
selectableElement = default;
return false;
}
- PaginatedMenuScreenBuilder builder = new(name);
- builder.AddRange(elements);
- var menu = builder.Build();
+ // Skip past any universal prefix.
+ List subpageNames = [];
+ FindFirstNonEmptyChild(ref elementsTree, subpageNames);
+ var menu = BuildSubtreeScreen(name, subpageNames, elementsTree);
selectableElement = new TextButton(name)
{
OnSubmit = () => MenuScreenNavigation.Show(menu),
@@ -315,6 +417,12 @@ public static bool GenerateStringElement(
menuElement = text;
return true;
}
+
+ private record ElementTreeNode
+ {
+ public readonly List<(string path, MenuElement element)> Elements = [];
+ public int TotalElements;
+ }
}
///
diff --git a/Silksong.ModMenu/Plugin/ConfigEntrySubgroup.cs b/Silksong.ModMenu/Plugin/ConfigEntrySubgroup.cs
new file mode 100644
index 0000000..adf3bf4
--- /dev/null
+++ b/Silksong.ModMenu/Plugin/ConfigEntrySubgroup.cs
@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using System.Linq;
+using Silksong.ModMenu.Elements;
+
+namespace Silksong.ModMenu.Plugin;
+
+///
+/// A config entry attribute that designates how this element should be organized into subpages hierarchically.
+/// If present, this overrides the default hierarchy names generated (or not generated) by ConfigEntryFactory.
+///
+public record ConfigEntrySubgroup
+{
+ ///
+ /// Construct subgroup names from a list.
+ ///
+ public ConfigEntrySubgroup(IEnumerable subgroups) => Subgroups = [.. subgroups];
+
+ ///
+ /// Construct subgroup names from explicit parameters.
+ ///
+ public ConfigEntrySubgroup(LocalizedText name1, params LocalizedText[] otherNames) =>
+ Subgroups = [name1, .. otherNames];
+
+ ///
+ /// The subgroup names that designate this config element's place in the subpage hierarchy. An empty list designates the root page.
+ ///
+ /// This can describe the page this entry belogs to, or the full path to the element itself explicitly.
+ /// Both will work for grouping purposes, but the choice must be consistent throughout the plugin. The default implementation generates full paths to each individual element.
+ ///
+ public readonly IReadOnlyList Subgroups;
+}
diff --git a/Silksong.ModMenu/Plugin/IModMenuInterface.cs b/Silksong.ModMenu/Plugin/IModMenuInterface.cs
index 6941546..749fa92 100644
--- a/Silksong.ModMenu/Plugin/IModMenuInterface.cs
+++ b/Silksong.ModMenu/Plugin/IModMenuInterface.cs
@@ -1,4 +1,8 @@
-namespace Silksong.ModMenu.Plugin;
+using System.Reflection;
+using BepInEx;
+using Silksong.ModMenu.Elements;
+
+namespace Silksong.ModMenu.Plugin;
///
/// Marker interface for all plugin interfaces intended to generate mod menus.
@@ -8,5 +12,6 @@ public interface IModMenuInterface
///
/// A unique identifier for this mod menu, used as a case-insensitive sort key.
///
- public string ModMenuName();
+ public LocalizedText ModMenuName() =>
+ GetType().GetCustomAttribute()?.Name ?? "###UNKNOWN###";
}
diff --git a/Silksong.ModMenu/Plugin/IModMenuNestedMenu.cs b/Silksong.ModMenu/Plugin/IModMenuNestedMenu.cs
new file mode 100644
index 0000000..99eec17
--- /dev/null
+++ b/Silksong.ModMenu/Plugin/IModMenuNestedMenu.cs
@@ -0,0 +1,13 @@
+namespace Silksong.ModMenu.Plugin;
+
+///
+/// Marker interface for a mod menu that generates hierarchical submenus, based on config keys.
+///
+public interface IModMenuNestedMenu : IModMenuInterface
+{
+ ///
+ /// The minimum number of elements required in a subgroup to generate a screen for it.
+ /// If a sub-page has too few elements, it is flattened into its containing page.
+ ///
+ int MinSubgroupSize() => 2;
+}
diff --git a/Silksong.ModMenu/Plugin/MenuElementGenerators.cs b/Silksong.ModMenu/Plugin/MenuElementGenerators.cs
index 65eee6a..9b1b8e8 100644
--- a/Silksong.ModMenu/Plugin/MenuElementGenerators.cs
+++ b/Silksong.ModMenu/Plugin/MenuElementGenerators.cs
@@ -91,7 +91,7 @@ out ListChoiceModel
public static ConfigEntryFactory.MenuElementGenerator CreateIntSliderGenerator()
{
- bool gen(ConfigEntryBase entry, [MaybeNullWhen(false)] out MenuElement menuElement)
+ static bool gen(ConfigEntryBase entry, [MaybeNullWhen(false)] out MenuElement menuElement)
{
menuElement = default;
diff --git a/Silksong.ModMenu/Plugin/PluginRegistry.cs b/Silksong.ModMenu/Plugin/PluginRegistry.cs
index 2a9408d..c0817eb 100644
--- a/Silksong.ModMenu/Plugin/PluginRegistry.cs
+++ b/Silksong.ModMenu/Plugin/PluginRegistry.cs
@@ -26,6 +26,7 @@ public static class PluginRegistry
[
HandleType(GenerateCustomElement),
HandleType(GenerateCustomMenu),
+ GenerateNestedMenu,
HandleType(GenerateToggle),
];
@@ -60,7 +61,7 @@ bool Handler(
internal static bool GenerateMenuElement(
BaseUnityPlugin plugin,
- out string name,
+ out LocalizedText name,
[MaybeNullWhen(false)] out SelectableElement menuElement
)
{
@@ -99,6 +100,29 @@ private static SelectableElement GenerateCustomMenu(IModMenuCustomMenu plugin)
};
}
+ private static bool GenerateNestedMenu(
+ IModMenuInterface plugin,
+ [MaybeNullWhen(false)] out SelectableElement menuElement
+ )
+ {
+ if (plugin is not IModMenuNestedMenu typed)
+ {
+ menuElement = default;
+ return false;
+ }
+
+ ConfigEntryFactory factory = new()
+ {
+ GenerateSubgroups = true,
+ MinSubgroupSize = typed.MinSubgroupSize(),
+ };
+ return factory.GenerateEntryButton(
+ plugin.ModMenuName(),
+ (BaseUnityPlugin)plugin,
+ out menuElement
+ );
+ }
+
private static SelectableElement GenerateToggle(IModMenuToggle plugin)
{
ChoiceElement element = new(
diff --git a/Silksong.ModMenu/Registry.cs b/Silksong.ModMenu/Registry.cs
index 5957203..02ab5d6 100644
--- a/Silksong.ModMenu/Registry.cs
+++ b/Silksong.ModMenu/Registry.cs
@@ -33,7 +33,7 @@ public static void AddModMenu(string name, MenuElementGenerator generator) =>
internal static IEnumerable GenerateAllMenuElements()
{
- List<(string, SelectableElement)> allElements = [];
+ List<(LocalizedText, SelectableElement)> allElements = [];
foreach (var (name, gen) in modMenuGenerators)
{
ExceptionUtil.Try(
@@ -62,6 +62,6 @@ internal static IEnumerable GenerateAllMenuElements()
);
}
- return [.. allElements.OrderBy(p => p.Item1.ToUpper()).Select(p => p.Item2)];
+ return [.. allElements.OrderBy(p => p.Item1.Text).Select(p => p.Item2)];
}
}
diff --git a/Silksong.ModMenu/Screens/AbstractMenuScreen.cs b/Silksong.ModMenu/Screens/AbstractMenuScreen.cs
index 56b8c08..886e519 100644
--- a/Silksong.ModMenu/Screens/AbstractMenuScreen.cs
+++ b/Silksong.ModMenu/Screens/AbstractMenuScreen.cs
@@ -22,7 +22,7 @@ public abstract class AbstractMenuScreen : MenuDisposable
///
/// Construct a menu screen with the given title.
///
- protected AbstractMenuScreen(string title)
+ protected AbstractMenuScreen(LocalizedText title)
{
Container = MenuPrefabs.Get().NewCustomMenu(title);
MenuScreen = Container.GetComponent();
@@ -40,7 +40,7 @@ protected AbstractMenuScreen(string title)
lateUpdate.OnLateUpdate += UpdateLayout;
lateUpdate.OnLateUpdate += UpdateLastSelected;
- TitleText.text = title;
+ TitleText.LocalizedText = title;
}
#endregion
diff --git a/Silksong.ModMenu/Screens/BasicMenuScreen.cs b/Silksong.ModMenu/Screens/BasicMenuScreen.cs
index 76e1c74..4a3bcf0 100644
--- a/Silksong.ModMenu/Screens/BasicMenuScreen.cs
+++ b/Silksong.ModMenu/Screens/BasicMenuScreen.cs
@@ -14,7 +14,7 @@ public class BasicMenuScreen : AbstractMenuScreen
///
/// Construct a basic menu screen with a single content entity.
///
- public BasicMenuScreen(string title, INavigableMenuEntity content)
+ public BasicMenuScreen(LocalizedText title, INavigableMenuEntity content)
: base(title) => Content = content;
///
diff --git a/Silksong.ModMenu/Screens/PaginatedMenuScreen.cs b/Silksong.ModMenu/Screens/PaginatedMenuScreen.cs
index fd41f47..cd37588 100644
--- a/Silksong.ModMenu/Screens/PaginatedMenuScreen.cs
+++ b/Silksong.ModMenu/Screens/PaginatedMenuScreen.cs
@@ -20,7 +20,7 @@ public class PaginatedMenuScreen : AbstractMenuScreen
///
/// Construct a PaginatedMenuScreen with the given title.
///
- public PaginatedMenuScreen(string title)
+ public PaginatedMenuScreen(LocalizedText title)
: base(title)
{
pageNumberModel = new(0, 0, 0) { Circular = true, DisplayFn = i => $"{i + 1}" };
diff --git a/Silksong.ModMenu/Screens/PaginatedMenuScreenBuilder.cs b/Silksong.ModMenu/Screens/PaginatedMenuScreenBuilder.cs
index 17b69bb..1389a9a 100644
--- a/Silksong.ModMenu/Screens/PaginatedMenuScreenBuilder.cs
+++ b/Silksong.ModMenu/Screens/PaginatedMenuScreenBuilder.cs
@@ -8,9 +8,9 @@ namespace Silksong.ModMenu.Screens;
///
/// A convience class for building a paginated menu screen from a stream of elements, grouping them into VerticalGroups.
///
-public class PaginatedMenuScreenBuilder(string title, int pageSize = 8)
+public class PaginatedMenuScreenBuilder(LocalizedText title, int pageSize = 8)
{
- private readonly string title = title;
+ private readonly LocalizedText title = title;
private readonly int pageSize = pageSize;
private readonly List menuElements = [];
diff --git a/Silksong.ModMenuTesting/ModMenuNestedTestingPlugin.cs b/Silksong.ModMenuTesting/ModMenuNestedTestingPlugin.cs
new file mode 100644
index 0000000..b57eb68
--- /dev/null
+++ b/Silksong.ModMenuTesting/ModMenuNestedTestingPlugin.cs
@@ -0,0 +1,34 @@
+using BepInEx;
+using Silksong.ModMenu.Elements;
+using Silksong.ModMenu.Plugin;
+
+namespace Silksong.ModMenuTesting;
+
+// This test requires its own ConfigFile so it's not part of the general testing framework.
+[BepInAutoPlugin(id: "org.silksong_modding.modmenunestedtesting")]
+public partial class ModMenuNestedTestingPlugin : BaseUnityPlugin, IModMenuNestedMenu
+{
+ private void Awake()
+ {
+ Config.Bind(new("Main", "Int1"), 1, new("Root Integer 1"));
+ Config.Bind(new("Main", "Int2"), 2, new("Root Integer 2"));
+ // Place this in Main instead of its config path.
+ Config.Bind(
+ new("Other.Group", "Int3"),
+ 3,
+ new("Other Integer 3", tags: new ConfigEntrySubgroup(["Main", "Int3"]))
+ );
+
+ // We should generte a single 'Sub' button for the three below
+ Config.Bind(new("Main.Sub", "Int4"), 4, new("Sub Integer 4"));
+ Config.Bind(new("Main.Sub", "Int5"), 5, new("Sub Integer 5"));
+ // This gets flattened into the 'Sub' menu because there are not enough 'Sub.Sub' elements.
+ Config.Bind(new("Main.Sub.Sub", "Int6"), 6, new("Sub Sub Integer 6"));
+
+ // Test
+ Config.Bind(new("Main.B.C", "Int7"), 7, new("ABC Integer 7"));
+ Config.Bind(new("Main.B.C", "Int8"), 8, new("ABC Integer 8"));
+ }
+
+ public LocalizedText ModMenuName() => "Mod Menu Nested Testing";
+}
diff --git a/Silksong.ModMenuTesting/ModMenuTestingPlugin.cs b/Silksong.ModMenuTesting/ModMenuTestingPlugin.cs
index 12a656c..7d83523 100644
--- a/Silksong.ModMenuTesting/ModMenuTestingPlugin.cs
+++ b/Silksong.ModMenuTesting/ModMenuTestingPlugin.cs
@@ -68,5 +68,5 @@ public AbstractMenuScreen BuildCustomMenu()
return builder.Build();
}
- public string ModMenuName() => "Mod Menu Testing";
+ public LocalizedText ModMenuName() => "Mod Menu Testing";
}
diff --git a/Silksong.ModMenuTesting/Tests/ElementSizesTest.cs b/Silksong.ModMenuTesting/Tests/ElementSizesTest.cs
index df0358c..61f3607 100644
--- a/Silksong.ModMenuTesting/Tests/ElementSizesTest.cs
+++ b/Silksong.ModMenuTesting/Tests/ElementSizesTest.cs
@@ -1,7 +1,7 @@
-using Silksong.ModMenu.Elements;
-using Silksong.ModMenu.Screens;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Linq;
+using Silksong.ModMenu.Elements;
+using Silksong.ModMenu.Screens;
namespace Silksong.ModMenuTesting.Tests;
diff --git a/Silksong.ModMenuTesting/Tests/StandardElementsTest.cs b/Silksong.ModMenuTesting/Tests/StandardElementsTest.cs
index 9c8df1a..302e5ea 100644
--- a/Silksong.ModMenuTesting/Tests/StandardElementsTest.cs
+++ b/Silksong.ModMenuTesting/Tests/StandardElementsTest.cs
@@ -1,9 +1,9 @@
-using Silksong.ModMenu.Elements;
-using Silksong.ModMenu.Models;
-using Silksong.ModMenu.Screens;
-using System;
+using System;
using System.Collections.Generic;
using System.Text;
+using Silksong.ModMenu.Elements;
+using Silksong.ModMenu.Models;
+using Silksong.ModMenu.Screens;
namespace Silksong.ModMenuTesting.Tests;
@@ -34,7 +34,11 @@ internal static IEnumerable CreateUnboundElements()
{
ListChoiceModel listChoiceModel = new(["First", "Second", "Third"]);
- ChoiceElement choiceElement = new("The List Choice", listChoiceModel, "Here is where to choose option(s)");
+ ChoiceElement choiceElement = new(
+ "The List Choice",
+ listChoiceModel,
+ "Here is where to choose option(s)"
+ );
listChoiceModel.OnValueChanged += v => Log($"List choice -> {v}");
yield return choiceElement;
}
@@ -53,7 +57,11 @@ internal static IEnumerable CreateUnboundElements()
{
ITextModel textModel = TextModels.ForStrings();
- TextInput stringInput = new("The Text Input", textModel, "Here is where to input text");
+ TextInput stringInput = new(
+ "The Text Input",
+ textModel,
+ "Here is where to input text"
+ );
textModel.OnValueChanged += s => Log($"Text model -> {s}");
yield return stringInput;
}