Skip to content

Commit

Permalink
Page search for specific things on the page (#133)
Browse files Browse the repository at this point in the history
This will close #65 by allowing you select where on a page you want to
search, though I may have gone overboard:


![image](https://github.com/have-fun-was-taken/yafc-ce/assets/21223975/f2c7f04c-cf72-4148-af92-dcd3afd6ad04)

In addition to what's obvious from the screenshot: The default setting
is to only search in page names, ~~you always have to have at least one
box checked~~, the radio buttons are disabled if you're only searching
in the page names, unchecking the "All" box will restore the checkbox
state that was present when you checked it, and the input focus is now
set to the search box when you open the dropdown. Most of these are in
their own commits, if one or more should be removed.
  • Loading branch information
shpaass authored May 18, 2024
2 parents 70b8761 + 2d0dc4b commit 4e31251
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 34 deletions.
2 changes: 1 addition & 1 deletion Yafc.UI/ImGui/ImGuiTextInputHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
127 changes: 127 additions & 0 deletions Yafc/Windows/MainScreen.PageListSearch.cs
Original file line number Diff line number Diff line change
@@ -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 {
/// <summary>
/// Encapsulates the text box and checkboxes used for searching in the page list dropdown.
/// </summary>
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;

/// <summary>
/// Draws the search header for the page list dropdown.
/// </summary>
/// <param name="updatePageList">The action to perform if the user updates any of the search parameters.</param>
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();
}
}
}

/// <summary>
/// Searches a list of <see cref="ProjectPage"/>s to find the ones that satisfy the current search criteria.
/// This is typically called by the <c>updatePageList</c> parameter to <see cref="Build"/>.
/// </summary>
/// <param name="pages">The <see cref="ProjectPage"/>s to search.</param>
/// <returns>The <see cref="ProjectPage"/>s that match the current search text and options.</returns>
public IEnumerable<ProjectPage> Search(IEnumerable<ProjectPage> 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));
}
}
}
}
58 changes: 25 additions & 33 deletions Yafc/Windows/MainScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)> {
///<summary>Unique ID for the Summary page</summary>
public static readonly Guid SummaryGuid = Guid.Parse("9bdea333-4be2-4be3-b708-b36a64672a40");
public static MainScreen Instance { get; private set; }
Expand All @@ -35,8 +36,7 @@ public class MainScreen : WindowMain, IKeyboardFocus, IProgress<(string, string)

private bool analysisUpdatePending;
private SearchQuery pageSearch;
private SearchQuery pageListSearch;
private readonly List<ProjectPage> sortedAndFilteredPageList = [];
private readonly PageListSearch pageListSearch = new();
private readonly ImGui searchGui;
private Rect searchBoxRect;

Expand Down Expand Up @@ -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) {
/// <summary>
/// Draws the tab bar across the top of the window, including the pancake menu, add page button, and search pages dropdown.
/// </summary>
private void BuildTabBar(ImGui gui) {
using (gui.EnterRow()) {
gui.spacing = 0f;
if (gui.BuildButton(Icon.Menu)) {
Expand All @@ -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<ProjectPage> 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) {
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 4e31251

Please sign in to comment.