From 9308f74d7c91a4a983268b9026c8453d76a8d2fd Mon Sep 17 00:00:00 2001 From: Maarten Bezemer Date: Wed, 29 Dec 2021 16:01:39 +0100 Subject: [PATCH 01/14] Add Summary tab (with an empty page) --- YAFC/Windows/MainScreen.cs | 20 ++++++++++++++++++++ YAFC/Windows/ProjectPageSettingsPanel.cs | 4 ++-- YAFC/Workspace/SummaryView.cs | 20 ++++++++++++++++++++ YAFCmodel/Model/Summary.cs | 12 ++++++++++++ changelog.txt | 3 ++- 5 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 YAFC/Workspace/SummaryView.cs create mode 100644 YAFCmodel/Model/Summary.cs diff --git a/YAFC/Windows/MainScreen.cs b/YAFC/Windows/MainScreen.cs index 68a102e7..e5097f35 100644 --- a/YAFC/Windows/MainScreen.cs +++ b/YAFC/Windows/MainScreen.cs @@ -12,6 +12,8 @@ namespace YAFC { public 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; } private readonly ObjectTooltip objectTooltip = new ObjectTooltip(); private readonly List pseudoScreens = new List(); @@ -43,6 +45,7 @@ public MainScreen(int display, Project project) : base(default) { RegisterPageView(new ProductionTableView()); RegisterPageView(new AutoPlannerView()); RegisterPageView(new ProductionSummaryView()); + RegisterPageView(new SummaryView()); searchGui = new ImGui(BuildSearch, new Padding(1f)) { boxShadow = RectangleBorder.Thin, boxColor = SchemeColor.Background }; Instance = this; tabBar = new MainScreenTabBar(this); @@ -338,6 +341,9 @@ private void SettingsDropdown(ImGui gui) { if (gui.BuildContextMenuButton("Preferences") && gui.CloseDropdown()) PreferencesScreen.Show(); + if (gui.BuildContextMenuButton("Summary") && gui.CloseDropdown()) + ShowSummaryTab(); + if (gui.BuildContextMenuButton("Never Enough Items Explorer", "Ctrl+" + ImGuiUtils.ScanToString(SDL.SDL_Scancode.SDL_SCANCODE_N)) && gui.CloseDropdown()) ShowNeie(); @@ -443,6 +449,20 @@ public void ClosePseudoScreen(PseudoScreen screen) { rootGui.Rebuild(); } + public void ShowSummaryTab() { + + ProjectPage summaryPage = project.FindPage(SummaryGuid); + if (summaryPage == null) { + + summaryPage = new ProjectPage(project, typeof(Summary), SummaryGuid) { + name = "Summary", + }; + project.pages.Add(summaryPage); + } + + SetActivePage(summaryPage); + } + public bool KeyDown(SDL.SDL_Keysym key) { var ctrl = (key.mod & SDL.SDL_Keymod.KMOD_CTRL) != 0; if (ctrl) { diff --git a/YAFC/Windows/ProjectPageSettingsPanel.cs b/YAFC/Windows/ProjectPageSettingsPanel.cs index b9857881..a3e87ddb 100644 --- a/YAFC/Windows/ProjectPageSettingsPanel.cs +++ b/YAFC/Windows/ProjectPageSettingsPanel.cs @@ -75,7 +75,7 @@ public override void Build(ImGui gui) { } private void OtherToolsDropdown(ImGui gui) { - if (gui.BuildContextMenuButton("Duplicate page")) { + if (editingPage.guid != MainScreen.SummaryGuid && gui.BuildContextMenuButton("Duplicate page")) { gui.CloseDropdown(); var project = editingPage.owner; var collector = new ErrorCollector(); @@ -92,7 +92,7 @@ private void OtherToolsDropdown(ImGui gui) { } } - if (gui.BuildContextMenuButton("Share (export string to clipboard)")) { + if (editingPage.guid != MainScreen.SummaryGuid && gui.BuildContextMenuButton("Share (export string to clipboard)")) { gui.CloseDropdown(); var data = JsonUtils.SaveToJson(editingPage); using (var targetStream = new MemoryStream()) { diff --git a/YAFC/Workspace/SummaryView.cs b/YAFC/Workspace/SummaryView.cs new file mode 100644 index 00000000..7210da8a --- /dev/null +++ b/YAFC/Workspace/SummaryView.cs @@ -0,0 +1,20 @@ +using System; +using YAFC.Model; +using YAFC.UI; + +namespace YAFC { + public class SummaryView : ProjectPageView { + public override void SetModel(ProjectPage page) { + base.SetModel(page); + } + protected override void BuildPageTooltip(ImGui gui, Summary contents) { + } + + protected override void BuildContent(ImGui gui) { + + } + + public override void CreateModelDropdown(ImGui gui, Type type, Project project) { + } + } +} \ No newline at end of file diff --git a/YAFCmodel/Model/Summary.cs b/YAFCmodel/Model/Summary.cs new file mode 100644 index 00000000..e6253854 --- /dev/null +++ b/YAFCmodel/Model/Summary.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; + +namespace YAFC.Model { + public class Summary : ProjectPageContents { + + public Summary(ModelObject page) : base(page) { } + + public override async Task Solve(ProjectPage page) { + return "Summary"; + } + } +} \ No newline at end of file diff --git a/changelog.txt b/changelog.txt index 25783c76..9ff104f6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,7 +2,8 @@ Version: 0.6.2 Date: soon(tm) Changes: - - + - Add summary view + ---------------------------------------------------------------------------------------------------------------------- Version: 0.6.1 Date: Feb 2024 From e6f42ac3313df6d19d47db0841cbe58dc7915d95 Mon Sep 17 00:00:00 2001 From: Maarten Bezemer Date: Wed, 29 Dec 2021 23:24:25 +0100 Subject: [PATCH 02/14] Show link and flow information in SummaryView --- YAFC/Windows/MainScreen.cs | 8 +- .../ProductionTable/ProductionTableView.cs | 4 +- YAFC/Workspace/SummaryView.cs | 96 ++++++++++++++++++- 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/YAFC/Windows/MainScreen.cs b/YAFC/Windows/MainScreen.cs index e5097f35..9dc8f1b4 100644 --- a/YAFC/Windows/MainScreen.cs +++ b/YAFC/Windows/MainScreen.cs @@ -45,7 +45,7 @@ public MainScreen(int display, Project project) : base(default) { RegisterPageView(new ProductionTableView()); RegisterPageView(new AutoPlannerView()); RegisterPageView(new ProductionSummaryView()); - RegisterPageView(new SummaryView()); + RegisterPageView(new SummaryView(this)); searchGui = new ImGui(BuildSearch, new Padding(1f)) { boxShadow = RectangleBorder.Thin, boxColor = SchemeColor.Background }; Instance = this; tabBar = new MainScreenTabBar(this); @@ -74,6 +74,12 @@ private void SetProject(Project project) { if (project.displayPages.Count == 0) project.displayPages.Add(project.pages[0].guid); + // Hack to activate all page solvers for the summary view + foreach (var page in project.pages) { + page.SetActive(true); + page.SetActive(false); + } + SetActivePage(project.FindPage(project.displayPages[0])); project.metaInfoChanged += ProjectOnMetaInfoChanged; project.settings.changed += ProjectSettingsChanged; diff --git a/YAFC/Workspace/ProductionTable/ProductionTableView.cs b/YAFC/Workspace/ProductionTable/ProductionTableView.cs index 1db55edb..1bc95ea9 100644 --- a/YAFC/Workspace/ProductionTable/ProductionTableView.cs +++ b/YAFC/Workspace/ProductionTable/ProductionTableView.cs @@ -712,9 +712,9 @@ private void DrawDesiredProduct(ImGui gui, ProductionLink element) { element.RecordUndo().amount = newAmount; } - public override void Rebuild(bool visuaOnly = false) { + public override void Rebuild(bool visualOnly = false) { flatHierarchyBuilder.SetData(model); - base.Rebuild(visuaOnly); + base.Rebuild(visualOnly); } private void BuildGoodsIcon(ImGui gui, Goods goods, ProductionLink link, float amount, ProductDropdownType dropdownType, RecipeRow recipe, ProductionTable context, Goods[] variants = null) { diff --git a/YAFC/Workspace/SummaryView.cs b/YAFC/Workspace/SummaryView.cs index 7210da8a..c36d5d14 100644 --- a/YAFC/Workspace/SummaryView.cs +++ b/YAFC/Workspace/SummaryView.cs @@ -4,14 +4,106 @@ namespace YAFC { public class SummaryView : ProjectPageView { - public override void SetModel(ProjectPage page) { - base.SetModel(page); + private class SummaryTabColumn : TextDataColumn { + public SummaryTabColumn() : base("Tab", 6f) { + } + + public override void BuildElement(ImGui gui, ProjectPage page) { + if (page?.contentType != typeof(ProductionTable)) { + return; + } + + using (gui.EnterGroup(new Padding(0.5f, 0.2f, 0.2f, 0.5f))) { + gui.spacing = 0.2f; + if (page.icon != null) + gui.BuildIcon(page.icon.icon); + else gui.AllocateRect(0f, 1.5f); + gui.BuildText(page.name); + } + } + } + + private class SummaryDataColumn : TextDataColumn { + protected readonly SummaryView view; + private ProjectPage invokedPage; + + public SummaryDataColumn(SummaryView view) : base("Linked", float.MaxValue) { + this.view = view; + } + + public override void BuildElement(ImGui gui, ProjectPage page) { + if (page?.contentType != typeof(ProductionTable)) { + return; + } + + using var grid = gui.EnterInlineGrid(3f, 1f); + foreach (ProductionLink link in (page.content as ProductionTable).links) { + if (link.amount != 0f) { + grid.Next(); + DrawProvideProduct(gui, link, page); + } + + } + + foreach (ProductionTableFlow flow in (page.content as ProductionTable).flow) { + if (flow.amount >= -1e-5f) + break; + grid.Next(); + DrawRequestProduct(gui, flow); + } + } + + private void DrawProvideProduct(ImGui gui, ProductionLink element, ProjectPage page) { + gui.allocator = RectAllocator.Stretch; + gui.spacing = 0f; + GoodsWithAmountEvent evt = gui.BuildFactorioObjectWithEditableAmount(element.goods, element.amount, element.goods.flowUnitOfMeasure, out float newAmount, SchemeColor.Primary); + if (evt == GoodsWithAmountEvent.TextEditing && newAmount != 0) { + element.RecordUndo().amount = newAmount; + // Hack: Force recalculate the page (and make sure to catch the content change event caused by the recalculation) + invokedPage = page; + page.contentChanged += RebuildInvoked; + page.SetActive(true); + page.SetToRecalculate(); + page.SetActive(false); + } + } + + static private void DrawRequestProduct(ImGui gui, ProductionTableFlow flow) { + gui.allocator = RectAllocator.Stretch; + gui.spacing = 0f; + gui.BuildFactorioObjectWithAmount(flow.goods, -flow.amount, flow.goods?.flowUnitOfMeasure ?? UnitOfMeasure.None, SchemeColor.None); + } + + private void RebuildInvoked(bool visualOnly = false) { + view.Rebuild(visualOnly); + invokedPage.contentChanged -= RebuildInvoked; + } } + + private readonly MainScreen screen; + + private readonly DataGrid mainGrid; + + + public SummaryView(MainScreen screen) { + this.screen = screen; + var columns = new TextDataColumn[] + { + new SummaryTabColumn(), + new SummaryDataColumn(this), + }; + mainGrid = new DataGrid(columns); + } + protected override void BuildPageTooltip(ImGui gui, Summary contents) { } protected override void BuildContent(ImGui gui) { + foreach (Guid displayPage in screen.project.displayPages) { + ProjectPage page = screen.project.FindPage(displayPage); + mainGrid.BuildRow(gui, page); + } } public override void CreateModelDropdown(ImGui gui, Type type, Project project) { From 7fd5391b2bf58841df0f3113d52cbadaf50af60b Mon Sep 17 00:00:00 2001 From: Maarten Bezemer Date: Thu, 30 Dec 2021 01:26:10 +0100 Subject: [PATCH 03/14] Align goods underneath eachother and mark linked goods when there is not enough production --- YAFC/Workspace/SummaryView.cs | 59 ++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/YAFC/Workspace/SummaryView.cs b/YAFC/Workspace/SummaryView.cs index c36d5d14..0336ccbc 100644 --- a/YAFC/Workspace/SummaryView.cs +++ b/YAFC/Workspace/SummaryView.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using YAFC.Model; using YAFC.UI; @@ -36,27 +37,33 @@ public override void BuildElement(ImGui gui, ProjectPage page) { return; } - using var grid = gui.EnterInlineGrid(3f, 1f); - foreach (ProductionLink link in (page.content as ProductionTable).links) { - if (link.amount != 0f) { - grid.Next(); - DrawProvideProduct(gui, link, page); - } + var table = page.content as ProductionTable; - } - - foreach (ProductionTableFlow flow in (page.content as ProductionTable).flow) { - if (flow.amount >= -1e-5f) - break; + using var grid = gui.EnterInlineGrid(3f, 1f); + foreach (KeyValuePair entry in view.allGoods) { grid.Next(); - DrawRequestProduct(gui, flow); + ProductionLink link = table.links.Find(x => x.goods.name == entry.Key); + if (link != null) { + if (link.amount != 0f) { + DrawProvideProduct(gui, link, page, entry.Value); + } + } + else { + if (Array.Exists(table.flow, x => x.goods.name == entry.Key)) { + ProductionTableFlow flow = Array.Find(table.flow, x => x.goods.name == entry.Key); + if (flow.amount < -1e-5f) { + DrawRequestProduct(gui, flow); + } + } + } } } - private void DrawProvideProduct(ImGui gui, ProductionLink element, ProjectPage page) { + + private void DrawProvideProduct(ImGui gui, ProductionLink element, ProjectPage page, float requiredOutput) { gui.allocator = RectAllocator.Stretch; gui.spacing = 0f; - GoodsWithAmountEvent evt = gui.BuildFactorioObjectWithEditableAmount(element.goods, element.amount, element.goods.flowUnitOfMeasure, out float newAmount, SchemeColor.Primary); + GoodsWithAmountEvent evt = gui.BuildFactorioObjectWithEditableAmount(element.goods, element.amount, element.goods.flowUnitOfMeasure, out float newAmount, requiredOutput > element.amount ? SchemeColor.Error : SchemeColor.Primary); if (evt == GoodsWithAmountEvent.TextEditing && newAmount != 0) { element.RecordUndo().amount = newAmount; // Hack: Force recalculate the page (and make sure to catch the content change event caused by the recalculation) @@ -84,6 +91,8 @@ private void RebuildInvoked(bool visualOnly = false) { private readonly DataGrid mainGrid; + private readonly Dictionary allGoods = new Dictionary(); + public SummaryView(MainScreen screen) { this.screen = screen; @@ -99,6 +108,28 @@ protected override void BuildPageTooltip(ImGui gui, Summary contents) { } protected override void BuildContent(ImGui gui) { + // TODO Can we detect if things changed? + allGoods.Clear(); + foreach (Guid displayPage in screen.project.displayPages) { + ProjectPage page = screen.project.FindPage(displayPage); + ProductionTable content = page?.content as ProductionTable; + if (content == null) { + continue; + } + + foreach (ProductionLink link in content.links) { + if (link.amount != 0f && !allGoods.ContainsKey(link.goods.name)) + allGoods[link.goods.name] = 0; + } + + foreach (ProductionTableFlow flow in content.flow) { + if (flow.amount < -1e-5f) { + float value = allGoods.GetValueOrDefault(flow.goods.name); + value -= flow.amount; + allGoods[flow.goods.name] = value; + } + } + } foreach (Guid displayPage in screen.project.displayPages) { ProjectPage page = screen.project.FindPage(displayPage); From 48d3fd8fc0f442be935264d04a03ee8735448a41 Mon Sep 17 00:00:00 2001 From: Maarten Bezemer Date: Thu, 30 Dec 2021 23:01:41 +0100 Subject: [PATCH 04/14] Hide columns that have no problems (produced/provided == consumed/requested) --- YAFC/Workspace/SummaryView.cs | 50 ++++++++++++++++++++++++----------- YAFCmodel/Data/DataUtils.cs | 8 +++--- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/YAFC/Workspace/SummaryView.cs b/YAFC/Workspace/SummaryView.cs index 0336ccbc..659d5f42 100644 --- a/YAFC/Workspace/SummaryView.cs +++ b/YAFC/Workspace/SummaryView.cs @@ -38,32 +38,37 @@ public override void BuildElement(ImGui gui, ProjectPage page) { } var table = page.content as ProductionTable; - using var grid = gui.EnterInlineGrid(3f, 1f); - foreach (KeyValuePair entry in view.allGoods) { + foreach (KeyValuePair entry in view.allGoods) { + float amountAvailable = entry.Value.totalProvided > 0 ? entry.Value.totalProvided : entry.Value.extraProduced; + float amountNeeded = entry.Value.totalProvided < 0 ? -entry.Value.totalProvided : entry.Value.totalNeeded; + if (DataUtils.FormatAmount(amountAvailable, UnitOfMeasure.None) == DataUtils.FormatAmount(amountNeeded, UnitOfMeasure.None)) { + continue; + } + grid.Next(); ProductionLink link = table.links.Find(x => x.goods.name == entry.Key); if (link != null) { if (link.amount != 0f) { - DrawProvideProduct(gui, link, page, entry.Value); + DrawProvideProduct(gui, link, page, entry.Value.extraProduced, amountAvailable >= entry.Value.totalNeeded); } } else { if (Array.Exists(table.flow, x => x.goods.name == entry.Key)) { ProductionTableFlow flow = Array.Find(table.flow, x => x.goods.name == entry.Key); - if (flow.amount < -1e-5f) { - DrawRequestProduct(gui, flow); + if (Math.Abs(flow.amount) > 1e-5f) { + DrawRequestProduct(gui, flow, entry.Value.extraProduced >= entry.Value.totalNeeded); } } } } } - - private void DrawProvideProduct(ImGui gui, ProductionLink element, ProjectPage page, float requiredOutput) { + private void DrawProvideProduct(ImGui gui, ProductionLink element, ProjectPage page, float extraProduced, bool enoughOutput) { gui.allocator = RectAllocator.Stretch; gui.spacing = 0f; - GoodsWithAmountEvent evt = gui.BuildFactorioObjectWithEditableAmount(element.goods, element.amount, element.goods.flowUnitOfMeasure, out float newAmount, requiredOutput > element.amount ? SchemeColor.Error : SchemeColor.Primary); + + GoodsWithAmountEvent evt = gui.BuildFactorioObjectWithEditableAmount(element.goods, element.amount, element.goods.flowUnitOfMeasure, out float newAmount, (element.amount > 0 && enoughOutput) || (element.amount < 0 && DataUtils.FormatAmount(extraProduced, UnitOfMeasure.None) == DataUtils.FormatAmount(-element.amount, UnitOfMeasure.None)) ? SchemeColor.Primary : SchemeColor.Error); if (evt == GoodsWithAmountEvent.TextEditing && newAmount != 0) { element.RecordUndo().amount = newAmount; // Hack: Force recalculate the page (and make sure to catch the content change event caused by the recalculation) @@ -74,11 +79,10 @@ private void DrawProvideProduct(ImGui gui, ProductionLink element, ProjectPage p page.SetActive(false); } } - - static private void DrawRequestProduct(ImGui gui, ProductionTableFlow flow) { + static private void DrawRequestProduct(ImGui gui, ProductionTableFlow flow, bool enoughProduced) { gui.allocator = RectAllocator.Stretch; gui.spacing = 0f; - gui.BuildFactorioObjectWithAmount(flow.goods, -flow.amount, flow.goods?.flowUnitOfMeasure ?? UnitOfMeasure.None, SchemeColor.None); + gui.BuildFactorioObjectWithAmount(flow.goods, -flow.amount, flow.goods?.flowUnitOfMeasure ?? UnitOfMeasure.None, flow.amount > 1e-5f ? enoughProduced ? SchemeColor.Green : SchemeColor.Error : SchemeColor.None); } private void RebuildInvoked(bool visualOnly = false) { @@ -87,11 +91,17 @@ private void RebuildInvoked(bool visualOnly = false) { } } + struct GoodDetails { + public float totalProvided; + public float totalNeeded; + public float extraProduced; + } + private readonly MainScreen screen; private readonly DataGrid mainGrid; - private readonly Dictionary allGoods = new Dictionary(); + private readonly Dictionary allGoods = new Dictionary(); public SummaryView(MainScreen screen) { @@ -118,14 +128,22 @@ protected override void BuildContent(ImGui gui) { } foreach (ProductionLink link in content.links) { - if (link.amount != 0f && !allGoods.ContainsKey(link.goods.name)) - allGoods[link.goods.name] = 0; + if (link.amount != 0f) { + GoodDetails value = allGoods.GetValueOrDefault(link.goods.name); + value.totalProvided += link.amount; + allGoods[link.goods.name] = value; + } } foreach (ProductionTableFlow flow in content.flow) { if (flow.amount < -1e-5f) { - float value = allGoods.GetValueOrDefault(flow.goods.name); - value -= flow.amount; + GoodDetails value = allGoods.GetValueOrDefault(flow.goods.name); + value.totalNeeded -= flow.amount; + allGoods[flow.goods.name] = value; + } + else if (flow.amount > 1e-5f) { + GoodDetails value = allGoods.GetValueOrDefault(flow.goods.name); + value.extraProduced += flow.amount; allGoods[flow.goods.name] = value; } } diff --git a/YAFCmodel/Data/DataUtils.cs b/YAFCmodel/Data/DataUtils.cs index d74767d0..62cba571 100644 --- a/YAFCmodel/Data/DataUtils.cs +++ b/YAFCmodel/Data/DataUtils.cs @@ -375,11 +375,11 @@ public static string FormatTime(float time) { } public static string FormatAmount(float amount, UnitOfMeasure unit, string prefix = null, string suffix = null, bool precise = false) { - var (multplier, unitSuffix) = Project.current == null ? (1f, null) : Project.current.ResolveUnitOfMeasure(unit); - return FormatAmountRaw(amount, multplier, unitSuffix, prefix, suffix, precise ? PreciseFormat : FormatSpec); + var (multiplier, unitSuffix) = Project.current == null ? (1f, null) : Project.current.ResolveUnitOfMeasure(unit); + return FormatAmountRaw(amount, multiplier, unitSuffix, prefix, suffix, precise ? PreciseFormat : FormatSpec); } - public static string FormatAmountRaw(float amount, float unitMultipler, string unitSuffix, string prefix = null, string suffix = null, (char suffix, float multiplier, string format)[] formatSpec = null) { + public static string FormatAmountRaw(float amount, float unitMultiplier, string unitSuffix, string prefix = null, string suffix = null, (char suffix, float multiplier, string format)[] formatSpec = null) { if (float.IsNaN(amount) || float.IsInfinity(amount)) return "-"; if (amount == 0f) @@ -393,7 +393,7 @@ public static string FormatAmountRaw(float amount, float unitMultipler, string u amount = -amount; } - amount *= unitMultipler; + amount *= unitMultiplier; var idx = MathUtils.Clamp(MathUtils.Floor(MathF.Log10(amount)) + 8, 0, formatSpec.Length - 1); var val = formatSpec[idx]; amountBuilder.Append((amount * val.multiplier).ToString(val.format)); From 7faa7428afe2b009af1b3561b8d174299996c6cd Mon Sep 17 00:00:00 2001 From: Maarten Bezemer Date: Fri, 31 Dec 2021 01:01:52 +0100 Subject: [PATCH 05/14] Improve to match UI better ('YAFC rounding' and float rounding) --- YAFC/Workspace/SummaryView.cs | 44 +++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/YAFC/Workspace/SummaryView.cs b/YAFC/Workspace/SummaryView.cs index 659d5f42..0c5d4818 100644 --- a/YAFC/Workspace/SummaryView.cs +++ b/YAFC/Workspace/SummaryView.cs @@ -40,9 +40,9 @@ public override void BuildElement(ImGui gui, ProjectPage page) { var table = page.content as ProductionTable; using var grid = gui.EnterInlineGrid(3f, 1f); foreach (KeyValuePair entry in view.allGoods) { - float amountAvailable = entry.Value.totalProvided > 0 ? entry.Value.totalProvided : entry.Value.extraProduced; - float amountNeeded = entry.Value.totalProvided < 0 ? -entry.Value.totalProvided : entry.Value.totalNeeded; - if (DataUtils.FormatAmount(amountAvailable, UnitOfMeasure.None) == DataUtils.FormatAmount(amountNeeded, UnitOfMeasure.None)) { + float amountAvailable = YAFCRounding((entry.Value.totalProvided > 0 ? entry.Value.totalProvided : 0) + entry.Value.extraProduced); + float amountNeeded = YAFCRounding((entry.Value.totalProvided < 0 ? -entry.Value.totalProvided : 0) + entry.Value.totalNeeded); + if (Math.Abs(amountAvailable - amountNeeded) < Epsilon || amountNeeded == 0) { continue; } @@ -50,14 +50,14 @@ public override void BuildElement(ImGui gui, ProjectPage page) { ProductionLink link = table.links.Find(x => x.goods.name == entry.Key); if (link != null) { if (link.amount != 0f) { - DrawProvideProduct(gui, link, page, entry.Value.extraProduced, amountAvailable >= entry.Value.totalNeeded); + DrawProvideProduct(gui, link, page, entry.Value.extraProduced, amountAvailable >= amountNeeded); } } else { if (Array.Exists(table.flow, x => x.goods.name == entry.Key)) { ProductionTableFlow flow = Array.Find(table.flow, x => x.goods.name == entry.Key); - if (Math.Abs(flow.amount) > 1e-5f) { - DrawRequestProduct(gui, flow, entry.Value.extraProduced >= entry.Value.totalNeeded); + if (Math.Abs(flow.amount) > Epsilon) { + DrawRequestProduct(gui, flow, amountAvailable >= amountNeeded); } } } @@ -68,7 +68,7 @@ private void DrawProvideProduct(ImGui gui, ProductionLink element, ProjectPage p gui.allocator = RectAllocator.Stretch; gui.spacing = 0f; - GoodsWithAmountEvent evt = gui.BuildFactorioObjectWithEditableAmount(element.goods, element.amount, element.goods.flowUnitOfMeasure, out float newAmount, (element.amount > 0 && enoughOutput) || (element.amount < 0 && DataUtils.FormatAmount(extraProduced, UnitOfMeasure.None) == DataUtils.FormatAmount(-element.amount, UnitOfMeasure.None)) ? SchemeColor.Primary : SchemeColor.Error); + GoodsWithAmountEvent evt = gui.BuildFactorioObjectWithEditableAmount(element.goods, element.amount, element.goods.flowUnitOfMeasure, out float newAmount, (element.amount > 0 && enoughOutput) || (element.amount < 0 && extraProduced == -element.amount) ? SchemeColor.Primary : SchemeColor.Error); if (evt == GoodsWithAmountEvent.TextEditing && newAmount != 0) { element.RecordUndo().amount = newAmount; // Hack: Force recalculate the page (and make sure to catch the content change event caused by the recalculation) @@ -82,7 +82,7 @@ private void DrawProvideProduct(ImGui gui, ProductionLink element, ProjectPage p static private void DrawRequestProduct(ImGui gui, ProductionTableFlow flow, bool enoughProduced) { gui.allocator = RectAllocator.Stretch; gui.spacing = 0f; - gui.BuildFactorioObjectWithAmount(flow.goods, -flow.amount, flow.goods?.flowUnitOfMeasure ?? UnitOfMeasure.None, flow.amount > 1e-5f ? enoughProduced ? SchemeColor.Green : SchemeColor.Error : SchemeColor.None); + gui.BuildFactorioObjectWithAmount(flow.goods, -flow.amount, flow.goods?.flowUnitOfMeasure ?? UnitOfMeasure.None, flow.amount > Epsilon ? enoughProduced ? SchemeColor.Green : SchemeColor.Error : SchemeColor.None); } private void RebuildInvoked(bool visualOnly = false) { @@ -91,6 +91,8 @@ private void RebuildInvoked(bool visualOnly = false) { } } + static readonly float Epsilon = 1e-5f; + struct GoodDetails { public float totalProvided; public float totalNeeded; @@ -130,21 +132,24 @@ protected override void BuildContent(ImGui gui) { foreach (ProductionLink link in content.links) { if (link.amount != 0f) { GoodDetails value = allGoods.GetValueOrDefault(link.goods.name); - value.totalProvided += link.amount; + value.totalProvided += YAFCRounding(link.amount); ; allGoods[link.goods.name] = value; } } foreach (ProductionTableFlow flow in content.flow) { - if (flow.amount < -1e-5f) { + if (flow.amount < -Epsilon) { GoodDetails value = allGoods.GetValueOrDefault(flow.goods.name); - value.totalNeeded -= flow.amount; + value.totalNeeded -= YAFCRounding(flow.amount); ; allGoods[flow.goods.name] = value; } - else if (flow.amount > 1e-5f) { - GoodDetails value = allGoods.GetValueOrDefault(flow.goods.name); - value.extraProduced += flow.amount; - allGoods[flow.goods.name] = value; + else if (flow.amount > Epsilon) { + if (!content.links.Exists(x => x.goods == flow.goods)) { + // Only count extras if not linked + GoodDetails value = allGoods.GetValueOrDefault(flow.goods.name); + value.extraProduced += YAFCRounding(flow.amount); + allGoods[flow.goods.name] = value; + } } } } @@ -155,6 +160,15 @@ protected override void BuildContent(ImGui gui) { } } + // Convert/truncate value as shown in UI to prevent slight mismatches + static private float YAFCRounding(float value) { +#pragma warning disable CA1806 // We don't care about the returned value as result is updated independently whether the function return true or not + DataUtils.TryParseAmount(DataUtils.FormatAmount(value, UnitOfMeasure.Second), out float result, UnitOfMeasure.Second); +#pragma warning restore CA1806 + + return result; + } + public override void CreateModelDropdown(ImGui gui, Type type, Project project) { } } From dee2119b589c122037803fb8445bbec549ae892d Mon Sep 17 00:00:00 2001 From: Maarten Bezemer Date: Sun, 2 Jan 2022 14:22:07 +0100 Subject: [PATCH 06/14] Also calculate width when header is not rendered --- YAFC/Widgets/DataGrid.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/YAFC/Widgets/DataGrid.cs b/YAFC/Widgets/DataGrid.cs index 96f70d94..5795637f 100644 --- a/YAFC/Widgets/DataGrid.cs +++ b/YAFC/Widgets/DataGrid.cs @@ -89,6 +89,14 @@ private void BuildHeaderResizer(ImGui gui, DataColumn column, Rect rect) } } + private void CalculateWidth(ImGui gui) { + var x = 0f; + foreach (var column in columns) { + x += column.width + spacing; + } + width = MathF.Max(x + 0.2f - spacing, gui.width - 1f); + } + public void BuildHeader(ImGui gui) { var spacing = innerPadding.left + innerPadding.right; var x = 0f; @@ -111,7 +119,7 @@ public void BuildHeader(ImGui gui) { } } } - width = MathF.Max(x + 0.2f - spacing, gui.width - 1f); + CalculateWidth(gui); var separator = gui.AllocateRect(x, 0.1f); if (gui.isBuilding) { @@ -141,6 +149,7 @@ public Rect BuildRow(ImGui gui, TData element, float startX = 0f) { buildGroup.Complete(); } + CalculateWidth(gui); var rect = gui.lastRect; var bottom = gui.lastRect.Bottom; if (gui.isBuilding) From 3a70d6d2768ef1e01f127cc2a2b6436cf94c3747 Mon Sep 17 00:00:00 2001 From: Maarten Bezemer Date: Sun, 2 Jan 2022 14:50:38 +0100 Subject: [PATCH 07/14] Make Summary table scrollable and add proper header/title text Note that the horizontal scrollbar is not clickable (ctrl+scroll and keyboard scrolling is working) --- YAFC/Workspace/SummaryView.cs | 35 ++++++++++++++++++++++++++++------- YAFCmodel/Model/Summary.cs | 2 +- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/YAFC/Workspace/SummaryView.cs b/YAFC/Workspace/SummaryView.cs index 0c5d4818..08e36d53 100644 --- a/YAFC/Workspace/SummaryView.cs +++ b/YAFC/Workspace/SummaryView.cs @@ -38,7 +38,7 @@ public override void BuildElement(ImGui gui, ProjectPage page) { } var table = page.content as ProductionTable; - using var grid = gui.EnterInlineGrid(3f, 1f); + using var grid = gui.EnterInlineGrid(ElementWidth, 1f); foreach (KeyValuePair entry in view.allGoods) { float amountAvailable = YAFCRounding((entry.Value.totalProvided > 0 ? entry.Value.totalProvided : 0) + entry.Value.extraProduced); float amountNeeded = YAFCRounding((entry.Value.totalProvided < 0 ? -entry.Value.totalProvided : 0) + entry.Value.totalNeeded); @@ -47,28 +47,30 @@ public override void BuildElement(ImGui gui, ProjectPage page) { } grid.Next(); + bool enoughProduced = amountAvailable >= amountNeeded; ProductionLink link = table.links.Find(x => x.goods.name == entry.Key); if (link != null) { if (link.amount != 0f) { - DrawProvideProduct(gui, link, page, entry.Value.extraProduced, amountAvailable >= amountNeeded); + DrawProvideProduct(gui, link, page, entry.Value.extraProduced, enoughProduced); } } else { if (Array.Exists(table.flow, x => x.goods.name == entry.Key)) { ProductionTableFlow flow = Array.Find(table.flow, x => x.goods.name == entry.Key); if (Math.Abs(flow.amount) > Epsilon) { - DrawRequestProduct(gui, flow, amountAvailable >= amountNeeded); + + DrawRequestProduct(gui, flow, enoughProduced); } } } } } - private void DrawProvideProduct(ImGui gui, ProductionLink element, ProjectPage page, float extraProduced, bool enoughOutput) { + private void DrawProvideProduct(ImGui gui, ProductionLink element, ProjectPage page, float extraProduced, bool enoughProduced) { gui.allocator = RectAllocator.Stretch; gui.spacing = 0f; - GoodsWithAmountEvent evt = gui.BuildFactorioObjectWithEditableAmount(element.goods, element.amount, element.goods.flowUnitOfMeasure, out float newAmount, (element.amount > 0 && enoughOutput) || (element.amount < 0 && extraProduced == -element.amount) ? SchemeColor.Primary : SchemeColor.Error); + GoodsWithAmountEvent evt = gui.BuildFactorioObjectWithEditableAmount(element.goods, element.amount, element.goods.flowUnitOfMeasure, out float newAmount, (element.amount > 0 && enoughProduced) || (element.amount < 0 && extraProduced == -element.amount) ? SchemeColor.Primary : SchemeColor.Error); if (evt == GoodsWithAmountEvent.TextEditing && newAmount != 0) { element.RecordUndo().amount = newAmount; // Hack: Force recalculate the page (and make sure to catch the content change event caused by the recalculation) @@ -87,12 +89,13 @@ static private void DrawRequestProduct(ImGui gui, ProductionTableFlow flow, bool private void RebuildInvoked(bool visualOnly = false) { view.Rebuild(visualOnly); + view.scrollArea.RebuildContents(); invokedPage.contentChanged -= RebuildInvoked; } } static readonly float Epsilon = 1e-5f; - + static readonly float ElementWidth = 3; struct GoodDetails { public float totalProvided; public float totalNeeded; @@ -101,6 +104,8 @@ struct GoodDetails { private readonly MainScreen screen; + private readonly ScrollArea scrollArea; + private readonly SummaryDataColumn goodsColumn; private readonly DataGrid mainGrid; private readonly Dictionary allGoods = new Dictionary(); @@ -108,17 +113,28 @@ struct GoodDetails { public SummaryView(MainScreen screen) { this.screen = screen; + goodsColumn = new SummaryDataColumn(this); var columns = new TextDataColumn[] { new SummaryTabColumn(), - new SummaryDataColumn(this), + goodsColumn, }; + // TODO Make height relative to window height instead of fixed + scrollArea = new ScrollArea(30, BuildScrollArea, vertical: true, horizontal: true); mainGrid = new DataGrid(columns); } protected override void BuildPageTooltip(ImGui gui, Summary contents) { } + protected override void BuildHeader(ImGui gui) { + base.BuildHeader(gui); + + gui.allocator = RectAllocator.Center; + gui.BuildText("Production Sheet Summary", Font.header, false, RectAlignment.Middle); + gui.allocator = RectAllocator.LeftAlign; + } + protected override void BuildContent(ImGui gui) { // TODO Can we detect if things changed? allGoods.Clear(); @@ -154,6 +170,11 @@ protected override void BuildContent(ImGui gui) { } } + goodsColumn.width = allGoods.Count * ElementWidth; + scrollArea.Build(gui); + } + + private void BuildScrollArea(ImGui gui) { foreach (Guid displayPage in screen.project.displayPages) { ProjectPage page = screen.project.FindPage(displayPage); mainGrid.BuildRow(gui, page); diff --git a/YAFCmodel/Model/Summary.cs b/YAFCmodel/Model/Summary.cs index e6253854..7cb66ea5 100644 --- a/YAFCmodel/Model/Summary.cs +++ b/YAFCmodel/Model/Summary.cs @@ -6,7 +6,7 @@ public class Summary : ProjectPageContents { public Summary(ModelObject page) : base(page) { } public override async Task Solve(ProjectPage page) { - return "Summary"; + return null; } } } \ No newline at end of file From d468f64308d5eabc0e6ffcb6bfaca34b47e66f75 Mon Sep 17 00:00:00 2001 From: Maarten Bezemer Date: Sun, 2 Jan 2022 20:54:43 +0100 Subject: [PATCH 08/14] Use event system to only recalculate/prepare when needed --- YAFC/Windows/MainScreen.cs | 5 ++- YAFC/Workspace/SummaryView.cs | 64 ++++++++++++++++++++++------------- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/YAFC/Windows/MainScreen.cs b/YAFC/Windows/MainScreen.cs index 9dc8f1b4..33b2bef3 100644 --- a/YAFC/Windows/MainScreen.cs +++ b/YAFC/Windows/MainScreen.cs @@ -30,6 +30,7 @@ public class MainScreen : WindowMain, IKeyboardFocus, IProgress<(string, string) private ProjectPage _secondaryPage; public ProjectPage secondaryPage => _secondaryPage; private ProjectPageView secondaryPageView; + private readonly SummaryView summaryView; private bool analysisUpdatePending; private SearchQuery pageSearch; @@ -42,10 +43,11 @@ public class MainScreen : WindowMain, IKeyboardFocus, IProgress<(string, string) private readonly Dictionary secondaryPageViews = new Dictionary(); public MainScreen(int display, Project project) : base(default) { + summaryView = new SummaryView(); RegisterPageView(new ProductionTableView()); RegisterPageView(new AutoPlannerView()); RegisterPageView(new ProductionSummaryView()); - RegisterPageView(new SummaryView(this)); + RegisterPageView(summaryView); searchGui = new ImGui(BuildSearch, new Padding(1f)) { boxShadow = RectangleBorder.Thin, boxColor = SchemeColor.Background }; Instance = this; tabBar = new MainScreenTabBar(this); @@ -84,6 +86,7 @@ private void SetProject(Project project) { project.metaInfoChanged += ProjectOnMetaInfoChanged; project.settings.changed += ProjectSettingsChanged; InputSystem.Instance.SetDefaultKeyboardFocus(this); + summaryView.SetProject(project); } private void ProjectSettingsChanged(bool visualOnly) { diff --git a/YAFC/Workspace/SummaryView.cs b/YAFC/Workspace/SummaryView.cs index 08e36d53..9f65d3a8 100644 --- a/YAFC/Workspace/SummaryView.cs +++ b/YAFC/Workspace/SummaryView.cs @@ -26,7 +26,6 @@ public override void BuildElement(ImGui gui, ProjectPage page) { private class SummaryDataColumn : TextDataColumn { protected readonly SummaryView view; - private ProjectPage invokedPage; public SummaryDataColumn(SummaryView view) : base("Linked", float.MaxValue) { this.view = view; @@ -66,7 +65,7 @@ public override void BuildElement(ImGui gui, ProjectPage page) { } } - private void DrawProvideProduct(ImGui gui, ProductionLink element, ProjectPage page, float extraProduced, bool enoughProduced) { + static private void DrawProvideProduct(ImGui gui, ProductionLink element, ProjectPage page, float extraProduced, bool enoughProduced) { gui.allocator = RectAllocator.Stretch; gui.spacing = 0f; @@ -74,8 +73,6 @@ private void DrawProvideProduct(ImGui gui, ProductionLink element, ProjectPage p if (evt == GoodsWithAmountEvent.TextEditing && newAmount != 0) { element.RecordUndo().amount = newAmount; // Hack: Force recalculate the page (and make sure to catch the content change event caused by the recalculation) - invokedPage = page; - page.contentChanged += RebuildInvoked; page.SetActive(true); page.SetToRecalculate(); page.SetActive(false); @@ -86,12 +83,6 @@ static private void DrawRequestProduct(ImGui gui, ProductionTableFlow flow, bool gui.spacing = 0f; gui.BuildFactorioObjectWithAmount(flow.goods, -flow.amount, flow.goods?.flowUnitOfMeasure ?? UnitOfMeasure.None, flow.amount > Epsilon ? enoughProduced ? SchemeColor.Green : SchemeColor.Error : SchemeColor.None); } - - private void RebuildInvoked(bool visualOnly = false) { - view.Rebuild(visualOnly); - view.scrollArea.RebuildContents(); - invokedPage.contentChanged -= RebuildInvoked; - } } static readonly float Epsilon = 1e-5f; @@ -102,7 +93,7 @@ struct GoodDetails { public float extraProduced; } - private readonly MainScreen screen; + private Project project; private readonly ScrollArea scrollArea; private readonly SummaryDataColumn goodsColumn; @@ -111,19 +102,34 @@ struct GoodDetails { private readonly Dictionary allGoods = new Dictionary(); - public SummaryView(MainScreen screen) { - this.screen = screen; + public SummaryView() { goodsColumn = new SummaryDataColumn(this); var columns = new TextDataColumn[] { new SummaryTabColumn(), goodsColumn, }; - // TODO Make height relative to window height instead of fixed + // TODO Make height relative to min(window,content) height instead of fixed scrollArea = new ScrollArea(30, BuildScrollArea, vertical: true, horizontal: true); mainGrid = new DataGrid(columns); } + public void SetProject(Project project) { + if (this.project != null) { + this.project.metaInfoChanged -= Recalculate; + foreach (ProjectPage page in project.pages) { + page.contentChanged -= Recalculate; + } + } + + this.project = project; + + project.metaInfoChanged += Recalculate; + foreach (ProjectPage page in project.pages) { + page.contentChanged += Recalculate; + } + } + protected override void BuildPageTooltip(ImGui gui, Summary contents) { } @@ -136,10 +142,25 @@ protected override void BuildHeader(ImGui gui) { } protected override void BuildContent(ImGui gui) { - // TODO Can we detect if things changed? + scrollArea.Build(gui); + } + + private void BuildScrollArea(ImGui gui) { + foreach (Guid displayPage in project.displayPages) { + ProjectPage page = project.FindPage(displayPage); + if (page?.contentType != typeof(ProductionTable)) + continue; + + mainGrid.BuildRow(gui, page); + } + } + + private void Recalculate() => Recalculate(false); + + private void Recalculate(bool visualOnly) { allGoods.Clear(); - foreach (Guid displayPage in screen.project.displayPages) { - ProjectPage page = screen.project.FindPage(displayPage); + foreach (Guid displayPage in project.displayPages) { + ProjectPage page = project.FindPage(displayPage); ProductionTable content = page?.content as ProductionTable; if (content == null) { continue; @@ -171,14 +192,9 @@ protected override void BuildContent(ImGui gui) { } goodsColumn.width = allGoods.Count * ElementWidth; - scrollArea.Build(gui); - } - private void BuildScrollArea(ImGui gui) { - foreach (Guid displayPage in screen.project.displayPages) { - ProjectPage page = screen.project.FindPage(displayPage); - mainGrid.BuildRow(gui, page); - } + Rebuild(visualOnly); + scrollArea.RebuildContents(); } // Convert/truncate value as shown in UI to prevent slight mismatches From 13040de0af6558b07fa72e81c0d2d09400083362 Mon Sep 17 00:00:00 2001 From: Maarten Bezemer Date: Sun, 2 Jan 2022 22:41:10 +0100 Subject: [PATCH 09/14] Add checkbox to only show goods with 'issues' (and fix width calculation of table) --- YAFC/Workspace/SummaryView.cs | 22 +++++++++++++++++++--- YAFCmodel/Model/Summary.cs | 2 ++ changelog.txt | 1 + 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/YAFC/Workspace/SummaryView.cs b/YAFC/Workspace/SummaryView.cs index 9f65d3a8..95a16825 100644 --- a/YAFC/Workspace/SummaryView.cs +++ b/YAFC/Workspace/SummaryView.cs @@ -37,11 +37,11 @@ public override void BuildElement(ImGui gui, ProjectPage page) { } var table = page.content as ProductionTable; - using var grid = gui.EnterInlineGrid(ElementWidth, 1f); + using var grid = gui.EnterInlineGrid(ElementWidth, ElementSpacing); foreach (KeyValuePair entry in view.allGoods) { float amountAvailable = YAFCRounding((entry.Value.totalProvided > 0 ? entry.Value.totalProvided : 0) + entry.Value.extraProduced); float amountNeeded = YAFCRounding((entry.Value.totalProvided < 0 ? -entry.Value.totalProvided : 0) + entry.Value.totalNeeded); - if (Math.Abs(amountAvailable - amountNeeded) < Epsilon || amountNeeded == 0) { + if (view.model.showOnlyIssues && (Math.Abs(amountAvailable - amountNeeded) < Epsilon || amountNeeded == 0)) { continue; } @@ -87,6 +87,7 @@ static private void DrawRequestProduct(ImGui gui, ProductionTableFlow flow, bool static readonly float Epsilon = 1e-5f; static readonly float ElementWidth = 3; + static readonly float ElementSpacing = 1; struct GoodDetails { public float totalProvided; public float totalNeeded; @@ -142,6 +143,11 @@ protected override void BuildHeader(ImGui gui) { } protected override void BuildContent(ImGui gui) { + if (gui.BuildCheckBox("Only show issues", model.showOnlyIssues, out bool newValue)) { + model.showOnlyIssues = newValue; + Recalculate(); + } + scrollArea.Build(gui); } @@ -191,7 +197,17 @@ private void Recalculate(bool visualOnly) { } } - goodsColumn.width = allGoods.Count * ElementWidth; + int count = 0; + foreach (KeyValuePair entry in allGoods) { + float amountAvailable = YAFCRounding((entry.Value.totalProvided > 0 ? entry.Value.totalProvided : 0) + entry.Value.extraProduced); + float amountNeeded = YAFCRounding((entry.Value.totalProvided < 0 ? -entry.Value.totalProvided : 0) + entry.Value.totalNeeded); + if (model != null && model.showOnlyIssues && (Math.Abs(amountAvailable - amountNeeded) < Epsilon || amountNeeded == 0)) { + continue; + } + count++; + } + + goodsColumn.width = count * (ElementWidth + ElementSpacing); Rebuild(visualOnly); scrollArea.RebuildContents(); diff --git a/YAFCmodel/Model/Summary.cs b/YAFCmodel/Model/Summary.cs index 7cb66ea5..d40c7a33 100644 --- a/YAFCmodel/Model/Summary.cs +++ b/YAFCmodel/Model/Summary.cs @@ -3,6 +3,8 @@ namespace YAFC.Model { public class Summary : ProjectPageContents { + public bool showOnlyIssues { get; set; } + public Summary(ModelObject page) : base(page) { } public override async Task Solve(ProjectPage page) { diff --git a/changelog.txt b/changelog.txt index 9ff104f6..1148489f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,7 @@ Version: 0.6.2 Date: soon(tm) Changes: - Add summary view + - Checkbox to show only goods with 'issues': different consuming/producing amounts ---------------------------------------------------------------------------------------------------------------------- Version: 0.6.1 From bf9c88740a70fcbb456018de4b2d9e81f61b56f7 Mon Sep 17 00:00:00 2001 From: Maarten Bezemer Date: Tue, 18 Jan 2022 22:03:00 +0100 Subject: [PATCH 10/14] Calculate required amount when click on a link icon --- YAFC/Workspace/SummaryView.cs | 38 ++++++++++++++++++++++------------- changelog.txt | 1 + 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/YAFC/Workspace/SummaryView.cs b/YAFC/Workspace/SummaryView.cs index 95a16825..fa113448 100644 --- a/YAFC/Workspace/SummaryView.cs +++ b/YAFC/Workspace/SummaryView.cs @@ -38,24 +38,24 @@ public override void BuildElement(ImGui gui, ProjectPage page) { var table = page.content as ProductionTable; using var grid = gui.EnterInlineGrid(ElementWidth, ElementSpacing); - foreach (KeyValuePair entry in view.allGoods) { - float amountAvailable = YAFCRounding((entry.Value.totalProvided > 0 ? entry.Value.totalProvided : 0) + entry.Value.extraProduced); - float amountNeeded = YAFCRounding((entry.Value.totalProvided < 0 ? -entry.Value.totalProvided : 0) + entry.Value.totalNeeded); + foreach (KeyValuePair goodInfo in view.allGoods) { + float amountAvailable = YAFCRounding((goodInfo.Value.totalProvided > 0 ? goodInfo.Value.totalProvided : 0) + goodInfo.Value.extraProduced); + float amountNeeded = YAFCRounding((goodInfo.Value.totalProvided < 0 ? -goodInfo.Value.totalProvided : 0) + goodInfo.Value.totalNeeded); if (view.model.showOnlyIssues && (Math.Abs(amountAvailable - amountNeeded) < Epsilon || amountNeeded == 0)) { continue; } grid.Next(); bool enoughProduced = amountAvailable >= amountNeeded; - ProductionLink link = table.links.Find(x => x.goods.name == entry.Key); + ProductionLink link = table.links.Find(x => x.goods.name == goodInfo.Key); if (link != null) { if (link.amount != 0f) { - DrawProvideProduct(gui, link, page, entry.Value.extraProduced, enoughProduced); + DrawProvideProduct(gui, link, page, goodInfo.Value, enoughProduced); } } else { - if (Array.Exists(table.flow, x => x.goods.name == entry.Key)) { - ProductionTableFlow flow = Array.Find(table.flow, x => x.goods.name == entry.Key); + if (Array.Exists(table.flow, x => x.goods.name == goodInfo.Key)) { + ProductionTableFlow flow = Array.Find(table.flow, x => x.goods.name == goodInfo.Key); if (Math.Abs(flow.amount) > Epsilon) { DrawRequestProduct(gui, flow, enoughProduced); @@ -65,17 +65,16 @@ public override void BuildElement(ImGui gui, ProjectPage page) { } } - static private void DrawProvideProduct(ImGui gui, ProductionLink element, ProjectPage page, float extraProduced, bool enoughProduced) { + static private void DrawProvideProduct(ImGui gui, ProductionLink element, ProjectPage page, GoodDetails goodInfo, bool enoughProduced) { gui.allocator = RectAllocator.Stretch; gui.spacing = 0f; - GoodsWithAmountEvent evt = gui.BuildFactorioObjectWithEditableAmount(element.goods, element.amount, element.goods.flowUnitOfMeasure, out float newAmount, (element.amount > 0 && enoughProduced) || (element.amount < 0 && extraProduced == -element.amount) ? SchemeColor.Primary : SchemeColor.Error); + GoodsWithAmountEvent evt = gui.BuildFactorioObjectWithEditableAmount(element.goods, element.amount, element.goods.flowUnitOfMeasure, out float newAmount, (element.amount > 0 && enoughProduced) || (element.amount < 0 && goodInfo.extraProduced == -element.amount) ? SchemeColor.Primary : SchemeColor.Error); if (evt == GoodsWithAmountEvent.TextEditing && newAmount != 0) { - element.RecordUndo().amount = newAmount; - // Hack: Force recalculate the page (and make sure to catch the content change event caused by the recalculation) - page.SetActive(true); - page.SetToRecalculate(); - page.SetActive(false); + SetProviderAmount(element, page, newAmount); + } + else if (evt == GoodsWithAmountEvent.ButtonClick) { + SetProviderAmount(element, page, YAFCRounding(goodInfo.sum)); } } static private void DrawRequestProduct(ImGui gui, ProductionTableFlow flow, bool enoughProduced) { @@ -83,6 +82,14 @@ static private void DrawRequestProduct(ImGui gui, ProductionTableFlow flow, bool gui.spacing = 0f; gui.BuildFactorioObjectWithAmount(flow.goods, -flow.amount, flow.goods?.flowUnitOfMeasure ?? UnitOfMeasure.None, flow.amount > Epsilon ? enoughProduced ? SchemeColor.Green : SchemeColor.Error : SchemeColor.None); } + + static private void SetProviderAmount(ProductionLink element, ProjectPage page, float newAmount) { + element.RecordUndo().amount = newAmount; + // Hack: Force recalculate the page (and make sure to catch the content change event caused by the recalculation) + page.SetActive(true); + page.SetToRecalculate(); + page.SetActive(false); + } } static readonly float Epsilon = 1e-5f; @@ -92,6 +99,7 @@ struct GoodDetails { public float totalProvided; public float totalNeeded; public float extraProduced; + public float sum; } private Project project; @@ -184,6 +192,7 @@ private void Recalculate(bool visualOnly) { if (flow.amount < -Epsilon) { GoodDetails value = allGoods.GetValueOrDefault(flow.goods.name); value.totalNeeded -= YAFCRounding(flow.amount); ; + value.sum -= YAFCRounding(flow.amount); ; allGoods[flow.goods.name] = value; } else if (flow.amount > Epsilon) { @@ -191,6 +200,7 @@ private void Recalculate(bool visualOnly) { // Only count extras if not linked GoodDetails value = allGoods.GetValueOrDefault(flow.goods.name); value.extraProduced += YAFCRounding(flow.amount); + value.sum -= YAFCRounding(flow.amount); allGoods[flow.goods.name] = value; } } diff --git a/changelog.txt b/changelog.txt index 1148489f..db8ce792 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,7 @@ Date: soon(tm) Changes: - Add summary view - Checkbox to show only goods with 'issues': different consuming/producing amounts + - Balance producing side to match the consuming when clicking an 'issue' ---------------------------------------------------------------------------------------------------------------------- Version: 0.6.1 From f213eb885a1eb28546c6d5074da927a7714b4b9a Mon Sep 17 00:00:00 2001 From: Maarten Bezemer Date: Wed, 19 Jan 2022 00:45:43 +0100 Subject: [PATCH 11/14] Support the search box (ctrl+F) --- YAFC/Workspace/SummaryView.cs | 11 +++++++++++ changelog.txt | 1 + 2 files changed, 12 insertions(+) diff --git a/YAFC/Workspace/SummaryView.cs b/YAFC/Workspace/SummaryView.cs index fa113448..c26b1f75 100644 --- a/YAFC/Workspace/SummaryView.cs +++ b/YAFC/Workspace/SummaryView.cs @@ -39,6 +39,10 @@ public override void BuildElement(ImGui gui, ProjectPage page) { var table = page.content as ProductionTable; using var grid = gui.EnterInlineGrid(ElementWidth, ElementSpacing); foreach (KeyValuePair goodInfo in view.allGoods) { + if (!view.searchQuery.Match(goodInfo.Key)) { + continue; + } + float amountAvailable = YAFCRounding((goodInfo.Value.totalProvided > 0 ? goodInfo.Value.totalProvided : 0) + goodInfo.Value.extraProduced); float amountNeeded = YAFCRounding((goodInfo.Value.totalProvided < 0 ? -goodInfo.Value.totalProvided : 0) + goodInfo.Value.totalNeeded); if (view.model.showOnlyIssues && (Math.Abs(amountAvailable - amountNeeded) < Epsilon || amountNeeded == 0)) { @@ -103,6 +107,7 @@ struct GoodDetails { } private Project project; + private SearchQuery searchQuery; private readonly ScrollArea scrollArea; private readonly SummaryDataColumn goodsColumn; @@ -232,6 +237,12 @@ static private float YAFCRounding(float value) { return result; } + public override void SetSearchQuery(SearchQuery query) { + searchQuery = query; + bodyContent.Rebuild(); + scrollArea.Rebuild(); + } + public override void CreateModelDropdown(ImGui gui, Type type, Project project) { } } diff --git a/changelog.txt b/changelog.txt index db8ce792..dd90305f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -5,6 +5,7 @@ Date: soon(tm) - Add summary view - Checkbox to show only goods with 'issues': different consuming/producing amounts - Balance producing side to match the consuming when clicking an 'issue' + - Support the search box (ctrl+F) ---------------------------------------------------------------------------------------------------------------------- Version: 0.6.1 From 03ffc07285699079e91a526bd5a217e7f79950d5 Mon Sep 17 00:00:00 2001 From: Maarten Bezemer Date: Mon, 12 Dec 2022 20:08:46 +0100 Subject: [PATCH 12/14] Adjust Summary View to window height It is a bit hackish but it seems to work (after updating the UI to trigger some 'recalculation') Fixes: https://github.com/veger/yafc-ce/issues/2 --- YAFC/Workspace/SummaryView.cs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/YAFC/Workspace/SummaryView.cs b/YAFC/Workspace/SummaryView.cs index c26b1f75..5e2f66d3 100644 --- a/YAFC/Workspace/SummaryView.cs +++ b/YAFC/Workspace/SummaryView.cs @@ -5,6 +5,18 @@ namespace YAFC { public class SummaryView : ProjectPageView { + private class SummaryScrollArea : ScrollArea { + static float DefaultHeight = 10; + + public SummaryScrollArea(GuiBuilder builder) : base(DefaultHeight, builder, default, false, true, true) { + } + + public new void Build(ImGui gui) { + // Maximize scroll area to fit parent area (minus header and 'show issues' heights, and some (2) padding probably) + Build(gui, gui.valid ? gui.parent.contentSize.Y - HeaderFont.size - Font.text.size - ScrollbarSize - 2 : DefaultHeight); + } + } + private class SummaryTabColumn : TextDataColumn { public SummaryTabColumn() : base("Tab", 6f) { } @@ -106,10 +118,12 @@ struct GoodDetails { public float sum; } + static Font HeaderFont = Font.header; + private Project project; private SearchQuery searchQuery; - private readonly ScrollArea scrollArea; + private readonly SummaryScrollArea scrollArea; private readonly SummaryDataColumn goodsColumn; private readonly DataGrid mainGrid; @@ -123,8 +137,7 @@ public SummaryView() { new SummaryTabColumn(), goodsColumn, }; - // TODO Make height relative to min(window,content) height instead of fixed - scrollArea = new ScrollArea(30, BuildScrollArea, vertical: true, horizontal: true); + scrollArea = new SummaryScrollArea(BuildScrollArea); mainGrid = new DataGrid(columns); } @@ -151,7 +164,7 @@ protected override void BuildHeader(ImGui gui) { base.BuildHeader(gui); gui.allocator = RectAllocator.Center; - gui.BuildText("Production Sheet Summary", Font.header, false, RectAlignment.Middle); + gui.BuildText("Production Sheet Summary", HeaderFont, false, RectAlignment.Middle); gui.allocator = RectAllocator.LeftAlign; } From 212f93848162395ba72d17b203d0182a21049c06 Mon Sep 17 00:00:00 2001 From: Maarten Bezemer Date: Thu, 15 Feb 2024 15:27:14 +0100 Subject: [PATCH 13/14] Fix error when deleting Summary tab As this is a singleton (with a fixed GUID), it sohuld not be fully deleted. Instead we hide it as if the clsoe button on the tab was clicked. --- YAFC/Widgets/MainScreenTabBar.cs | 2 +- YAFC/Windows/MainScreen.cs | 6 +++++- YAFC/Windows/ProjectPageSettingsPanel.cs | 8 +++++++- YAFCmodel/Model/ProjectPage.cs | 4 +++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/YAFC/Widgets/MainScreenTabBar.cs b/YAFC/Widgets/MainScreenTabBar.cs index 740010ad..2393678e 100644 --- a/YAFC/Widgets/MainScreenTabBar.cs +++ b/YAFC/Widgets/MainScreenTabBar.cs @@ -41,7 +41,7 @@ private void BuildContents(ImGui gui) { changePageTo = prevPage; changePage = isActive ? 1 : 2; } - project.RecordUndo(true).displayPages.RemoveAt(i); + screen.ClosePage(pageGuid); i--; } } diff --git a/YAFC/Windows/MainScreen.cs b/YAFC/Windows/MainScreen.cs index 33b2bef3..9c80c660 100644 --- a/YAFC/Windows/MainScreen.cs +++ b/YAFC/Windows/MainScreen.cs @@ -458,12 +458,16 @@ public void ClosePseudoScreen(PseudoScreen screen) { rootGui.Rebuild(); } + public void ClosePage(Guid page) { + project.RecordUndo(true).displayPages.Remove(page); + } + public void ShowSummaryTab() { ProjectPage summaryPage = project.FindPage(SummaryGuid); if (summaryPage == null) { - summaryPage = new ProjectPage(project, typeof(Summary), SummaryGuid) { + summaryPage = new ProjectPage(project, typeof(Summary), false, SummaryGuid) { name = "Summary", }; project.pages.Add(summaryPage); diff --git a/YAFC/Windows/ProjectPageSettingsPanel.cs b/YAFC/Windows/ProjectPageSettingsPanel.cs index a3e87ddb..dee124fe 100644 --- a/YAFC/Windows/ProjectPageSettingsPanel.cs +++ b/YAFC/Windows/ProjectPageSettingsPanel.cs @@ -68,7 +68,13 @@ public override void Build(ImGui gui) { gui.allocator = RectAllocator.LeftRow; if (editingPage != null && gui.BuildRedButton("Delete page")) { - Project.current.RemovePage(editingPage); + if (editingPage.canDelete) { + Project.current.RemovePage(editingPage); + } + else { + // Only hide if the (singleton) page cannot be deleted + MainScreen.Instance.ClosePage(editingPage.guid); + } Close(); } } diff --git a/YAFCmodel/Model/ProjectPage.cs b/YAFCmodel/Model/ProjectPage.cs index 0d9cb53a..1c0ad896 100644 --- a/YAFCmodel/Model/ProjectPage.cs +++ b/YAFCmodel/Model/ProjectPage.cs @@ -13,16 +13,18 @@ public class ProjectPage : ModelObject { public bool visible { get; internal set; } [SkipSerialization] public string modelError { get; set; } public bool deleted { get; private set; } + public bool canDelete { get; } private uint lastSolvedVersion; private uint currentSolvingVersion; private uint actualVersion; public event Action contentChanged; - public ProjectPage(Project project, Type contentType, Guid guid = default) : base(project) { + public ProjectPage(Project project, Type contentType, bool canDelete = true, Guid guid = default) : base(project) { this.guid = guid == default ? Guid.NewGuid() : guid; actualVersion = project.projectVersion; this.contentType = contentType; + this.canDelete = canDelete; content = Activator.CreateInstance(contentType, this) as ProjectPageContents; } From b89a7320b07a5a3df05ba5725ae0be41a9d659f5 Mon Sep 17 00:00:00 2001 From: Maarten Bezemer Date: Tue, 27 Feb 2024 11:17:23 +0100 Subject: [PATCH 14/14] Make Tab column wider to fit more tab names The DataGrid component does not (properly) support automatic width calculation, so a sensible hard-coded width is applied --- YAFC/Workspace/SummaryView.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/YAFC/Workspace/SummaryView.cs b/YAFC/Workspace/SummaryView.cs index 5e2f66d3..4abb34ee 100644 --- a/YAFC/Workspace/SummaryView.cs +++ b/YAFC/Workspace/SummaryView.cs @@ -18,7 +18,9 @@ public SummaryScrollArea(GuiBuilder builder) : base(DefaultHeight, builder, defa } private class SummaryTabColumn : TextDataColumn { - public SummaryTabColumn() : base("Tab", 6f) { + private const float FirstColumnWidth = 14f; // About 20 'o' wide + + public SummaryTabColumn() : base("Tab", FirstColumnWidth) { } public override void BuildElement(ImGui gui, ProjectPage page) {