Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor UI control drawing to add styles and reduce the number of parameters #227

Merged
merged 6 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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