Skip to content

Commit

Permalink
Refactor UI control drawing to add styles and reduce the number of pa…
Browse files Browse the repository at this point in the history
…rameters (shpaass#227)

This is my proposal for improving the too-many-parameters problem
brought up in
shpaass#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))
  • Loading branch information
shpaass authored Aug 8, 2024
2 parents e3d8e55 + 412f62c commit 269c5a0
Show file tree
Hide file tree
Showing 35 changed files with 491 additions and 296 deletions.
4 changes: 2 additions & 2 deletions Yafc.UI/Core/ExceptionScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ protected internal override void Close() {

protected override void BuildContents(ImGui gui) {
gui.BuildText(ex.GetType().Name, Font.header);
gui.BuildText(ex.Message, Font.subheader, true);
gui.BuildText(ex.StackTrace, Font.text, true);
gui.BuildText(ex.Message, new TextBlockDisplayStyle(Font.subheader, true));
gui.BuildText(ex.StackTrace, TextBlockDisplayStyle.WrappedText);
using (gui.EnterRow(0.5f, RectAllocator.RightRow)) {
if (gui.BuildButton("Close")) {
Close();
Expand Down
15 changes: 15 additions & 0 deletions Yafc.UI/Core/Structs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,21 @@ public enum SchemeColor {
TagColorBlueTextFaint
}

public enum SchemeColorGroup {
Pure = SchemeColor.PureBackground,
Background = SchemeColor.Background,
Primary = SchemeColor.Primary,
Secondary = SchemeColor.Secondary,
Error = SchemeColor.Error,
Grey = SchemeColor.Grey,
Magenta = SchemeColor.Magenta,
Green = SchemeColor.Green,
TagColorGreen = SchemeColor.TagColorGreen,
TagColorYellow = SchemeColor.TagColorYellow,
TagColorRed = SchemeColor.TagColorRed,
TagColorBlue = SchemeColor.TagColorBlue,
}

public enum RectangleBorder {
None,
Thin,
Expand Down
25 changes: 15 additions & 10 deletions Yafc.UI/ImGui/ImGuiBuilding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,14 @@ public SchemeColor textColor {
set => state.textColor = value;
}

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) {
public void BuildText(string? text, TextBlockDisplayStyle? displayStyle = null, float topOffset = 0f, float maxWidth = float.MaxValue) {
displayStyle ??= TextBlockDisplayStyle.Default();
SchemeColor color = displayStyle.Color;
if (color == SchemeColor.None) {
color = state.textColor;
}

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

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) {
var fontSize = GetFontSize(font);
public Rect AllocateTextRect(out TextCache? cache, string? text, TextBlockDisplayStyle displayStyle, float topOffset = 0f, float maxWidth = float.MaxValue) {
FontFile.FontSize fontSize = GetFontSize(displayStyle.Font);
Rect rect;
if (string.IsNullOrEmpty(text)) {
cache = null;
rect = AllocateRect(0f, topOffset + (fontSize.lineSize / pixelsPerUnit));
}
else {
Vector2 textSize = GetTextDimensions(out cache, text, font, wrap, maxWidth);
rect = AllocateRect(textSize.X, topOffset + (textSize.Y), align);
Vector2 textSize = GetTextDimensions(out cache, text, displayStyle.Font, displayStyle.WrapText, maxWidth);
rect = AllocateRect(textSize.X, topOffset + (textSize.Y), displayStyle.Alignment);
}

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

private ImGuiTextInputHelper? textInputHelper;
public bool BuildTextInput(string? text, out string newText, string? placeholder, Icon icon = Icon.None, bool delayed = false, bool setInitialFocus = false) {
Padding padding = new Padding(icon == Icon.None ? 0.8f : 0.5f, 0.5f);
return BuildTextInput(text, out newText, placeholder, icon, delayed, padding, setInitialFocus: setInitialFocus);
TextBoxDisplayStyle displayStyle = TextBoxDisplayStyle.DefaultTextInput;
if (icon != Icon.None) {
displayStyle = displayStyle with { Icon = icon };
}
return BuildTextInput(text, out newText, placeholder, displayStyle, delayed, setInitialFocus);
}

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) {
public bool BuildTextInput(string? text, out string newText, string? placeholder, TextBoxDisplayStyle displayStyle, bool delayed, bool setInitialFocus = false) {
setInitialFocus &= textInputHelper == null;
textInputHelper ??= new ImGuiTextInputHelper(this);
bool result = textInputHelper.BuildTextInput(text, out newText, placeholder, GetFontSize(), delayed, icon, padding, alignment, color);
bool result = textInputHelper.BuildTextInput(text, out newText, placeholder, GetFontSize(), delayed, displayStyle);
if (setInitialFocus) {
SetTextInputFocus(lastRect, "");
}
Expand Down
22 changes: 11 additions & 11 deletions Yafc.UI/ImGui/ImGuiTextInputHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ private void GetTextParameters(string? textToBuild, Rect textRect, FontFile.Font
}
}

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

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

if (gui.ConsumeMouseDown(boundingRect)) {
SetFocus(boundingRect, text ?? "");
GetTextParameters(this.text, textRect, fontSize, alignment, out _, out _, out _, out realTextRect);
GetTextParameters(this.text, textRect, fontSize, displayStyle.Alignment, out _, out _, out _, out realTextRect);
SetCaret(FindCaretIndex(text, gui.mousePosition.X - realTextRect.X, fontSize, textRect.Width));
}
break;
case ImGuiAction.MouseMove:
if (focused && gui.actionParameter == SDL.SDL_BUTTON_LEFT) {
GetTextParameters(this.text, textRect, fontSize, alignment, out _, out _, out _, out realTextRect);
GetTextParameters(this.text, textRect, fontSize, displayStyle.Alignment, out _, out _, out _, out realTextRect);
SetCaret(caret, FindCaretIndex(this.text, gui.mousePosition.X - realTextRect.X, fontSize, textRect.Width));
}
_ = gui.ConsumeMouseOver(boundingRect, RenderingUtils.cursorCaret, false);
break;
case ImGuiAction.Build:
var textColor = color + 2;
SchemeColor textColor = (SchemeColor)displayStyle.ColorGroup + 2;
string? textToBuild;
if (focused && !string.IsNullOrEmpty(text)) {
textToBuild = this.text;
}
else if (string.IsNullOrEmpty(text)) {
textToBuild = placeholder;
textColor = color + 3;
textColor = (SchemeColor)displayStyle.ColorGroup + 3;
}
else {
textToBuild = text;
}

GetTextParameters(textToBuild, textRect, fontSize, alignment, out var cachedText, out float scale, out float textWidth, out realTextRect);
GetTextParameters(textToBuild, textRect, fontSize, displayStyle.Alignment, out TextCache? cachedText, out float scale, out float textWidth, out realTextRect);
if (cachedText != null) {
gui.DrawRenderable(realTextRect, cachedText, textColor);
}
Expand All @@ -125,11 +125,11 @@ public bool BuildTextInput(string? text, out string newText, string? placeholder
gui.SetNextRebuild(nextCaretTimer);
if (caretVisible) {
float caretPosition = GetCharacterPosition(caret, fontSize, textWidth) * scale;
gui.DrawRectangle(new Rect(caretPosition + realTextRect.X - 0.05f, realTextRect.Y, 0.1f, realTextRect.Height), color + 2);
gui.DrawRectangle(new Rect(caretPosition + realTextRect.X - 0.05f, realTextRect.Y, 0.1f, realTextRect.Height), (SchemeColor)displayStyle.ColorGroup + 2);
}
}
}
gui.DrawRectangle(boundingRect, color);
gui.DrawRectangle(boundingRect, (SchemeColor)displayStyle.ColorGroup);
break;
}

Expand Down
18 changes: 9 additions & 9 deletions Yafc.UI/ImGui/ImGuiUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public static ButtonEvent BuildButton(this ImGui gui, Rect rect, SchemeColor nor
public static string ScanToString(SDL.SDL_Scancode scancode) => SDL.SDL_GetKeyName(SDL.SDL_GetKeyFromScancode(scancode));

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

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

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

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

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

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

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

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

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

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);
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);

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

Expand Down
59 changes: 59 additions & 0 deletions Yafc.UI/ImGui/TextDisplayStyles.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
namespace Yafc.UI;

/// <summary>
/// Contains the display parameters for fixed text (<c>TextBlock</c> in WPF, <c>Label</c> in WinForms)
/// </summary>
/// <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>
/// <param name="WrapText">Specifies whether or not the text should be wrapped.</param>
/// <param name="Alignment">Where the text should be drawn within the renderable area.</param>
/// <param name="Color">The color to use, or <see cref="SchemeColor.None"/> to use the previous color.</param>
public record TextBlockDisplayStyle(Font? Font = null, bool WrapText = false, RectAlignment Alignment = RectAlignment.MiddleLeft, SchemeColor Color = SchemeColor.None) {
/// <summary>
/// Gets the default display style (<see cref="Font.text"/>, not wrapped, left-aligned), with the specified color.
/// </summary>
/// <param name="color">The color to use, or <see cref="SchemeColor.None"/> to use the previous color.</param>
public static TextBlockDisplayStyle Default(SchemeColor color = SchemeColor.None) => new(Color: color);
/// <summary>
/// Gets the display style for nonwrapped centered text.
/// </summary>
public static TextBlockDisplayStyle Centered { get; } = new(Alignment: RectAlignment.Middle);
/// <summary>
/// Gets the display style for hint text.
/// </summary>
public static TextBlockDisplayStyle HintText { get; } = new(Color: SchemeColor.BackgroundTextFaint);
/// <summary>
/// Gets the display style for wrapped, left-aligned text.
/// </summary>
public static TextBlockDisplayStyle WrappedText { get; } = new(WrapText: true);
/// <summary>
/// Gets the display style for most error messages.
/// </summary>
public static TextBlockDisplayStyle ErrorText { get; } = new(WrapText: true, Color: SchemeColor.Error);

/// <summary>
/// Converts a font to the default display style (not wrapped, left-aligned, default color) for that font.
/// </summary>
public static implicit operator TextBlockDisplayStyle(Font font) => new(font);
}

/// <summary>
/// Contains the display parameters for editable text (<c>TextBox</c> in both WPF and WinForms)
/// </summary>
/// <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>
/// <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>
/// <param name="Alignment">The <see cref="RectAlignment"/> to apply when drawing the text within the edit box.</param>
/// <param name="ColorGroup">The <see cref="SchemeColorGroup"/> to use when drawing the edit box.</param>
public record TextBoxDisplayStyle(Icon Icon, Padding Padding, RectAlignment Alignment, SchemeColorGroup ColorGroup) {
/// <summary>
/// Gets the default display style, used for the Preferences screen and calls to <see cref="ImGui.BuildTextInput(string?, out string, string?, Icon, bool, bool)"/>.
/// </summary>
public static TextBoxDisplayStyle DefaultTextInput { get; } = new(Icon.None, new Padding(.5f), RectAlignment.MiddleLeft, SchemeColorGroup.Grey);
/// <summary>
/// Gets the display style for input boxes on the Module Filler Parameters screen.
/// </summary>
public static TextBoxDisplayStyle ModuleParametersTextInput { get; } = new(Icon.None, new Padding(.5f, 0), RectAlignment.MiddleLeft, SchemeColorGroup.Grey);
/// <summary>
/// Gets the display style for amounts associated with Factorio objects. (<c><see langword="with"/> { ColorGroup = <see cref="SchemeColorGroup.Grey"/> }</c> for built building counts.)
/// </summary>
public static TextBoxDisplayStyle FactorioObjectInput { get; } = new(Icon.None, default, RectAlignment.Middle, SchemeColorGroup.Secondary);
}
Loading

0 comments on commit 269c5a0

Please sign in to comment.