diff --git a/managed/src/SwiftlyS2.Core/Modules/Menus/Menu.cs b/managed/src/SwiftlyS2.Core/Modules/Menus/Menu.cs index c86b4040..5958cb9c 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Menus/Menu.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Menus/Menu.cs @@ -61,7 +61,7 @@ internal partial class Menu : IMenu opt.OverflowStyle?.OverflowStyle == MenuHorizontalOverflowStyle.ScrollLeftLoop || opt.OverflowStyle?.OverflowStyle == MenuHorizontalOverflowStyle.ScrollRightLoop); - public void Close(IPlayer player) + public void Close( IPlayer player ) { NativePlayer.ClearCenterMenuRender(player.PlayerID); OnClose?.Invoke(player); @@ -80,7 +80,7 @@ public void Close(IPlayer player) ScrollPauseCounts.Clear(); } - public void MoveSelection(IPlayer player, int offset) + public void MoveSelection( IPlayer player, int offset ) { if (!SelectedIndex.ContainsKey(player)) { @@ -104,10 +104,10 @@ public void MoveSelection(IPlayer player, int offset) OnMove?.Invoke(player); OnItemHovered?.Invoke(player, Options[SelectedIndex[player]]); - Rerender(player); + Rerender(player, false); } - public void Rerender(IPlayer player, bool updateDisplayText = false) + public void Rerender( IPlayer player, bool updateHorizontalStyle = false ) { BeforeRender?.Invoke(player); @@ -146,8 +146,7 @@ public void Rerender(IPlayer player, bool updateDisplayText = false) var selectedIdx = SelectedIndex[player]; var halfVisible = maxVisibleOptions / 2; - var (startIndex, arrowPosition) = VerticalScrollStyle switch - { + var (startIndex, arrowPosition) = VerticalScrollStyle switch { MenuVerticalScrollStyle.WaitingCenter when selectedIdx < halfVisible => (0, selectedIdx), // WaitingCenter: (Near top) start from 0, arrow at selected MenuVerticalScrollStyle.WaitingCenter when selectedIdx >= totalOptions - halfVisible @@ -187,10 +186,7 @@ MenuVerticalScrollStyle.LinearScroll when selectedIdx < maxVisibleOptions - 1 html.Append("\u00A0\u00A0\u00A0 "); } - if (updateDisplayText) - { - html.Append(option.GetDisplayText(player)); - } + html.Append(option.GetDisplayText(player, updateHorizontalStyle)); html.Append("
"); } @@ -212,10 +208,7 @@ MenuVerticalScrollStyle.LinearScroll when selectedIdx < maxVisibleOptions - 1 html.Append("\u00A0\u00A0\u00A0 "); } - if (updateDisplayText) - { - html.Append(option.GetDisplayText(player)); - } + html.Append(option.GetDisplayText(player, updateHorizontalStyle)); html.Append("
"); } @@ -237,21 +230,21 @@ MenuVerticalScrollStyle.LinearScroll when selectedIdx < maxVisibleOptions - 1 private string BuildFooter() { - var footer = new StringBuilder(""); - - var isWASD = MenuManager.Settings.InputMode == "wasd"; - var selectDisplay = isWASD ? "D" : MenuManager.Settings.ButtonsUse.ToUpper(); - var scrollDisplay = isWASD ? "W/S" : MenuManager.Settings.ButtonsScroll.ToUpper(); - var exitDisplay = isWASD ? "A" : MenuManager.Settings.ButtonsExit.ToUpper(); + var isWasd = MenuManager.Settings.InputMode == "wasd"; - footer.Append($"Move: {scrollDisplay}"); - - footer.Append($" | Use: {selectDisplay}"); - - footer.Append($" | Exit: {exitDisplay}"); - - footer.Append("
"); - return footer.ToString(); + var selectDisplay = isWasd ? "D" : (ButtonOverrides?.Select?.ToString() ?? MenuManager.Settings.ButtonsUse).ToUpper(); + var scrollDisplay = isWasd ? "W/S" : (ButtonOverrides?.Move, ButtonOverrides?.MoveBack) switch { + (not null, not null) => $"{ButtonOverrides.Move}/{ButtonOverrides.MoveBack}", + _ => MenuManager.Settings.ButtonsScroll.ToUpper() + }; + var exitDisplay = isWasd ? "A" : (ButtonOverrides?.Exit?.ToString() ?? MenuManager.Settings.ButtonsExit).ToUpper(); + + return new StringBuilder("") + .Append($"Move: {scrollDisplay}") + .Append($" | Use: {selectDisplay}") + .Append($" | Exit: {exitDisplay}") + .Append("
") + .ToString(); } private void OnTickRender() @@ -262,7 +255,7 @@ private void OnTickRender() } } - public void Show(IPlayer player) + public void Show( IPlayer player ) { if (!SelectedIndex.TryAdd(player, 0)) { @@ -280,7 +273,7 @@ public void Show(IPlayer player) _Core.Event.OnTick += OnTickRender; } - Rerender(player); + Rerender(player, true); OnOpen?.Invoke(player); if (ShouldFreeze == true) SetFreezeState(player, true); @@ -297,7 +290,7 @@ public void Show(IPlayer player) ScrollPauseCounts.Clear(); } - public void UseSelection(IPlayer player) + public void UseSelection( IPlayer player ) { var selectedOption = Options[SelectedIndex[player]]; OnItemSelected?.Invoke(player, selectedOption); @@ -372,7 +365,7 @@ public void UseSelection(IPlayer player) } } - public void UseSlideOption(IPlayer player, bool isRight) + public void UseSlideOption( IPlayer player, bool isRight ) { var selectedOption = Options[SelectedIndex[player]]; @@ -389,22 +382,22 @@ public void UseSlideOption(IPlayer player, bool isRight) break; } - Rerender(player); + Rerender(player, false); } - public bool IsOptionSlider(IPlayer player) + public bool IsOptionSlider( IPlayer player ) { var option = Options[SelectedIndex[player]]; return option is SliderMenuButton || option is ChoiceMenuOption; } - public bool IsCurrentOptionSelectable(IPlayer player) + public bool IsCurrentOptionSelectable( IPlayer player ) { var option = Options[SelectedIndex[player]]; return IsOptionSelectable(option); } - public bool IsOptionSelectable(IOption option) + public bool IsOptionSelectable( IOption option ) { return option is ButtonMenuOption || option is ToggleMenuOption || @@ -414,7 +407,7 @@ option is AsyncButtonMenuOption || (option is DynamicMenuOption dynamic && dynamic.CanInteract(null!)); } - public void SetFreezeState(IPlayer player, bool freeze) + public void SetFreezeState( IPlayer player, bool freeze ) { if (!player.IsValid || player.IsFakeClient) return; @@ -427,7 +420,7 @@ public void SetFreezeState(IPlayer player, bool freeze) pawn.MoveTypeUpdated(); } - internal string ApplyHorizontalStyle(string text, MenuHorizontalStyle? overflowStyle = null) + internal string ApplyHorizontalStyle( string text, MenuHorizontalStyle? overflowStyle = null, bool updateHorizontalStyle = false ) { var activeStyle = overflowStyle ?? HorizontalStyle; @@ -442,19 +435,18 @@ internal string ApplyHorizontalStyle(string text, MenuHorizontalStyle? overflowS return text; } - return activeStyle.Value.OverflowStyle switch - { + return activeStyle.Value.OverflowStyle switch { MenuHorizontalOverflowStyle.TruncateEnd => TruncateTextEnd(text, activeStyle.Value.MaxWidth), MenuHorizontalOverflowStyle.TruncateBothEnds => TruncateTextBothEnds(text, activeStyle.Value.MaxWidth), - MenuHorizontalOverflowStyle.ScrollLeftFade => ScrollTextWithFade(text, activeStyle.Value.MaxWidth, true, activeStyle), - MenuHorizontalOverflowStyle.ScrollRightFade => ScrollTextWithFade(text, activeStyle.Value.MaxWidth, false, activeStyle), - MenuHorizontalOverflowStyle.ScrollLeftLoop => ScrollTextWithLoop($"{text.TrimEnd()} ", activeStyle.Value.MaxWidth, true, activeStyle), - MenuHorizontalOverflowStyle.ScrollRightLoop => ScrollTextWithLoop($" {text.TrimStart()}", activeStyle.Value.MaxWidth, false, activeStyle), + MenuHorizontalOverflowStyle.ScrollLeftFade => ScrollTextWithFade(updateHorizontalStyle, text, activeStyle.Value.MaxWidth, true, activeStyle), + MenuHorizontalOverflowStyle.ScrollRightFade => ScrollTextWithFade(updateHorizontalStyle, text, activeStyle.Value.MaxWidth, false, activeStyle), + MenuHorizontalOverflowStyle.ScrollLeftLoop => ScrollTextWithLoop(updateHorizontalStyle, $"{text.TrimEnd()} ", activeStyle.Value.MaxWidth, true, activeStyle), + MenuHorizontalOverflowStyle.ScrollRightLoop => ScrollTextWithLoop(updateHorizontalStyle, $" {text.TrimStart()}", activeStyle.Value.MaxWidth, false, activeStyle), _ => text }; } - private string ScrollTextWithFade(string text, float maxWidth, bool scrollLeft, MenuHorizontalStyle? style = null) + private string ScrollTextWithFade( bool updateHorizontalStyle, string text, float maxWidth, bool scrollLeft, MenuHorizontalStyle? style = null ) { // Prepare scroll data and validate var (plainChars, segments, targetCharCount) = PrepareScrollData(text, maxWidth); @@ -468,7 +460,7 @@ private string ScrollTextWithFade(string text, float maxWidth, bool scrollLeft, } // Update scroll offset (allow scrolling beyond end for complete fade-out) - var offset = UpdateScrollOffset(StripHtmlTags(text), scrollLeft, plainChars.Length + 1, style); + var offset = UpdateScrollOffset(updateHorizontalStyle, StripHtmlTags(text), scrollLeft, plainChars.Length + 1, style); // Calculate visible character range var (skipStart, skipEnd) = scrollLeft @@ -518,7 +510,7 @@ private string ScrollTextWithFade(string text, float maxWidth, bool scrollLeft, return result.ToString(); } - private string ScrollTextWithLoop(string text, float maxWidth, bool scrollLeft, MenuHorizontalStyle? style = null) + private string ScrollTextWithLoop( bool updateHorizontalStyle, string text, float maxWidth, bool scrollLeft, MenuHorizontalStyle? style = null ) { // Prepare scroll data and validate var (plainChars, segments, targetCharCount) = PrepareScrollData(text, maxWidth); @@ -532,7 +524,7 @@ private string ScrollTextWithLoop(string text, float maxWidth, bool scrollLeft, } // Update scroll offset for circular wrapping - var offset = UpdateScrollOffset(StripHtmlTags(text), scrollLeft, plainChars.Length, style); + var offset = UpdateScrollOffset(updateHorizontalStyle, StripHtmlTags(text), scrollLeft, plainChars.Length, style); // Build character-to-tags mapping for circular access Dictionary> charToActiveTags = []; @@ -607,7 +599,7 @@ private string ScrollTextWithLoop(string text, float maxWidth, bool scrollLeft, return result.ToString(); } - private static string TruncateTextEnd(string text, float maxWidth, string suffix = "...") + private static string TruncateTextEnd( string text, float maxWidth, string suffix = "..." ) { // Reserve space for suffix var targetWidth = maxWidth - Helper.EstimateTextWidth(suffix); @@ -657,7 +649,7 @@ private static string TruncateTextEnd(string text, float maxWidth, string suffix return result.ToString(); } - private static string TruncateTextBothEnds(string text, float maxWidth) + private static string TruncateTextBothEnds( string text, float maxWidth ) { if (string.IsNullOrEmpty(text)) { @@ -753,7 +745,7 @@ internal partial class Menu /// /// The text containing HTML tags. /// The text with all HTML tags removed. - private static string StripHtmlTags(string text) + private static string StripHtmlTags( string text ) { if (string.IsNullOrEmpty(text)) { @@ -768,7 +760,7 @@ private static string StripHtmlTags(string text) /// /// The text to parse. /// A list of segments where each segment is either a tag or plain text content. - private static List<(string Content, bool IsTag)> ParseHtmlSegments(string text) + private static List<(string Content, bool IsTag)> ParseHtmlSegments( string text ) { var tagMatches = HtmlTagRegex().Matches(text); if (tagMatches.Count == 0) @@ -803,10 +795,9 @@ private static string StripHtmlTags(string text) /// /// The HTML tag to process. /// The list of currently open tag names. - private static void ProcessOpenTag(string tag, List openTags) + private static void ProcessOpenTag( string tag, List openTags ) { - var tagName = tag switch - { + var tagName = tag switch { ['<', '/', .. var rest] => new string(rest).TrimEnd('>').Split(' ', 2)[0], ['<', '!', ..] => null, [.. var chars] when chars[^1] == '/' && chars[^2] == '>' => null, @@ -835,7 +826,7 @@ private static void ProcessOpenTag(string tag, List openTags) /// /// The StringBuilder to append closing tags to. /// The list of currently open tag names. - private static void CloseOpenTags(StringBuilder result, List openTags) + private static void CloseOpenTags( StringBuilder result, List openTags ) { openTags.AsEnumerable().Reverse().ToList().ForEach(tag => result.Append($"")); } @@ -846,7 +837,7 @@ private static void CloseOpenTags(StringBuilder result, List openTags) /// The characters to measure. /// The maximum width allowed. /// The number of characters that fit within the width. - private static int CalculateTargetCharCount(ReadOnlySpan plainChars, float maxWidth) + private static int CalculateTargetCharCount( ReadOnlySpan plainChars, float maxWidth ) { var currentWidth = 0f; var count = 0; @@ -866,12 +857,13 @@ private static int CalculateTargetCharCount(ReadOnlySpan plainChars, float /// Updates and returns the scroll offset for the given text. /// The offset increments based on tick count and wraps around at the specified length. /// + /// Whether to update the horizontal style. /// The plain text being scrolled. /// Whether scrolling left or right. /// The length at which the offset wraps around. /// Optional horizontal style settings. /// The current scroll offset. - private int UpdateScrollOffset(string plainText, bool scrollLeft, int wrapLength, MenuHorizontalStyle? style) + private int UpdateScrollOffset( bool updateHorizontalStyle, string plainText, bool scrollLeft, int wrapLength, MenuHorizontalStyle? style ) { var key = $"{plainText}_{scrollLeft}"; ScrollOffsets.TryAdd(key, 0); @@ -889,7 +881,7 @@ private int UpdateScrollOffset(string plainText, bool scrollLeft, int wrapLength } // Increment call count and scroll when threshold is reached - if (++ScrollCallCounts[key] >= ticksPerScroll) + if (updateHorizontalStyle && ++ScrollCallCounts[key] >= ticksPerScroll) { ScrollCallCounts[key] = 0; var newOffset = (ScrollOffsets[key] + 1) % wrapLength; @@ -912,7 +904,7 @@ private int UpdateScrollOffset(string plainText, bool scrollLeft, int wrapLength /// /// The HTML tag content to process. /// The list of currently active tags. - private static void UpdateTagState(string content, List activeTags) + private static void UpdateTagState( string content, List activeTags ) { if (!content.StartsWith("")) { @@ -935,7 +927,7 @@ private static void UpdateTagState(string content, List activeTags) /// The text to prepare for scrolling. /// The maximum width available for display. /// A tuple containing plain characters array, HTML segments, and target character count. - private static (char[]? PlainChars, List<(string Content, bool IsTag)> Segments, int TargetCharCount) PrepareScrollData(string text, float maxWidth) + private static (char[]? PlainChars, List<(string Content, bool IsTag)> Segments, int TargetCharCount) PrepareScrollData( string text, float maxWidth ) { var plainText = StripHtmlTags(text); if (Helper.EstimateTextWidth(plainText) <= maxWidth) diff --git a/managed/src/SwiftlyS2.Core/Modules/Menus/MenuManager.cs b/managed/src/SwiftlyS2.Core/Modules/Menus/MenuManager.cs index f352a332..dea2ac72 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Menus/MenuManager.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Menus/MenuManager.cs @@ -47,13 +47,12 @@ internal class MenuManager : IMenuManager private SoundEvent _exitSound = new(); private SoundEvent _scrollSound = new(); - public MenuManager(ISwiftlyCore core) + public MenuManager( ISwiftlyCore core ) { _Core = core; var settings = NativeEngineHelpers.GetMenuSettings(); var parts = settings.Split('\x01'); - Settings = new MenuSettings - { + Settings = new MenuSettings { NavigationPrefix = parts[0], InputMode = parts[1], ButtonsUse = parts[2], @@ -92,7 +91,7 @@ public MenuManager(ISwiftlyCore core) _Core.Event.OnMapUnload -= OnMapUnload; } - void KeyStateChange(IOnClientKeyStateChangedEvent @event) + void KeyStateChange( IOnClientKeyStateChangedEvent @event ) { var player = _Core.PlayerManager.GetPlayer(@event.PlayerId); var menu = GetMenu(player); @@ -106,6 +105,17 @@ void KeyStateChange(IOnClientKeyStateChangedEvent @event) var exitKey = menu.ButtonOverrides?.Exit ?? StringToKeyKind.GetValueOrDefault(Settings.ButtonsExit); var useKey = menu.ButtonOverrides?.Select ?? StringToKeyKind.GetValueOrDefault(Settings.ButtonsUse); + new Dictionary { { "Scroll", scrollKey }, { "ScrollBack", scrollBackKey }, { "Exit", exitKey }, { "Use", useKey } } + .GroupBy(kvp => kvp.Value) + .Where(g => g.Count() > 1 && @event.Key.HasFlag(g.Key)) + .ToList() + .ForEach(group => + { + Spectre.Console.AnsiConsole.WriteException( + new InvalidOperationException($"Duplicate key binding detected in menu '{menu.Title}': Key '{group.Key}' is used by: {string.Join(", ", group.Select(kvp => kvp.Key))}") + ); + }); + if (@event.Key == scrollKey) { menu.MoveSelection(player, 1); @@ -201,7 +211,7 @@ void KeyStateChange(IOnClientKeyStateChangedEvent @event) } } - public void OnClientDisconnected(IOnClientDisconnectedEvent @event) + public void OnClientDisconnected( IOnClientDisconnectedEvent @event ) { var player = _Core.PlayerManager.GetPlayer(@event.PlayerId); if (player == null) @@ -221,7 +231,7 @@ public void OnClientDisconnected(IOnClientDisconnectedEvent @event) } } - public void OnMapUnload(IOnMapUnloadEvent _) + public void OnMapUnload( IOnMapUnloadEvent _ ) { CloseAllMenus(); } @@ -242,7 +252,7 @@ public void CloseAllMenus() OpenMenus.Clear(); } - public void CloseMenu(IMenu menu) + public void CloseMenu( IMenu menu ) { foreach (var kvp in OpenMenus) { @@ -256,7 +266,7 @@ public void CloseMenu(IMenu menu) } } - public void CloseMenuByTitle(string title, bool exact = false) + public void CloseMenuByTitle( string title, bool exact = false ) { foreach (var kvp in OpenMenus) { @@ -270,7 +280,7 @@ public void CloseMenuByTitle(string title, bool exact = false) } } - public void CloseMenuForPlayer(IPlayer player) + public void CloseMenuForPlayer( IPlayer player ) { if (OpenMenus.TryRemove(player, out var menu)) { @@ -283,17 +293,17 @@ public void CloseMenuForPlayer(IPlayer player) } } - public IMenu CreateMenu(string title) + public IMenu CreateMenu( string title ) { return new Menu { Title = title, MenuManager = this, MaxVisibleOptions = Settings.ItemsPerPage, _Core = _Core }; } - public IMenu? GetMenu(IPlayer player) + public IMenu? GetMenu( IPlayer player ) { return OpenMenus.TryGetValue(player, out var menu) ? menu : null; } - public void OpenMenu(IPlayer player, IMenu menu) + public void OpenMenu( IPlayer player, IMenu menu ) { if (OpenMenus.TryGetValue(player, out var currentMenu)) { @@ -306,7 +316,7 @@ public void OpenMenu(IPlayer player, IMenu menu) OnMenuOpened?.Invoke(player, menu); } - public bool HasMenuOpen(IPlayer player) + public bool HasMenuOpen( IPlayer player ) { return NativePlayer.HasMenuShown(player.PlayerID); } diff --git a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/AsyncButtonMenuOption.cs b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/AsyncButtonMenuOption.cs index 626169c6..eaa5acc5 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/AsyncButtonMenuOption.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/AsyncButtonMenuOption.cs @@ -50,7 +50,7 @@ public bool CanInteract(IPlayer player) return !IsLoading && (EnabledCheck?.Invoke(player) ?? true); } - public string GetDisplayText(IPlayer player) + public string GetDisplayText(IPlayer player, bool updateHorizontalStyle = false) { var sizeClass = MenuSizeHelper.GetSizeClass(Size); @@ -59,7 +59,7 @@ public string GetDisplayText(IPlayer player) return $"{_loadingText ?? "Loading..."}"; } - var text = (Menu as Menus.Menu)?.ApplyHorizontalStyle(Text, OverflowStyle) ?? Text; + var text = (Menu as Menus.Menu)?.ApplyHorizontalStyle(Text, OverflowStyle, updateHorizontalStyle) ?? Text; if (!CanInteract(player)) { return $"{text}"; diff --git a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ButtonMenuOption.cs b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ButtonMenuOption.cs index 86afbe89..d8206467 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ButtonMenuOption.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ButtonMenuOption.cs @@ -47,11 +47,11 @@ public bool CanInteract(IPlayer player) return EnabledCheck?.Invoke(player) ?? true; } - public string GetDisplayText(IPlayer player) + public string GetDisplayText(IPlayer player, bool updateHorizontalStyle = false) { var sizeClass = MenuSizeHelper.GetSizeClass(Size); - var text = (Menu as Menus.Menu)?.ApplyHorizontalStyle(Text, OverflowStyle) ?? Text; + var text = (Menu as Menus.Menu)?.ApplyHorizontalStyle(Text, OverflowStyle, updateHorizontalStyle) ?? Text; if (!CanInteract(player)) { return $"{text}"; diff --git a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ChoiceMenuButton.cs b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ChoiceMenuButton.cs index 798fd42e..ef7857c0 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ChoiceMenuButton.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ChoiceMenuButton.cs @@ -67,13 +67,13 @@ public bool CanInteract(IPlayer player) return EnabledCheck?.Invoke(player) ?? true; } - public string GetDisplayText(IPlayer player) + public string GetDisplayText(IPlayer player, bool updateHorizontalStyle = false) { var sizeClass = MenuSizeHelper.GetSizeClass(Size); var choice = $"[{SelectedChoice}]"; - var text = (Menu as Menus.Menu)?.ApplyHorizontalStyle(Text, OverflowStyle) ?? Text; + var text = (Menu as Menus.Menu)?.ApplyHorizontalStyle(Text, OverflowStyle, updateHorizontalStyle) ?? Text; if (!CanInteract(player)) { return $"{text}: {choice}"; diff --git a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/DynamicMenuOption.cs b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/DynamicMenuOption.cs index ca1d401b..ef61f4a0 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/DynamicMenuOption.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/DynamicMenuOption.cs @@ -56,7 +56,7 @@ public bool CanInteract(IPlayer player) return _onClick != null && (_enabledCheck?.Invoke(player) ?? true); } - public string GetDisplayText(IPlayer player) + public string GetDisplayText(IPlayer player, bool updateHorizontalStyle = false) { var sizeClass = MenuSizeHelper.GetSizeClass(_size); @@ -69,7 +69,7 @@ public string GetDisplayText(IPlayer player) if (oldText != _cachedText && Menu != null) { - Menu.Rerender(player, true); + Menu.Rerender(player, false); } } diff --git a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ProgressBarMenuButton.cs b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ProgressBarMenuButton.cs index 7ca66740..c54c0631 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ProgressBarMenuButton.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ProgressBarMenuButton.cs @@ -22,7 +22,7 @@ internal class ProgressBarMenuOption(string text, Func progressProvider, public bool ShouldShow(IPlayer player) => true; public bool CanInteract(IPlayer player) => false; - public string GetDisplayText(IPlayer player) + public string GetDisplayText(IPlayer player, bool updateHorizontalStyle = false) { var sizeClass = MenuSizeHelper.GetSizeClass(Size); @@ -37,7 +37,7 @@ public string GetDisplayText(IPlayer player) bar += $"{EmptyChar}"; var percentage = ShowPercentage ? $" {(int)(progress * 100)}%" : ""; - return $"{((Menu as Menus.Menu)?.ApplyHorizontalStyle(Text, OverflowStyle) ?? Text)}: {bar}{percentage}"; + return $"{((Menu as Menus.Menu)?.ApplyHorizontalStyle(Text, OverflowStyle, updateHorizontalStyle) ?? Text)}: {bar}{percentage}"; } public IMenuTextSize GetTextSize() diff --git a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/SeparatorMenuButton.cs b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/SeparatorMenuButton.cs index d0936b2f..0377eb30 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/SeparatorMenuButton.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/SeparatorMenuButton.cs @@ -20,7 +20,7 @@ public SeparatorMenuOption() public bool ShouldShow(IPlayer player) => true; public bool CanInteract(IPlayer player) => false; - public string GetDisplayText(IPlayer player) + public string GetDisplayText(IPlayer player, bool updateHorizontalStyle = false) { return $"{Text}"; } diff --git a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/SliderMenuButton.cs b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/SliderMenuButton.cs index d5dbeb7d..c45ffa78 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/SliderMenuButton.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/SliderMenuButton.cs @@ -57,7 +57,7 @@ public bool CanInteract(IPlayer player) return EnabledCheck?.Invoke(player) ?? true; } - public string GetDisplayText(IPlayer player) + public string GetDisplayText(IPlayer player, bool updateHorizontalStyle = false) { var sizeClass = MenuSizeHelper.GetSizeClass(Size); @@ -75,7 +75,7 @@ public string GetDisplayText(IPlayer player) } slider += $") {Value:F1}"; - var text = (Menu as Menus.Menu)?.ApplyHorizontalStyle(Text, OverflowStyle) ?? Text; + var text = (Menu as Menus.Menu)?.ApplyHorizontalStyle(Text, OverflowStyle, updateHorizontalStyle) ?? Text; if (!CanInteract(player)) { return $"{text}: {slider}"; diff --git a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/SubmenuMenuOption.cs b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/SubmenuMenuOption.cs index 6321bf99..2f0bafc2 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/SubmenuMenuOption.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/SubmenuMenuOption.cs @@ -42,7 +42,7 @@ public bool CanInteract(IPlayer player) return EnabledCheck?.Invoke(player) ?? true; } - public string GetDisplayText(IPlayer player) + public string GetDisplayText(IPlayer player, bool updateHorizontalStyle = false) { var sizeClass = MenuSizeHelper.GetSizeClass(Size); diff --git a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/TextMenuOption.cs b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/TextMenuOption.cs index 565f39cd..4fca0796 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/TextMenuOption.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/TextMenuOption.cs @@ -44,11 +44,12 @@ public bool CanInteract(IPlayer player) return true; } - public string GetDisplayText(IPlayer player) + public string GetDisplayText(IPlayer player, bool updateHorizontalStyle) { var text = DynamicText?.Invoke() ?? Text; - text = (Menu as Menus.Menu)?.ApplyHorizontalStyle(text, OverflowStyle) ?? text; + + text = (Menu as Menus.Menu)?.ApplyHorizontalStyle(text, OverflowStyle, updateHorizontalStyle) ?? text; var sizeClass = MenuSizeHelper.GetSizeClass(Size); diff --git a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ToggleMenuOption.cs b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ToggleMenuOption.cs index d488af44..cfd4f313 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ToggleMenuOption.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Menus/Options/ToggleMenuOption.cs @@ -50,13 +50,13 @@ public bool CanInteract(IPlayer player) return EnabledCheck?.Invoke(player) ?? true; } - public string GetDisplayText(IPlayer player) + public string GetDisplayText(IPlayer player, bool updateHorizontalStyle = false) { var sizeClass = MenuSizeHelper.GetSizeClass(Size); var status = Value ? "" : ""; - var text = (Menu as Menus.Menu)?.ApplyHorizontalStyle(Text, OverflowStyle) ?? Text; + var text = (Menu as Menus.Menu)?.ApplyHorizontalStyle(Text, OverflowStyle, updateHorizontalStyle) ?? Text; if (!CanInteract(player)) { return $"{text}: {status}/"; diff --git a/managed/src/SwiftlyS2.Shared/Modules/Menus/IMenu.cs b/managed/src/SwiftlyS2.Shared/Modules/Menus/IMenu.cs index 12e6f406..ff7b515a 100644 --- a/managed/src/SwiftlyS2.Shared/Modules/Menus/IMenu.cs +++ b/managed/src/SwiftlyS2.Shared/Modules/Menus/IMenu.cs @@ -175,8 +175,8 @@ public interface IMenu /// Updates the menu display with current state and options. /// /// The player to re-render the menu for. - /// True to update display text, false to render without updating display text. - public void Rerender(IPlayer player, bool updateDisplayText = false); + /// True to update horizontal style, false to render without updating horizontal style. + public void Rerender(IPlayer player, bool updateHorizontalStyle = false); /// /// Determines whether the currently selected option is selectable for the specified player. diff --git a/managed/src/SwiftlyS2.Shared/Modules/Menus/IOption.cs b/managed/src/SwiftlyS2.Shared/Modules/Menus/IOption.cs index 87d979bd..d8765642 100644 --- a/managed/src/SwiftlyS2.Shared/Modules/Menus/IOption.cs +++ b/managed/src/SwiftlyS2.Shared/Modules/Menus/IOption.cs @@ -50,8 +50,9 @@ public interface IOption /// Gets the display text for this option as it should appear to the specified player. /// /// The player requesting the display text. + /// Indicates whether to update the horizontal style of the text. /// The formatted display text for the option. - public string GetDisplayText(IPlayer player); + public string GetDisplayText(IPlayer player, bool updateHorizontalStyle); /// /// Gets the text size configuration for this option. diff --git a/managed/src/TestPlugin/TestPlugin.cs b/managed/src/TestPlugin/TestPlugin.cs index 4895f243..9ae46f8b 100644 --- a/managed/src/TestPlugin/TestPlugin.cs +++ b/managed/src/TestPlugin/TestPlugin.cs @@ -554,6 +554,36 @@ public HookResult TestServerNetMessageHandler(CCSUsrMsg_SendPlayerItemDrops msg) return HookResult.Continue; } + [Command("i76")] + public void TestIssue76Command(ICommandContext context) + { + var player = context.Sender!; + IMenu settingsMenu = Core.Menus.CreateMenu("Settings"); + // Add the following code to render text properly + //settingsMenu.Builder.AddText("123", overflowStyle: MenuHorizontalStyle.ScrollLeftLoop(25f)); + settingsMenu.Builder.AddText("123"); + settingsMenu.Builder.AddText("1234"); + settingsMenu.Builder.AddText("12345"); + + Core.Menus.OpenMenu(player, settingsMenu); + } + + [Command("i78")] + public void TestIssue78Command(ICommandContext context) + { + var player = context.Sender!; + IMenu settingsMenu = Core.Menus.CreateMenu("Settings"); + settingsMenu.Builder.AddButton("123", (p) => + { + player.SendMessage(MessageType.Chat, "Button"); + }); + + settingsMenu.Builder.Design.OverrideExitButton("shift"); + settingsMenu.Builder.Design.OverrideSelectButton("e"); + + Core.Menus.OpenMenu(player, settingsMenu); + } + [Command("mt")] public void MenuTestCommand(ICommandContext context) {