Skip to content

Commit 269c5a0

Browse files
authored
Refactor UI control drawing to add styles and reduce the number of parameters (#227)
This is my proposal for improving the too-many-parameters problem brought up in #220 (comment). It combines parameters that routinely appear together, provides easy access to common combinations, and eliminates most situations where a caller can't pass all the appropriate options. The intentional user-visible changes are: - Refuse to accept negative numbers for things like fixed and built building count. - Reduce the leading padding from 0.8 to 0.5 in all text input boxes that didn't already use 0.5. (Except the 'create directory' text box in the filesystem window, which still uses 0.2 x 0.2.) - Change the `MilestoneDisplay.Normal` icons in the NEIE to `Contained` instead. (see [ImmediateWidget.cs:12-24](https://github.com/shpaass/yafc-ce/blob/412f62c9aad6d87a0047def1037e77aa03d43ea5/Yafc/Widgets/ImmediateWidgets.cs#L12-L24))
2 parents e3d8e55 + 412f62c commit 269c5a0

35 files changed

+491
-296
lines changed

Yafc.UI/Core/ExceptionScreen.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ protected internal override void Close() {
3535

3636
protected override void BuildContents(ImGui gui) {
3737
gui.BuildText(ex.GetType().Name, Font.header);
38-
gui.BuildText(ex.Message, Font.subheader, true);
39-
gui.BuildText(ex.StackTrace, Font.text, true);
38+
gui.BuildText(ex.Message, new TextBlockDisplayStyle(Font.subheader, true));
39+
gui.BuildText(ex.StackTrace, TextBlockDisplayStyle.WrappedText);
4040
using (gui.EnterRow(0.5f, RectAllocator.RightRow)) {
4141
if (gui.BuildButton("Close")) {
4242
Close();

Yafc.UI/Core/Structs.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@ public enum SchemeColor {
6464
TagColorBlueTextFaint
6565
}
6666

67+
public enum SchemeColorGroup {
68+
Pure = SchemeColor.PureBackground,
69+
Background = SchemeColor.Background,
70+
Primary = SchemeColor.Primary,
71+
Secondary = SchemeColor.Secondary,
72+
Error = SchemeColor.Error,
73+
Grey = SchemeColor.Grey,
74+
Magenta = SchemeColor.Magenta,
75+
Green = SchemeColor.Green,
76+
TagColorGreen = SchemeColor.TagColorGreen,
77+
TagColorYellow = SchemeColor.TagColorYellow,
78+
TagColorRed = SchemeColor.TagColorRed,
79+
TagColorBlue = SchemeColor.TagColorBlue,
80+
}
81+
6782
public enum RectangleBorder {
6883
None,
6984
Thin,

Yafc.UI/ImGui/ImGuiBuilding.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,14 @@ public SchemeColor textColor {
8888
set => state.textColor = value;
8989
}
9090

91-
public void BuildText(string? text, Font? font = null, bool wrap = false, RectAlignment align = RectAlignment.MiddleLeft, SchemeColor color = SchemeColor.None, float topOffset = 0f, float maxWidth = float.MaxValue) {
91+
public void BuildText(string? text, TextBlockDisplayStyle? displayStyle = null, float topOffset = 0f, float maxWidth = float.MaxValue) {
92+
displayStyle ??= TextBlockDisplayStyle.Default();
93+
SchemeColor color = displayStyle.Color;
9294
if (color == SchemeColor.None) {
9395
color = state.textColor;
9496
}
9597

96-
var rect = AllocateTextRect(out var cache, text, font, wrap, align, topOffset, maxWidth);
98+
Rect rect = AllocateTextRect(out TextCache? cache, text, displayStyle, topOffset, maxWidth);
9799
if (action == ImGuiAction.Build && cache != null) {
98100
DrawRenderable(rect, cache, color);
99101
}
@@ -112,16 +114,16 @@ public Vector2 GetTextDimensions(out TextCache? cache, string? text, Font? font
112114
return new Vector2(textWidth, cache.texRect.h / pixelsPerUnit);
113115
}
114116

115-
public Rect AllocateTextRect(out TextCache? cache, string? text, Font? font = null, bool wrap = false, RectAlignment align = RectAlignment.MiddleLeft, float topOffset = 0f, float maxWidth = float.MaxValue) {
116-
var fontSize = GetFontSize(font);
117+
public Rect AllocateTextRect(out TextCache? cache, string? text, TextBlockDisplayStyle displayStyle, float topOffset = 0f, float maxWidth = float.MaxValue) {
118+
FontFile.FontSize fontSize = GetFontSize(displayStyle.Font);
117119
Rect rect;
118120
if (string.IsNullOrEmpty(text)) {
119121
cache = null;
120122
rect = AllocateRect(0f, topOffset + (fontSize.lineSize / pixelsPerUnit));
121123
}
122124
else {
123-
Vector2 textSize = GetTextDimensions(out cache, text, font, wrap, maxWidth);
124-
rect = AllocateRect(textSize.X, topOffset + (textSize.Y), align);
125+
Vector2 textSize = GetTextDimensions(out cache, text, displayStyle.Font, displayStyle.WrapText, maxWidth);
126+
rect = AllocateRect(textSize.X, topOffset + (textSize.Y), displayStyle.Alignment);
125127
}
126128

127129
if (topOffset != 0f) {
@@ -146,14 +148,17 @@ public void DrawText(Rect rect, string text, RectAlignment alignment = RectAlign
146148

147149
private ImGuiTextInputHelper? textInputHelper;
148150
public bool BuildTextInput(string? text, out string newText, string? placeholder, Icon icon = Icon.None, bool delayed = false, bool setInitialFocus = false) {
149-
Padding padding = new Padding(icon == Icon.None ? 0.8f : 0.5f, 0.5f);
150-
return BuildTextInput(text, out newText, placeholder, icon, delayed, padding, setInitialFocus: setInitialFocus);
151+
TextBoxDisplayStyle displayStyle = TextBoxDisplayStyle.DefaultTextInput;
152+
if (icon != Icon.None) {
153+
displayStyle = displayStyle with { Icon = icon };
154+
}
155+
return BuildTextInput(text, out newText, placeholder, displayStyle, delayed, setInitialFocus);
151156
}
152157

153-
public bool BuildTextInput(string? text, out string newText, string? placeholder, Icon icon, bool delayed, Padding padding, RectAlignment alignment = RectAlignment.MiddleLeft, SchemeColor color = SchemeColor.Grey, bool setInitialFocus = false) {
158+
public bool BuildTextInput(string? text, out string newText, string? placeholder, TextBoxDisplayStyle displayStyle, bool delayed, bool setInitialFocus = false) {
154159
setInitialFocus &= textInputHelper == null;
155160
textInputHelper ??= new ImGuiTextInputHelper(this);
156-
bool result = textInputHelper.BuildTextInput(text, out newText, placeholder, GetFontSize(), delayed, icon, padding, alignment, color);
161+
bool result = textInputHelper.BuildTextInput(text, out newText, placeholder, GetFontSize(), delayed, displayStyle);
157162
if (setInitialFocus) {
158163
SetTextInputFocus(lastRect, "");
159164
}

Yafc.UI/ImGui/ImGuiTextInputHelper.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ private void GetTextParameters(string? textToBuild, Rect textRect, FontFile.Font
5555
}
5656
}
5757

58-
public bool BuildTextInput(string? text, out string newText, string? placeholder, FontFile.FontSize fontSize, bool delayed, Icon icon, Padding padding, RectAlignment alignment, SchemeColor color) {
58+
public bool BuildTextInput(string? text, out string newText, string? placeholder, FontFile.FontSize fontSize, bool delayed, TextBoxDisplayStyle displayStyle) {
5959
newText = text ?? "";
6060
Rect textRect, realTextRect;
61-
using (gui.EnterGroup(padding, RectAllocator.LeftRow)) {
61+
using (gui.EnterGroup(displayStyle.Padding, RectAllocator.LeftRow)) {
6262
float lineSize = gui.PixelsToUnits(fontSize.lineSize);
63-
if (icon != Icon.None) {
64-
gui.BuildIcon(icon, lineSize, color + 3);
63+
if (displayStyle.Icon != Icon.None) {
64+
gui.BuildIcon(displayStyle.Icon, lineSize, (SchemeColor)displayStyle.ColorGroup + 3);
6565
}
6666

6767
textRect = gui.RemainingRow(0.3f).AllocateRect(0, lineSize, RectAlignment.MiddleFullRow);
@@ -81,32 +81,32 @@ public bool BuildTextInput(string? text, out string newText, string? placeholder
8181

8282
if (gui.ConsumeMouseDown(boundingRect)) {
8383
SetFocus(boundingRect, text ?? "");
84-
GetTextParameters(this.text, textRect, fontSize, alignment, out _, out _, out _, out realTextRect);
84+
GetTextParameters(this.text, textRect, fontSize, displayStyle.Alignment, out _, out _, out _, out realTextRect);
8585
SetCaret(FindCaretIndex(text, gui.mousePosition.X - realTextRect.X, fontSize, textRect.Width));
8686
}
8787
break;
8888
case ImGuiAction.MouseMove:
8989
if (focused && gui.actionParameter == SDL.SDL_BUTTON_LEFT) {
90-
GetTextParameters(this.text, textRect, fontSize, alignment, out _, out _, out _, out realTextRect);
90+
GetTextParameters(this.text, textRect, fontSize, displayStyle.Alignment, out _, out _, out _, out realTextRect);
9191
SetCaret(caret, FindCaretIndex(this.text, gui.mousePosition.X - realTextRect.X, fontSize, textRect.Width));
9292
}
9393
_ = gui.ConsumeMouseOver(boundingRect, RenderingUtils.cursorCaret, false);
9494
break;
9595
case ImGuiAction.Build:
96-
var textColor = color + 2;
96+
SchemeColor textColor = (SchemeColor)displayStyle.ColorGroup + 2;
9797
string? textToBuild;
9898
if (focused && !string.IsNullOrEmpty(text)) {
9999
textToBuild = this.text;
100100
}
101101
else if (string.IsNullOrEmpty(text)) {
102102
textToBuild = placeholder;
103-
textColor = color + 3;
103+
textColor = (SchemeColor)displayStyle.ColorGroup + 3;
104104
}
105105
else {
106106
textToBuild = text;
107107
}
108108

109-
GetTextParameters(textToBuild, textRect, fontSize, alignment, out var cachedText, out float scale, out float textWidth, out realTextRect);
109+
GetTextParameters(textToBuild, textRect, fontSize, displayStyle.Alignment, out TextCache? cachedText, out float scale, out float textWidth, out realTextRect);
110110
if (cachedText != null) {
111111
gui.DrawRenderable(realTextRect, cachedText, textColor);
112112
}
@@ -125,11 +125,11 @@ public bool BuildTextInput(string? text, out string newText, string? placeholder
125125
gui.SetNextRebuild(nextCaretTimer);
126126
if (caretVisible) {
127127
float caretPosition = GetCharacterPosition(caret, fontSize, textWidth) * scale;
128-
gui.DrawRectangle(new Rect(caretPosition + realTextRect.X - 0.05f, realTextRect.Y, 0.1f, realTextRect.Height), color + 2);
128+
gui.DrawRectangle(new Rect(caretPosition + realTextRect.X - 0.05f, realTextRect.Y, 0.1f, realTextRect.Height), (SchemeColor)displayStyle.ColorGroup + 2);
129129
}
130130
}
131131
}
132-
gui.DrawRectangle(boundingRect, color);
132+
gui.DrawRectangle(boundingRect, (SchemeColor)displayStyle.ColorGroup);
133133
break;
134134
}
135135

Yafc.UI/ImGui/ImGuiUtils.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public static ButtonEvent BuildButton(this ImGui gui, Rect rect, SchemeColor nor
5858
public static string ScanToString(SDL.SDL_Scancode scancode) => SDL.SDL_GetKeyName(SDL.SDL_GetKeyFromScancode(scancode));
5959

6060
public static bool BuildLink(this ImGui gui, string text) {
61-
gui.BuildText(text, color: SchemeColor.Link);
61+
gui.BuildText(text, TextBlockDisplayStyle.Default(SchemeColor.Link));
6262
var rect = gui.lastRect;
6363
switch (gui.action) {
6464
case ImGuiAction.MouseMove:
@@ -105,7 +105,7 @@ public static ButtonEvent BuildButton(this ImGui gui, string text, SchemeColor c
105105
}
106106

107107
using (gui.EnterGroup(padding ?? DefaultButtonPadding, active ? color + 2 : color + 3)) {
108-
gui.BuildText(text, Font.text, align: RectAlignment.Middle);
108+
gui.BuildText(text, TextBlockDisplayStyle.Centered);
109109
}
110110

111111
return active ? gui.BuildButton(gui.lastRect, color, color + 1) : ButtonEvent.None;
@@ -119,10 +119,10 @@ public static ButtonEvent BuildContextMenuButton(this ImGui gui, string text, st
119119
gui.BuildIcon(icon, color: icon >= Icon.FirstCustom ? disabled ? SchemeColor.SourceFaint : SchemeColor.Source : textColor);
120120
}
121121

122-
gui.BuildText(text, Font.text, true, color: textColor);
122+
gui.BuildText(text, TextBlockDisplayStyle.WrappedText with { Color = textColor });
123123
if (rightText != null) {
124124
gui.allocator = RectAllocator.RightRow;
125-
gui.BuildText(rightText, align: RectAlignment.MiddleRight);
125+
gui.BuildText(rightText, new TextBlockDisplayStyle(Alignment: RectAlignment.MiddleRight));
126126
}
127127
}
128128
return gui.BuildButton(gui.lastRect, SchemeColor.None, SchemeColor.Grey);
@@ -142,7 +142,7 @@ public static ButtonEvent BuildRedButton(this ImGui gui, string text) {
142142
Rect textRect;
143143
TextCache? cache;
144144
using (gui.EnterGroup(DefaultButtonPadding)) {
145-
textRect = gui.AllocateTextRect(out cache, text, align: RectAlignment.Middle);
145+
textRect = gui.AllocateTextRect(out cache, text, TextBlockDisplayStyle.Centered);
146146
}
147147

148148
var evt = gui.BuildButton(gui.lastRect, SchemeColor.None, SchemeColor.Error);
@@ -200,7 +200,7 @@ public static bool WithTooltip(this ButtonEvent evt, ImGui gui, string tooltip,
200200
public static bool BuildCheckBox(this ImGui gui, string text, bool value, out bool newValue, SchemeColor color = SchemeColor.None, RectAllocator allocator = RectAllocator.LeftRow) {
201201
using (gui.EnterRow(allocator: allocator)) {
202202
gui.BuildIcon(value ? Icon.CheckBoxCheck : Icon.CheckBoxEmpty, 1.5f, color);
203-
gui.BuildText(text, Font.text, color: color);
203+
gui.BuildText(text, TextBlockDisplayStyle.Default(color));
204204
}
205205

206206
if (gui.OnClick(gui.lastRect)) {
@@ -215,7 +215,7 @@ public static bool BuildCheckBox(this ImGui gui, string text, bool value, out bo
215215
public static bool BuildRadioButton(this ImGui gui, string option, bool selected, SchemeColor color = SchemeColor.None) {
216216
using (gui.EnterRow()) {
217217
gui.BuildIcon(selected ? Icon.RadioCheck : Icon.RadioEmpty, 1.5f, color);
218-
gui.BuildText(option, Font.text, color: color, wrap: true);
218+
gui.BuildText(option, TextBlockDisplayStyle.WrappedText with { Color = color });
219219
}
220220

221221
return !selected && gui.OnClick(gui.lastRect);
@@ -239,7 +239,7 @@ public static bool BuildErrorRow(this ImGui gui, string text) {
239239
closed = true;
240240
}
241241

242-
gui.RemainingRow().BuildText(text, align: RectAlignment.Middle);
242+
gui.RemainingRow().BuildText(text, TextBlockDisplayStyle.Centered);
243243
}
244244
if (gui.isBuilding) {
245245
gui.DrawRectangle(gui.lastRect, SchemeColor.Error);
@@ -263,7 +263,7 @@ public static bool BuildIntegerInput(this ImGui gui, int value, out int newValue
263263

264264
public static void ShowTooltip(this ImGui gui, Rect rect, GuiBuilder builder, float width = 20f) => gui.window?.ShowTooltip(gui, rect, builder, width);
265265

266-
public static void ShowTooltip(this ImGui gui, Rect rect, string text, float width = 20f) => gui.window?.ShowTooltip(gui, rect, x => x.BuildText(text, wrap: true), width);
266+
public static void ShowTooltip(this ImGui gui, Rect rect, string text, float width = 20f) => gui.window?.ShowTooltip(gui, rect, x => x.BuildText(text, TextBlockDisplayStyle.WrappedText), width);
267267

268268
public static void ShowTooltip(this ImGui gui, GuiBuilder builder, float width = 20f) => gui.window?.ShowTooltip(gui, gui.lastRect, builder, width);
269269

Yafc.UI/ImGui/TextDisplayStyles.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
namespace Yafc.UI;
2+
3+
/// <summary>
4+
/// Contains the display parameters for fixed text (<c>TextBlock</c> in WPF, <c>Label</c> in WinForms)
5+
/// </summary>
6+
/// <param name="Font">The <see cref="UI.Font"/> to use when drawing the text, or <see langword="null"/> to use <see cref="Font.text"/>.</param>
7+
/// <param name="WrapText">Specifies whether or not the text should be wrapped.</param>
8+
/// <param name="Alignment">Where the text should be drawn within the renderable area.</param>
9+
/// <param name="Color">The color to use, or <see cref="SchemeColor.None"/> to use the previous color.</param>
10+
public record TextBlockDisplayStyle(Font? Font = null, bool WrapText = false, RectAlignment Alignment = RectAlignment.MiddleLeft, SchemeColor Color = SchemeColor.None) {
11+
/// <summary>
12+
/// Gets the default display style (<see cref="Font.text"/>, not wrapped, left-aligned), with the specified color.
13+
/// </summary>
14+
/// <param name="color">The color to use, or <see cref="SchemeColor.None"/> to use the previous color.</param>
15+
public static TextBlockDisplayStyle Default(SchemeColor color = SchemeColor.None) => new(Color: color);
16+
/// <summary>
17+
/// Gets the display style for nonwrapped centered text.
18+
/// </summary>
19+
public static TextBlockDisplayStyle Centered { get; } = new(Alignment: RectAlignment.Middle);
20+
/// <summary>
21+
/// Gets the display style for hint text.
22+
/// </summary>
23+
public static TextBlockDisplayStyle HintText { get; } = new(Color: SchemeColor.BackgroundTextFaint);
24+
/// <summary>
25+
/// Gets the display style for wrapped, left-aligned text.
26+
/// </summary>
27+
public static TextBlockDisplayStyle WrappedText { get; } = new(WrapText: true);
28+
/// <summary>
29+
/// Gets the display style for most error messages.
30+
/// </summary>
31+
public static TextBlockDisplayStyle ErrorText { get; } = new(WrapText: true, Color: SchemeColor.Error);
32+
33+
/// <summary>
34+
/// Converts a font to the default display style (not wrapped, left-aligned, default color) for that font.
35+
/// </summary>
36+
public static implicit operator TextBlockDisplayStyle(Font font) => new(font);
37+
}
38+
39+
/// <summary>
40+
/// Contains the display parameters for editable text (<c>TextBox</c> in both WPF and WinForms)
41+
/// </summary>
42+
/// <param name="Icon">The <see cref="Icon"/> to display to the left of the text, or <see cref="Icon.None"/> to display no icon.</param>
43+
/// <param name="Padding">The <see cref="UI.Padding"/> to place between the text and the edges of the editable area. (The box area not used by <paramref name="Icon"/>.)</param>
44+
/// <param name="Alignment">The <see cref="RectAlignment"/> to apply when drawing the text within the edit box.</param>
45+
/// <param name="ColorGroup">The <see cref="SchemeColorGroup"/> to use when drawing the edit box.</param>
46+
public record TextBoxDisplayStyle(Icon Icon, Padding Padding, RectAlignment Alignment, SchemeColorGroup ColorGroup) {
47+
/// <summary>
48+
/// Gets the default display style, used for the Preferences screen and calls to <see cref="ImGui.BuildTextInput(string?, out string, string?, Icon, bool, bool)"/>.
49+
/// </summary>
50+
public static TextBoxDisplayStyle DefaultTextInput { get; } = new(Icon.None, new Padding(.5f), RectAlignment.MiddleLeft, SchemeColorGroup.Grey);
51+
/// <summary>
52+
/// Gets the display style for input boxes on the Module Filler Parameters screen.
53+
/// </summary>
54+
public static TextBoxDisplayStyle ModuleParametersTextInput { get; } = new(Icon.None, new Padding(.5f, 0), RectAlignment.MiddleLeft, SchemeColorGroup.Grey);
55+
/// <summary>
56+
/// Gets the display style for amounts associated with Factorio objects. (<c><see langword="with"/> { ColorGroup = <see cref="SchemeColorGroup.Grey"/> }</c> for built building counts.)
57+
/// </summary>
58+
public static TextBoxDisplayStyle FactorioObjectInput { get; } = new(Icon.None, default, RectAlignment.Middle, SchemeColorGroup.Secondary);
59+
}

0 commit comments

Comments
 (0)