diff --git a/Yafc.UI/ImGui/ImGuiTextInputHelper.cs b/Yafc.UI/ImGui/ImGuiTextInputHelper.cs index 6222b009..4c76f5ea 100644 --- a/Yafc.UI/ImGui/ImGuiTextInputHelper.cs +++ b/Yafc.UI/ImGui/ImGuiTextInputHelper.cs @@ -33,7 +33,7 @@ public void SetFocus(Rect boundingRect, string setText) { } InputSystem.Instance.SetKeyboardFocus(this); rect = boundingRect; - caret = selectionAnchor = 0; + caret = selectionAnchor = setText.Length; } private void GetTextParameters(string textToBuild, Rect textRect, FontFile.FontSize fontSize, RectAlignment alignment, out TextCache cachedText, out float scale, out float textWidth, out Rect realTextRect) { diff --git a/Yafc/Windows/MainScreen.PageListSearch.cs b/Yafc/Windows/MainScreen.PageListSearch.cs new file mode 100644 index 00000000..7e04eb01 --- /dev/null +++ b/Yafc/Windows/MainScreen.PageListSearch.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Yafc.Model; +using Yafc.UI; +using YAFC.Model; + +namespace Yafc; + +public partial class MainScreen { + /// + /// Encapsulates the text box and checkboxes used for searching in the page list dropdown. + /// + private class PageListSearch { + private SearchQuery query; + + // The state of the checkboxes. To add a new checkbox: Add a new value to PageSearchOption, + // draw the new checkbox in Build, and obey the new checkbox in Search. + private readonly bool[] checkboxValues = new bool[(int)PageSearchOption.MustBeLastValue]; + private readonly bool[] previousCheckboxValues = new bool[(int)PageSearchOption.MustBeLastValue]; + // Named constants for which bool means which type of search. + private enum PageSearchOption { + PageName, + DesiredProducts, + ExtraProducts, + Ingredients, + Recipes, + // Add values for any new search checkboxes above this point. + MustBeLastValue + } + + private enum SearchNameMode { Localized, Internal, Both } + private SearchNameMode searchNameMode = SearchNameMode.Both; + + // Initialize both the current and previous states to searching by page name only. + public PageListSearch() => checkboxValues[0] = previousCheckboxValues[0] = true; + + /// + /// Draws the search header for the page list dropdown. + /// + /// The action to perform if the user updates any of the search parameters. + public void Build(ImGui gui, Action updatePageList) { + using (gui.EnterGroup(new Padding(1f))) { + if (gui.BuildSearchBox(query, out query)) { + updatePageList(); + } + gui.SetTextInputFocus(gui.lastContentRect, query.query); + + gui.BuildText("Search in:"); + using (gui.EnterRow()) { + buildCheckbox(gui, "Page name", ref checkboxValues[(int)PageSearchOption.PageName]); + buildCheckbox(gui, "Desired products", ref checkboxValues[(int)PageSearchOption.DesiredProducts]); + buildCheckbox(gui, "Recipes", ref checkboxValues[(int)PageSearchOption.Recipes]); + } + using (gui.EnterRow()) { + buildCheckbox(gui, "Ingredients", ref checkboxValues[(int)PageSearchOption.Ingredients]); + buildCheckbox(gui, "Extra products", ref checkboxValues[(int)PageSearchOption.ExtraProducts]); + if (gui.BuildCheckBox("All", checkboxValues.All(x => x), out bool checkAll)) { + if (checkAll) { + // Save the previous state, so we can restore it if necessary. + Array.Copy(checkboxValues, previousCheckboxValues, (int)PageSearchOption.MustBeLastValue); + Array.Fill(checkboxValues, true); + } + else { + // Restore the previous state. + Array.Copy(previousCheckboxValues, checkboxValues, (int)PageSearchOption.MustBeLastValue); + } + updatePageList(); + } + } + using (gui.EnterRow()) { + buildRadioButton(gui, "Localized names", SearchNameMode.Localized); + buildRadioButton(gui, "Internal names", SearchNameMode.Internal); + buildRadioButton(gui, "Both", SearchNameMode.Both); + } + } + + void buildCheckbox(ImGui gui, string text, ref bool isChecked) { + if (gui.BuildCheckBox(text, isChecked, out isChecked)) { + updatePageList(); + } + } + + void buildRadioButton(ImGui gui, string text, SearchNameMode thisValue) { + // All checkboxes except PageSearchOption.PageName search object names. + bool isObjectNameSearching = checkboxValues[1..].Any(x => x); + if (gui.BuildRadioButton(text, searchNameMode == thisValue, isObjectNameSearching ? SchemeColor.PrimaryText : SchemeColor.PrimaryTextFaint) && isObjectNameSearching) { + searchNameMode = thisValue; + updatePageList(); + } + } + } + + /// + /// Searches a list of s to find the ones that satisfy the current search criteria. + /// This is typically called by the updatePageList parameter to . + /// + /// The s to search. + /// The s that match the current search text and options. + public IEnumerable Search(IEnumerable pages) { + foreach (var page in pages) { + if (checkboxValues[(int)PageSearchOption.PageName] && query.Match(page.name)) { + yield return page; + } + else if (page.content is ProductionTable table) { + if (checkboxValues[(int)PageSearchOption.DesiredProducts] && table.links.Any(l => l.amount != 0 && isMatch(l.goods.name, l.goods.locName))) { + yield return page; + } + else if (checkboxValues[(int)PageSearchOption.Ingredients] && table.flow.Any(f => f.amount < 0 && isMatch(f.goods.name, f.goods.locName))) { + yield return page; + } + else if (checkboxValues[(int)PageSearchOption.ExtraProducts] && table.flow.Any(f => f.amount > 0 && isMatch(f.goods.name, f.goods.locName))) { + yield return page; + } + else if (checkboxValues[(int)PageSearchOption.Recipes] && table.GetAllRecipes().Any(r => isMatch(r.recipe.name, r.recipe.locName))) { + yield return page; + } + } + } + + bool isMatch(string internalName, string localizedName) { + return (searchNameMode != SearchNameMode.Internal && query.Match(localizedName)) || + (searchNameMode != SearchNameMode.Localized && query.Match(internalName)); + } + } + } +} diff --git a/Yafc/Windows/MainScreen.cs b/Yafc/Windows/MainScreen.cs index cf607e06..13206fa9 100644 --- a/Yafc/Windows/MainScreen.cs +++ b/Yafc/Windows/MainScreen.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Net.Http; using System.Numerics; using System.Text.Json; @@ -12,7 +13,7 @@ using YAFC.Model; namespace Yafc { - public class MainScreen : WindowMain, IKeyboardFocus, IProgress<(string, string)> { + public partial class MainScreen : WindowMain, IKeyboardFocus, IProgress<(string, string)> { ///Unique ID for the Summary page public static readonly Guid SummaryGuid = Guid.Parse("9bdea333-4be2-4be3-b708-b36a64672a40"); public static MainScreen Instance { get; private set; } @@ -35,8 +36,7 @@ public class MainScreen : WindowMain, IKeyboardFocus, IProgress<(string, string) private bool analysisUpdatePending; private SearchQuery pageSearch; - private SearchQuery pageListSearch; - private readonly List sortedAndFilteredPageList = []; + private readonly PageListSearch pageListSearch = new(); private readonly ImGui searchGui; private Rect searchBoxRect; @@ -218,23 +218,15 @@ protected override void BuildContent(ImGui gui) { ReRunAnalysis(); } } - BuildHeader(gui); + BuildTabBar(gui); BuildPage(gui); } } - private void UpdatePageList() { - sortedAndFilteredPageList.Clear(); - foreach (var page in project.pages) { - if (pageListSearch.Match(page.name)) { - sortedAndFilteredPageList.Add(page); - } - } - 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 BuildHeader(ImGui gui) { + /// + /// Draws the tab bar across the top of the window, including the pancake menu, add page button, and search pages dropdown. + /// + private void BuildTabBar(ImGui gui) { using (gui.EnterRow()) { gui.spacing = 0f; if (gui.BuildButton(Icon.Menu)) { @@ -248,20 +240,29 @@ private void BuildHeader(ImGui gui) { gui.allocator = RectAllocator.RightRow; var spaceForDropdown = gui.AllocateRect(2.1f, 2.1f); tabBar.Build(gui); - if (project.hiddenPages > 0 || tabBar.maxScroll > 0f) { - if (gui.isBuilding) { - gui.DrawIcon(spaceForDropdown.Expand(-0.3f), Icon.DropDown, SchemeColor.BackgroundText); - } + if (gui.isBuilding) { + gui.DrawIcon(spaceForDropdown.Expand(-0.3f), Icon.DropDown, SchemeColor.BackgroundText); + } - if (gui.BuildButton(spaceForDropdown, SchemeColor.None, SchemeColor.Grey)) { - UpdatePageList(); - ShowDropDown(gui, spaceForDropdown, MissingPagesDropdown, new Padding(0f, 0f, 0f, 0.5f), 30f); - } + if (gui.BuildButton(spaceForDropdown, SchemeColor.None, SchemeColor.Grey)) { + updatePageList(); + ShowDropDown(gui, spaceForDropdown, missingPagesDropdown, new Padding(0f, 0f, 0f, 0.5f), 30f); } } if (gui.isBuilding) { gui.DrawRectangle(gui.lastRect, SchemeColor.PureBackground); } + + void updatePageList() { + List sortedAndFilteredPageList = pageListSearch.Search(project.pages).ToList(); + sortedAndFilteredPageList.Sort((a, b) => a.visible == b.visible ? string.Compare(a.name, b.name, StringComparison.InvariantCultureIgnoreCase) : a.visible ? -1 : 1); + allPages.data = sortedAndFilteredPageList; + } + + void missingPagesDropdown(ImGui gui) { + pageListSearch.Build(gui, updatePageList); + allPages.Build(gui); + } } private void BuildPage(ImGui gui) { @@ -319,15 +320,6 @@ private void CreatePageDropdown(ImGui gui) { } } - private void MissingPagesDropdown(ImGui gui) { - using (gui.EnterGroup(new Padding(1f))) { - if (gui.BuildSearchBox(pageListSearch, out pageListSearch)) { - UpdatePageList(); - } - } - allPages.Build(gui); - } - public void BuildSubHeader(ImGui gui, string text) { using (gui.EnterGroup(ObjectTooltip.contentPadding)) { gui.BuildText(text, Font.subheader); diff --git a/changelog.txt b/changelog.txt index 2b9cfaba..1c378c65 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,7 @@ Date: soon Features: - Add the option to specify a number of belts of production, and to specify per-second/minute/hour production regardless of the current display setting. + - When searching in the page list, allow searching in page contents as well as in page names. Changes: - Add a help message and proper handling for command line arguments - Removed default pollution cost from calculation. Added a setting to customize pollution cost.