Skip to content

Commit

Permalink
feature: Add display options for the Shopping List window.
Browse files Browse the repository at this point in the history
  • Loading branch information
DaleStan committed Sep 9, 2024
1 parent 21cc8f9 commit 68e09ad
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 44 deletions.
16 changes: 12 additions & 4 deletions Yafc.UI/ImGui/ImGuiUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,21 @@ public static bool BuildCheckBox(this ImGui gui, string text, bool value, out bo
return false;
}

public static bool BuildRadioButton(this ImGui gui, string option, bool selected, SchemeColor color = SchemeColor.None) {
public static ButtonEvent BuildRadioButton(this ImGui gui, string option, bool selected, SchemeColor textColor = SchemeColor.None, bool enabled = true) {
if (textColor == SchemeColor.None) {
textColor = enabled ? SchemeColor.PrimaryText : SchemeColor.PrimaryTextFaint;
}
using (gui.EnterRow()) {
gui.BuildIcon(selected ? Icon.RadioCheck : Icon.RadioEmpty, 1.5f, color);
gui.BuildText(option, TextBlockDisplayStyle.WrappedText with { Color = color });
gui.BuildIcon(selected ? Icon.RadioCheck : Icon.RadioEmpty, 1.5f, textColor);
gui.BuildText(option, TextBlockDisplayStyle.WrappedText with { Color = textColor });
}
if (!enabled) {
return ButtonEvent.None;
}

return !selected && gui.OnClick(gui.lastRect);
ButtonEvent click = gui.BuildButton(gui.lastRect, SchemeColor.None, SchemeColor.None);
if (click == ButtonEvent.Click && selected) { return ButtonEvent.None; }
return click;
}

public static bool BuildRadioGroup(this ImGui gui, IReadOnlyList<string> options, int selected, out int newSelected, SchemeColor color = SchemeColor.None) {
Expand Down
4 changes: 4 additions & 0 deletions Yafc/Utils/Preferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ public void Save() {
/// - Your system has a very old graphics card that is not supported by Windows DX12
/// </summary>
public bool forceSoftwareRenderer { get; set; } = false;
/// <summary>
/// An opaque integer that the shopping list uses to store its display options. See the ShoppingListScreen properties that read and write this value.
/// </summary>
public int shoppingDisplayState { get; set; } = 3;

public void AddProject(string dataPath, string modsPath, string projectPath, bool expensiveRecipes, bool netProduction) {
recentProjects = recentProjects.Where(x => string.Compare(projectPath, x.path, StringComparison.InvariantCultureIgnoreCase) != 0)
Expand Down
2 changes: 1 addition & 1 deletion Yafc/Windows/MainScreen.PageListSearch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ void buildCheckbox(ImGui gui, string text, ref bool isChecked) {
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) {
if (gui.BuildRadioButton(text, searchNameMode == thisValue, enabled: isObjectNameSearching)) {
searchNameMode = thisValue;
updatePageList();
}
Expand Down
110 changes: 92 additions & 18 deletions Yafc/Windows/ShoppingListScreen.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Yafc.Blueprints;
Expand All @@ -7,13 +8,32 @@

namespace Yafc {
public class ShoppingListScreen : PseudoScreen {
private static readonly ShoppingListScreen Instance = new ShoppingListScreen();

private enum DisplayState { Total, Built, Missing }
private readonly VirtualScrollList<(FactorioObject, float)> list;
private float shoppingCost, totalBuildings, totalModules;
private bool decomposed = false;
private static DisplayState displayState {
get => (DisplayState)(Preferences.Instance.shoppingDisplayState >> 1);
set {
Preferences.Instance.shoppingDisplayState = ((int)value) << 1 | (Preferences.Instance.shoppingDisplayState & 1);
Preferences.Instance.Save();
}
}
private static bool assumeAdequate {
get => (Preferences.Instance.shoppingDisplayState & 1) != 0;
set {
Preferences.Instance.shoppingDisplayState = (Preferences.Instance.shoppingDisplayState & ~1) | (value ? 1 : 0);
Preferences.Instance.Save();
}
}

private readonly List<RecipeRow> recipes;

private ShoppingListScreen() => list = new VirtualScrollList<(FactorioObject, float)>(30f, new Vector2(float.PositiveInfinity, 2), ElementDrawer);
private ShoppingListScreen(List<RecipeRow> recipes) {
list = new VirtualScrollList<(FactorioObject, float)>(30f, new Vector2(float.PositiveInfinity, 2), ElementDrawer);
this.recipes = recipes;
RebuildData();
}

private void ElementDrawer(ImGui gui, (FactorioObject obj, float count) element, int index) {
using (gui.EnterRow()) {
Expand All @@ -23,31 +43,85 @@ private void ElementDrawer(ImGui gui, (FactorioObject obj, float count) element,
_ = gui.BuildFactorioObjectButtonBackground(gui.lastRect, element.obj);
}

public static void Show(Dictionary<FactorioObject, int> counts) {
float cost = 0f, buildings = 0f, modules = 0f;
Instance.decomposed = false;
Instance.list.data = counts.Select(x => (x.Key, Value: (float)x.Value)).OrderByDescending(x => x.Value).ToArray();
foreach (var (obj, count) in Instance.list.data) {
if (obj is Entity) {
buildings += count;
public static void Show(List<RecipeRow> recipes) => _ = MainScreen.Instance.ShowPseudoScreen(new ShoppingListScreen(recipes));

private void RebuildData() {
decomposed = false;

// Count buildings and modules
Dictionary<FactorioObject, int> counts = [];
foreach (RecipeRow recipe in recipes) {
if (recipe.entity != null) {
FactorioObject shopItem = recipe.entity.itemsToPlace?.FirstOrDefault() ?? (FactorioObject)recipe.entity;
_ = counts.TryGetValue(shopItem, out int prev);
int builtCount = recipe.builtBuildings ?? (assumeAdequate ? MathUtils.Ceil(recipe.buildingCount) : 0);
int displayCount = displayState switch {
DisplayState.Total => MathUtils.Ceil(recipe.buildingCount),
DisplayState.Built => builtCount,
DisplayState.Missing => MathUtils.Ceil(Math.Max(recipe.buildingCount - builtCount, 0)),
_ => throw new InvalidOperationException(nameof(displayState) + " has an unrecognized value.")
};
counts[shopItem] = prev + displayCount;
if (recipe.usedModules.modules != null) {
foreach ((Module module, int moduleCount, bool beacon) in recipe.usedModules.modules) {
if (!beacon) {
_ = counts.TryGetValue(module, out prev);
counts[module] = prev + displayCount * moduleCount;
}
}
}
}
else if (obj is Module module) {
}
list.data = [.. counts.Where(x => x.Value > 0).Select(x => (x.Key, Value: (float)x.Value)).OrderByDescending(x => x.Value)];

// Summarize building requirements
float cost = 0f, buildings = 0f, modules = 0f;
decomposed = false;
foreach ((FactorioObject obj, float count) in list.data) {
if (obj is Module module) {
modules += count;
}

else if (obj is Entity or Item) {
buildings += count;
}
cost += obj.Cost() * count;
}
Instance.shoppingCost = cost;
Instance.totalBuildings = buildings;
Instance.totalModules = modules;
_ = MainScreen.Instance.ShowPseudoScreen(Instance);
shoppingCost = cost;
totalBuildings = buildings;
totalModules = modules;
}

public override void Build(ImGui gui) {
BuildHeader(gui, "Shopping list");
gui.BuildText(
"Total cost of all objects: " + DataUtils.FormatAmount(shoppingCost, UnitOfMeasure.None, "¥") + ", buildings: " +
DataUtils.FormatAmount(totalBuildings, UnitOfMeasure.None) + ", modules: " + DataUtils.FormatAmount(totalModules, UnitOfMeasure.None), TextBlockDisplayStyle.Centered);
using (gui.EnterRow()) {
if (gui.BuildRadioButton("Total buildings", displayState == DisplayState.Total).WithTooltip(gui, "Display the total number of buildings required, ignoring the built building count.")) {
displayState = DisplayState.Total;
RebuildData();
}
if (gui.BuildRadioButton("Built buildings", displayState == DisplayState.Built).WithTooltip(gui, "Display the number of buildings that are reported in built building count.")) {
displayState = DisplayState.Built;
RebuildData();
}
if (gui.BuildRadioButton("Missing buildings", displayState == DisplayState.Missing).WithTooltip(gui, "Display the number of additional buildings that need to be built.")) {
displayState = DisplayState.Missing;
RebuildData();
}
}
using (gui.EnterRow()) {
SchemeColor textColor = displayState == DisplayState.Total ? SchemeColor.PrimaryTextFaint : SchemeColor.PrimaryText;
gui.BuildText("When not specified, assume:", TextBlockDisplayStyle.Default(textColor), topOffset: .15f);
if (gui.BuildRadioButton("No buildings", !assumeAdequate, enabled: displayState != DisplayState.Total).WithTooltip(gui, "When the built building count is not specified, behave as if it was set to 0.")) {
assumeAdequate = false;
RebuildData();
}
if (gui.BuildRadioButton("Enough buildings", assumeAdequate, enabled: displayState != DisplayState.Total).WithTooltip(gui, "When the built building count is not specified, behave as if it matches the required building count.")) {
assumeAdequate = true;
RebuildData();
}
}
gui.AllocateSpacing(1f);
list.Build(gui);
using (gui.EnterRow(allocator: RectAllocator.RightRow)) {
Expand Down Expand Up @@ -99,7 +173,7 @@ private void ExportBlueprintDropdown(ImGui gui) {

private Recipe? FindSingleProduction(Recipe[] production) {
Recipe? current = null;
foreach (var recipe in production) {
foreach (Recipe recipe in production) {
if (recipe.IsAccessible()) {
if (current != null) {
return null;
Expand Down
22 changes: 1 addition & 21 deletions Yafc/Workspace/ProductionTable/ProductionTableView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1163,27 +1163,7 @@ private List<RecipeRow> GetRecipesRecursive(RecipeRow recipeRoot) {
return list;
}

private void BuildShoppingList(RecipeRow? recipeRoot) {
Dictionary<FactorioObject, int> shopList = [];
var recipes = recipeRoot == null ? GetRecipesRecursive() : GetRecipesRecursive(recipeRoot);
foreach (var recipe in recipes) {
if (recipe.entity != null) {
FactorioObject shopItem = recipe.entity.itemsToPlace?.FirstOrDefault() ?? (FactorioObject)recipe.entity;
_ = shopList.TryGetValue(shopItem, out int prev);
int count = MathUtils.Ceil(recipe.builtBuildings ?? recipe.buildingCount);
shopList[shopItem] = prev + count;
if (recipe.usedModules.modules != null) {
foreach (var module in recipe.usedModules.modules) {
if (!module.beacon) {
_ = shopList.TryGetValue(module.module, out prev);
shopList[module.module] = prev + (count * module.count);
}
}
}
}
}
ShoppingListScreen.Show(shopList);
}
private void BuildShoppingList(RecipeRow? recipeRoot) => ShoppingListScreen.Show(recipeRoot == null ? GetRecipesRecursive() : GetRecipesRecursive(recipeRoot));

private void BuildBeltInserterInfo(ImGui gui, float amount, float buildingCount) {
var prefs = Project.current.preferences;
Expand Down
3 changes: 3 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ Date:
Features:
- Add OSX-arm64 build.
- Display link warnings in both the tooltips and the dropdowns.
- Add additional ways of counting buildings and modules when displaying the shopping list.
Bugfixes:
- Fixed recipes now become accessible when their crafter does.
Internal changes:
- Allow tooltips to be displayed when hovering over radio buttons.
----------------------------------------------------------------------------------------------------------------------
Version: 0.9.1
Date: September 8th 2024
Expand Down

0 comments on commit 68e09ad

Please sign in to comment.