From 9625f587efcabf5e697284f24ad2b2fe2ae5d1dc Mon Sep 17 00:00:00 2001 From: jciurc Date: Sun, 4 Oct 2026 21:58:23 -0700 Subject: [PATCH 1/2] Place the top bar between the vanilla header and the mods panel Inline on the title row, the profile bar covered the vanilla Make Mods and Get Mods buttons. Always lay it out at the panel's width, directly under the header: lower the panel's top edge by the bar's height and keep the header where it was. Generated with Claude Code (Claude Opus 5.5) --- Patches/ModdingScreenChromeOps.cs | 55 ++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/Patches/ModdingScreenChromeOps.cs b/Patches/ModdingScreenChromeOps.cs index aaa83ab..18841a0 100644 --- a/Patches/ModdingScreenChromeOps.cs +++ b/Patches/ModdingScreenChromeOps.cs @@ -8,6 +8,8 @@ namespace BetterModMenu.Patches; internal static class ModdingScreenChromeOps { + private const string TopBarRoomMeta = "better_mod_menu_top_bar_room"; + private static readonly string[] LayoutControlPaths = { "%InstalledModsTitle", @@ -93,18 +95,17 @@ public static void UpdateLayout(NModdingScreen screen, ModdingScreenSession sess float groupBarHeight = GetGroupBarHeight(scrollContainer); float searchBarHeight = GetSearchBarHeight(scrollContainer); - bool stackTopBar = session.TopBarControls != null && - GodotObject.IsInstanceValid(session.TopBarControls.Bar) && - ShouldStackTopBar(session.TopBarControls, titleNode, scrollContainer, screenOffset); - float stackedTopBarHeight = stackTopBar - ? session.TopBarControls!.Bar.GetCombinedMinimumSize().Y + ModdingScreenConstants.TopBarStackGap - : 0f; + var topBarControls = session.TopBarControls is { } controls && GodotObject.IsInstanceValid(controls.Bar) + ? controls + : null; + if (topBarControls != null && scrollContainer?.GetParent() is Control modsPanel) + MakeRoomAboveModsPanel(modsPanel, scrollContainer, topBarControls.Bar.GetCombinedMinimumSize().Y + ModdingScreenConstants.TopBarStackGap); if (scrollContainer != null) - ReserveModListChromeSpace(session, scrollContainer, groupBarHeight, searchBarHeight, stackedTopBarHeight); + ReserveModListChromeSpace(session, scrollContainer, groupBarHeight, searchBarHeight); - if (session.TopBarControls != null && GodotObject.IsInstanceValid(session.TopBarControls.Bar)) - LayoutTopBar(session.TopBarControls, titleNode, scrollContainer, screenOffset, groupBarHeight, stackTopBar); + if (topBarControls != null) + LayoutTopBar(topBarControls, titleNode, scrollContainer, screenOffset, groupBarHeight, stackTopBar: true); if (session.GroupBarControls != null && GodotObject.IsInstanceValid(session.GroupBarControls.Bar)) { @@ -420,15 +421,11 @@ private static void LayoutTopBar( if (titleNode != null && scrollContainer != null) { float leftPanelRight = scrollContainer.GlobalPosition.X - screenOffset.X + scrollContainer.Size.X; - if (stackTopBar) + if (stackTopBar && scrollContainer.GetParent() is Control modsPanel) { - x = scrollContainer.GlobalPosition.X - screenOffset.X; - y = scrollContainer.GlobalPosition.Y - screenOffset.Y - - groupBarHeight - - ModdingScreenConstants.GroupBarListGap - - height - - ModdingScreenConstants.TopBarStackGap; - width = scrollContainer.Size.X; + x = modsPanel.GlobalPosition.X - screenOffset.X; + y = modsPanel.GlobalPosition.Y - screenOffset.Y - height - ModdingScreenConstants.TopBarStackGap; + width = modsPanel.Size.X; } else { @@ -456,6 +453,25 @@ private static bool ShouldStackTopBar(TopBarControls topBarControls, Control? ti return ModdingScreenLayoutRules.ShouldStackTopBar(availableInlineWidth, topBarControls.Bar.GetCombinedMinimumSize().X); } + // Makes room for the top bar between the vanilla header and the mods panel + // Applied once per panel because layout runs repeatedly + private static void MakeRoomAboveModsPanel(Control modsPanel, Control scrollContainer, float height) + { + if (modsPanel.HasMeta(TopBarRoomMeta)) + return; + + modsPanel.SetMeta(TopBarRoomMeta, height); + float scrollHeight = scrollContainer.Size.Y; + modsPanel.Position += new Vector2(0f, height); + modsPanel.Size -= new Vector2(0f, height); + foreach (var child in modsPanel.GetChildren()) + { + if (child is Control control && control != scrollContainer) + control.Position -= new Vector2(0f, height); + } + scrollContainer.Size = new Vector2(scrollContainer.Size.X, scrollHeight - height); + } + private static void PrepareInstalledModsTitle(Control? titleNode) { if (titleNode is not Label label) @@ -483,8 +499,7 @@ private static void ReserveModListChromeSpace( ModdingScreenSession session, Control scrollContainer, float groupBarHeight, - float searchBarHeight, - float stackedTopBarHeight) + float searchBarHeight) { if (!session.OriginalModsScrollPosition.HasValue) session.OriginalModsScrollPosition = scrollContainer.Position; @@ -494,7 +509,7 @@ private static void ReserveModListChromeSpace( Vector2 originalPosition = session.OriginalModsScrollPosition.Value; Vector2 originalSize = session.OriginalModsScrollSize.Value; - float reservedTopHeight = groupBarHeight + ModdingScreenConstants.GroupBarListGap + stackedTopBarHeight; + float reservedTopHeight = groupBarHeight + ModdingScreenConstants.GroupBarListGap; float reservedBottomHeight = searchBarHeight + ModdingScreenConstants.SearchBarListGap; scrollContainer.Position = new Vector2(originalPosition.X, originalPosition.Y + reservedTopHeight); scrollContainer.Size = new Vector2(originalSize.X, Math.Max(120f, originalSize.Y - reservedTopHeight - reservedBottomHeight)); From ad729c8a92ae1e30bb3694c082c26a48d019be9b Mon Sep 17 00:00:00 2001 From: jciurc Date: Sun, 4 Oct 2026 22:36:29 -0700 Subject: [PATCH 2/2] Remove the unreachable inline top bar layout The top bar is always placed under the vanilla header now, so the inline title-row layout, ShouldStackTopBar (and its rule and test), and the TopBarGap / TopBarTrailingPadding constants are no longer used. Generated with Claude Code (Claude Opus 5.5) --- BetterModMenu.Tests/LogicTests.cs | 8 ------ Patches/ModdingScreenChromeOps.cs | 41 +++++------------------------ Patches/ModdingScreenConstants.cs | 2 -- Patches/ModdingScreenLayoutRules.cs | 5 ---- 4 files changed, 6 insertions(+), 50 deletions(-) diff --git a/BetterModMenu.Tests/LogicTests.cs b/BetterModMenu.Tests/LogicTests.cs index f1bd33b..56c4b0f 100644 --- a/BetterModMenu.Tests/LogicTests.cs +++ b/BetterModMenu.Tests/LogicTests.cs @@ -554,14 +554,6 @@ public void GetTopBarPresentation_UsesIconOnlyActions() Assert.AreEqual(ModdingScreenConstants.TopBarButtonCompactWidth, wide.ButtonWidth); } - [TestMethod] - public void ShouldStackTopBar_UsesAvailableLocalizedTitleSpace() - { - Assert.IsTrue(ModdingScreenLayoutRules.ShouldStackTopBar(0f, 275f)); - Assert.IsTrue(ModdingScreenLayoutRules.ShouldStackTopBar(274f, 275f)); - Assert.IsFalse(ModdingScreenLayoutRules.ShouldStackTopBar(275f, 275f)); - } - [TestMethod] public void ShouldShowRowMoveButtons_PreservesGroupPickerOnNarrowRows() { diff --git a/Patches/ModdingScreenChromeOps.cs b/Patches/ModdingScreenChromeOps.cs index 18841a0..37890de 100644 --- a/Patches/ModdingScreenChromeOps.cs +++ b/Patches/ModdingScreenChromeOps.cs @@ -105,7 +105,7 @@ public static void UpdateLayout(NModdingScreen screen, ModdingScreenSession sess ReserveModListChromeSpace(session, scrollContainer, groupBarHeight, searchBarHeight); if (topBarControls != null) - LayoutTopBar(topBarControls, titleNode, scrollContainer, screenOffset, groupBarHeight, stackTopBar: true); + LayoutTopBar(topBarControls, scrollContainer, screenOffset); if (session.GroupBarControls != null && GodotObject.IsInstanceValid(session.GroupBarControls.Bar)) { @@ -404,13 +404,7 @@ private static float GetCenteredModsScrollbarX(NModdingScreen screen, NScrollabl ModdingScreenConstants.ModsScrollbarOpticalCenterOffset; } - private static void LayoutTopBar( - TopBarControls topBarControls, - Control? titleNode, - Control? scrollContainer, - Vector2 screenOffset, - float groupBarHeight, - bool stackTopBar) + private static void LayoutTopBar(TopBarControls topBarControls, Control? scrollContainer, Vector2 screenOffset) { var topBar = topBarControls.Bar; float x = ModdingScreenConstants.TopBarFallbackX; @@ -418,22 +412,11 @@ private static void LayoutTopBar( float width = ModdingScreenConstants.TopBarFallbackWidth; float height = topBar.GetCombinedMinimumSize().Y; - if (titleNode != null && scrollContainer != null) + if (scrollContainer?.GetParent() is Control modsPanel) { - float leftPanelRight = scrollContainer.GlobalPosition.X - screenOffset.X + scrollContainer.Size.X; - if (stackTopBar && scrollContainer.GetParent() is Control modsPanel) - { - x = modsPanel.GlobalPosition.X - screenOffset.X; - y = modsPanel.GlobalPosition.Y - screenOffset.Y - height - ModdingScreenConstants.TopBarStackGap; - width = modsPanel.Size.X; - } - else - { - x = titleNode.GlobalPosition.X - screenOffset.X + titleNode.Size.X + ModdingScreenConstants.TopBarGap; - y = titleNode.GlobalPosition.Y - screenOffset.Y; - width = Math.Max(0f, leftPanelRight - x - ModdingScreenConstants.TopBarTrailingPadding); - height = Math.Max(height, titleNode.Size.Y); - } + x = modsPanel.GlobalPosition.X - screenOffset.X; + y = modsPanel.GlobalPosition.Y - screenOffset.Y - height - ModdingScreenConstants.TopBarStackGap; + width = modsPanel.Size.X; } topBarControls.SetCompact(width < ModdingScreenConstants.TopBarCompactThreshold); @@ -441,18 +424,6 @@ private static void LayoutTopBar( topBar.Size = new Vector2(width, height); } - private static bool ShouldStackTopBar(TopBarControls topBarControls, Control? titleNode, Control? scrollContainer, Vector2 screenOffset) - { - if (titleNode == null || scrollContainer == null) - return false; - - float titleRight = titleNode.GlobalPosition.X - screenOffset.X + titleNode.Size.X; - float leftPanelRight = scrollContainer.GlobalPosition.X - screenOffset.X + scrollContainer.Size.X; - float availableInlineWidth = leftPanelRight - titleRight - ModdingScreenConstants.TopBarGap - ModdingScreenConstants.TopBarTrailingPadding; - topBarControls.SetCompact(true); - return ModdingScreenLayoutRules.ShouldStackTopBar(availableInlineWidth, topBarControls.Bar.GetCombinedMinimumSize().X); - } - // Makes room for the top bar between the vanilla header and the mods panel // Applied once per panel because layout runs repeatedly private static void MakeRoomAboveModsPanel(Control modsPanel, Control scrollContainer, float height) diff --git a/Patches/ModdingScreenConstants.cs b/Patches/ModdingScreenConstants.cs index fddcfd3..319c5b1 100644 --- a/Patches/ModdingScreenConstants.cs +++ b/Patches/ModdingScreenConstants.cs @@ -8,8 +8,6 @@ internal static class ModdingScreenConstants public const string TickboxPath = "Tickbox"; public const string UnassignedGroup = "Unassigned"; - public const float TopBarGap = 10f; - public const float TopBarTrailingPadding = 30f; public const float TopBarCompactThreshold = 470f; public const float TopBarFallbackX = 300f; public const float TopBarFallbackY = 55f; diff --git a/Patches/ModdingScreenLayoutRules.cs b/Patches/ModdingScreenLayoutRules.cs index cb3c71d..b3a219e 100644 --- a/Patches/ModdingScreenLayoutRules.cs +++ b/Patches/ModdingScreenLayoutRules.cs @@ -33,11 +33,6 @@ public static VisibleRowSpan IntersectVisibleRowSpan( return new VisibleRowSpan(left, right); } - public static bool ShouldStackTopBar(float availableInlineWidth, float requiredWidth) - { - return availableInlineWidth < requiredWidth; - } - public static bool ShouldShowRowMoveButtons(float rowWidth, float controlsWidth) { float trailingInset = ModdingScreenConstants.RowControlsRightPadding + ModdingScreenConstants.RowNativeTickboxReserveWidth;