diff --git a/Yafc.Model/Data/DataClasses.cs b/Yafc.Model/Data/DataClasses.cs index e634d88e..14069ae8 100644 --- a/Yafc.Model/Data/DataClasses.cs +++ b/Yafc.Model/Data/DataClasses.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Runtime.CompilerServices; using Yafc.I18n; -using Yafc.UI; [assembly: InternalsVisibleTo("Yafc.Parser")] namespace Yafc.Model; @@ -39,7 +38,12 @@ public abstract class FactorioObject : IFactorioObjectWrapper, IComparable + /// Integer icon handle assigned during data loading. + /// This value matches the numeric value of Yafc.UI.Icon + /// and should only be interpreted in the UI layer via GetIcon(). + /// + public int iconId { get; internal set; } public FactorioId id { get; internal set; } internal abstract FactorioObjectSortOrder sortingOrder { get; } public FactorioObjectSpecialType specialType { get; internal set; } diff --git a/Yafc.Model/Data/DataUtils.cs b/Yafc.Model/Data/DataUtils.cs index dc5d9f8d..c0167af9 100644 --- a/Yafc.Model/Data/DataUtils.cs +++ b/Yafc.Model/Data/DataUtils.cs @@ -128,9 +128,6 @@ public int Compare(T? x, T? y) { /// but will appear as only producing U-235 and consuming U-238 when . /// public static bool netProduction { get; internal set; } - public static Icon NoFuelIcon { get; internal set; } - public static Icon WarningIcon { get; internal set; } - public static Icon HandIcon { get; internal set; } public static readonly Random random = new Random(); diff --git a/Yafc.Model/Model/ProductionSummary.cs b/Yafc.Model/Model/ProductionSummary.cs index 97d4cdd6..1ea073ad 100644 --- a/Yafc.Model/Model/ProductionSummary.cs +++ b/Yafc.Model/Model/ProductionSummary.cs @@ -54,19 +54,10 @@ protected internal override void AfterDeserialize() { [SkipSerialization] public Dictionary, float> flow { get; } = []; private bool needRefreshFlow = true; - public Icon icon { - get { - if (subgroup != null) { - return Icon.Folder; - } - - if (page?.page == null) { - return Icon.Warning; - } - - return page.page.icon?.icon ?? Icon.None; - } - } + /// True when this entry represents a folder/subgroup rather than a project page. + public bool IsSubgroup => subgroup != null; + /// True when this entry's referenced page has been removed or could not be found. + public bool IsMissingPage => !IsSubgroup && page?.page == null; public string name { get { diff --git a/Yafc.Parser/Data/FactorioDataDeserializer.cs b/Yafc.Parser/Data/FactorioDataDeserializer.cs index c463d63a..d0ac4dec 100644 --- a/Yafc.Parser/Data/FactorioDataDeserializer.cs +++ b/Yafc.Parser/Data/FactorioDataDeserializer.cs @@ -230,9 +230,10 @@ private void RenderIcons() { cache[(".", digit.ToString())] = SDL_image.IMG_Load("Data/Digits/" + digit + ".png"); } - DataUtils.NoFuelIcon = CreateSimpleIcon(cache, "fuel-icon-red"); - DataUtils.WarningIcon = CreateSimpleIcon(cache, "warning-icon"); - DataUtils.HandIcon = CreateSimpleIcon(cache, "hand"); + SystemIcons.Initialize( + noFuelIcon: CreateSimpleIcon(cache, "fuel-icon-red"), + warningIcon: CreateSimpleIcon(cache, "warning-icon"), + handIcon: CreateSimpleIcon(cache, "hand")); Dictionary simpleSpritesCache = []; int rendered = 0; @@ -246,15 +247,15 @@ private void RenderIcons() { bool simpleSprite = o.iconSpec.Length == 1 && o.iconSpec[0].IsSimple(); if (simpleSprite && simpleSpritesCache.TryGetValue(o.iconSpec[0].path, out var icon)) { - o.icon = icon; + o.iconId = (int)icon; continue; } try { - o.icon = CreateIconFromSpec(cache, o.iconSpec); + o.iconId = (int)CreateIconFromSpec(cache, o.iconSpec); if (simpleSprite) { - simpleSpritesCache[o.iconSpec[0].path] = o.icon; + simpleSpritesCache[o.iconSpec[0].path] = (Icon)o.iconId; } } catch (Exception ex) { @@ -262,7 +263,7 @@ private void RenderIcons() { } } else if (o is Recipe recipe && recipe.mainProduct != null) { - o.icon = recipe.mainProduct.icon; + o.iconId = recipe.mainProduct.iconId; } } } diff --git a/Yafc.UI/Core/SystemIcons.cs b/Yafc.UI/Core/SystemIcons.cs new file mode 100644 index 00000000..9dca5dd5 --- /dev/null +++ b/Yafc.UI/Core/SystemIcons.cs @@ -0,0 +1,33 @@ +namespace Yafc.UI; + +/// +/// Holds the set of game-sourced system icons (e.g. fuel, warning, hand) that are +/// rendered from Factorio's own sprite files during the data loading phase. +/// These are distinct from the built-in YAFC vector icons defined in . +/// +public static class SystemIcons { + /// + /// The icon used to indicate a missing or invalid fuel source. + /// Set during data loading; no UI consumer yet (pre-existing). + /// + public static Icon NoFuelIcon { get; private set; } + + /// + /// The icon used to display a general warning. + /// Set during data loading; no UI consumer yet (pre-existing). + /// + public static Icon WarningIcon { get; private set; } + + /// The hand/grab icon used to mark certain special goods (e.g. void energy). + public static Icon HandIcon { get; private set; } + + /// + /// Initializes the set of game-sourced system icons after they are rendered from Factorio assets. + /// + public static void Initialize(Icon noFuelIcon, Icon warningIcon, Icon handIcon) { + NoFuelIcon = noFuelIcon; + WarningIcon = warningIcon; + HandIcon = handIcon; + } +} + diff --git a/Yafc/Widgets/FactorioObjectIconExtensions.cs b/Yafc/Widgets/FactorioObjectIconExtensions.cs new file mode 100644 index 00000000..6d597bfc --- /dev/null +++ b/Yafc/Widgets/FactorioObjectIconExtensions.cs @@ -0,0 +1,19 @@ +using Yafc.Model; +using Yafc.UI; + +namespace Yafc; + +/// +/// Extension methods that bridge domain objects () to their +/// UI representation (), keeping the domain layer free of UI concerns. +/// This bridge currently lives in the app layer because Yafc.UI does not reference +/// Yafc.Model; move it once dependency boundaries are reworked. +/// +public static class FactorioObjectIconExtensions { + /// + /// Returns the value associated with this . + /// The domain object stores this as an integer handle (); + /// this method converts it to the enum for rendering. + /// + public static Icon GetIcon(this FactorioObject obj) => (Icon)obj.iconId; +} diff --git a/Yafc/Widgets/ImmediateWidgets.cs b/Yafc/Widgets/ImmediateWidgets.cs index b0f6f8c7..7240eb19 100644 --- a/Yafc/Widgets/ImmediateWidgets.cs +++ b/Yafc/Widgets/ImmediateWidgets.cs @@ -72,10 +72,10 @@ public static void BuildFactorioObjectIcon(this ImGui gui, IFactorioObjectWrappe SchemeColor color = (obj.target.IsAccessible() || displayStyle.AlwaysAccessible) ? SchemeColor.Source : SchemeColor.SourceFaint; if (displayStyle.UseScaleSetting) { Rect rect = gui.AllocateRect(displayStyle.Size, displayStyle.Size, RectAlignment.Middle); - gui.DrawIcon(rect.Expand(displayStyle.Size * (Project.current.preferences.iconScale - 1) / 2), obj.target.icon, color); + gui.DrawIcon(rect.Expand(displayStyle.Size * (Project.current.preferences.iconScale - 1) / 2), obj.target.GetIcon(), color); } else { - gui.BuildIcon(obj.target.icon, displayStyle.Size, color); + gui.BuildIcon(obj.target.GetIcon(), displayStyle.Size, color); } if (gui.isBuilding && displayStyle.MilestoneDisplay != MilestoneDisplay.None && (obj.target.IsAccessible() || Project.current.preferences.showMilestoneOnInaccessible)) { @@ -86,7 +86,7 @@ public static void BuildFactorioObjectIcon(this ImGui gui, IFactorioObjectWrappe Vector2 size = new Vector2(displayStyle.Size / 2f); var delta = contain ? size : size / 2f; Rect milestoneIcon = new Rect(gui.lastRect.BottomRight - delta, size); - var icon = milestone == Database.voidEnergy.target ? DataUtils.HandIcon : milestone.icon; + var icon = milestone == Database.voidEnergy.target ? SystemIcons.HandIcon : milestone.GetIcon(); gui.DrawIcon(milestoneIcon, icon, color); } } @@ -97,7 +97,7 @@ public static void BuildFactorioObjectIcon(this ImGui gui, IFactorioObjectWrappe Vector2 delta = new(0, size.Y); Rect qualityRect = new Rect(gui.lastRect.BottomLeft - delta, size); - gui.DrawIcon(qualityRect, quality.icon, SchemeColor.Source); + gui.DrawIcon(qualityRect, quality.GetIcon(), SchemeColor.Source); } if (gui.isBuilding && obj.target is Item { baseSpoilTime: > 0 } or Entity { baseSpoilTime: > 0 }) { Vector2 size = new Vector2(displayStyle.Size / 2.5f); diff --git a/Yafc/Widgets/MainScreenTabBar.cs b/Yafc/Widgets/MainScreenTabBar.cs index 380d7421..3ac7bbbf 100644 --- a/Yafc/Widgets/MainScreenTabBar.cs +++ b/Yafc/Widgets/MainScreenTabBar.cs @@ -41,7 +41,7 @@ private void BuildContents(ImGui gui) { 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); + gui.BuildIcon(page.icon.GetIcon()); } else { _ = gui.AllocateRect(0f, 1.5f); diff --git a/Yafc/Widgets/ObjectTooltip.cs b/Yafc/Widgets/ObjectTooltip.cs index dd9f2c1a..188a2bb9 100644 --- a/Yafc/Widgets/ObjectTooltip.cs +++ b/Yafc/Widgets/ObjectTooltip.cs @@ -72,7 +72,7 @@ private void BuildHeader(ImGui gui) { goto doneDrawing; } if (milestoneMask[maskBit]) { - gui.BuildIcon(milestones.Current.icon, 1f, SchemeColor.Source); + gui.BuildIcon(milestones.Current.GetIcon(), 1f, SchemeColor.Source); j++; } diff --git a/Yafc/Windows/MainScreen.cs b/Yafc/Windows/MainScreen.cs index e40cde84..0fafd33d 100644 --- a/Yafc/Windows/MainScreen.cs +++ b/Yafc/Windows/MainScreen.cs @@ -119,7 +119,7 @@ private void ReRunAnalysis() { private void BuildPage(ImGui gui, ProjectPage element, int index) { using (gui.EnterGroup(new Padding(1f, 0.25f), RectAllocator.LeftRow)) { if (element.icon != null) { - gui.BuildIcon(element.icon.icon); + gui.BuildIcon(element.icon.GetIcon()); } gui.RemainingRow().BuildText(element.name, TextBlockDisplayStyle.Default(element.visible ? SchemeColor.BackgroundText : SchemeColor.BackgroundTextFaint)); @@ -301,7 +301,7 @@ private void BuildPage(ImGui gui) { else { if (gui.isBuilding && Database.objectsByTypeName.TryGetValue("Entity.compilatron", out var compilatron)) { gui.AllocateSpacing((pageVisibleSize.Y - 3f) / 2); - gui.BuildIcon(compilatron.icon, 3f); + gui.BuildIcon(compilatron.GetIcon(), 3f); } } } diff --git a/Yafc/Workspace/ProductionSummary/ProductionSummaryView.cs b/Yafc/Workspace/ProductionSummary/ProductionSummaryView.cs index bc50311d..26e20090 100644 --- a/Yafc/Workspace/ProductionSummary/ProductionSummaryView.cs +++ b/Yafc/Workspace/ProductionSummary/ProductionSummaryView.cs @@ -30,9 +30,10 @@ public override void BuildHeader(ImGui gui) { } public override void BuildElement(ImGui gui, ProductionSummaryEntry row) { gui.allocator = RectAllocator.Center; gui.spacing = 0f; - if (row.subgroup != null) { - if (gui.BuildButton(row.subgroup.expanded ? Icon.ChevronDown : Icon.ChevronRight)) { - row.subgroup.RecordChange().expanded = !row.subgroup.expanded; + if (row.IsSubgroup) { + var subgroup = row.subgroup!; // IsSubgroup guarantees subgroup != null + if (gui.BuildButton(subgroup.expanded ? Icon.ChevronDown : Icon.ChevronRight)) { + subgroup.RecordChange().expanded = !subgroup.expanded; view.flatHierarchy.SetData(view.model.group); } } @@ -75,21 +76,22 @@ private GuiBuilder AddProductionTableDropdown(SearchableList pagesD public override void BuildElement(ImGui gui, ProductionSummaryEntry entry) { gui.allocator = RectAllocator.LeftAlign; - if (entry.subgroup != null) { - if (entry.subgroup.expanded) { - BuildButtons(gui, 1.5f, entry.subgroup); + if (entry.IsSubgroup) { + var subgroup = entry.subgroup!; // IsSubgroup guarantees subgroup != null + if (subgroup.expanded) { + BuildButtons(gui, 1.5f, subgroup); } else { - if (gui.BuildTextInput(entry.subgroup.name, out string newText, LSs.LegacySummaryGroupNameHint, delayed: true)) { - entry.subgroup.RecordUndo().name = newText; + if (gui.BuildTextInput(subgroup.name, out string newText, LSs.LegacySummaryGroupNameHint, delayed: true)) { + subgroup.RecordUndo().name = newText; } } } else if (entry.page != null) { // The constructor should have thrown if this check fails, but it helps the nullability analysis using (gui.EnterGroup(new Padding(0.3f), RectAllocator.LeftRow, SchemeColor.None, 0.2f)) { - var icon = entry.icon; + Icon icon = entry.IsMissingPage ? Icon.Warning : entry.page.page!.icon?.GetIcon() ?? Icon.None; if (icon != Icon.None) { - gui.BuildIcon(entry.icon); + gui.BuildIcon(icon); } gui.BuildText(entry.name); @@ -129,7 +131,7 @@ public override void BuildElement(ImGui gui, ProductionSummaryEntry entry) { private static VirtualScrollList.Drawer PagesDropdownDrawer(ProductionSummaryGroup group) => (gui, element, _) => { using (gui.EnterGroup(new Padding(1f, 0.25f), RectAllocator.LeftRow)) { if (element.icon != null) { - gui.BuildIcon(element.icon.icon); + gui.BuildIcon(element.icon.GetIcon()); } gui.RemainingRow().BuildText(element.name, TextBlockDisplayStyle.Default(element.visible ? SchemeColor.BackgroundText : SchemeColor.BackgroundTextFaint)); @@ -189,7 +191,7 @@ public override void BuildElement(ImGui gui, ProductionSummaryEntry data) { if (!view.model.columnsExist.Contains(goods)) { grid.Next(); - var evt = gui.BuildButton(goods.target.icon, amount > 0f ? SchemeColor.Green : SchemeColor.None, size: 1.5f); + var evt = gui.BuildButton(goods.target.GetIcon(), amount > 0f ? SchemeColor.Green : SchemeColor.None, size: 1.5f); if (evt == ButtonEvent.Click) { view.AddOrRemoveColumn(goods); } diff --git a/Yafc/Workspace/ProductionTable/ModuleFillerParametersScreen.cs b/Yafc/Workspace/ProductionTable/ModuleFillerParametersScreen.cs index 11c25f6a..4b36611a 100644 --- a/Yafc/Workspace/ProductionTable/ModuleFillerParametersScreen.cs +++ b/Yafc/Workspace/ProductionTable/ModuleFillerParametersScreen.cs @@ -41,8 +41,8 @@ private void ListDrawer(ImGui gui, KeyValuePair moduleTemplateList = new VirtualScrollList(15f, new Vector2(20f, 2.5f), ModuleTemplateDrawer, collapsible: true); private void ModuleTemplateDrawer(ImGui gui, ProjectModuleTemplate element, int index) { - var evt = gui.BuildContextMenuButton(element.name, icon: element.icon?.icon ?? default, disabled: !element.template.IsCompatibleWith(editingRecipeModules)); + var evt = gui.BuildContextMenuButton(element.name, icon: element.icon?.GetIcon() ?? default, disabled: !element.template.IsCompatibleWith(editingRecipeModules)); if (evt == ButtonEvent.Click && gui.CloseDropdown()) { var copied = JsonUtils.Copy(element.template, editingRecipeModules, null); diff --git a/Yafc/Workspace/SummaryView.cs b/Yafc/Workspace/SummaryView.cs index c204797c..68d865dd 100644 --- a/Yafc/Workspace/SummaryView.cs +++ b/Yafc/Workspace/SummaryView.cs @@ -41,7 +41,7 @@ public override void BuildElement(ImGui gui, ProjectPage page) { gui.spacing = 0.2f; if (page.icon != null) { - gui.BuildIcon(page.icon.icon, FirstColumnIconSize); + gui.BuildIcon(page.icon.GetIcon(), FirstColumnIconSize); } else { _ = gui.AllocateRect(0f, FirstColumnIconSize);