From 8a304b279c9cfc54c0f559d1ec0f4342765a4424 Mon Sep 17 00:00:00 2001 From: flibber-hk <76987839+flibber-hk@users.noreply.github.com> Date: Mon, 27 Apr 2026 21:48:05 +0100 Subject: [PATCH 1/4] Add option to localize enum description --- Silksong.ModMenu/Models/Attributes.cs | 19 ++++++++++ .../Plugin/MenuElementGenerators.cs | 38 +++++++++++++++---- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/Silksong.ModMenu/Models/Attributes.cs b/Silksong.ModMenu/Models/Attributes.cs index cf42398..a6dab46 100644 --- a/Silksong.ModMenu/Models/Attributes.cs +++ b/Silksong.ModMenu/Models/Attributes.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Reflection; +using Silksong.ModMenu.Plugin; namespace Silksong.ModMenu.Models; @@ -31,3 +32,21 @@ internal static bool IgnoreForModMenu(this MemberInfo self) => || self.GetCustomAttributes(true) .Any(attr => attr.GetType().Name == nameof(ModMenuIgnoreAttribute)); } + +/// +/// Attribute added to an enum member to indicate how its description should be localized, +/// for use in . +/// +[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] +public class LocalizedDescriptionAttribute(string sheetTitle, string key) : Attribute +{ + /// + /// The sheet title for the localized description. + /// + public string SheetTitle => sheetTitle; + + /// + /// The key for the localized description. + /// + public string Key => key; +} diff --git a/Silksong.ModMenu/Plugin/MenuElementGenerators.cs b/Silksong.ModMenu/Plugin/MenuElementGenerators.cs index 9b1b8e8..8b206ec 100644 --- a/Silksong.ModMenu/Plugin/MenuElementGenerators.cs +++ b/Silksong.ModMenu/Plugin/MenuElementGenerators.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -15,7 +16,7 @@ namespace Silksong.ModMenu.Plugin; public static class MenuElementGenerators { /// - /// Returns a Generator that creates a choice element with a description below the option value. + /// Returns a generator that creates a choice element with a description below the option value. /// /// The description will be taken from the attributes on the enum members. /// @@ -43,9 +44,9 @@ out ListChoiceModel? model return false; } - Dictionary descriptionLookup = model.Values.ToDictionary( + Dictionary descriptionLookup = model.Values.ToDictionary( t => t, - t => string.Empty + t => LocalizedText.Raw(string.Empty) ); bool anyDesc = false; foreach (object member in model.Values) @@ -54,10 +55,10 @@ out ListChoiceModel? model member.ToString(), BindingFlags.Static | BindingFlags.Public ); - DescriptionAttribute? desc = enumField.GetCustomAttribute(); - if (desc is not null) + + if (TryGetDescription(enumField, out LocalizedText? descText)) { - descriptionLookup[member] = desc.Description; + descriptionLookup[member] = descText; anyDesc = true; } } @@ -85,6 +86,29 @@ out ListChoiceModel? model return gen; } + private static bool TryGetDescription( + FieldInfo enumField, + [NotNullWhen(true)] out LocalizedText? descText + ) + { + LocalizedDescriptionAttribute lDesc = + enumField.GetCustomAttribute(); + if (lDesc is not null) + { + descText = LocalizedText.Key(new(lDesc.SheetTitle, lDesc.Key)); + return true; + } + + DescriptionAttribute? desc = enumField.GetCustomAttribute(); + if (desc is not null) + { + descText = desc.Description; + return true; + } + descText = default; + return false; + } + /// /// Create a generator that converts an int-valued /// with a AcceptableValueRange to a slider element. From e8ec7090552f4e865167f702bb07ad401b10bf32 Mon Sep 17 00:00:00 2001 From: flibber-hk <76987839+flibber-hk@users.noreply.github.com> Date: Mon, 27 Apr 2026 21:49:08 +0100 Subject: [PATCH 2/4] Update docstring --- Silksong.ModMenu/Plugin/MenuElementGenerators.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Silksong.ModMenu/Plugin/MenuElementGenerators.cs b/Silksong.ModMenu/Plugin/MenuElementGenerators.cs index 8b206ec..cd301a5 100644 --- a/Silksong.ModMenu/Plugin/MenuElementGenerators.cs +++ b/Silksong.ModMenu/Plugin/MenuElementGenerators.cs @@ -16,9 +16,10 @@ namespace Silksong.ModMenu.Plugin; public static class MenuElementGenerators { /// - /// Returns a generator that creates a choice element with a description below the option value. + /// Returns a generator that creates a choice element with a description below the option value for an enum option. /// - /// The description will be taken from the attributes on the enum members. + /// The description will be taken from the or + /// attributes on the enum members. /// /// If true, will also include the default description below the setting name. public static ConfigEntryFactory.MenuElementGenerator CreateRightDescGenerator( From 52f9e69b79f5b255d1e71bb2357d3870e366cdb9 Mon Sep 17 00:00:00 2001 From: flibber-hk <76987839+flibber-hk@users.noreply.github.com> Date: Mon, 27 Apr 2026 22:04:57 +0100 Subject: [PATCH 3/4] Factor out method to place elements onto a subpage --- Silksong.ModMenu/Plugin/ConfigEntryFactory.cs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Silksong.ModMenu/Plugin/ConfigEntryFactory.cs b/Silksong.ModMenu/Plugin/ConfigEntryFactory.cs index 254c45f..422edc9 100644 --- a/Silksong.ModMenu/Plugin/ConfigEntryFactory.cs +++ b/Silksong.ModMenu/Plugin/ConfigEntryFactory.cs @@ -131,8 +131,29 @@ TreeNode tree subpageNames.RemoveRange(origSize, subpageNames.Count - origSize); } - PaginatedMenuScreenBuilder builder = new(subpageNames.LastOrDefault() ?? menuName); - builder.AddRange(elements.OrderBy(e => e.path).Select(e => e.element)); + return ArrangeScreen( + elements.OrderBy(e => e.path).ToList(), + subpageNames.LastOrDefault() ?? menuName + ); + } + + /// + /// Given a list of menu elements, create a menu screen for those elements. + /// + /// This function is used by the default implementation of + /// + /// to build each subpage. + /// + /// Pairs (path to element, element) to arrange. + /// The title of the menu. + /// A menu screen containing those elements. + protected virtual AbstractMenuScreen ArrangeScreen( + List<(string path, MenuElement element)> elements, + LocalizedText menuName + ) + { + PaginatedMenuScreenBuilder builder = new(menuName); + builder.AddRange(elements.Select(e => e.element)); return builder.Build(); } From 1b9181693482785f6b8eb1b5f65fedd5bfedb43f Mon Sep 17 00:00:00 2001 From: flibber-hk <76987839+flibber-hk@users.noreply.github.com> Date: Mon, 27 Apr 2026 22:54:13 +0100 Subject: [PATCH 4/4] Add convenience overload for text button ctor Unfortunately the use cases in this repo can't use this overload but it does work in general --- Silksong.ModMenu/Elements/TextButton.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Silksong.ModMenu/Elements/TextButton.cs b/Silksong.ModMenu/Elements/TextButton.cs index e21e46d..c9379a3 100644 --- a/Silksong.ModMenu/Elements/TextButton.cs +++ b/Silksong.ModMenu/Elements/TextButton.cs @@ -1,5 +1,6 @@ using System; using Silksong.ModMenu.Internal; +using Silksong.ModMenu.Screens; using Silksong.UnityHelper.Extensions; using UnityEngine; using UnityEngine.EventSystems; @@ -45,6 +46,18 @@ public TextButton(LocalizedText text, LocalizedText description) public TextButton(LocalizedText text) : this(text, string.Empty) { } + /// + /// Construct a text button that jumps to the given menu screen on click. + /// + /// The text button will have the same text as the linked screen's title. + /// + /// + public TextButton(AbstractMenuScreen screen) + : this(screen.TitleText.LocalizedText) + { + OnSubmit = () => MenuScreenNavigation.Show(screen); + } + /// /// The action(s) to perform when this button is selected. /// This takes place on the UI Thread and so must be relatively instantaneous.