Skip to content

Commit 8f49648

Browse files
authored
Include quality selection in dropdown-opened panels (#489)
Object selection panels did not display quality prompts if opened from a dropdown. I made some structural changes to facilitate fixing this, and then fixed it. ![image](https://github.com/user-attachments/assets/095bb6e9-0e05-4c56-8083-55826dd246ca)
2 parents 1454982 + dbf4d1b commit 8f49648

17 files changed

+566
-271
lines changed

Yafc/Data/locale/en/yafc.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ per-second-suffix=/s
3434
per-minute-suffix=/m
3535
per-hour-suffix=/h
3636
seconds-per-stack=__1__ per stack
37-
select-quality=Select quality
37+
; __1__ is 1 if the user is only allowed to select one quality, or the number of qualities displayed if the user is allowed to select multiple qualities.
38+
select-quality=Select qualit__plural_for_parameter__1__{1=y|rest=ies}__
3839

3940
; MainScreenTabBar.cs
4041
edit-page-properties=Edit properties

Yafc/Utils/ObjectDisplayStyles.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ public record ButtonDisplayStyle(float Size, MilestoneDisplay MilestoneDisplay,
3939
/// </summary>
4040
public static new ButtonDisplayStyle Default { get; } = new(2, MilestoneDisplay.Normal);
4141
/// <summary>
42-
/// Gets the button style for the <see cref="SelectObjectPanel{T}"/>s: Size 2.5, <see cref="MilestoneDisplay.Contained"/>, and scaled.
42+
/// Gets the button style for the <see cref="SelectObjectPanel{TResult, TDisplay}"/>s: Size 2.5, <see cref="MilestoneDisplay.Contained"/>,
43+
/// and scaled.
4344
/// </summary>
4445
/// <param name="backgroundColor">The background color to use for this button.</param>
4546
public static ButtonDisplayStyle SelectObjectPanel(SchemeColor backgroundColor) => new(2.5f, MilestoneDisplay.Contained, backgroundColor);

Yafc/Widgets/ImmediateWidgets.cs

Lines changed: 117 additions & 61 deletions
Large diffs are not rendered by default.
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Linq;
5+
using Yafc.I18n;
6+
using Yafc.Model;
7+
using Yafc.UI;
8+
9+
namespace Yafc;
10+
11+
/// <summary>
12+
/// Encapsulates the options for drawing an object selection list, which may be a dropdown, a <see cref="SelectObjectPanel{TResult, TDisplay}"/>,
13+
/// or both with the dropdown displayed first.
14+
/// </summary>
15+
/// <typeparam name="T">The type of <see cref="FactorioObject"/> to be selected</typeparam>
16+
/// <param name="Header">The header text to display, or <see langword="null"/> to display no header text.</param>
17+
/// <param name="Ordering">The sort order to use when displaying the items. Defaults to <see cref="DataUtils.DefaultOrdering"/>.</param>
18+
/// <param name="MaxCount">The maximum number of elements in a dropdown list. If there are more, and a dropdown is being displayed, a
19+
/// <see cref="SelectObjectPanel{TResult, TDisplay}"/> can be opened by the user to show the full list.</param>
20+
/// <param name="Multiple">If <see langword="true"/>, the user will be allowed to select multiple items.<br/>
21+
/// Must be <see langword="false"/> when selecting with a 'None' item.</param>
22+
/// <param name="Checkmark">If not <see langword="null"/>, this will be called to determine if a checkmark should be drawn on the item.
23+
/// Not used when <paramref name="Multiple"/> is <see langword="false"/>.</param>
24+
/// <param name="YellowMark">If Checkmark is not set, draw a less distinct checkmark instead.</param>
25+
/// <param name="ExtraText">If not <see langword="null"/>, this will be called to get extra text to be displayed right-justified in a dropdown
26+
/// list, after the item's name. Not used when displaying <see cref="SelectObjectPanel{TResult, TDisplay}"/>s.</param>
27+
public record ObjectSelectOptions<T>(string? Header, [AllowNull] IComparer<T> Ordering = null, int MaxCount = 6, bool Multiple = false, Predicate<T>? Checkmark = null,
28+
Predicate<T>? YellowMark = null, Func<T, string>? ExtraText = null) where T : class, IFactorioObjectWrapper {
29+
30+
public IComparer<T> Ordering { get; init; } = Ordering ?? DataUtils.DefaultOrdering;
31+
}
32+
33+
/// <summary>
34+
/// Encapsulates the options for drawing an object selection list, which may be a dropdown, a <see cref="SelectObjectPanel{TResult, TDisplay}"/>,
35+
/// or both with the dropdown displayed first, along with options to select the quality.<br/>
36+
/// Also stores the state of the quality selection, for storing the state across draw cycles, reacting to quality changes, and/or reading the
37+
/// selected qualities when an item is selected or the selection panel is closed.
38+
/// </summary>
39+
/// <typeparam name="T"><inheritdoc/></typeparam>
40+
/// <param name="Header">The header text to display, or <see langword="null"/> to display no header text.</param>
41+
/// <param name="Ordering">The sort order to use when displaying the items. Defaults to <see cref="DataUtils.DefaultOrdering"/>.</param>
42+
/// <param name="MaxCount">The maximum number of elements in a dropdown list. If there are more and a dropdown is being displayed, a
43+
/// <see cref="SelectObjectPanel{TResult, TDisplay}"/> can be opened by the user to show the full list.</param>
44+
/// <param name="Multiple">If <see langword="true"/>, the user will be allowed to select multiple items and multiple qualities.<br/>
45+
/// Must be <see langword="false"/> when selecting with a 'None' item.</param>
46+
/// <param name="Checkmark">If not <see langword="null"/>, this will be called to determine if a checkmark should be drawn on the item.
47+
/// Not used when <paramref name="Multiple"/> is <see langword="false"/>.</param>
48+
/// <param name="YellowMark">If Checkmark is not set, draw a less distinct checkmark instead.</param>
49+
/// <param name="ExtraText">If not <see langword="null"/>, this will be called to get extra text to be displayed right-justified in a dropdown
50+
/// list, after the item's name. Not used when displaying <see cref="SelectObjectPanel{TResult, TDisplay}"/>s.</param>
51+
/// <param name="AllowMultipleWithoutControl">When this and <paramref name="Multiple"/> are both <see langword="true"/>, the quality icons in the
52+
/// selection list will always behave like checkboxes. When <paramref name="Multiple"/> is <see langword="true"/> and this is
53+
/// <see langword="false"/>, the quality icons will behave like checkboxes when ctrl-clicking, and like radio buttons when clicking without
54+
/// control. When <paramref name="Multiple"/> is <see langword="false"/>, the quality icons will always behave like radio buttons.</param>
55+
/// <param name="SelectedQuality">The initially selected <see cref="Quality"/>. If this parameter is <see langword="null"/> and
56+
/// <paramref name="Multiple"/> is <see langword="false"/>, <see cref="SelectedQuality"/> will be initialized to <see cref="Quality.Normal"/>.
57+
/// </param>
58+
/// <remarks>If the quality selections are not preserved across draw cycles, construct the QSO in the containing or calling method.</remarks>
59+
public record QualitySelectOptions<T>(string? Header, [AllowNull] IComparer<T> Ordering = null, int MaxCount = 6, bool Multiple = false,
60+
Predicate<T>? Checkmark = null, Predicate<T>? YellowMark = null, Func<T, string>? ExtraText = null, bool AllowMultipleWithoutControl = false,
61+
Quality? SelectedQuality = null)
62+
: ObjectSelectOptions<T>(Header, Ordering, MaxCount, Multiple, Checkmark, YellowMark, ExtraText) where T : class, IFactorioObjectWrapper {
63+
64+
/// <summary>
65+
/// Creates a new <see cref="QualitySelectOptions{T}"/> using just the header text and selected quality.
66+
/// </summary>
67+
/// <param name="header">The header text to display, or <see langword="null"/> to display no header text.</param>
68+
/// <param name="selectedQuality">The initially selected <see cref="Quality"/>. If this parameter is <see langword="null"/>,
69+
/// <see cref="SelectedQuality"/> will be initialized to <see cref="Quality.Normal"/>.</param>
70+
// (Seven of the seventeen constructor calls use only these two parameters.)
71+
public QualitySelectOptions(string? header, Quality? selectedQuality) : this(header, null, SelectedQuality: selectedQuality) { }
72+
73+
/// <summary>
74+
/// The translation key for text to draw above the quality icons. By default this is <see cref="LSs.SelectQuality"/>, which will be
75+
/// pluralized as appropriate based on <see cref="ObjectSelectOptions{T}.Multiple"/>.
76+
/// </summary>
77+
public LocalizableString QualityHeader { get; init; } = LSs.SelectQuality;
78+
79+
/// <summary>
80+
/// Gets the (first) quality selected and sets the only quality selected. Gets or sets <see langword="null"/> if no qualities are (or should
81+
/// be) selected. If the constructor parameter Multiple is <see langword="false"/> and the constructor parameter SelectedQuality is
82+
/// <see langword="null"/>, this property's initial value is <see cref="Quality.Normal"/>. Otherwise, its initial value is the same as the
83+
/// constructor parameter SelectedQuality.<br/>
84+
/// When <see cref="ObjectSelectOptions{T}.Multiple"/> is <see langword="false"/>, this cannot become <see langword="null"/> as a result of
85+
/// user action, though it may be programmatically set to <see langword="null"/>.<br/>
86+
/// </summary>
87+
public Quality? SelectedQuality {
88+
get => SelectedQualities.FirstOrDefault();
89+
set {
90+
SelectedQualities.Clear();
91+
if (value != null) {
92+
SelectedQualities.Add(value);
93+
}
94+
}
95+
}
96+
97+
/// <summary>
98+
/// Called when <see cref="SelectedQualities"/>, and possibly <see cref="SelectedQuality"/> has changed due to user input. The parameter is
99+
/// the active <see cref="ImGui"/>.
100+
/// </summary>
101+
public event Action<ImGui>? SelectedQualitiesChanged;
102+
internal void OnSelectedQualitiesChanged(ImGui gui) => SelectedQualitiesChanged?.Invoke(gui);
103+
104+
/// <summary>
105+
/// Gets all qualities selected by the user. If no qualities are selected (equivalently, if <see cref="SelectedQuality"/> is
106+
/// <see langword="null"/>), this is an empty list.
107+
/// </summary>
108+
public List<Quality> SelectedQualities { get; } = SelectedQuality == null ? Multiple ? [] : [Quality.Normal] : [SelectedQuality];
109+
}
110+
111+
public static class QualityOptionsExtensions {
112+
/// <summary>
113+
/// Applies the <see cref="QualitySelectOptions{T}.SelectedQualities"/> to <paramref name="obj"/>. If no qualities are selected, nothing is
114+
/// returned. Otherwise, a quality-immune <typeparamref name="T"/> is returned once in normal quality, and a quality-compatible
115+
/// <typeparamref name="T"/> is returned once for each selected quality.
116+
/// returned only once, in normal quality, while quality aware</summary>
117+
/// <param name="obj">The object to combine with the currently selected qualities.</param>
118+
/// <returns>A sequence of <see cref="IObjectWithQuality{T}"/>s containing each valid combination of <paramref name="obj"/> and a selected
119+
/// <see cref="Quality"/>.</returns>
120+
public static IEnumerable<IObjectWithQuality<T>> ApplyQualitiesTo<T>(this QualitySelectOptions<T> options, T obj) where T : FactorioObject {
121+
bool addedSomething = false;
122+
foreach (var quality in options.SelectedQualities) {
123+
if (obj.With(quality).quality == quality) {
124+
addedSomething = true;
125+
yield return obj.With(quality);
126+
}
127+
}
128+
129+
if (!addedSomething) {
130+
if (options.SelectedQualities.Count > 0) {
131+
// If the selected object is normal-only and normal quality wasn't selected, return it anyway.
132+
yield return obj.With(Quality.Normal);
133+
}
134+
}
135+
}
136+
}

Yafc/Windows/DependencyExplorer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public override void Build(ImGui gui) {
116116
using (gui.EnterRow()) {
117117
gui.BuildText(LSs.DependencyCurrentlyInspecting, Font.subheader);
118118
if (gui.BuildFactorioObjectButtonWithText(current) == Click.Left) {
119-
SelectSingleObjectPanel.Select(Database.objects.explorable, LSs.DependencySelectSomething, Change);
119+
SelectSingleObjectPanel.Select(Database.objects.explorable, new(LSs.DependencySelectSomething), Change);
120120
}
121121

122122
gui.DrawText(gui.lastRect, LSs.DependencyClickToChangeHint, RectAlignment.MiddleRight, color: TextBlockDisplayStyle.HintText.Color);

Yafc/Windows/MainScreen.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ public static void BuildSubHeader(ImGui gui, string text) {
329329
}
330330
}
331331

332-
private static void ShowNeie() => SelectSingleObjectPanel.Select(Database.goods.explorable, LSs.MenuOpenNeie, NeverEnoughItemsPanel.Show);
332+
private static void ShowNeie() => SelectSingleObjectPanel.Select(Database.goods.explorable, new(LSs.MenuOpenNeie), NeverEnoughItemsPanel.Show);
333333

334334
private void SetSearch(SearchQuery searchQuery) {
335335
pageSearch = searchQuery;
@@ -412,7 +412,7 @@ private void SettingsDropdown(ImGui gui) {
412412
}
413413

414414
if (gui.BuildContextMenuButton(LSs.DependencyExplorer) && gui.CloseDropdown()) {
415-
SelectSingleObjectPanel.Select(Database.objects.explorable, LSs.DependencyExplorer, DependencyExplorer.Show);
415+
SelectSingleObjectPanel.Select(Database.objects.explorable, new(LSs.DependencyExplorer), DependencyExplorer.Show);
416416
}
417417

418418
if (gui.BuildContextMenuButton(LSs.MenuImportFromClipboard, disabled: !ImGuiUtils.HasClipboardText()) && gui.CloseDropdown()) {

Yafc/Windows/MilestonesEditor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public override void Build(ImGui gui) {
6262
milestoneList.RebuildContents();
6363
}
6464
if (gui.BuildButton(LSs.MilestoneAdd)) {
65-
SelectMultiObjectPanel.Select(Database.objects.explorable.Except(Project.current.settings.milestones), LSs.MilestoneAddNew, AddMilestone);
65+
SelectMultiObjectPanel.Select(Database.objects.explorable.Except(Project.current.settings.milestones), new(LSs.MilestoneAddNew, Multiple: true), AddMilestone);
6666
}
6767
}
6868
}

Yafc/Windows/NeverEnoughItemsPanel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ public override void Build(ImGui gui) {
359359
}
360360

361361
if (gui.BuildFactorioObjectButtonBackground(gui.lastRect, current, SchemeColor.Grey) == Click.Left) {
362-
SelectSingleObjectPanel.Select(Database.goods.explorable, LSs.SelectItem, SetItem);
362+
SelectSingleObjectPanel.Select(Database.goods.explorable, new(LSs.SelectItem), SetItem);
363363
}
364364

365365
using (var split = gui.EnterHorizontalSplit(2)) {

Yafc/Windows/ProjectPageSettingsPanel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ private ProjectPageSettingsPanel(ProjectPage? editingPage, Action<string, Factor
3030
private void Build(ImGui gui, Action<FactorioObject?> setIcon) {
3131
_ = gui.BuildTextInput(name, out name, LSs.PageSettingsNameHint, setKeyboardFocus: editingPage == null ? SetKeyboardFocus.OnFirstPanelDraw : SetKeyboardFocus.No);
3232
if (gui.BuildFactorioObjectButton(icon, new ButtonDisplayStyle(4f, MilestoneDisplay.None, SchemeColor.Grey) with { UseScaleSetting = false }) == Click.Left) {
33-
SelectSingleObjectPanel.Select(Database.objects.all, LSs.SelectIcon, setIcon);
33+
SelectSingleObjectPanel.Select(Database.objects.all, new(LSs.SelectIcon), setIcon);
3434
}
3535

3636
if (icon == null && gui.isBuilding) {

0 commit comments

Comments
 (0)