From a057218acceed6fe5aec329ef4d2ae736b3d3878 Mon Sep 17 00:00:00 2001 From: Dale McCoy <21223975+DaleStan@users.noreply.github.com> Date: Sat, 8 Jun 2024 14:35:26 -0400 Subject: [PATCH 1/3] Replace the extendHeader parameter with an ObjectTooltipOptions parameter. This allows addition of new options without changing every method that might eventually display a tooltip. --- Yafc/Widgets/ImmediateWidgets.cs | 10 +++++----- Yafc/Widgets/ObjectTooltip.cs | 19 ++++++++++++------- Yafc/Windows/DependencyExplorer.cs | 2 +- Yafc/Windows/MainScreen.cs | 4 ++-- Yafc/Windows/SelectMultiObjectPanel.cs | 4 ++-- Yafc/Windows/SelectObjectPanel.cs | 6 +++--- Yafc/Windows/SelectSingleObjectPanel.cs | 4 ++-- 7 files changed, 27 insertions(+), 22 deletions(-) diff --git a/Yafc/Widgets/ImmediateWidgets.cs b/Yafc/Widgets/ImmediateWidgets.cs index 7befb7d3..802a8c6a 100644 --- a/Yafc/Widgets/ImmediateWidgets.cs +++ b/Yafc/Widgets/ImmediateWidgets.cs @@ -69,7 +69,7 @@ public static bool BuildFloatInput(this ImGui gui, float value, out float newVal return false; } - public static Click BuildFactorioObjectButton(this ImGui gui, Rect rect, FactorioObject? obj, SchemeColor bgColor = SchemeColor.None, bool extendHeader = false) { + public static Click BuildFactorioObjectButton(this ImGui gui, Rect rect, FactorioObject? obj, SchemeColor bgColor = SchemeColor.None, ObjectTooltipOptions tooltipOptions = default) { SchemeColor overColor; if (bgColor == SchemeColor.None) { overColor = SchemeColor.Grey; @@ -82,7 +82,7 @@ public static Click BuildFactorioObjectButton(this ImGui gui, Rect rect, Factori } var evt = gui.BuildButton(rect, bgColor, overColor, button: 0); if (evt == ButtonEvent.MouseOver && obj != null) { - MainScreen.Instance.ShowTooltip(obj, gui, rect, extendHeader); + MainScreen.Instance.ShowTooltip(obj, gui, rect, tooltipOptions); } else if (evt == ButtonEvent.Click) { if (gui.actionParameter == SDL.SDL_BUTTON_MIDDLE && obj != null) { @@ -109,9 +109,9 @@ public static Click BuildFactorioObjectButton(this ImGui gui, Rect rect, Factori /// Draws a button displaying the icon belonging to a , or an empty box as a placeholder if no object is available. /// Draw the icon for this object, or an empty box if this is . /// If , this icon will be displayed at , instead of at 100% scale. - public static Click BuildFactorioObjectButton(this ImGui gui, FactorioObject? obj, float size = 2f, MilestoneDisplay display = MilestoneDisplay.Normal, SchemeColor bgColor = SchemeColor.None, bool extendHeader = false, bool useScale = false) { + public static Click BuildFactorioObjectButton(this ImGui gui, FactorioObject? obj, float size = 2f, MilestoneDisplay display = MilestoneDisplay.Normal, SchemeColor bgColor = SchemeColor.None, bool useScale = false, ObjectTooltipOptions tooltipOptions = default) { gui.BuildFactorioObjectIcon(obj, display, size, useScale); - return gui.BuildFactorioObjectButton(gui.lastRect, obj, bgColor, extendHeader); + return gui.BuildFactorioObjectButton(gui.lastRect, obj, bgColor, tooltipOptions); } public static Click BuildFactorioObjectButtonWithText(this ImGui gui, FactorioObject? obj, string? extraText = null, float size = 2f, MilestoneDisplay display = MilestoneDisplay.Normal) { @@ -209,7 +209,7 @@ public static Click BuildFactorioObjectWithAmount(this ImGui gui, FactorioObject using (gui.EnterFixedPositioning(3f, 3f, default)) { gui.allocator = RectAllocator.Stretch; gui.spacing = 0f; - Click clicked = gui.BuildFactorioObjectButton(goods, 3f, MilestoneDisplay.Contained, bgColor, useScale: useScale); + Click clicked = gui.BuildFactorioObjectButton(goods, 3f, MilestoneDisplay.Contained, bgColor, useScale); if (goods != null) { gui.BuildText(DataUtils.FormatAmount(amount, unit), Font.text, false, RectAlignment.Middle, textColor); if (InputSystem.Instance.control && gui.BuildButton(gui.lastRect, SchemeColor.None, SchemeColor.Grey) == ButtonEvent.MouseOver) { diff --git a/Yafc/Widgets/ObjectTooltip.cs b/Yafc/Widgets/ObjectTooltip.cs index 7a037f5a..f9651be5 100644 --- a/Yafc/Widgets/ObjectTooltip.cs +++ b/Yafc/Widgets/ObjectTooltip.cs @@ -10,15 +10,12 @@ public class ObjectTooltip : Tooltip { public ObjectTooltip() : base(new Padding(0f, 0f, 0f, 0.5f), 25f) { } private IFactorioObjectWrapper target = null!; // null-forgiving: Set by SetFocus, aka ShowTooltip. - /// - /// If and the target object is not a , this tooltip will specify the type of object. - /// - private bool extendHeader; + private ObjectTooltipOptions tooltipOptions; private void BuildHeader(ImGui gui) { using (gui.EnterGroup(new Padding(1f, 0.5f), RectAllocator.LeftAlign, spacing: 0f)) { string name = target.text; - if (extendHeader && target is not Goods) { + if (tooltipOptions.ExtendHeader && target is not Goods) { name = name + " (" + target.target.type + ")"; } @@ -500,12 +497,20 @@ private void BuildTechnology(Technology technology, ImGui gui) { } } - public void SetFocus(IFactorioObjectWrapper target, ImGui gui, Rect rect, bool extendHeader = false) { - this.extendHeader = extendHeader; + public void SetFocus(IFactorioObjectWrapper target, ImGui gui, Rect rect, ObjectTooltipOptions tooltipOptions) { + this.tooltipOptions = tooltipOptions; this.target = target; base.SetFocus(gui, rect); } public bool IsSameObjectHovered(ImGui gui, FactorioObject? factorioObject) => source == gui && factorioObject == target.target && gui.IsMouseOver(sourceRect); } + + public struct ObjectTooltipOptions { + /// + /// If and the target object is not a , this tooltip will specify the type of object. + /// e.g. "Radar" is the item, "Radar (Recipe)" is the recipe, and "Radar (Entity)" is the building. + /// + public bool ExtendHeader { get; set; } + } } diff --git a/Yafc/Windows/DependencyExplorer.cs b/Yafc/Windows/DependencyExplorer.cs index 94a4bfd2..2c86de92 100644 --- a/Yafc/Windows/DependencyExplorer.cs +++ b/Yafc/Windows/DependencyExplorer.cs @@ -45,7 +45,7 @@ private void DrawFactorioObject(ImGui gui, FactorioId id) { string text = fobj.locName + " (" + fobj.type + ")"; gui.RemainingRow(0.5f).BuildText(text, null, true, color: fobj.IsAccessible() ? SchemeColor.BackgroundText : SchemeColor.BackgroundTextFaint); } - if (gui.BuildFactorioObjectButton(gui.lastRect, fobj, extendHeader: true) == Click.Left) { + if (gui.BuildFactorioObjectButton(gui.lastRect, fobj, tooltipOptions: new() { ExtendHeader = true }) == Click.Left) { Change(fobj); } } diff --git a/Yafc/Windows/MainScreen.cs b/Yafc/Windows/MainScreen.cs index b9aa39dc..761c78e5 100644 --- a/Yafc/Windows/MainScreen.cs +++ b/Yafc/Windows/MainScreen.cs @@ -523,8 +523,8 @@ private async void DoCheckForUpdates() { } } - public void ShowTooltip(IFactorioObjectWrapper obj, ImGui source, Rect sourceRect, bool extendHeader = false) { - objectTooltip.SetFocus(obj, source, sourceRect, extendHeader); + public void ShowTooltip(IFactorioObjectWrapper obj, ImGui source, Rect sourceRect, ObjectTooltipOptions tooltipOptions = default) { + objectTooltip.SetFocus(obj, source, sourceRect, tooltipOptions); ShowTooltip(objectTooltip); } diff --git a/Yafc/Windows/SelectMultiObjectPanel.cs b/Yafc/Windows/SelectMultiObjectPanel.cs index 64a43ecd..dd5d1d6a 100644 --- a/Yafc/Windows/SelectMultiObjectPanel.cs +++ b/Yafc/Windows/SelectMultiObjectPanel.cs @@ -28,8 +28,8 @@ public static void Select(IEnumerable list, string header, Action selec }, false); } - protected override void NonNullElementDrawer(ImGui gui, FactorioObject element, int index) { - Click click = gui.BuildFactorioObjectButton(element, 2.5f, MilestoneDisplay.Contained, results.Contains(element) ? SchemeColor.Primary : SchemeColor.None, extendHeader, true); + protected override void NonNullElementDrawer(ImGui gui, FactorioObject element) { + Click click = gui.BuildFactorioObjectButton(element, 2.5f, MilestoneDisplay.Contained, results.Contains(element) ? SchemeColor.Primary : SchemeColor.None, true, new() { ExtendHeader = extendHeader }); if (checkMark(element)) { gui.DrawIcon(Rect.SideRect(gui.lastRect.TopLeft + new Vector2(1, 0), gui.lastRect.BottomRight - new Vector2(0, 1)), Icon.Check, SchemeColor.Green); diff --git a/Yafc/Windows/SelectObjectPanel.cs b/Yafc/Windows/SelectObjectPanel.cs index 5278b848..a0a02a46 100644 --- a/Yafc/Windows/SelectObjectPanel.cs +++ b/Yafc/Windows/SelectObjectPanel.cs @@ -17,7 +17,7 @@ public abstract class SelectObjectPanel : PseudoScreen { private string? noneTooltip; /// /// If and the object being hovered is not a , the should specify the type of object. - /// See also . + /// See also . /// protected bool extendHeader { get; private set; } @@ -80,7 +80,7 @@ private void ElementDrawer(ImGui gui, FactorioObject? element, int index) { } } else { - NonNullElementDrawer(gui, element, index); + NonNullElementDrawer(gui, element); } } @@ -88,7 +88,7 @@ private void ElementDrawer(ImGui gui, FactorioObject? element, int index) { /// Called to draw a that should be displayed in this panel, and to handle mouse-over and click events. /// will not be null. If a "none" or "clear" option is present, takes care of that option. /// - protected abstract void NonNullElementDrawer(ImGui gui, FactorioObject element, int index); + protected abstract void NonNullElementDrawer(ImGui gui, FactorioObject element); private bool ElementFilter(FactorioObject? data, SearchQuery query) => data?.Match(query) ?? true; diff --git a/Yafc/Windows/SelectSingleObjectPanel.cs b/Yafc/Windows/SelectSingleObjectPanel.cs index fec00ee3..2fb17b30 100644 --- a/Yafc/Windows/SelectSingleObjectPanel.cs +++ b/Yafc/Windows/SelectSingleObjectPanel.cs @@ -33,8 +33,8 @@ public static void Select(IEnumerable list, string header, Action selec public static void SelectWithNone(IEnumerable list, string header, Action selectItem, IComparer? ordering = null, string? noneTooltip = null) where T : FactorioObject => Instance.Select(list, header, selectItem, ordering, (obj, mappedAction) => mappedAction(obj), true, noneTooltip); - protected override void NonNullElementDrawer(ImGui gui, FactorioObject element, int index) { - if (gui.BuildFactorioObjectButton(element, 2.5f, MilestoneDisplay.Contained, extendHeader: extendHeader, useScale: true) == Click.Left) { + protected override void NonNullElementDrawer(ImGui gui, FactorioObject element) { + if (gui.BuildFactorioObjectButton(element, 2.5f, MilestoneDisplay.Contained, useScale: true, tooltipOptions: new() { ExtendHeader = extendHeader }) == Click.Left) { CloseWithResult(element); } } From f008fbfd196160aa8a60084c0c82c5e04b8474e9 Mon Sep 17 00:00:00 2001 From: Dale McCoy <21223975+DaleStan@users.noreply.github.com> Date: Fri, 17 May 2024 20:13:15 -0400 Subject: [PATCH 2/3] Add hints that ctrl+click can be used to add a recipe, or to explain why it can't. --- Yafc.Model/Data/DataUtils.cs | 34 ++++++++++++--- Yafc/Widgets/ImmediateWidgets.cs | 8 ++-- Yafc/Widgets/ObjectTooltip.cs | 36 ++++++++++++++++ .../ProductionTable/ProductionTableView.cs | 41 ++++++++++--------- changelog.txt | 2 + 5 files changed, 92 insertions(+), 29 deletions(-) diff --git a/Yafc.Model/Data/DataUtils.cs b/Yafc.Model/Data/DataUtils.cs index 1e574b7e..8ff39883 100644 --- a/Yafc.Model/Data/DataUtils.cs +++ b/Yafc.Model/Data/DataUtils.cs @@ -96,10 +96,31 @@ public static Bits GetMilestoneOrder(FactorioId id) { public static readonly Random random = new Random(); - public static bool SelectSingle(this T[] list, [NotNullWhen(true)] out T? element) where T : FactorioObject { + /// + /// Call to get the favorite or only useful item in the list, considering milestones, accessibility, and , provided there is exactly one such item. + /// If no best item exists, returns . Always returns a tooltip applicable to using ctrl+click to add a recipe. + /// + /// The element type of . This type must be derived from . + /// The array of items to search. + /// Upon return, contains a hint that is applicable to using ctrl+click to add a recipe. + /// This will either suggest using ctrl+click, or explain why ctrl+click cannot be used. + /// It is not useful when is not . + /// Items that are not accessible at the current milestones are always ignored. After those have been discarded, the return value is the first applicable entry in the following list: + /// + /// The only normal item in . + /// The only normal user favorite in . + /// If no previous options are applicable, . + /// + public static T? SelectSingle(this T[] list, out string recipeHint) where T : FactorioObject { var userFavorites = Project.current.preferences.favorites; bool acceptOnlyFavorites = false; - element = null; + T? element = null; + if (list.Any(t => t.IsAccessible())) { + recipeHint = "Hint: Complete milestones to enable ctrl+click"; + } + else { + recipeHint = "Hint: Mark a recipe as accessible to enable ctrl+click"; + } foreach (var elem in list) { if (!elem.IsAccessibleWithCurrentMilestones() || elem.specialType != FactorioObjectSpecialType.Normal) { continue; @@ -108,25 +129,28 @@ public static bool SelectSingle(this T[] list, [NotNullWhen(true)] out T? ele if (userFavorites.Contains(elem)) { if (!acceptOnlyFavorites || element == null) { element = elem; + recipeHint = "Hint: ctrl+click to add your favorited recipe"; acceptOnlyFavorites = true; } else { - element = null; - return false; + recipeHint = "Hint: Cannot ctrl+click with multiple favorited recipes"; + return null; } } else if (!acceptOnlyFavorites) { if (element == null) { element = elem; + recipeHint = "Hint: ctrl+click to add the accessible recipe"; } else { element = null; + recipeHint = "Hint: Set a favorite recipe to add it with ctrl+click"; acceptOnlyFavorites = true; } } } - return element != null; + return element; } public static void SetupForProject(Project project) { diff --git a/Yafc/Widgets/ImmediateWidgets.cs b/Yafc/Widgets/ImmediateWidgets.cs index 802a8c6a..380acdd5 100644 --- a/Yafc/Widgets/ImmediateWidgets.cs +++ b/Yafc/Widgets/ImmediateWidgets.cs @@ -205,11 +205,11 @@ public static void BuildInlineObjectListAndButtonWithNone(this ImGui gui, ICo /// Display this value, formatted appropriately for . /// Use this unit of measure when formatting for display. /// If , this icon will be displayed at , instead of at 100% scale. - public static Click BuildFactorioObjectWithAmount(this ImGui gui, FactorioObject? goods, float amount, UnitOfMeasure unit, SchemeColor bgColor = SchemeColor.None, SchemeColor textColor = SchemeColor.None, bool useScale = true) { + public static Click BuildFactorioObjectWithAmount(this ImGui gui, FactorioObject? goods, float amount, UnitOfMeasure unit, SchemeColor bgColor = SchemeColor.None, SchemeColor textColor = SchemeColor.None, bool useScale = true, ObjectTooltipOptions tooltipOptions = default) { using (gui.EnterFixedPositioning(3f, 3f, default)) { gui.allocator = RectAllocator.Stretch; gui.spacing = 0f; - Click clicked = gui.BuildFactorioObjectButton(goods, 3f, MilestoneDisplay.Contained, bgColor, useScale); + Click clicked = gui.BuildFactorioObjectButton(goods, 3f, MilestoneDisplay.Contained, bgColor, useScale, tooltipOptions); if (goods != null) { gui.BuildText(DataUtils.FormatAmount(amount, unit), Font.text, false, RectAlignment.Middle, textColor); if (InputSystem.Instance.control && gui.BuildButton(gui.lastRect, SchemeColor.None, SchemeColor.Grey) == ButtonEvent.MouseOver) { @@ -266,11 +266,11 @@ public static void BuildObjectSelectDropDownWithNone(this ImGui gui, ICollect /// The new value entered by the user, if this returns . Otherwise, the original . /// If , the default, the user can adjust the value by using the scroll wheel while hovering over the editable text. /// If , the scroll wheel will be ignored when hovering. - public static GoodsWithAmountEvent BuildFactorioObjectWithEditableAmount(this ImGui gui, FactorioObject? obj, float amount, UnitOfMeasure unit, out float newAmount, SchemeColor color = SchemeColor.None, bool useScale = true, bool allowScroll = true) { + public static GoodsWithAmountEvent BuildFactorioObjectWithEditableAmount(this ImGui gui, FactorioObject? obj, float amount, UnitOfMeasure unit, out float newAmount, SchemeColor color = SchemeColor.None, bool useScale = true, bool allowScroll = true, ObjectTooltipOptions tooltipOptions = default) { using var group = gui.EnterGroup(default, RectAllocator.Stretch, spacing: 0f); group.SetWidth(3f); newAmount = amount; - GoodsWithAmountEvent evt = (GoodsWithAmountEvent)gui.BuildFactorioObjectButton(obj, 3f, MilestoneDisplay.Contained, color); + GoodsWithAmountEvent evt = (GoodsWithAmountEvent)gui.BuildFactorioObjectButton(obj, 3f, MilestoneDisplay.Contained, color, useScale, tooltipOptions); if (gui.BuildTextInput(DataUtils.FormatAmount(amount, unit), out string newText, null, Icon.None, true, default, RectAlignment.Middle, SchemeColor.Secondary)) { if (DataUtils.TryParseAmount(newText, out newAmount, unit)) { diff --git a/Yafc/Widgets/ObjectTooltip.cs b/Yafc/Widgets/ObjectTooltip.cs index f9651be5..8c00f740 100644 --- a/Yafc/Widgets/ObjectTooltip.cs +++ b/Yafc/Widgets/ObjectTooltip.cs @@ -4,6 +4,27 @@ using Yafc.UI; namespace Yafc { + /// + /// The location(s) where should display hints + /// (currently only "ctrl+click to add recipe" hints) + /// + [Flags] + public enum HintLocations { + /// + /// Do not display any hints. + /// + None = 0, + /// + /// Display the ctrl+click recipe-selection hint associated with recipes that produce this . + /// + OnProducingRecipes = 1, + /// + /// Display the ctrl+click recipe-selection hint associated with recipes that consume this . + /// + OnConsumingRecipes = 2, + // NOTE: This is [Flags]. The next item, if applicable, should be 4. + } + public class ObjectTooltip : Tooltip { public static readonly Padding contentPadding = new Padding(1f, 0.25f); @@ -280,6 +301,10 @@ private void BuildGoods(Goods goods, ImGui gui) { BuildSubHeader(gui, "Made with"); using (gui.EnterGroup(contentPadding)) { BuildIconRow(gui, goods.production, 2); + if (tooltipOptions.HintLocations.HasFlag(HintLocations.OnProducingRecipes)) { + goods.production.SelectSingle(out string recipeTip); + gui.BuildText(recipeTip, color: SchemeColor.BackgroundTextFaint); + } } } @@ -294,6 +319,10 @@ private void BuildGoods(Goods goods, ImGui gui) { BuildSubHeader(gui, "Needed for"); using (gui.EnterGroup(contentPadding)) { BuildIconRow(gui, goods.usages, 4); + if (tooltipOptions.HintLocations.HasFlag(HintLocations.OnConsumingRecipes)) { + goods.usages.SelectSingle(out string recipeTip); + gui.BuildText(recipeTip, color: SchemeColor.BackgroundTextFaint); + } } } @@ -512,5 +541,12 @@ public struct ObjectTooltipOptions { /// e.g. "Radar" is the item, "Radar (Recipe)" is the recipe, and "Radar (Entity)" is the building. /// public bool ExtendHeader { get; set; } + /// + /// Gets or sets flags indicating where hints should be displayed in the tooltip. + /// + public HintLocations HintLocations { get; set; } + + // Reduce boilerplate by permitting unambiguous and relatively obvious implicit conversions. + public static implicit operator ObjectTooltipOptions(HintLocations hintLocations) => new() { HintLocations = hintLocations }; } } diff --git a/Yafc/Workspace/ProductionTable/ProductionTableView.cs b/Yafc/Workspace/ProductionTable/ProductionTableView.cs index f8a6862d..22b6b377 100644 --- a/Yafc/Workspace/ProductionTable/ProductionTableView.cs +++ b/Yafc/Workspace/ProductionTable/ProductionTableView.cs @@ -326,7 +326,7 @@ public override void BuildElement(ImGui gui, RecipeRow recipe) { gui.AllocateSpacing(0.5f); if (recipe.fuel != Database.voidEnergy || recipe.entity == null || recipe.entity.energy.type != EntityEnergyType.Void) { - view.BuildGoodsIcon(gui, recipe.fuel, recipe.links.fuel, (float)(recipe.parameters.fuelUsagePerSecondPerRecipe * recipe.recipesPerSecond), ProductDropdownType.Fuel, recipe, recipe.linkRoot); + view.BuildGoodsIcon(gui, recipe.fuel, recipe.links.fuel, (float)(recipe.parameters.fuelUsagePerSecondPerRecipe * recipe.recipesPerSecond), ProductDropdownType.Fuel, recipe, recipe.linkRoot, HintLocations.OnProducingRecipes); } else { if (recipe.recipe == Database.electricityGeneration && recipe.entity.factorioType == "solar-panel") { @@ -475,7 +475,7 @@ public override void BuildElement(ImGui gui, RecipeRow recipe) { var link = recipe.hierarchyEnabled ? recipe.links.ingredients[i] : null; var goods = recipe.hierarchyEnabled ? recipe.links.ingredientGoods[i] : null; grid.Next(); - view.BuildGoodsIcon(gui, goods, link, (float)(ingredient.amount * recipe.recipesPerSecond), ProductDropdownType.Ingredient, recipe, recipe.linkRoot, ingredient.variants); + view.BuildGoodsIcon(gui, goods, link, (float)(ingredient.amount * recipe.recipesPerSecond), ProductDropdownType.Ingredient, recipe, recipe.linkRoot, HintLocations.OnProducingRecipes, ingredient.variants); } } grid.Dispose(); @@ -502,8 +502,7 @@ public override void BuildElement(ImGui gui, RecipeRow recipe) { amount += (float)(recipe.parameters.fuelUsagePerSecondPerRecipe * recipe.recipesPerSecond); handledSpentFuel = true; } - view.BuildGoodsIcon(gui, goods, link, amount, ProductDropdownType.Product, - recipe, recipe.linkRoot); + view.BuildGoodsIcon(gui, goods, link, amount, ProductDropdownType.Product, recipe, recipe.linkRoot, HintLocations.OnConsumingRecipes); } if (!handledSpentFuel && spentFuel != null) { _ = recipe.FindLink(spentFuel, out ProductionLink? link); @@ -512,7 +511,7 @@ public override void BuildElement(ImGui gui, RecipeRow recipe) { link = null; } grid.Next(); - view.BuildGoodsIcon(gui, spentFuel, link, (float)(recipe.parameters.fuelUsagePerSecondPerRecipe * recipe.recipesPerSecond), ProductDropdownType.Product, recipe, recipe.linkRoot); + view.BuildGoodsIcon(gui, spentFuel, link, (float)(recipe.parameters.fuelUsagePerSecondPerRecipe * recipe.recipesPerSecond), ProductDropdownType.Product, recipe, recipe.linkRoot, HintLocations.OnConsumingRecipes); } } grid.Dispose(); @@ -739,7 +738,7 @@ async void addRecipe(RecipeOrTechnology rec) { if (InputSystem.Instance.control) { bool isInput = type == ProductDropdownType.Fuel || type == ProductDropdownType.Ingredient || (type == ProductDropdownType.DesiredProduct && amount > 0); var recipeList = isInput ? goods.production : goods.usages; - if (recipeList.SelectSingle(out var selected)) { + if (recipeList.SelectSingle(out _) is Recipe selected) { addRecipe(selected); return; } @@ -771,7 +770,7 @@ void dropDownContent(ImGui gui) { using (var grid = gui.EnterInlineGrid(3f)) { foreach (var variant in variants) { grid.Next(); - if (gui.BuildFactorioObjectButton(variant, 3f, MilestoneDisplay.Contained, variant == goods ? SchemeColor.Primary : SchemeColor.None) == Click.Left && + if (gui.BuildFactorioObjectButton(variant, 3f, MilestoneDisplay.Contained, variant == goods ? SchemeColor.Primary : SchemeColor.None, tooltipOptions: HintLocations.OnProducingRecipes) == Click.Left && variant != goods) { recipe!.RecordUndo().ChangeVariant(goods, variant); // null-forgiving: If variants is not null, neither is recipe: Only the call from BuildGoodsIcon sets variants, and the only call to BuildGoodsIcon that sets variants also sets recipe. _ = gui.CloseDropdown(); @@ -925,15 +924,17 @@ private void DrawDesiredProduct(ImGui gui, ProductionLink element) { } } - var evt = gui.BuildFactorioObjectWithEditableAmount(element.goods, element.amount, element.goods.flowUnitOfMeasure, out float newAmount, iconColor); - if (evt == GoodsWithAmountEvent.LeftButtonClick) { - OpenProductDropdown(gui, gui.lastRect, element.goods, element.amount, element, ProductDropdownType.DesiredProduct, null, element.owner); - } - else if (evt == GoodsWithAmountEvent.RightButtonClick) { - DestroyLink(element); - } - else if (evt == GoodsWithAmountEvent.TextEditing && newAmount != 0) { - element.RecordUndo().amount = newAmount; + ObjectTooltipOptions tooltipOptions = element.amount < 0 ? HintLocations.OnConsumingRecipes : HintLocations.OnProducingRecipes; + switch (gui.BuildFactorioObjectWithEditableAmount(element.goods, element.amount, element.goods.flowUnitOfMeasure, out float newAmount, iconColor, tooltipOptions: tooltipOptions)) { + case GoodsWithAmountEvent.LeftButtonClick: + OpenProductDropdown(gui, gui.lastRect, element.goods, element.amount, element, ProductDropdownType.DesiredProduct, null, element.owner); + break; + case GoodsWithAmountEvent.RightButtonClick: + DestroyLink(element); + break; + case GoodsWithAmountEvent.TextEditing when newAmount != 0: + element.RecordUndo().amount = newAmount; + break; } } @@ -942,7 +943,7 @@ public override void Rebuild(bool visualOnly = false) { base.Rebuild(visualOnly); } - private void BuildGoodsIcon(ImGui gui, Goods? goods, ProductionLink? link, float amount, ProductDropdownType dropdownType, RecipeRow? recipe, ProductionTable context, Goods[]? variants = null) { + private void BuildGoodsIcon(ImGui gui, Goods? goods, ProductionLink? link, float amount, ProductDropdownType dropdownType, RecipeRow? recipe, ProductionTable context, ObjectTooltipOptions tooltipOptions, Goods[]? variants = null) { SchemeColor iconColor; if (link != null) { // The icon is part of a production link @@ -978,7 +979,7 @@ private void BuildGoodsIcon(ImGui gui, Goods? goods, ProductionLink? link, float textColor = SchemeColor.BackgroundTextFaint; } - switch (gui.BuildFactorioObjectWithAmount(goods, amount, goods?.flowUnitOfMeasure ?? UnitOfMeasure.None, iconColor, textColor)) { + switch (gui.BuildFactorioObjectWithAmount(goods, amount, goods?.flowUnitOfMeasure ?? UnitOfMeasure.None, iconColor, textColor, tooltipOptions: tooltipOptions)) { case Click.Left when goods is not null: OpenProductDropdown(gui, gui.lastRect, goods, amount, link, dropdownType, recipe, context, variants); break; @@ -1010,7 +1011,7 @@ private void BuildTableProducts(ImGui gui, ProductionTable table, ProductionTabl } grid.Next(); - BuildGoodsIcon(gui, flow[i].goods, flow[i].link, amt, ProductDropdownType.Product, null, context); + BuildGoodsIcon(gui, flow[i].goods, flow[i].link, amt, ProductDropdownType.Product, null, context, HintLocations.OnConsumingRecipes); } } @@ -1126,7 +1127,7 @@ private void BuildTableIngredients(ImGui gui, ProductionTable table, ProductionT } grid.Next(); - BuildGoodsIcon(gui, flow.goods, flow.link, -flow.amount, ProductDropdownType.Ingredient, null, context); + BuildGoodsIcon(gui, flow.goods, flow.link, -flow.amount, ProductDropdownType.Ingredient, null, context, HintLocations.OnProducingRecipes); } } diff --git a/changelog.txt b/changelog.txt index dbc95b98..f64a1fc8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -13,6 +13,8 @@ ---------------------------------------------------------------------------------------------------------------------- Version x.y.z Date: + Features: + - Provide hints that contol+clicking can add recipes, or to explain how to change things so it can. Bugfixes: - Fix regression in fluid variant selection when adding recipes. ---------------------------------------------------------------------------------------------------------------------- From 5317f13d6faabd7c17219853e2f9d83b1d21e6b4 Mon Sep 17 00:00:00 2001 From: Dale McCoy <21223975+DaleStan@users.noreply.github.com> Date: Sat, 18 May 2024 13:42:44 -0400 Subject: [PATCH 3/3] Before failing to add a recipe, look for special recipes too. This allows you to use ctrl+click to produce or consume filled barrels, stacked items, or similar. --- Yafc.Model/Data/DataUtils.cs | 73 ++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/Yafc.Model/Data/DataUtils.cs b/Yafc.Model/Data/DataUtils.cs index 8ff39883..3eef19c8 100644 --- a/Yafc.Model/Data/DataUtils.cs +++ b/Yafc.Model/Data/DataUtils.cs @@ -109,48 +109,55 @@ public static Bits GetMilestoneOrder(FactorioId id) { /// /// The only normal item in . /// The only normal user favorite in . + /// The only item in , considering both normal and special items. + /// The only user favorite in , considering both normal and special items. /// If no previous options are applicable, . /// public static T? SelectSingle(this T[] list, out string recipeHint) where T : FactorioObject { - var userFavorites = Project.current.preferences.favorites; - bool acceptOnlyFavorites = false; - T? element = null; - if (list.Any(t => t.IsAccessible())) { - recipeHint = "Hint: Complete milestones to enable ctrl+click"; - } - else { - recipeHint = "Hint: Mark a recipe as accessible to enable ctrl+click"; - } - foreach (var elem in list) { - if (!elem.IsAccessibleWithCurrentMilestones() || elem.specialType != FactorioObjectSpecialType.Normal) { - continue; + return @internal(list, true, out recipeHint) ?? @internal(list, false, out recipeHint); + + static T? @internal(T[] list, bool excludeSpecial, out string recipeHint) { + HashSet userFavorites = Project.current.preferences.favorites; + bool acceptOnlyFavorites = false; + T? element = null; + if (list.Any(t => t.IsAccessible())) { + recipeHint = "Hint: Complete milestones to enable ctrl+click"; } - - if (userFavorites.Contains(elem)) { - if (!acceptOnlyFavorites || element == null) { - element = elem; - recipeHint = "Hint: ctrl+click to add your favorited recipe"; - acceptOnlyFavorites = true; - } - else { - recipeHint = "Hint: Cannot ctrl+click with multiple favorited recipes"; - return null; - } + else { + recipeHint = "Hint: Mark a recipe as accessible to enable ctrl+click"; } - else if (!acceptOnlyFavorites) { - if (element == null) { - element = elem; - recipeHint = "Hint: ctrl+click to add the accessible recipe"; + foreach (T elem in list) { + // Always consider normal entries. A list with two normals and one special should select nothing, rather than selecting the only special item. + if (!elem.IsAccessibleWithCurrentMilestones() || (elem.specialType != FactorioObjectSpecialType.Normal && excludeSpecial)) { + continue; } - else { - element = null; - recipeHint = "Hint: Set a favorite recipe to add it with ctrl+click"; - acceptOnlyFavorites = true; + + if (userFavorites.Contains(elem)) { + if (!acceptOnlyFavorites || element == null) { + element = elem; + recipeHint = "Hint: ctrl+click to add your favorited recipe"; + acceptOnlyFavorites = true; + } + else { + recipeHint = "Hint: Cannot ctrl+click with multiple favorited recipes"; + return null; + } + } + else if (!acceptOnlyFavorites) { + if (element == null) { + element = elem; + recipeHint = excludeSpecial ? "Hint: ctrl+click to add the accessible normal recipe" : "Hint: ctrl+click to add the accessible recipe"; + } + else { + element = null; + recipeHint = "Hint: Set a favorite recipe to add it with ctrl+click"; + acceptOnlyFavorites = true; + } } } - } - return element; + return element; + } } public static void SetupForProject(Project project) {