From 315170dfe1181e7eae5dfa920b8973d6e4e3132a Mon Sep 17 00:00:00 2001 From: kubiix Date: Tue, 23 Sep 2025 23:21:11 +0200 Subject: [PATCH 1/2] Pinnable "Page Search" with Right click --- Yafc.Model/Model/Project.cs | 5 +- Yafc.UI/Core/Window.cs | 57 +++++++++++++++++--- Yafc.UI/ImGui/DropDownPanel.cs | 19 ++++++- Yafc.UI/ImGui/ImGuiUtils.cs | 11 +++- Yafc/Data/locale/en/yafc.cfg | 2 +- Yafc/Windows/MainScreen.cs | 67 +++++++++++++++++++----- Yafc/Windows/ProjectPageSettingsPanel.cs | 2 + 7 files changed, 140 insertions(+), 23 deletions(-) diff --git a/Yafc.Model/Model/Project.cs b/Yafc.Model/Model/Project.cs index deb95f84..5a38224f 100644 --- a/Yafc.Model/Model/Project.cs +++ b/Yafc.Model/Model/Project.cs @@ -14,6 +14,7 @@ public class Project : ModelObject { public static Version currentYafcVersion { get; set; } = new Version(0, 4, 0); public uint projectVersion => undo.version; public string? attachedFileName { get; private set; } + public bool justCreated { get; private set; } = true; public ProjectSettings settings { get; } public ProjectPreferences preferences { get; } @@ -183,6 +184,7 @@ public void Save(string fileName) { public void Save(Stream stream) { using Utf8JsonWriter writer = new Utf8JsonWriter(stream, JsonUtils.DefaultWriterOptions); + SerializationMap.SerializeToJson(this, writer); } @@ -291,7 +293,8 @@ public class ProjectSettings(Project project) : ModelObject(project) { public int reactorSizeX { get; set; } = 2; public int reactorSizeY { get; set; } = 2; public float PollutionCostModifier { get; set; } = 0; - public float spoilingRate { get; set; } = 1; + public float spoilingRate { get; set; } = 1; + public bool isPagesListPinned { get; set; } public event Action? changed; protected internal override void ThisChanged(bool visualOnly) => changed?.Invoke(visualOnly); diff --git a/Yafc.UI/Core/Window.cs b/Yafc.UI/Core/Window.cs index 204d7c14..50b31cfa 100644 --- a/Yafc.UI/Core/Window.cs +++ b/Yafc.UI/Core/Window.cs @@ -23,11 +23,15 @@ public abstract class Window : IDisposable { private Tooltip? tooltip; private SimpleTooltip? simpleTooltip; - protected DropDownPanel? dropDown; + protected DropDownPanel? commonDropDown; private SimpleDropDown? simpleDropDown; + protected DropDownPanel? pagesDropDown; + private SimpleDropDown? pagesSimpleDropDown; private ImGui.DragOverlay? draggingOverlay; private bool disposedValue; + public bool PagesSimpleDropPinned => pagesSimpleDropDown?.pinnedMode ?? false; + public DrawingSurface? surface { get; protected set; } public int displayIndex => SDL.SDL_GetWindowDisplayIndex(window); @@ -215,7 +219,7 @@ public void ShowTooltip(ImGui targetGui, Rect target, GuiBuilder builder, float } public void ShowDropDown(DropDownPanel dropDown) { - this.dropDown = dropDown; + this.commonDropDown = dropDown; Rebuild(); } @@ -226,16 +230,57 @@ public void ShowDropDown(ImGui targetGui, Rect target, GuiBuilder builder, Paddi ShowDropDown(simpleDropDown); } + public void ShowPagesListDropDown(ImGui targetGui, Rect target, GuiBuilder builder, Padding padding, float width = 20f, bool pinned = false) { + + if (simpleDropDown?.active == true) { + simpleDropDown.Close(); + } + + pagesSimpleDropDown ??= new SimpleDropDown(); + + pagesSimpleDropDown.SetPadding(padding); + pagesSimpleDropDown.SetFocus(targetGui, target, builder, width, pinned); + + this.pagesDropDown = pagesSimpleDropDown; + this. + Rebuild(); + } + + public bool ClosePagesListDropDown(ImGui targetGui, Rect target) { + pagesSimpleDropDown ??= new SimpleDropDown(); + // If a pinned dropdown is already open for this source/rect and caller asked to open pinned, close it. + if (pagesSimpleDropDown.active && pagesSimpleDropDown.pinnedMode && pagesSimpleDropDown.MatchesSource(targetGui, target)) { + pagesSimpleDropDown.pinnedMode = false; + pagesSimpleDropDown.Close(); + // Ensure top-level reference is cleared so Build won't try to render it next frame + if (pagesDropDown == pagesSimpleDropDown) { + pagesDropDown = null; + } + Rebuild(); + + return true; + } + + return false; + } + private void Build(ImGui gui) { if (closed) { return; } BuildContents(gui); - if (dropDown != null) { - dropDown.Build(gui); - if (!dropDown.active) { - dropDown = null; + if (commonDropDown != null) { + commonDropDown.Build(gui); + if (!commonDropDown.active) { + commonDropDown = null; + } + } + + if (pagesDropDown != null) { + pagesDropDown.Build(gui); + if (!pagesDropDown.active) { + pagesDropDown = null; } } diff --git a/Yafc.UI/ImGui/DropDownPanel.cs b/Yafc.UI/ImGui/DropDownPanel.cs index 13ad4495..bcae73bf 100644 --- a/Yafc.UI/ImGui/DropDownPanel.cs +++ b/Yafc.UI/ImGui/DropDownPanel.cs @@ -50,10 +50,14 @@ public void Build(ImGui gui) { protected abstract Vector2 CalculatePosition(ImGui gui, Rect targetRect, Vector2 contentSize); protected abstract bool ShouldBuild(ImGui source, Rect sourceRect, ImGui parent, Rect parentRect); protected abstract void BuildContents(ImGui gui); + + // Expose a way to check whether this panel was focused from a given source/rect + public bool MatchesSource(ImGui source, Rect rect) => this.source == source && this.sourceRect.Equals(rect); } public abstract class DropDownPanel(Padding padding, float width) : AttachedPanel(padding, width), IMouseFocus { private bool focused; + private bool pinned; protected override bool ShouldBuild(ImGui source, Rect sourceRect, ImGui parent, Rect parentRect) => focused; @@ -75,9 +79,20 @@ public bool FilterPanel(IPanel? panel) { } public void FocusChanged(bool focused) { + // If we're pinned, ignore losing focus so dropdown remains open. + if (!focused && pinned) { + return; + } + this.focused = focused; contents.parent?.Rebuild(); } + + // Allow external code to control pinned state + public bool pinnedMode { + get => pinned; + set => pinned = value; + } } public class SimpleDropDown : DropDownPanel { @@ -94,9 +109,11 @@ private bool HandleDropdownClosed(ImGuiUtils.CloseDropdownEvent _) { public void SetPadding(Padding padding) => contents.initialPadding = padding; - public void SetFocus(ImGui source, Rect rect, GuiBuilder builder, float width = 20f) { + // Added pinned parameter + public void SetFocus(ImGui source, Rect rect, GuiBuilder builder, float width = 20f, bool pinned = false) { this.width = width; this.builder = builder; + this.pinnedMode = pinned; base.SetFocus(source, rect); } diff --git a/Yafc.UI/ImGui/ImGuiUtils.cs b/Yafc.UI/ImGui/ImGuiUtils.cs index 0e085721..5d875b8a 100644 --- a/Yafc.UI/ImGui/ImGuiUtils.cs +++ b/Yafc.UI/ImGui/ImGuiUtils.cs @@ -198,13 +198,13 @@ public static ButtonEvent BuildRedButton(this ImGui gui, Icon icon, float size = } public static ButtonEvent BuildButton(this ImGui gui, Icon icon, SchemeColor normal = SchemeColor.None, - SchemeColor over = SchemeColor.Grey, SchemeColor down = SchemeColor.None, float size = 1.5f) { + SchemeColor over = SchemeColor.Grey, SchemeColor down = SchemeColor.None, float size = 1.5f, uint button = SDL.SDL_BUTTON_LEFT) { using (gui.EnterGroup(new Padding(0.3f))) { gui.BuildIcon(icon, size); } - return gui.BuildButton(gui.lastRect, normal, over, down); + return gui.BuildButton(gui.lastRect, normal, over, down, button); } public static ButtonEvent BuildButton(this ImGui gui, Icon icon, string text, SchemeColor normal = SchemeColor.None, @@ -225,6 +225,13 @@ public static bool WithTooltip(this ButtonEvent evt, ImGui gui, string tooltip, return evt; } + public static bool WithTooltipConditional(this ButtonEvent evt, bool condition, ImGui gui, string tooltip, Rect? rect = null) { + if (evt == ButtonEvent.MouseOver && condition) { + gui.ShowTooltip(rect ?? gui.lastRect, tooltip); + } + + return evt; + } public static bool BuildCheckBox(this ImGui gui, string text, bool value, out bool newValue, SchemeColor color = SchemeColor.None, RectAllocator allocator = RectAllocator.LeftRow, string? tooltip = null) { diff --git a/Yafc/Data/locale/en/yafc.cfg b/Yafc/Data/locale/en/yafc.cfg index 5bfbaaf9..a5403d30 100644 --- a/Yafc/Data/locale/en/yafc.cfg +++ b/Yafc/Data/locale/en/yafc.cfg @@ -224,7 +224,7 @@ copy-to-clipboard-with-shortcut=Copy to clipboard (Ctrl+__1__) ; MainScreen.cs full-name-with-version=Yet Another Factorio Calculator CE v__1__ create-production-sheet=Create production sheet (Ctrl+__1__) -list-and-search-all=List and search all pages (Ctrl+Shift+__1__) +list-and-search-all=List and search all pages (Ctrl+Shift+__1__)\nRight click to open pinned menu-open-neie=Open NEIE search-header=Find on page: undo=Undo diff --git a/Yafc/Windows/MainScreen.cs b/Yafc/Windows/MainScreen.cs index 5b51d010..bd5eedbe 100644 --- a/Yafc/Windows/MainScreen.cs +++ b/Yafc/Windows/MainScreen.cs @@ -5,6 +5,7 @@ using System.IO; using System.Net.Http; using System.Numerics; +using System.Runtime.CompilerServices; using System.Text.Json; using System.Threading.Tasks; using SDL2; @@ -137,7 +138,9 @@ private void BuildPage(ImGui gui, ProjectPage element, int index) { if (evt) { if (gui.actionParameter == SDL.SDL_BUTTON_MIDDLE) { ProjectPageSettingsPanel.Show(element); - dropDown?.Close(); + commonDropDown?.Close(); + pagesDropDown?.Close(); + HideTooltip(); } else { SetActivePage(element); @@ -152,6 +155,8 @@ private void ProjectOnMetaInfoChanged() { if (_activePage != null && project.FindPage(_activePage.guid) != _activePage) { SetActivePage(null); } + // Keep the missing-pages dropdown list in sync with project changes (add/remove/rename) + UpdatePageList(); } private void ChangePage(ref ProjectPage? activePage, ProjectPage? newPage, ref ProjectPageView? activePageView, ProjectPageView? newPageView) { @@ -254,29 +259,59 @@ private void BuildTabBar(ImGui gui) { ProductionTableView.CreateProductionSheet(); } + var padding = new Padding(0f, 0f, 0f, 0.5f); + gui.allocator = RectAllocator.RightRow; - if (gui.BuildButton(Icon.DropDown, SchemeColor.None, SchemeColor.Grey).WithTooltip(gui, LSs.ListAndSearchAll.L(ImGuiUtils.ScanToString(SDL.SDL_Scancode.SDL_SCANCODE_F))) || showSearchAll) { + + var isPinned = PagesSimpleDropPinned || this.project.settings.isPagesListPinned; + + if (isPinned) { + gui.window?.HideTooltip(); + } + + if (gui.BuildButton(isPinned ? Icon.Close : Icon.DropDown, SchemeColor.None, SchemeColor.Grey, button: 0).WithTooltipConditional(!isPinned, gui, LSs.ListAndSearchAll.L(ImGuiUtils.ScanToString(SDL.SDL_Scancode.SDL_SCANCODE_F))) || showSearchAll) { showSearchAll = false; - updatePageList(); - ShowDropDown(gui, gui.lastRect, missingPagesDropdown, new Padding(0f, 0f, 0f, 0.5f), 30f); + UpdatePageList(); + + // determine pinned by right-click (mouse button == 3) + bool pin = InputSystem.Instance.mouseDownButton == 3; + + if (InputSystem.Instance.mouseDownButton == 1 || InputSystem.Instance.mouseDownButton == 3) { + if (!ClosePagesListDropDown(gui, gui.lastRect)) { + showPagesListDropDown(pin); + this.project.settings.isPagesListPinned = pin; + + } + else { + this.project.settings.isPagesListPinned = false; + } + } + } + + if (this.project.settings.isPagesListPinned && pagesDropDown == null && pseudoScreens.Count == 0) { + showPagesListDropDown(true); } tabBar.Build(gui); - } - gui.DrawRectangle(gui.lastRect, SchemeColor.PureBackground); - void updatePageList() { - List sortedAndFilteredPageList = [.. pageListSearch.Search(project.pages)]; - sortedAndFilteredPageList.Sort((a, b) => a.visible == b.visible ? string.Compare(a.name, b.name, StringComparison.InvariantCultureIgnoreCase) : a.visible ? -1 : 1); - allPages.data = sortedAndFilteredPageList; + void showPagesListDropDown(bool pinned) => ShowPagesListDropDown(gui, gui.lastRect, missingPagesDropdown, padding, 30f, pinned); } + gui.DrawRectangle(gui.lastRect, SchemeColor.PureBackground); void missingPagesDropdown(ImGui gui) { - pageListSearch.Build(gui, updatePageList); + pageListSearch.Build(gui, UpdatePageList); allPages.Build(gui); } } + // Centralized update for the pages list shown in the dropdown so callers (e.g. after renaming) + // can refresh the list. + public void UpdatePageList() { + List sortedAndFilteredPageList = [.. pageListSearch.Search(project.pages)]; + sortedAndFilteredPageList.Sort((a, b) => a.visible == b.visible ? string.Compare(a.name, b.name, StringComparison.InvariantCultureIgnoreCase) : a.visible ? -1 : 1); + allPages.data = sortedAndFilteredPageList; + } + private void BuildPage(ImGui gui) { float usedHeaderSpace = gui.statePosition.Y; var pageVisibleSize = size; @@ -305,13 +340,15 @@ private void BuildPage(ImGui gui) { } } - public ProjectPage AddProjectPage(string name, FactorioObject? icon, Type contentType, bool setActive, bool initNew) { + public ProjectPage AddProjectPage(String name, FactorioObject? icon, Type contentType, bool setActive, bool initNew) { ProjectPage page = new ProjectPage(project, contentType) { name = name, icon = icon }; if (initNew) { page.content.InitNew(); } project.RecordUndo().pages.Add(page); + // Ensure any visible page lists are updated immediately + UpdatePageList(); if (setActive) { SetActivePage(page); } @@ -543,6 +580,12 @@ public bool ShowPseudoScreen(PseudoScreen screen) { if (topScreen == null) { Ui.DispatchInMainThread(x => fadeDrawer.CreateDownscaledImage(), null); } + + if (pagesDropDown != null) { + pagesDropDown.Close(); + pagesDropDown = null; + } + project.undo.Suspend(); screen.Rebuild(); pseudoScreens.Insert(0, screen); diff --git a/Yafc/Windows/ProjectPageSettingsPanel.cs b/Yafc/Windows/ProjectPageSettingsPanel.cs index 031d4b8a..f637f56c 100644 --- a/Yafc/Windows/ProjectPageSettingsPanel.cs +++ b/Yafc/Windows/ProjectPageSettingsPanel.cs @@ -90,6 +90,8 @@ protected override void ReturnPressed() { else if (editingPage.name != name || editingPage.icon != icon) { editingPage.RecordUndo(true).name = name!; // null-forgiving: The button is disabled if name is null or empty. editingPage.icon = icon; + // Ensure the main screen's page list is refreshed to show the new name in dropdowns + MainScreen.Instance.UpdatePageList(); } Close(); } From 24d59605fcf8d8deeb50e7f80785b2a3ae0e8432 Mon Sep 17 00:00:00 2001 From: kubiix Date: Tue, 23 Sep 2025 23:59:18 +0200 Subject: [PATCH 2/2] Pinnable "Page Search" with Right click (changelog) --- changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.txt b/changelog.txt index db6a0521..0465076d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -21,6 +21,8 @@ ---------------------------------------------------------------------------------------------------------------------- Version: Date: + Features: + - Add Right-click option to show the Page Search dropdown pinned to keep it open. Fixes: - Fix rendering of multi-icon technologies (e.g. shooting speed techs) - Add simplified support for debug.getinfo(), only returns short_src. Fixes #455, #504.