Skip to content

Commit 604d07b

Browse files
authored
Feat: add checkbox if recipe is on the page (#439)
This adds a (yellow) checkbox to recipes already added to the sheet, but not part of the current sub-table (those are already marked with a greencheckbx). ![afbeelding](https://github.com/user-attachments/assets/ceaeb50c-a991-4cc6-827a-4eb1b59e55e6) Also works for the see full list: ![afbeelding](https://github.com/user-attachments/assets/77fad4cb-bf63-4025-a732-cc6463a59517) A list of nice-to-have things: * [x] A changelog entry. * The link from the PR to the issue that it fixes. -> This Pr describes the function. * A description of what testing was done. -> Tried various recipe combinations, including recipes part of parent nodes, sibling nodes, also green checkbox should always override yellow.
2 parents fba78bf + 797ab10 commit 604d07b

File tree

5 files changed

+80
-55
lines changed

5 files changed

+80
-55
lines changed

Yafc/Widgets/ImmediateWidgets.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ internal static bool BuildInlineObjectList<T>(this ImGui gui, IEnumerable<T> lis
219219
if (gui.isBuilding && (options.Checkmark?.Invoke(elem) ?? false)) {
220220
gui.DrawIcon(Rect.Square(gui.lastRect.Right - 1f, gui.lastRect.Center.Y, 1.5f), Icon.Check, SchemeColor.Green);
221221
}
222+
else if (gui.isBuilding && (options.YellowMark?.Invoke(elem) ?? false)) {
223+
gui.DrawIcon(Rect.Square(gui.lastRect.Right - 1f, gui.lastRect.Center.Y, 1.5f), Icon.Check, SchemeColor.TagColorYellowText);
224+
}
222225
}
223226

224227
return selected != null;
@@ -235,7 +238,7 @@ public static void BuildInlineObjectListAndButton<T>(this ImGui gui, ICollection
235238

236239
if (list.Count > options.MaxCount && gui.BuildButton("See full list") && gui.CloseDropdown()) {
237240
if (options.Multiple) {
238-
SelectMultiObjectPanel.Select(list, options.Header, selectItem, options.Ordering, options.Checkmark);
241+
SelectMultiObjectPanel.Select(list, options.Header, selectItem, options.Ordering, options.Checkmark, options.YellowMark);
239242
}
240243
else {
241244
SelectSingleObjectPanel.Select(list, options.Header, selectItem, options.Ordering);
@@ -461,9 +464,10 @@ public record DisplayAmount(float Value, UnitOfMeasure Unit = UnitOfMeasure.None
461464
/// Not used (treated as <see langword="false"/>) when selecting with a 'None' item.</param>
462465
/// <param name="Checkmark">If not <see langword="null"/>, this will be called to determine if a checkmark should be drawn on the item.
463466
/// Not used when selecting with a 'None' item or when <paramref name="Multiple"/> is <see langword="false"/>.</param>
467+
/// <param name="YellowMark">If Checkmark is not set, draw a less distinct checkmark instead.</param>
464468
/// <param name="ExtraText">If not <see langword="null"/>, this will be called to get extra text to be displayed right-justified after the item's name.</param>
465469
public sealed record ObjectSelectOptions<T>(string? Header, [AllowNull] IComparer<T> Ordering = null, int MaxCount = 6, bool Multiple = false, Predicate<T>? Checkmark = null,
466-
Func<T, string>? ExtraText = null) where T : IFactorioObjectWrapper {
470+
Predicate<T>? YellowMark = null, Func<T, string>? ExtraText = null) where T : IFactorioObjectWrapper {
467471

468472
public IComparer<T> Ordering { get; init; } = Ordering ?? (IComparer<T>)DataUtils.DefaultOrdering;
469473
}

Yafc/Windows/SelectMultiObjectPanel.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ namespace Yafc;
99
public class SelectMultiObjectPanel : SelectObjectPanel<IEnumerable<FactorioObject>> {
1010
private readonly HashSet<FactorioObject> results = [];
1111
private readonly Predicate<FactorioObject> checkMark;
12+
private readonly Predicate<FactorioObject> yellowMark;
1213
private bool allowAutoClose = true;
1314

14-
private SelectMultiObjectPanel(Predicate<FactorioObject> checkMark) => this.checkMark = checkMark;
15+
private SelectMultiObjectPanel(Predicate<FactorioObject> checkMark, Predicate<FactorioObject> yellowMark) {
16+
this.checkMark = checkMark;
17+
this.yellowMark = yellowMark;
18+
}
1519

1620
/// <summary>
1721
/// Opens a <see cref="SelectMultiObjectPanel"/> to allow the user to select one or more <see cref="FactorioObject"/>s.
@@ -20,8 +24,8 @@ public class SelectMultiObjectPanel : SelectObjectPanel<IEnumerable<FactorioObje
2024
/// <param name="header">The string that describes to the user why they're selecting these items.</param>
2125
/// <param name="selectItem">An action to be called for each selected item when the panel is closed.</param>
2226
/// <param name="ordering">An optional ordering specifying how to sort the displayed items. If <see langword="null"/>, defaults to <see cref="DataUtils.DefaultOrdering"/>.</param>
23-
public static void Select<T>(IEnumerable<T> list, string? header, Action<T> selectItem, IComparer<T>? ordering = null, Predicate<T>? checkMark = null) where T : FactorioObject {
24-
SelectMultiObjectPanel panel = new(o => checkMark?.Invoke((T)o) ?? false); // This casting is messy, but pushing T all the way around the call stack and type tree was messier.
27+
public static void Select<T>(IEnumerable<T> list, string? header, Action<T> selectItem, IComparer<T>? ordering = null, Predicate<T>? checkMark = null, Predicate<T>? yellowMark = null) where T : FactorioObject {
28+
SelectMultiObjectPanel panel = new(o => checkMark?.Invoke((T)o) ?? false, o => yellowMark?.Invoke((T)o) ?? false); // This casting is messy, but pushing T all the way around the call stack and type tree was messier.
2529
panel.Select(list, header, selectItem!, ordering, (objs, mappedAction) => { // null-forgiving: selectItem will not be called with null, because allowNone is false.
2630
foreach (var obj in objs!) { // null-forgiving: mapResult will not be called with null, because allowNone is false.
2731
mappedAction(obj);
@@ -37,9 +41,9 @@ public static void Select<T>(IEnumerable<T> list, string? header, Action<T> sele
3741
/// <param name="selectItem">An action to be called for each selected item when the panel is closed.</param>
3842
/// <param name="ordering">An optional ordering specifying how to sort the displayed items. If <see langword="null"/>, defaults to <see cref="DataUtils.DefaultOrdering"/>.</param>
3943
public static void SelectWithQuality<T>(IEnumerable<T> list, string header, Action<ObjectWithQuality<T>> selectItem, Quality currentQuality,
40-
IComparer<T>? ordering = null, Predicate<T>? checkMark = null) where T : FactorioObject {
44+
IComparer<T>? ordering = null, Predicate<T>? checkMark = null, Predicate<T>? yellowMark = null) where T : FactorioObject {
4145

42-
SelectMultiObjectPanel panel = new(o => checkMark?.Invoke((T)o) ?? false); // This casting is messy, but pushing T all the way around the call stack and type tree was messier.
46+
SelectMultiObjectPanel panel = new(o => checkMark?.Invoke((T)o) ?? false, o => yellowMark?.Invoke((T)o) ?? false); // This casting is messy, but pushing T all the way around the call stack and type tree was messier.
4347
panel.SelectWithQuality(list, header, selectItem!, ordering, (objs, mappedAction) => { // null-forgiving: selectItem will not be called with null, because allowNone is false.
4448
foreach (var obj in objs!) { // null-forgiving: mapResult will not be called with null, because allowNone is false.
4549
mappedAction(obj);
@@ -54,6 +58,9 @@ protected override void NonNullElementDrawer(ImGui gui, FactorioObject element)
5458
if (checkMark(element)) {
5559
gui.DrawIcon(Rect.SideRect(gui.lastRect.TopLeft + new Vector2(1, 0), gui.lastRect.BottomRight - new Vector2(0, 1)), Icon.Check, SchemeColor.Green);
5660
}
61+
else if (yellowMark(element)) {
62+
gui.DrawIcon(Rect.SideRect(gui.lastRect.TopLeft + new Vector2(1, 0), gui.lastRect.BottomRight - new Vector2(0, 1)), Icon.Check, SchemeColor.TagColorYellowText);
63+
}
5764

5865
if (click == Click.Left) {
5966
if (!results.Add(element)) {

Yafc/Workspace/ProductionTable/ProductionLinkSummaryScreen.cs

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -51,24 +51,14 @@ private void BuildScrollArea(ImGui gui) {
5151
}
5252

5353
private void ShowRelatedLinks(ImGui gui) {
54-
ModelObject o = link;
55-
List<ModelObject> parents = [];
56-
while (o.ownerObject is not ProjectPage) {
57-
var t = o.ownerObject;
58-
if (t is null) {
59-
break;
60-
}
61-
o = t;
62-
if (t == o.ownerObject) continue;
63-
parents.Add(t);
64-
}
65-
if (o is ProductionTable page) {
66-
Dictionary<IProductionLink, List<(RecipeRow row, float flow)>> childLinks = [], parentLinks = [], otherLinks = [];
67-
List<(RecipeRow row, float flow)> unlinked = [], table;
54+
var prodTable = FindProductionTable(link, out List<ModelObject> parents);
55+
Dictionary<IProductionLink, List<(RecipeRow row, float flow)>> childLinks = [], parentLinks = [], otherLinks = [];
56+
List<(RecipeRow row, float flow)> unlinked = [], table;
57+
if (prodTable is not null) {
6858
foreach (var (row, relationLinks) in
69-
page.GetAllRecipes(link.owner)
70-
.Select(e => (e, IsLinkParent(e, parents) ? parentLinks : otherLinks))
71-
.Concat(link.owner.GetAllRecipes().Select(e => (e, childLinks)))) {
59+
prodTable.GetAllRecipes(link.owner)
60+
.Select(e => (e, IsLinkParent(e, parents) ? parentLinks : otherLinks))
61+
.Concat(link.owner.GetAllRecipes().Select(e => (e, childLinks)))) {
7262
if (isPartOfCurrentLink(row)
7363
|| isNotRelatedToCurrentLink(row)) {
7464
continue;
@@ -87,31 +77,46 @@ private void ShowRelatedLinks(ImGui gui) {
8777

8878
table.Add((row, localFlow));
8979
}
90-
var color = 1;
91-
gui.spacing = 0.75f;
92-
if (childLinks.Values.Any(e => e.Any())) {
93-
gui.BuildText("Child links: ", Font.productionTableHeader);
94-
foreach (var relTable in childLinks.Values) {
95-
BuildFlow(gui, relTable, relTable.Sum(e => Math.Abs(e.flow)), false, color++);
96-
}
80+
}
81+
var color = 1;
82+
gui.spacing = 0.75f;
83+
if (childLinks.Values.Any(e => e.Any())) {
84+
gui.BuildText("Child links: ", Font.productionTableHeader);
85+
foreach (var relTable in childLinks.Values) {
86+
BuildFlow(gui, relTable, relTable.Sum(e => Math.Abs(e.flow)), false, color++);
9787
}
98-
if (parentLinks.Values.Any(e => e.Any())) {
99-
gui.BuildText("Parent links: ", Font.productionTableHeader);
100-
foreach (var relTable in parentLinks.Values) {
101-
BuildFlow(gui, relTable, relTable.Sum(e => Math.Abs(e.flow)), false, color++);
102-
}
88+
}
89+
if (parentLinks.Values.Any(e => e.Any())) {
90+
gui.BuildText("Parent links: ", Font.productionTableHeader);
91+
foreach (var relTable in parentLinks.Values) {
92+
BuildFlow(gui, relTable, relTable.Sum(e => Math.Abs(e.flow)), false, color++);
10393
}
104-
if (otherLinks.Values.Any(e => e.Any())) {
105-
gui.BuildText("Unrelated links: ", Font.productionTableHeader);
106-
foreach (var relTable in otherLinks.Values) {
107-
BuildFlow(gui, relTable, relTable.Sum(e => Math.Abs(e.flow)), false, color++);
108-
}
94+
}
95+
if (otherLinks.Values.Any(e => e.Any())) {
96+
gui.BuildText("Unrelated links: ", Font.productionTableHeader);
97+
foreach (var relTable in otherLinks.Values) {
98+
BuildFlow(gui, relTable, relTable.Sum(e => Math.Abs(e.flow)), false, color++);
10999
}
110-
if (unlinked.Any()) {
111-
gui.BuildText("Unlinked: ", Font.subheader);
112-
BuildFlow(gui, unlinked, 0, false);
100+
}
101+
if (unlinked.Any()) {
102+
gui.BuildText("Unlinked: ", Font.subheader);
103+
BuildFlow(gui, unlinked, 0, false);
104+
}
105+
}
106+
107+
public static ProductionTable? FindProductionTable(ModelObject link, out List<ModelObject> parents) {
108+
ModelObject table = link;
109+
parents = [];
110+
while (table.ownerObject is not ProjectPage) {
111+
var t = table.ownerObject;
112+
if (t is null) {
113+
break;
113114
}
115+
table = t;
116+
if (t == table.ownerObject) continue;
117+
parents.Add(t);
114118
}
119+
return table as ProductionTable;
115120
}
116121

117122
private bool isNotRelatedToCurrentLink(RecipeRow? row) => (!row.Ingredients.Any(e => e.Goods == link.goods)

Yafc/Workspace/ProductionTable/ProductionTableView.cs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Yafc.Blueprints;
77
using Yafc.Model;
88
using Yafc.UI;
9+
using static Yafc.UI.ImGui;
910

1011
namespace Yafc;
1112

@@ -266,11 +267,12 @@ private static void BuildRecipeButton(ImGui gui, ProductionTable table) {
266267
if (gui.BuildButton("Add raw recipe").WithTooltip(gui, "Ctrl-click to add a technology instead") && gui.CloseDropdown()) {
267268
if (InputSystem.Instance.control) {
268269
SelectMultiObjectPanel.Select(Database.technologies.all, "Select technology",
269-
r => table.AddRecipe(new(r, Quality.Normal), DefaultVariantOrdering), checkMark: r => table.recipes.Any(rr => rr.recipe.target == r));
270+
r => table.AddRecipe(new(r, Quality.Normal), DefaultVariantOrdering), checkMark: r => table.recipes.Any(rr => rr.recipe.target == r), yellowMark: r => table.GetAllRecipes().Any(rr => rr.recipe.target == r));
270271
}
271272
else {
273+
var prodTable = ProductionLinkSummaryScreen.FindProductionTable(table, out List<ModelObject> parents);
272274
SelectMultiObjectPanel.SelectWithQuality(Database.recipes.explorable.AsEnumerable<RecipeOrTechnology>(), "Select raw recipe",
273-
r => table.AddRecipe(r, DefaultVariantOrdering), Quality.Normal, checkMark: r => table.recipes.Any(rr => rr.recipe.target == r));
275+
r => table.AddRecipe(r, DefaultVariantOrdering), Quality.Normal, checkMark: r => table.recipes.Any(rr => rr.recipe.target == r), yellowMark: r => prodTable?.GetAllRecipes().Any(rr => rr.recipe.target == r) ?? false);
274276
}
275277
}
276278
}
@@ -874,9 +876,12 @@ private void OpenProductDropdown(ImGui targetGui, Rect rect, ObjectWithQuality<G
874876
}
875877

876878
var comparer = DataUtils.GetRecipeComparerFor(goods.target);
877-
HashSet<IObjectWithQuality<RecipeOrTechnology>> allRecipes = [.. context.recipes.Select(x => x.recipe)];
879+
HashSet<IObjectWithQuality<RecipeOrTechnology>> curLevelRecipes = [.. context.recipes.Select(x => x.recipe)];
880+
var prodTable = ProductionLinkSummaryScreen.FindProductionTable(context, out List<ModelObject> parents);
881+
HashSet<IObjectWithQuality<RecipeOrTechnology>> allRecipes = [.. prodTable?.GetAllRecipes().Select(x => x.recipe) ?? []];
878882

879-
bool recipeExists(RecipeOrTechnology rec) => allRecipes.Contains(rec.With(goods.quality));
883+
bool recipeExists(RecipeOrTechnology rec) => curLevelRecipes.Contains(rec.With(goods.quality));
884+
bool recipeExistsAnywhere(RecipeOrTechnology rec) => allRecipes.Contains(rec.With(goods.quality));
880885

881886
ObjectWithQuality<Goods>? selectedFuel = null;
882887
ObjectWithQuality<Goods>? spentFuel = null;
@@ -902,7 +907,7 @@ async void addRecipe(RecipeOrTechnology rec) {
902907
}
903908
}
904909

905-
if (!allRecipes.Contains(qualityRecipe) || (await MessageBox.Show("Recipe already exists", $"Add a second copy of {rec.locName}?", "Add a copy", "Cancel")).choice) {
910+
if (!curLevelRecipes.Contains(qualityRecipe) || (await MessageBox.Show("Recipe already exists", $"Add a second copy of {rec.locName}?", "Add a copy", "Cancel")).choice) {
906911
context.AddRecipe(qualityRecipe, DefaultVariantOrdering, selectedFuel, spentFuel);
907912
}
908913
}
@@ -989,7 +994,7 @@ void dropDownContent(ImGui gui) {
989994
}
990995
}
991996
else if (type <= ProductDropdownType.Ingredient && allProduction.Length > 0) {
992-
gui.BuildInlineObjectListAndButton(allProduction, addRecipe, new("Add production recipe", comparer, 6, true, recipeExists));
997+
gui.BuildInlineObjectListAndButton(allProduction, addRecipe, new("Add production recipe", comparer, 6, true, recipeExists, recipeExistsAnywhere));
993998
numberOfShownRecipes += allProduction.Length;
994999

9951000
if (iLink == null) {
@@ -1014,7 +1019,8 @@ void dropDownContent(ImGui gui) {
10141019
DataUtils.AlreadySortedRecipe,
10151020
3,
10161021
true,
1017-
recipeExists));
1022+
recipeExists,
1023+
recipeExistsAnywhere));
10181024
numberOfShownRecipes += spentFuelRecipes.Length;
10191025
}
10201026

@@ -1026,7 +1032,8 @@ void dropDownContent(ImGui gui) {
10261032
DataUtils.DefaultRecipeOrdering,
10271033
6,
10281034
true,
1029-
recipeExists));
1035+
recipeExists,
1036+
recipeExistsAnywhere));
10301037
numberOfShownRecipes += goods.target.usages.Length;
10311038
}
10321039

@@ -1038,19 +1045,20 @@ void dropDownContent(ImGui gui) {
10381045
DataUtils.AlreadySortedRecipe,
10391046
6,
10401047
true,
1041-
recipeExists));
1048+
recipeExists,
1049+
recipeExistsAnywhere));
10421050
numberOfShownRecipes += fuelUseList.Length;
10431051
}
10441052

10451053
if (type >= ProductDropdownType.Product && Database.allSciencePacks.Contains(goods.target)
10461054
&& gui.BuildButton("Add consumption technology") && gui.CloseDropdown()) {
10471055
// Select from the technologies that consume this science pack.
10481056
SelectMultiObjectPanel.Select(Database.technologies.all.Where(t => t.ingredients.Select(i => i.goods).Contains(goods.target)),
1049-
"Add technology", addRecipe, checkMark: recipeExists);
1057+
"Add technology", addRecipe, checkMark: recipeExists, yellowMark: recipeExistsAnywhere);
10501058
}
10511059

10521060
if (type >= ProductDropdownType.Product && allProduction.Length > 0) {
1053-
gui.BuildInlineObjectListAndButton(allProduction, addRecipe, new("Add production recipe", comparer, 1, true, recipeExists));
1061+
gui.BuildInlineObjectListAndButton(allProduction, addRecipe, new("Add production recipe", comparer, 1, true, recipeExists, recipeExistsAnywhere));
10541062
numberOfShownRecipes += allProduction.Length;
10551063
}
10561064

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Date:
2121
- Open the preferences or milestones when you click on the icon for the reactor or quality warning messages.
2222
- Implemented Auto Save.
2323
- (SA) Research recipes correctly consume quality science packs, and the UI explains this.
24+
- Added marker to recipes already used on the current page.
2425
Fixes:
2526
- Fix export of blueprint chests from shopping list (broken in factorio >= 2.0.35)
2627
- (regression) The total sushi-input/-output items display belt counts again, and allow them as input.

0 commit comments

Comments
 (0)