From c50b963d86938227a0aeff85dbb0265afe71b79c Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Mon, 11 May 2026 15:32:42 -0700 Subject: [PATCH 01/19] disable labels in input paragraph --- FGEGraphics/UISystem/UIInputParagraph.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FGEGraphics/UISystem/UIInputParagraph.cs b/FGEGraphics/UISystem/UIInputParagraph.cs index ce58a22a..d8639d89 100644 --- a/FGEGraphics/UISystem/UIInputParagraph.cs +++ b/FGEGraphics/UISystem/UIInputParagraph.cs @@ -98,9 +98,9 @@ public UIInputParagraph(UIStyling styling, UIStyling textStyling, UIStyling high Styling = styling; TextStyling = textStyling; HighlightStyling = highlightStyling; - AddLabel(InputInternal.LabelLeft = new UILabel("", textStyling, new UILayout())); - AddLabel(InputInternal.LabelCenter = new UILabel("", highlightStyling, new UILayout())); - AddLabel(InputInternal.LabelRight = new UILabel("", textStyling, new UILayout())); + AddLabel(InputInternal.LabelLeft = new UILabel("", textStyling, new UILayout()) { IsEnabled = false }); + AddLabel(InputInternal.LabelCenter = new UILabel("", highlightStyling, new UILayout()) { IsEnabled = false }); + AddLabel(InputInternal.LabelRight = new UILabel("", textStyling, new UILayout()) { IsEnabled = false }); } /// Ensures the cursor positions lie within the current paragraph . From 0265f54d24bea406b6ae8309c7a731a2c1997966 Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Mon, 11 May 2026 16:39:05 -0700 Subject: [PATCH 02/19] basic UIScrollGroup improvements --- FGEGraphics/UISystem/UIInputLabel.cs | 32 +++--- FGEGraphics/UISystem/UINumberInputLabel.cs | 6 +- FGEGraphics/UISystem/UIScrollGroup.cs | 123 ++++++++++----------- 3 files changed, 72 insertions(+), 89 deletions(-) diff --git a/FGEGraphics/UISystem/UIInputLabel.cs b/FGEGraphics/UISystem/UIInputLabel.cs index 4bbd0a90..8ab8fedc 100644 --- a/FGEGraphics/UISystem/UIInputLabel.cs +++ b/FGEGraphics/UISystem/UIInputLabel.cs @@ -124,13 +124,7 @@ public struct InternalData() /// Whether to enforce a max width. If false, will use horizontal scrolling. /// Whether to render a box behind the input label. /// The padding between the box and the label. - /// The styles for the scroll bar. - /// The width of the scroll bar. - /// Whether to add a horizontal scroll bar. - /// Whether to add a vertical scroll bar. - /// The anchor of the horizontal scroll bar. - /// The anchor of the vertical scroll bar. - public UIInputLabel(string placeholderInfo, string defaultText, UIStyling styling, UIStyling inputStyling, UIStyling highlightStyling, UILayout layout, bool maxWidth = true, bool renderBox = false, int boxPadding = 0, UIInteractionStyles scrollBarStyles = null, int scrollBarWidth = 0, bool scrollBarX = false, bool scrollBarY = false, UIAnchor scrollBarXAnchor = null, UIAnchor scrollBarYAnchor = null) : base(styling, layout) + public UIInputLabel(string placeholderInfo, string defaultText, UIStyling styling, UIStyling inputStyling, UIStyling highlightStyling, UILayout layout, bool maxWidth = true, bool renderBox = false, int boxPadding = 0) : base(styling, layout) { if (renderBox) { @@ -142,7 +136,7 @@ public UIInputLabel(string placeholderInfo, string defaultText, UIStyling stylin } int Inset() => Box is not null ? ElementInternal.Style.BorderThickness : 0; // there should definitely be a system for this UILayout scrollGroupLayout = new UILayout().SetPosition(Inset, Inset).SetSize(() => layout.Width - Inset() * 2, () => layout.Height - Inset() * 2); - ScrollGroup = new(scrollGroupLayout, scrollBarStyles ?? UIStyling.Empty, scrollBarWidth, !maxWidth && scrollBarX, scrollBarY, scrollBarXAnchor, scrollBarYAnchor) { IsEnabled = false }; + ScrollGroup = new(scrollGroupLayout) { IsEnabled = false }; ScrollGroup.AddScrollableChild(PlaceholderInfo = new UILabel(placeholderInfo, styling.Bind(this), new UILayout().SetPosition(() => TextPadding, () => TextPadding)) { IsEnabled = false }); ScrollGroup.AddScrollableChild(Paragraph = new UIInputParagraph(highlightStyling, inputStyling, highlightStyling, new UILayout().SetPosition(() => TextPadding, () => TextPadding)) { IsEnabled = false }); AddChild(ScrollGroup); @@ -161,7 +155,7 @@ public override void Focused() /// public override void Unfocused() { - if ((ScrollGroup.ScrollX.ScrollBar?.IsPressed | ScrollGroup.ScrollY.ScrollBar?.IsPressed) ?? false) + if ((ScrollGroup.XAxis.ScrollBar?.IsPressed | ScrollGroup.YAxis.ScrollBar?.IsPressed) ?? false) { IsFocused = true; return; @@ -170,8 +164,8 @@ public override void Unfocused() Paragraph.SetCursorPosition(0); Paragraph.RenderCursor = false; Paragraph.UpdateRenderState(); - ScrollGroup.ScrollX.Reset(); - ScrollGroup.ScrollY.Reset(); + ScrollGroup.XAxis.Reset(); + ScrollGroup.YAxis.Reset(); PlaceholderInfo.RenderSelf = Content.Length == 0; } @@ -183,9 +177,9 @@ public void UpdateScrollGroupX() { return; } - ScrollGroup.ScrollX.MaxValue = Math.Max(Paragraph.Width + TextPadding * 2 - ScrollGroup.Width, 0); - ScrollGroup.ScrollX.ScrollToPos((int)Paragraph.InputInternal.CursorRenderOffset.X, (int)Paragraph.InputInternal.CursorRenderOffset.X + TextPadding * 2 - ScrollGroup.ScrollX.Value); - ScrollGroup.ScrollX.Clamp(); + ScrollGroup.XAxis.MaxValue = Math.Max(Paragraph.Width + TextPadding * 2 - ScrollGroup.Width, 0); + ScrollGroup.XAxis.ScrollToPos((int)Paragraph.InputInternal.CursorRenderOffset.X, (int)Paragraph.InputInternal.CursorRenderOffset.X + TextPadding * 2 - ScrollGroup.XAxis.Value); + ScrollGroup.XAxis.Clamp(); } /// Updates the vertical scroll values based on the text height and cursor position. @@ -193,13 +187,13 @@ public void UpdateScrollGroupY() { if (Paragraph.Internal.Renderables.Count == 0) { - ScrollGroup.ScrollY.Reset(); + ScrollGroup.YAxis.Reset(); return; } int lastLineHeight = Paragraph.InputInternal.LabelRight.Style.FontHeight + TextPadding * 2; - ScrollGroup.ScrollY.MaxValue = Math.Max((int)Paragraph.Internal.Renderables[^1].YOffset + lastLineHeight - ScrollGroup.Height, 0); - ScrollGroup.ScrollY.ScrollToPos((int)Paragraph.InputInternal.CursorRenderOffset.Y, (int)Paragraph.InputInternal.CursorRenderOffset.Y + lastLineHeight - ScrollGroup.ScrollY.Value); - ScrollGroup.ScrollX.Clamp(); + ScrollGroup.YAxis.MaxValue = Math.Max((int)Paragraph.Internal.Renderables[^1].YOffset + lastLineHeight - ScrollGroup.Height, 0); + ScrollGroup.YAxis.ScrollToPos((int)Paragraph.InputInternal.CursorRenderOffset.Y, (int)Paragraph.InputInternal.CursorRenderOffset.Y + lastLineHeight - ScrollGroup.YAxis.Value); + ScrollGroup.XAxis.Clamp(); } /// Updates the values. @@ -365,7 +359,7 @@ public void TickMouse() return; } bool BarPressed(UIBox bar) => (bar?.IsPressed | bar?.SelfContains((int)View.Client.MouseX, (int)View.Client.MouseY)) ?? false; - if (BarPressed(ScrollGroup.ScrollX.ScrollBar) || BarPressed(ScrollGroup.ScrollY.ScrollBar)) + if (BarPressed(ScrollGroup.XAxis.ScrollBar) || BarPressed(ScrollGroup.YAxis.ScrollBar)) { return; } diff --git a/FGEGraphics/UISystem/UINumberInputLabel.cs b/FGEGraphics/UISystem/UINumberInputLabel.cs index 9b5365c2..561c13ff 100644 --- a/FGEGraphics/UISystem/UINumberInputLabel.cs +++ b/FGEGraphics/UISystem/UINumberInputLabel.cs @@ -82,11 +82,7 @@ public double Value /// The text to display when the input is empty. /// Whether to render a box behind the label. /// The padding between the box and the label. - /// The styles for the scroll bar. - /// The width of the scroll bar. - /// Whether to add a horizontal scroll bar. - /// The anchor of the horizontal scroll bar. - public UINumberInputLabel(bool integer, UIStyling styling, UIStyle inputStyle, UIStyle highlightStyle, UILayout layout, double defaultValue = 0, string format = null, string placeholderInfo = "", bool renderBox = false, int boxPadding = 0, UIInteractionStyles scrollBarStyles = null, int scrollBarWidth = 0, bool scrollBarX = false, UIAnchor scrollBarXAnchor = null) : base(placeholderInfo, placeholderInfo.Length == 0 ? defaultValue.ToString(format) : "", styling, inputStyle, highlightStyle, layout, false, renderBox, boxPadding, scrollBarStyles, scrollBarWidth, scrollBarX, false, scrollBarXAnchor, null) + public UINumberInputLabel(bool integer, UIStyling styling, UIStyle inputStyle, UIStyle highlightStyle, UILayout layout, double defaultValue = 0, string format = null, string placeholderInfo = "", bool renderBox = false, int boxPadding = 0) : base(placeholderInfo, placeholderInfo.Length == 0 ? defaultValue.ToString(format) : "", styling, inputStyle, highlightStyle, layout, false, renderBox, boxPadding) { Integer = integer; Format = format ?? (integer ? "0" : "0.0"); diff --git a/FGEGraphics/UISystem/UIScrollGroup.cs b/FGEGraphics/UISystem/UIScrollGroup.cs index d15b3486..96649886 100644 --- a/FGEGraphics/UISystem/UIScrollGroup.cs +++ b/FGEGraphics/UISystem/UIScrollGroup.cs @@ -26,58 +26,37 @@ public class UIScrollGroup : UIElement /// The horizontal scroll axis. [UIDebug] - public Axis ScrollX; + public ScrollAxis XAxis; /// The vertical scroll axis. [UIDebug] - public Axis ScrollY; - - /// The scrollable scissor layer for child elements. - public UIScissorGroup ScrollableLayer; + public ScrollAxis YAxis; /// The scroll bar layer (above the scissor layer). public UIGroup ScrollBarLayer; + /// The scrollable scissor layer for child elements. + public UIScissorGroup ScrollableLayer; + /// Whether either of the scroll bars are pressed. - public bool ScrollBarPressed => ScrollX.ScrollBar?.IsPressed ?? ScrollY.ScrollBar?.IsPressed ?? false; + public bool ScrollBarPressed => XAxis.ScrollBar?.IsPressed ?? YAxis.ScrollBar?.IsPressed ?? false; /// Constructs the UI scroll group. /// The layout of the element. - /// The scroll bar styles. - /// The width of the scroll bars. - /// Whether to add a horizontal scroll bar. - /// Whether to add a vertical scroll bar. - /// The anchor of the horizontal scroll bar. - /// The anchor of the vertical scroll bar. - public UIScrollGroup(UILayout layout, UIStyling barStyling = default, int barWidth = 0, bool barX = false, bool barY = false, UIAnchor barXAnchor = null, UIAnchor barYAnchor = null) : base(UIStyling.Empty, layout) + public UIScrollGroup(UILayout layout) : base(UIStyling.Empty, layout) { - if (barXAnchor?.AlignmentX == UIAlignment.CENTER || barYAnchor?.AlignmentY == UIAlignment.CENTER) - { - throw new Exception("UIScrollGroup scroll bars must have non-central scroll directions"); - } // TODO: Fix scroll bar overlap - ScrollX = new(false, () => Width/* - (barY ? barWidth : 0)*/, barX, barWidth, barStyling, new UILayout().SetAnchor(barXAnchor ?? UIAnchor.BOTTOM_LEFT)); - ScrollY = new(true, () => Height/* - (barX ? barWidth : 0)*/, barY, barWidth, barStyling, new UILayout().SetAnchor(barYAnchor ?? UIAnchor.TOP_RIGHT)); - if (ScrollX.ScrollBar is not null || ScrollY.ScrollBar is not null) - { - base.AddChild(ScrollBarLayer = new(layout.AtOrigin().SetSize(() => Width, () => Height))); - if (ScrollX.ScrollBar is not null) - { - ScrollBarLayer.AddChild(ScrollX.ScrollBar); - } - if (ScrollY.ScrollBar is not null) - { - ScrollBarLayer.AddChild(ScrollY.ScrollBar); - } - } - base.AddChild(ScrollableLayer = new UIScissorGroup(layout.AtOrigin().SetSize(() => Width, () => Height))); + XAxis = new(this, false); + YAxis = new(this, true); + AddChild(ScrollBarLayer = new UIGroup(layout.AtOrigin())); + AddChild(ScrollableLayer = new UIScissorGroup(layout.AtOrigin())); } /// public void AddScrollableChild(UIElement child) { UILayout original = new(child.Layout); - child.Layout.SetPosition(() => original.Internal.X.Get() - ScrollX.Value, () => original.Internal.Y.Get() - ScrollY.Value); + child.Layout.SetPosition(() => original.Internal.X.Get() - XAxis.Value, () => original.Internal.Y.Get() - YAxis.Value); ScrollableLayer.AddChild(child); } @@ -85,13 +64,13 @@ public void AddScrollableChild(UIElement child) public override void Tick(double delta) { base.Tick(delta); - if (ScrollX.ScrollBar is not null) + if (XAxis.ScrollBar is not null) { - ScrollX.TickMouseDrag(View.Client.MouseX, X); + XAxis.TickMouseDrag(View.Client.MouseX, X); } - if (ScrollY.ScrollBar is not null) + if (YAxis.ScrollBar is not null) { - ScrollY.TickMouseDrag(View.Client.MouseY, Y); + YAxis.TickMouseDrag(View.Client.MouseY, Y); } } @@ -100,8 +79,8 @@ public override void Navigated(int horizontal, int vertical) { if (!ScrollBarPressed) { - ScrollX.TickMouseScroll(horizontal); - ScrollY.TickMouseScroll(vertical); + XAxis.TickMouseScroll(horizontal); + YAxis.TickMouseScroll(vertical); } } @@ -112,28 +91,32 @@ public override bool MouseScrolled(float horizontal, float vertical) { return true; } - if (ScrollY.MaxValue == 0 || View.Client.Window.KeyboardState.IsKeyDown(Keys.LeftShift)) + if (YAxis.MaxValue == 0 || View.Client.Window.KeyboardState.IsKeyDown(Keys.LeftShift)) { horizontal = vertical; vertical = 0; } - ScrollX.TickMouseScroll(horizontal); - ScrollY.TickMouseScroll(vertical); + XAxis.TickMouseScroll(horizontal); + YAxis.TickMouseScroll(vertical); return true; } /// public override void ScaleChanged(float from, float to) { - ScrollX.Clamp(); - ScrollY.Clamp(); + XAxis.Clamp(); + YAxis.Clamp(); } /// Contains scroll state for a direction. /// Whether the direction is vertical or horizontal. - /// The length of the outer group's relevant dimension. - public class Axis(bool vertical, Func rangeLength) + /// The length of the outer group's relevant dimension. + public class ScrollAxis(UIScrollGroup scrollGroup, bool vertical) { + public UIScrollGroup ScrollGroup = scrollGroup; + + public bool Vertical = vertical; + /// The current scroll position. public int Value = 0; @@ -146,32 +129,33 @@ public class Axis(bool vertical, Func rangeLength) /// The scroll bar button, if any. public UIBox ScrollBar = null; + // TODO: maybe explain more? this is the mouse position relative to the bar position when first clicking / dragging it /// The held position offset of the scroll bar. public int BarHeldOffset = -1; /// The length of the outer group's relevant dimension. - public int RangeLength => rangeLength(); + public int Length => Vertical ? ScrollGroup.Height : ScrollGroup.Width; /// The length of the scroll bar. - public int BarLength => MaxValue > 0 ? (int)((double)RangeLength / (MaxValue + RangeLength) * RangeLength) : 0; + public int BarLength => MaxValue > 0 ? (int)((double)Length / (MaxValue + Length) * Length) : 0; /// The scroll bar's position offset. - public int BarPosition => MaxValue > 0 ? (int)((RangeLength - BarLength) * ((double)Value / MaxValue)) : 0; - - /// Constructs a scroll direction. - /// Whether the direction is vertical or horizontal. - /// The length of the outer group's relevant dimension. - /// Whether to create the . - /// The width of the . - /// The styles. - /// The base layout of the . - public Axis(bool vertical, Func rangeLength, bool hasBar, int width, UIStyling styling, UILayout layout) : this(vertical, rangeLength) + public int BarPosition => MaxValue > 0 ? (int)((Length - BarLength) * ((double)Value / MaxValue)) : 0; + + public void AddScrollBar(UIStyling styling, int width, UIAnchor anchor = null) { - if (!hasBar) + if ((Vertical && anchor?.AlignmentY == UIAlignment.CENTER) || (!Vertical && anchor?.AlignmentX == UIAlignment.CENTER)) { - return; + throw new Exception($"Tried to add a scroll bar with a central scroll direction: {anchor}"); } - if (vertical) + if (ScrollBar is not null) + { + throw new Exception("TODO"); + } + anchor ??= Vertical ? UIAnchor.TOP_RIGHT : UIAnchor.BOTTOM_LEFT; + UILayout layout = new UILayout().SetAnchor(anchor); + // TODO: this seems like a good "transpose" opportunity + if (Vertical) { layout.SetY(() => BarPosition).SetHeight(() => BarLength).SetWidth(() => (int)(width * ScrollBar.Scale)); } @@ -180,8 +164,16 @@ public Axis(bool vertical, Func rangeLength, bool hasBar, int width, UIStyl layout.SetX(() => BarPosition).SetWidth(() => BarLength).SetHeight(() => (int)(width * ScrollBar.Scale)); } ScrollBar = new(styling, layout) { ScaleSize = false }; + ScrollGroup.ScrollBarLayer.AddChild(ScrollBar); + } + + public void RemoveScrollBar() + { + ScrollGroup.ScrollBarLayer.RemoveChild(ScrollBar); + ScrollBar = null; } + // TODO: why is this needed?? /// Sets the and to 0. public void Reset() { @@ -198,6 +190,7 @@ public void Clamp() } } + // TODO: what does this do?? /// Scrolls to encompass a min/max offset pair. /// The min offset. /// The max offset. @@ -207,9 +200,9 @@ public void ScrollToPos(int min, int max) { Value = min; } - else if (max > RangeLength) + else if (max > Length) { - Value += max - RangeLength; + Value += max - Length; } } @@ -225,9 +218,9 @@ public void TickMouseDrag(float mousePos, int groupPos) } if (BarHeldOffset == -1) { - BarHeldOffset = (int)mousePos - (vertical ? ScrollBar.Y : ScrollBar.X); + BarHeldOffset = (int)mousePos - (Vertical ? ScrollBar.Y : ScrollBar.X); } - Value = (int)((double)(mousePos - groupPos - BarHeldOffset) / (RangeLength - BarLength) * MaxValue); + Value = (int)((double)(mousePos - groupPos - BarHeldOffset) / (Length - BarLength) * MaxValue); Clamp(); } From bc9b4acf527d4969e014f61d79e3eee0c9a84c49 Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Mon, 11 May 2026 17:04:54 -0700 Subject: [PATCH 03/19] document scroll group changes --- FGEGraphics/UISystem/UIScrollGroup.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/FGEGraphics/UISystem/UIScrollGroup.cs b/FGEGraphics/UISystem/UIScrollGroup.cs index 96649886..2dbbaff7 100644 --- a/FGEGraphics/UISystem/UIScrollGroup.cs +++ b/FGEGraphics/UISystem/UIScrollGroup.cs @@ -108,13 +108,15 @@ public override void ScaleChanged(float from, float to) YAxis.Clamp(); } - /// Contains scroll state for a direction. - /// Whether the direction is vertical or horizontal. - /// The length of the outer group's relevant dimension. + /// Represents the state of a scroll axis. + /// The parent scroll group of the axis. + /// Whether the axis is vertical or horizontal. public class ScrollAxis(UIScrollGroup scrollGroup, bool vertical) { + /// The parent scroll group of the axis. public UIScrollGroup ScrollGroup = scrollGroup; + /// Whether the axis is vertical or horizontal. public bool Vertical = vertical; /// The current scroll position. @@ -142,6 +144,10 @@ public class ScrollAxis(UIScrollGroup scrollGroup, bool vertical) /// The scroll bar's position offset. public int BarPosition => MaxValue > 0 ? (int)((Length - BarLength) * ((double)Value / MaxValue)) : 0; + /// Adds a scroll bar to the parent that can be dragged by the mouse. + /// The styling logic of the scroll bar. + /// The width of the scroll bar (where the length aligns with the scroll direction). + /// The positional anchor of the scroll bar. public void AddScrollBar(UIStyling styling, int width, UIAnchor anchor = null) { if ((Vertical && anchor?.AlignmentY == UIAlignment.CENTER) || (!Vertical && anchor?.AlignmentX == UIAlignment.CENTER)) @@ -167,6 +173,7 @@ public void AddScrollBar(UIStyling styling, int width, UIAnchor anchor = null) ScrollGroup.ScrollBarLayer.AddChild(ScrollBar); } + /// Removes the from the parent . public void RemoveScrollBar() { ScrollGroup.ScrollBarLayer.RemoveChild(ScrollBar); From 15e2da9b39a40491007f83c5724824ba156f963f Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Mon, 11 May 2026 17:37:02 -0700 Subject: [PATCH 04/19] add UILayout.Transpose, Copy methods --- FGEGraphics/UISystem/UIDropdown.cs | 4 ++-- FGEGraphics/UISystem/UILabel.cs | 2 +- FGEGraphics/UISystem/UILayout.cs | 15 +++++++++++++-- FGEGraphics/UISystem/UINumberSlider.cs | 2 +- FGEGraphics/UISystem/UIScrollGroup.cs | 19 ++++++++----------- FGEGraphics/UISystem/UIToggleBox.cs | 2 +- 6 files changed, 26 insertions(+), 18 deletions(-) diff --git a/FGEGraphics/UISystem/UIDropdown.cs b/FGEGraphics/UISystem/UIDropdown.cs index 7f3660dc..57e3448e 100644 --- a/FGEGraphics/UISystem/UIDropdown.cs +++ b/FGEGraphics/UISystem/UIDropdown.cs @@ -76,8 +76,8 @@ public struct InternalData() public UIDropdown(int boxPadding, int listSpacing, UIStyling buttonStyling, UIStyling boxStyling, UILayout layout, string text = null, UIElement layer = null) : base(buttonStyling, layout) { PlaceholderInfo = text ?? "null"; - AddChild(Button = new UIBox(buttonStyling, layout.AtOrigin(), text) { OnClick = Open }); - Box = new UIBox(boxStyling, layout.AtOrigin()); + AddChild(Button = new UIBox(buttonStyling, layout.Copy().SetOrigin(), text) { OnClick = Open }); + Box = new UIBox(boxStyling, layout.Copy().SetOrigin()); Box.AddChild(Entries = new UIListGroup(listSpacing, new UILayout().SetAnchor(UIAnchor.TOP_CENTER).SetPosition(0, boxPadding))); Box.Layout.SetHeight(() => Entries.Layout.Height + boxPadding * 2); Internal.Layer = layer ?? this; diff --git a/FGEGraphics/UISystem/UILabel.cs b/FGEGraphics/UISystem/UILabel.cs index c50a37c1..e92c9e7a 100644 --- a/FGEGraphics/UISystem/UILabel.cs +++ b/FGEGraphics/UISystem/UILabel.cs @@ -187,7 +187,7 @@ public override void Render(double delta, UIStyle style) public static (UILabel Label, UIImage Icon, UIListGroup List) WithIcon(string text, Texture icon, int spacing, UIStyling styling, UILayout layout, UIAnchor listAnchor = null) { UIListGroup list = new(spacing, layout, vertical: false, anchor: listAnchor ?? UIAnchor.TOP_LEFT); - UILabel label = new(text, styling, layout.AtOrigin()); + UILabel label = new(text, styling, layout.Copy().SetOrigin()); UIImage image = new(icon, new UILayout().SetSize(() => label.Height, () => label.Height)); list.AddListItem(label); list.AddListItem(image); diff --git a/FGEGraphics/UISystem/UILayout.cs b/FGEGraphics/UISystem/UILayout.cs index 077e14eb..cd9b162d 100644 --- a/FGEGraphics/UISystem/UILayout.cs +++ b/FGEGraphics/UISystem/UILayout.cs @@ -68,6 +68,10 @@ public UILayout(UILayout layout) Internal = layout.Internal; } + /// Returns a copy of this layout. + /// + public UILayout Copy() => new(this); + /// Sets the anchor. /// This object. public UILayout SetAnchor(UIAnchor anchor) @@ -244,8 +248,15 @@ public UILayout SetScale(Func scale) /// This object. public UILayout SetOrigin() => SetAnchor(UIAnchor.TOP_LEFT).SetPosition(0, 0); - /// Returns a copy of this layout fixed at the top-left origin. - public UILayout AtOrigin() => new UILayout(this).SetOrigin(); + /// Flips X and Y, width and height, and the rotation direction. + /// This object. + public UILayout Transpose() + { + (Internal.X, Internal.Y) = (Internal.Y, Internal.X); + (Internal.Width, Internal.Height) = (Internal.Height, Internal.Width); + // TODO: flip the sign of rotation efficiently + return this; + } /// Gets the relative X value. public int X => Internal.X.Get() + (Element.Parent != null ? Anchor.GetX(Element) : 0); diff --git a/FGEGraphics/UISystem/UINumberSlider.cs b/FGEGraphics/UISystem/UINumberSlider.cs index f223e375..4230068f 100644 --- a/FGEGraphics/UISystem/UINumberSlider.cs +++ b/FGEGraphics/UISystem/UINumberSlider.cs @@ -82,7 +82,7 @@ public UINumberSlider(double min, double max, double defaultValue, double interv Max = Min + Interval * maxStep; } Value = Math.Clamp(Integer ? (int)Default : Default, Min, Max); // TODO: is this correct? - AddChild(Button = new(UIStyle.Empty, layout.AtOrigin().SetWidth(layout.Height / 2)) { RenderSelf = false, IsEnabled = false }); + AddChild(Button = new(UIStyle.Empty, layout.Copy().SetOrigin().SetWidth(layout.Height / 2)) { RenderSelf = false, IsEnabled = false }); Button.Layout.SetX(() => (int)(Progress * Width) - Button.Width / 2); } diff --git a/FGEGraphics/UISystem/UIScrollGroup.cs b/FGEGraphics/UISystem/UIScrollGroup.cs index 2dbbaff7..fb7f88f9 100644 --- a/FGEGraphics/UISystem/UIScrollGroup.cs +++ b/FGEGraphics/UISystem/UIScrollGroup.cs @@ -48,8 +48,8 @@ public UIScrollGroup(UILayout layout) : base(UIStyling.Empty, layout) // TODO: Fix scroll bar overlap XAxis = new(this, false); YAxis = new(this, true); - AddChild(ScrollBarLayer = new UIGroup(layout.AtOrigin())); - AddChild(ScrollableLayer = new UIScissorGroup(layout.AtOrigin())); + AddChild(ScrollBarLayer = new UIGroup(layout.Copy().SetOrigin())); + AddChild(ScrollableLayer = new UIScissorGroup(layout.Copy().SetOrigin())); } /// @@ -158,16 +158,13 @@ public void AddScrollBar(UIStyling styling, int width, UIAnchor anchor = null) { throw new Exception("TODO"); } - anchor ??= Vertical ? UIAnchor.TOP_RIGHT : UIAnchor.BOTTOM_LEFT; - UILayout layout = new UILayout().SetAnchor(anchor); - // TODO: this seems like a good "transpose" opportunity - if (Vertical) + UILayout layout = new UILayout() + .SetAnchor(anchor ?? (Vertical ? UIAnchor.TOP_RIGHT : UIAnchor.BOTTOM_LEFT)) + .SetY(() => BarPosition).SetHeight(() => BarLength) + .SetWidth(() => (int)(width * ScrollBar.Scale)); + if (!Vertical) { - layout.SetY(() => BarPosition).SetHeight(() => BarLength).SetWidth(() => (int)(width * ScrollBar.Scale)); - } - else - { - layout.SetX(() => BarPosition).SetWidth(() => BarLength).SetHeight(() => (int)(width * ScrollBar.Scale)); + layout.Transpose(); } ScrollBar = new(styling, layout) { ScaleSize = false }; ScrollGroup.ScrollBarLayer.AddChild(ScrollBar); diff --git a/FGEGraphics/UISystem/UIToggleBox.cs b/FGEGraphics/UISystem/UIToggleBox.cs index ac2cb157..50e1ead1 100644 --- a/FGEGraphics/UISystem/UIToggleBox.cs +++ b/FGEGraphics/UISystem/UIToggleBox.cs @@ -69,7 +69,7 @@ public void Toggle() /// A tuple of the toggle box, label, and their list container. public static (UIToggleBox Box, UILabel Label, UIListGroup List) WithLabel(string text, int spacing, UIStyling styling, UILayout layout, bool toggled = false, UIStyling labelStyling = default, UIAnchor listAnchor = null) { - UIToggleBox box = new(styling, layout.AtOrigin(), toggled); + UIToggleBox box = new(styling, layout.Copy().SetOrigin(), toggled); UIListGroup list = new(spacing, layout, vertical: false, anchor: listAnchor ?? UIAnchor.TOP_LEFT); UILabel label = new(text, labelStyling.IsEmpty ? styling.Bind(box) : labelStyling, new UILayout()); list.AddListItem(box); From 050007562ff578d7ecacad0058d45dfe21f2528a Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Mon, 11 May 2026 18:14:53 -0700 Subject: [PATCH 05/19] pull back on UIInputLabel --- FGEGraphics/UISystem/UIInputLabel.cs | 28 ++++------------------ FGEGraphics/UISystem/UINumberInputLabel.cs | 4 +--- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/FGEGraphics/UISystem/UIInputLabel.cs b/FGEGraphics/UISystem/UIInputLabel.cs index 8ab8fedc..3cea7f84 100644 --- a/FGEGraphics/UISystem/UIInputLabel.cs +++ b/FGEGraphics/UISystem/UIInputLabel.cs @@ -28,7 +28,7 @@ namespace FGEGraphics.UISystem; // TODO: Text alignment // TODO: Cap text length // TODO: HasEdited -public class UIInputLabel : UIElement +public class UIInputLabel : UIBox { /// An enumeration of operations. public enum EditType @@ -51,7 +51,7 @@ public struct Styles(UIInteractionStyles styles) /// The styling logic for an input label. public readonly UIStyle Styling(UIElement element) => element.IsFocused ? styles.Press : styles.Styling(element); - /// Calls . + /// Calls . public static implicit operator UIStyling(Styles styles) => new(styles.Styling); } @@ -61,9 +61,6 @@ public struct Styles(UIInteractionStyles styles) /// The paragraph to display the state of this input label. public UIInputParagraph Paragraph; - /// The box behind the input label. - public UIBox Box = null; - /// The scroll group containing the label text. public UIScrollGroup ScrollGroup; @@ -102,14 +99,11 @@ public string Content public int Lines => Paragraph.Internal.Renderables.Sum(piece => piece.Text.Lines.Length); /// The padding offset for the rendered text, if any. - public int TextPadding => Box is not null ? (Internal.BoxPadding - ElementInternal.Style.BorderThickness) : 0; + public int TextPadding => /*Box is not null ? (Internal.BoxPadding - ElementInternal.Style.BorderThickness) :*/ 0; /// Data internal to a instance. public struct InternalData() { - /// The padding between the and the label. - public int BoxPadding = 0; - /// Whether to enforce max width or use a horizontal scroll group. public bool HasMaxWidth; } @@ -122,21 +116,9 @@ public struct InternalData() /// The style of highlighted input content. /// The layout of the element. /// Whether to enforce a max width. If false, will use horizontal scrolling. - /// Whether to render a box behind the input label. - /// The padding between the box and the label. - public UIInputLabel(string placeholderInfo, string defaultText, UIStyling styling, UIStyling inputStyling, UIStyling highlightStyling, UILayout layout, bool maxWidth = true, bool renderBox = false, int boxPadding = 0) : base(styling, layout) + public UIInputLabel(string placeholderInfo, string defaultText, UIStyling styling, UIStyling inputStyling, UIStyling highlightStyling, UILayout layout, bool maxWidth = true) : base(styling, layout) { - if (renderBox) - { - Internal.BoxPadding = boxPadding; - // TODO: properly handle padding - //UILayout baseLayout = new(layout); - //layout.SetSize(() => baseLayout.Width + boxPadding * 2, () => baseLayout.Height + boxPadding * 2); - AddChild(Box = new(styling.Bind(this), new UILayout().SetSize(() => layout.Width, () => layout.Height)) { IsEnabled = false }); - } - int Inset() => Box is not null ? ElementInternal.Style.BorderThickness : 0; // there should definitely be a system for this - UILayout scrollGroupLayout = new UILayout().SetPosition(Inset, Inset).SetSize(() => layout.Width - Inset() * 2, () => layout.Height - Inset() * 2); - ScrollGroup = new(scrollGroupLayout) { IsEnabled = false }; + ScrollGroup = new(layout.Copy().SetOrigin()) { IsEnabled = false }; ScrollGroup.AddScrollableChild(PlaceholderInfo = new UILabel(placeholderInfo, styling.Bind(this), new UILayout().SetPosition(() => TextPadding, () => TextPadding)) { IsEnabled = false }); ScrollGroup.AddScrollableChild(Paragraph = new UIInputParagraph(highlightStyling, inputStyling, highlightStyling, new UILayout().SetPosition(() => TextPadding, () => TextPadding)) { IsEnabled = false }); AddChild(ScrollGroup); diff --git a/FGEGraphics/UISystem/UINumberInputLabel.cs b/FGEGraphics/UISystem/UINumberInputLabel.cs index 561c13ff..e5f38a76 100644 --- a/FGEGraphics/UISystem/UINumberInputLabel.cs +++ b/FGEGraphics/UISystem/UINumberInputLabel.cs @@ -80,9 +80,7 @@ public double Value /// The default number value. /// The format string for the label. /// The text to display when the input is empty. - /// Whether to render a box behind the label. - /// The padding between the box and the label. - public UINumberInputLabel(bool integer, UIStyling styling, UIStyle inputStyle, UIStyle highlightStyle, UILayout layout, double defaultValue = 0, string format = null, string placeholderInfo = "", bool renderBox = false, int boxPadding = 0) : base(placeholderInfo, placeholderInfo.Length == 0 ? defaultValue.ToString(format) : "", styling, inputStyle, highlightStyle, layout, false, renderBox, boxPadding) + public UINumberInputLabel(bool integer, UIStyling styling, UIStyle inputStyle, UIStyle highlightStyle, UILayout layout, double defaultValue = 0, string format = null, string placeholderInfo = "") : base(placeholderInfo, placeholderInfo.Length == 0 ? defaultValue.ToString(format) : "", styling, inputStyle, highlightStyle, layout, false) { Integer = integer; Format = format ?? (integer ? "0" : "0.0"); From dd8989c3f5d36bc8421ad9cc8941e70c0d75d777 Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Thu, 14 May 2026 17:18:19 -0700 Subject: [PATCH 06/19] add UIElement.RenderMode --- FGEGraphics/UISystem/UIElement.cs | 16 +++++++++++----- FGEGraphics/UISystem/UIInputLabel.cs | 4 ++-- FGEGraphics/UISystem/UINumberSlider.cs | 2 +- FGEGraphics/UISystem/UIParagraph.cs | 2 +- FGEGraphics/UISystem/UIRenderMode.cs | 22 ++++++++++++++++++++++ 5 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 FGEGraphics/UISystem/UIRenderMode.cs diff --git a/FGEGraphics/UISystem/UIElement.cs b/FGEGraphics/UISystem/UIElement.cs index b60b6871..a2ad25d7 100644 --- a/FGEGraphics/UISystem/UIElement.cs +++ b/FGEGraphics/UISystem/UIElement.cs @@ -99,7 +99,7 @@ public abstract class UIElement public object Tag = null; /// Whether this element should render itself. If false, may be called manually. - public bool RenderSelf = true; + public UIRenderMode RenderMode = UIRenderMode.FULL; /// The debug name of this element. public virtual string Name { get; set; } = null; @@ -637,13 +637,19 @@ public virtual void Render(double delta, UIStyle style) public virtual void RenderAll(double delta) { GraphicsUtil.CheckError("UIElement - PreRender"); - Render(delta); + if (RenderMode == UIRenderMode.FULL) + { + Render(delta); + } GraphicsUtil.CheckError("UIElement - PostRenderSelf", this); - foreach (UIElement child in ElementInternal.Children) + if (RenderMode != UIRenderMode.NONE) { - if (child.IsValid && child.RenderSelf) + foreach (UIElement child in ElementInternal.Children) { - child.RenderAll(delta); + if (child.IsValid) + { + child.RenderAll(delta); + } } } GraphicsUtil.CheckError("UIElement - PostRenderAll", this); diff --git a/FGEGraphics/UISystem/UIInputLabel.cs b/FGEGraphics/UISystem/UIInputLabel.cs index 3cea7f84..58eeb703 100644 --- a/FGEGraphics/UISystem/UIInputLabel.cs +++ b/FGEGraphics/UISystem/UIInputLabel.cs @@ -148,7 +148,7 @@ public override void Unfocused() Paragraph.UpdateRenderState(); ScrollGroup.XAxis.Reset(); ScrollGroup.YAxis.Reset(); - PlaceholderInfo.RenderSelf = Content.Length == 0; + PlaceholderInfo.RenderMode = Content.Length == 0 ? UIRenderMode.FULL : UIRenderMode.NONE; } // FIXME: Paragraph.Width still retains last value when deleting all, incorrect MaxValue calculation @@ -193,7 +193,7 @@ public void UpdateRenderState() { Paragraph.UpdateRenderState(); UpdateScrollGroup(); - PlaceholderInfo.RenderSelf = Content.Length == 0; + PlaceholderInfo.RenderMode = Content.Length == 0 ? UIRenderMode.FULL : UIRenderMode.NONE; } /// Performs a user edit on the text content. diff --git a/FGEGraphics/UISystem/UINumberSlider.cs b/FGEGraphics/UISystem/UINumberSlider.cs index 4230068f..8de79597 100644 --- a/FGEGraphics/UISystem/UINumberSlider.cs +++ b/FGEGraphics/UISystem/UINumberSlider.cs @@ -82,7 +82,7 @@ public UINumberSlider(double min, double max, double defaultValue, double interv Max = Min + Interval * maxStep; } Value = Math.Clamp(Integer ? (int)Default : Default, Min, Max); // TODO: is this correct? - AddChild(Button = new(UIStyle.Empty, layout.Copy().SetOrigin().SetWidth(layout.Height / 2)) { RenderSelf = false, IsEnabled = false }); + AddChild(Button = new(UIStyle.Empty, layout.Copy().SetOrigin().SetWidth(layout.Height / 2)) { RenderMode = UIRenderMode.NONE, IsEnabled = false }); Button.Layout.SetX(() => (int)(Progress * Width) - Button.Width / 2); } diff --git a/FGEGraphics/UISystem/UIParagraph.cs b/FGEGraphics/UISystem/UIParagraph.cs index 8a168bb9..59bad954 100644 --- a/FGEGraphics/UISystem/UIParagraph.cs +++ b/FGEGraphics/UISystem/UIParagraph.cs @@ -62,7 +62,7 @@ public void AddLabel(UILabel label) { Labels.Add(label); AddChild(label); - label.RenderSelf = false; + label.RenderMode = UIRenderMode.NONE; //label.Internal.OnRenderablesUpdate += UpdateRenderables; } diff --git a/FGEGraphics/UISystem/UIRenderMode.cs b/FGEGraphics/UISystem/UIRenderMode.cs new file mode 100644 index 00000000..642df3a3 --- /dev/null +++ b/FGEGraphics/UISystem/UIRenderMode.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +// +// This file is part of the Frenetic Game Engine, created by Frenetic LLC. +// This code is Copyright (C) Frenetic LLC under the terms of a strict license. +// See README.md or LICENSE.txt in the FreneticGameEngine source root for the contents of the license. +// If neither of these are available, assume that neither you nor anyone other than the copyright holder +// hold any right or permission to use this software until such time as the official license is identified. +// + +namespace FGEGraphics.UISystem; + +public enum UIRenderMode +{ + FULL, + SKIP_SELF, + NONE +} From 504a1ca81ab43ff15fa18223c8238d809ed7af0b Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Thu, 14 May 2026 17:18:56 -0700 Subject: [PATCH 07/19] naive UILayout.Container --- FGEGraphics/UISystem/UIDropdown.cs | 4 ++-- FGEGraphics/UISystem/UIInputLabel.cs | 2 +- FGEGraphics/UISystem/UILayout.cs | 3 +++ FGEGraphics/UISystem/UIScrollGroup.cs | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/FGEGraphics/UISystem/UIDropdown.cs b/FGEGraphics/UISystem/UIDropdown.cs index 57e3448e..7a13c6e9 100644 --- a/FGEGraphics/UISystem/UIDropdown.cs +++ b/FGEGraphics/UISystem/UIDropdown.cs @@ -76,8 +76,8 @@ public struct InternalData() public UIDropdown(int boxPadding, int listSpacing, UIStyling buttonStyling, UIStyling boxStyling, UILayout layout, string text = null, UIElement layer = null) : base(buttonStyling, layout) { PlaceholderInfo = text ?? "null"; - AddChild(Button = new UIBox(buttonStyling, layout.Copy().SetOrigin(), text) { OnClick = Open }); - Box = new UIBox(boxStyling, layout.Copy().SetOrigin()); + AddChild(Button = new UIBox(buttonStyling, layout.Container(), text) { OnClick = Open }); + Box = new UIBox(boxStyling, layout.Container()); Box.AddChild(Entries = new UIListGroup(listSpacing, new UILayout().SetAnchor(UIAnchor.TOP_CENTER).SetPosition(0, boxPadding))); Box.Layout.SetHeight(() => Entries.Layout.Height + boxPadding * 2); Internal.Layer = layer ?? this; diff --git a/FGEGraphics/UISystem/UIInputLabel.cs b/FGEGraphics/UISystem/UIInputLabel.cs index 58eeb703..8baa4ae8 100644 --- a/FGEGraphics/UISystem/UIInputLabel.cs +++ b/FGEGraphics/UISystem/UIInputLabel.cs @@ -118,7 +118,7 @@ public struct InternalData() /// Whether to enforce a max width. If false, will use horizontal scrolling. public UIInputLabel(string placeholderInfo, string defaultText, UIStyling styling, UIStyling inputStyling, UIStyling highlightStyling, UILayout layout, bool maxWidth = true) : base(styling, layout) { - ScrollGroup = new(layout.Copy().SetOrigin()) { IsEnabled = false }; + ScrollGroup = new(layout.Container()) { IsEnabled = false }; ScrollGroup.AddScrollableChild(PlaceholderInfo = new UILabel(placeholderInfo, styling.Bind(this), new UILayout().SetPosition(() => TextPadding, () => TextPadding)) { IsEnabled = false }); ScrollGroup.AddScrollableChild(Paragraph = new UIInputParagraph(highlightStyling, inputStyling, highlightStyling, new UILayout().SetPosition(() => TextPadding, () => TextPadding)) { IsEnabled = false }); AddChild(ScrollGroup); diff --git a/FGEGraphics/UISystem/UILayout.cs b/FGEGraphics/UISystem/UILayout.cs index cd9b162d..4e4ba621 100644 --- a/FGEGraphics/UISystem/UILayout.cs +++ b/FGEGraphics/UISystem/UILayout.cs @@ -258,6 +258,9 @@ public UILayout Transpose() return this; } + // TODO: what else to inherit? + public UILayout Container() => new UILayout().SetSize(() => Width, () => Height); + /// Gets the relative X value. public int X => Internal.X.Get() + (Element.Parent != null ? Anchor.GetX(Element) : 0); diff --git a/FGEGraphics/UISystem/UIScrollGroup.cs b/FGEGraphics/UISystem/UIScrollGroup.cs index fb7f88f9..3e903e98 100644 --- a/FGEGraphics/UISystem/UIScrollGroup.cs +++ b/FGEGraphics/UISystem/UIScrollGroup.cs @@ -48,8 +48,8 @@ public UIScrollGroup(UILayout layout) : base(UIStyling.Empty, layout) // TODO: Fix scroll bar overlap XAxis = new(this, false); YAxis = new(this, true); - AddChild(ScrollBarLayer = new UIGroup(layout.Copy().SetOrigin())); - AddChild(ScrollableLayer = new UIScissorGroup(layout.Copy().SetOrigin())); + AddChild(ScrollBarLayer = new UIGroup(layout.Container())); + AddChild(ScrollableLayer = new UIScissorGroup(layout.Container())); } /// From 585f029b987bbfe45d1f24ba3825daedcd1c8543 Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Thu, 14 May 2026 18:12:18 -0700 Subject: [PATCH 08/19] element container inset --- FGEGraphics/UISystem/UIElement.cs | 1 + FGEGraphics/UISystem/UIInputLabel.cs | 3 +++ FGEGraphics/UISystem/UIInteractionStyles.cs | 4 ++++ FGEGraphics/UISystem/UILayout.cs | 4 +++- FGEGraphics/UISystem/UIStyle.cs | 8 ++++++++ 5 files changed, 19 insertions(+), 1 deletion(-) diff --git a/FGEGraphics/UISystem/UIElement.cs b/FGEGraphics/UISystem/UIElement.cs index a2ad25d7..1194a68f 100644 --- a/FGEGraphics/UISystem/UIElement.cs +++ b/FGEGraphics/UISystem/UIElement.cs @@ -373,6 +373,7 @@ public void SetStyle(UIStyle style) { return; } + ElementInternal.Style = style; ElementInternal.Styles.Add(style); StyleChanged(previousStyle, ElementInternal.Style = style); OnStyleChange?.Invoke(previousStyle, style); diff --git a/FGEGraphics/UISystem/UIInputLabel.cs b/FGEGraphics/UISystem/UIInputLabel.cs index 8baa4ae8..3338c397 100644 --- a/FGEGraphics/UISystem/UIInputLabel.cs +++ b/FGEGraphics/UISystem/UIInputLabel.cs @@ -44,10 +44,13 @@ public enum EditType SUBMIT } + // TODO: rethink /// Wraps a instance with logic specific to input labels. /// The base interaction styles. public struct Styles(UIInteractionStyles styles) { + public UIInteractionStyles BaseStyles = styles; + /// The styling logic for an input label. public readonly UIStyle Styling(UIElement element) => element.IsFocused ? styles.Press : styles.Styling(element); diff --git a/FGEGraphics/UISystem/UIInteractionStyles.cs b/FGEGraphics/UISystem/UIInteractionStyles.cs index 7134efa2..43420718 100644 --- a/FGEGraphics/UISystem/UIInteractionStyles.cs +++ b/FGEGraphics/UISystem/UIInteractionStyles.cs @@ -36,6 +36,10 @@ public class UIInteractionStyles(UIStyle normal, UIStyle hover, UIStyle press, U /// The style of a disabled element. public UIStyle Disabled = disabled; + // TODO: should this also be a record + public UIInteractionStyles(UIInteractionStyles styles) : this(styles.Normal, styles.Hover, styles.Press, styles.Disabled) + { } + /// The styling logic for an interactable element. public virtual UIStyle Styling(UIElement element) => element.IsPressed ? Press : element.IsHovered ? Hover diff --git a/FGEGraphics/UISystem/UILayout.cs b/FGEGraphics/UISystem/UILayout.cs index 4e4ba621..a03c14dd 100644 --- a/FGEGraphics/UISystem/UILayout.cs +++ b/FGEGraphics/UISystem/UILayout.cs @@ -259,7 +259,9 @@ public UILayout Transpose() } // TODO: what else to inherit? - public UILayout Container() => new UILayout().SetSize(() => Width, () => Height); + public UILayout Container() => new UILayout() + .SetPosition(() => Element.Style.Inset, () => Element.Style.Inset) + .SetSize(() => Width - Element.Style.Inset * 2, () => Height - Element.Style.Inset * 2); /// Gets the relative X value. public int X => Internal.X.Get() + (Element.Parent != null ? Anchor.GetX(Element) : 0); diff --git a/FGEGraphics/UISystem/UIStyle.cs b/FGEGraphics/UISystem/UIStyle.cs index 2a9f9b82..9ab65173 100644 --- a/FGEGraphics/UISystem/UIStyle.cs +++ b/FGEGraphics/UISystem/UIStyle.cs @@ -36,6 +36,8 @@ public record UIStyle /// How thick the border outline should be (or 0 for none). public int BorderThickness = 0; + public int Padding = 0; + /// How big the drop-shadow effect should be (or 0 for none). public int DropShadowLength = 0; @@ -57,6 +59,9 @@ public UIStyle() { } + // TODO: since this is a record, we can simply do: + // style with { } + // TODO: determine whether UIStyle should stay a record /// Constructs a new style as a copy of another style. /// The style to copy. public UIStyle(UIStyle style) @@ -65,12 +70,15 @@ public UIStyle(UIStyle style) BaseTexture = style.BaseTexture; BorderColor = style.BorderColor; BorderThickness = style.BorderThickness; + Padding = style.Padding; DropShadowLength = style.DropShadowLength; TextFont = style.TextFont; TextStyling = style.TextStyling; TextBaseColor = style.TextBaseColor; } + public int Inset => BorderThickness + Padding; + /// Returns the font height, or 0 if is null. public int FontHeight => TextFont?.Height ?? 0; From 0b09acd728e3cdbbe8ff4dc9dcf35bbdf207a66c Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Mon, 18 May 2026 15:43:09 -0700 Subject: [PATCH 09/19] remove maxwidth from uiinputlabel --- FGEGraphics/UISystem/UIInputLabel.cs | 17 ++++++----------- FGEGraphics/UISystem/UINumberInputLabel.cs | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/FGEGraphics/UISystem/UIInputLabel.cs b/FGEGraphics/UISystem/UIInputLabel.cs index 3338c397..5ba534d0 100644 --- a/FGEGraphics/UISystem/UIInputLabel.cs +++ b/FGEGraphics/UISystem/UIInputLabel.cs @@ -101,9 +101,6 @@ public string Content /// The current number of input text lines. public int Lines => Paragraph.Internal.Renderables.Sum(piece => piece.Text.Lines.Length); - /// The padding offset for the rendered text, if any. - public int TextPadding => /*Box is not null ? (Internal.BoxPadding - ElementInternal.Style.BorderThickness) :*/ 0; - /// Data internal to a instance. public struct InternalData() { @@ -118,14 +115,12 @@ public struct InternalData() /// The style of normal input content. /// The style of highlighted input content. /// The layout of the element. - /// Whether to enforce a max width. If false, will use horizontal scrolling. - public UIInputLabel(string placeholderInfo, string defaultText, UIStyling styling, UIStyling inputStyling, UIStyling highlightStyling, UILayout layout, bool maxWidth = true) : base(styling, layout) + public UIInputLabel(string placeholderInfo, string defaultText, UIStyling styling, UIStyling inputStyling, UIStyling highlightStyling, UILayout layout) : base(styling, layout) { ScrollGroup = new(layout.Container()) { IsEnabled = false }; - ScrollGroup.AddScrollableChild(PlaceholderInfo = new UILabel(placeholderInfo, styling.Bind(this), new UILayout().SetPosition(() => TextPadding, () => TextPadding)) { IsEnabled = false }); - ScrollGroup.AddScrollableChild(Paragraph = new UIInputParagraph(highlightStyling, inputStyling, highlightStyling, new UILayout().SetPosition(() => TextPadding, () => TextPadding)) { IsEnabled = false }); + ScrollGroup.AddScrollableChild(PlaceholderInfo = new UILabel(placeholderInfo, styling.Bind(this), new UILayout()) { IsEnabled = false }); + ScrollGroup.AddScrollableChild(Paragraph = new UIInputParagraph(highlightStyling, inputStyling, highlightStyling, new UILayout()) { IsEnabled = false }); AddChild(ScrollGroup); - Internal.HasMaxWidth = maxWidth; Paragraph.SetContent(defaultText); } @@ -162,8 +157,8 @@ public void UpdateScrollGroupX() { return; } - ScrollGroup.XAxis.MaxValue = Math.Max(Paragraph.Width + TextPadding * 2 - ScrollGroup.Width, 0); - ScrollGroup.XAxis.ScrollToPos((int)Paragraph.InputInternal.CursorRenderOffset.X, (int)Paragraph.InputInternal.CursorRenderOffset.X + TextPadding * 2 - ScrollGroup.XAxis.Value); + ScrollGroup.XAxis.MaxValue = Math.Max(Paragraph.Width - ScrollGroup.Width, 0); + ScrollGroup.XAxis.ScrollToPos((int)Paragraph.InputInternal.CursorRenderOffset.X, (int)Paragraph.InputInternal.CursorRenderOffset.X - ScrollGroup.XAxis.Value); ScrollGroup.XAxis.Clamp(); } @@ -175,7 +170,7 @@ public void UpdateScrollGroupY() ScrollGroup.YAxis.Reset(); return; } - int lastLineHeight = Paragraph.InputInternal.LabelRight.Style.FontHeight + TextPadding * 2; + int lastLineHeight = Paragraph.InputInternal.LabelRight.Style.FontHeight; ScrollGroup.YAxis.MaxValue = Math.Max((int)Paragraph.Internal.Renderables[^1].YOffset + lastLineHeight - ScrollGroup.Height, 0); ScrollGroup.YAxis.ScrollToPos((int)Paragraph.InputInternal.CursorRenderOffset.Y, (int)Paragraph.InputInternal.CursorRenderOffset.Y + lastLineHeight - ScrollGroup.YAxis.Value); ScrollGroup.XAxis.Clamp(); diff --git a/FGEGraphics/UISystem/UINumberInputLabel.cs b/FGEGraphics/UISystem/UINumberInputLabel.cs index e5f38a76..951400cc 100644 --- a/FGEGraphics/UISystem/UINumberInputLabel.cs +++ b/FGEGraphics/UISystem/UINumberInputLabel.cs @@ -80,7 +80,7 @@ public double Value /// The default number value. /// The format string for the label. /// The text to display when the input is empty. - public UINumberInputLabel(bool integer, UIStyling styling, UIStyle inputStyle, UIStyle highlightStyle, UILayout layout, double defaultValue = 0, string format = null, string placeholderInfo = "") : base(placeholderInfo, placeholderInfo.Length == 0 ? defaultValue.ToString(format) : "", styling, inputStyle, highlightStyle, layout, false) + public UINumberInputLabel(bool integer, UIStyling styling, UIStyle inputStyle, UIStyle highlightStyle, UILayout layout, double defaultValue = 0, string format = null, string placeholderInfo = "") : base(placeholderInfo, placeholderInfo.Length == 0 ? defaultValue.ToString(format) : "", styling, inputStyle, highlightStyle, layout) { Integer = integer; Format = format ?? (integer ? "0" : "0.0"); From eb32f81019709656b45447b7318eb92792e56c92 Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Mon, 18 May 2026 17:04:06 -0700 Subject: [PATCH 10/19] dismantle style-based uilabel cache --- .../GraphicsHelpers/FontSets/FontSetEngine.cs | 19 ++++++ FGEGraphics/UISystem/UIElement.cs | 8 +-- FGEGraphics/UISystem/UILabel.cs | 62 ++++++------------- FGEGraphics/UISystem/UIParagraph.cs | 4 +- 4 files changed, 45 insertions(+), 48 deletions(-) diff --git a/FGEGraphics/GraphicsHelpers/FontSets/FontSetEngine.cs b/FGEGraphics/GraphicsHelpers/FontSets/FontSetEngine.cs index ecee5dad..2dad67db 100644 --- a/FGEGraphics/GraphicsHelpers/FontSets/FontSetEngine.cs +++ b/FGEGraphics/GraphicsHelpers/FontSets/FontSetEngine.cs @@ -44,6 +44,8 @@ public class FontSetEngine(GLFontEngine fontEngine) /// A list of all currently loaded font sets. public Dictionary<(string, int), FontSet> Fonts = []; + public Dictionary<(string, int), FontSet> ApproximateFonts = []; + /// Helper function to get a language data. public Func GetLanguageHelper; @@ -123,4 +125,21 @@ public FontSet GetFont(string fontname, int fontsize) Fonts.Add((toret.Name, fontsize), toret); return toret; } + + public FontSet GetApproximateFont(string fontname, int fontsize) + { + if (ApproximateFonts.TryGetValue((fontname, fontsize), out FontSet found)) + { + return found; + } + IEnumerable> fontVariants = Fonts.Where(pair => pair.Value.Name == fontname); + if (!fontVariants.Any()) + { + return null; + } + IEnumerable> fittingFonts = fontVariants.Where(pair => pair.Key.Item2 <= fontsize); + ((string, int) _, FontSet font) = fittingFonts.Any() ? fittingFonts.MinBy(pair => fontsize - pair.Key.Item2) : fontVariants.MinBy(pair => Math.Abs(fontsize - pair.Key.Item2)); + ApproximateFonts[(fontname, fontsize)] = font; + return font; + } } diff --git a/FGEGraphics/UISystem/UIElement.cs b/FGEGraphics/UISystem/UIElement.cs index 1194a68f..49df142e 100644 --- a/FGEGraphics/UISystem/UIElement.cs +++ b/FGEGraphics/UISystem/UIElement.cs @@ -175,7 +175,7 @@ public struct ElementInternalData() public UIStyle Style = UIStyle.Empty; /// Styles registered on this element. - public HashSet Styles = []; + //public HashSet Styles = []; } /// Data internal to a instance. @@ -374,7 +374,7 @@ public void SetStyle(UIStyle style) return; } ElementInternal.Style = style; - ElementInternal.Styles.Add(style); + //ElementInternal.Styles.Add(style); StyleChanged(previousStyle, ElementInternal.Style = style); OnStyleChange?.Invoke(previousStyle, style); } @@ -798,11 +798,11 @@ public List GetBaseDebugInfo(string baseColor, bool detailed) { info.Add(transforms); info.Add($"^7Enabled: ^{(IsEnabled ? "2" : "1")}{IsEnabled}^&, ^7Hovered: ^{(IsHovered ? "2" : "1")}{IsHovered}^&, ^7Pressed: ^{(IsPressed ? "2" : "1")}{IsPressed}^&, ^7Selected: ^{(IsFocused ? "2" : "1")}{IsFocused}"); - if (ElementInternal.Styles.Count > 0) + /*if (ElementInternal.Styles.Count > 0) { List styleNames = [.. ElementInternal.Styles.Select(style => style.Name is not null ? $"^{(style == ElementInternal.Style ? "3" : "7")}{style.Name}" : "^1unnamed")]; info.Add($"^7Styles: {string.Join("^&, ", styleNames)}"); - } + }*/ } return info; } diff --git a/FGEGraphics/UISystem/UILabel.cs b/FGEGraphics/UISystem/UILabel.cs index e92c9e7a..5c52074c 100644 --- a/FGEGraphics/UISystem/UILabel.cs +++ b/FGEGraphics/UISystem/UILabel.cs @@ -42,11 +42,8 @@ public struct InternalData() /// The maximum width of the text content. public int MaxWidth; - /// A cache of UI styles and their corresponding renderable objects. - public Dictionary Renderables = []; - - /// Fired after the renderables cache is updated. - public Action OnRenderablesUpdate; + /// The cached renderable object. + public RenderableText Renderable = RenderableText.Empty; } /// Data internal to a instance. @@ -62,7 +59,7 @@ public string Content set { Internal.Content = value ?? ""; - UpdateRenderables(); + UpdateRenderable(); } } @@ -77,7 +74,7 @@ public int MaxWidth set { Internal.MaxWidth = value; - UpdateRenderables(); + UpdateRenderable(); } } @@ -88,7 +85,7 @@ public int MaxWidth public UILabel(string text, UIStyling styling, UILayout layout) : base(styling, layout) { Internal = new() { Content = text ?? "" }; - UpdateRenderables(); + UpdateRenderable(); } /// Creates a object from given a style. @@ -101,14 +98,11 @@ public RenderableText CreateRenderable(UIStyle style) { return RenderableText.Empty; } - // TODO: cache this somewhere, as it's likely for many elements with text to have the same scale value - IEnumerable> fontVariants = style.TextFont.Engine.Fonts.Where(pair => pair.Value.Name == style.TextFont.Name); - if (!fontVariants.Any()) + FontSet font = style.TextFont.Engine.GetApproximateFont(style.TextFont.Name, fontSize); + if (font is null) { return RenderableText.Empty; } - IEnumerable> fittingFonts = fontVariants.Where(pair => pair.Key.Item2 <= fontSize); - ((string, int) _, FontSet font) = fittingFonts.Any() ? fittingFonts.MinBy(pair => fontSize - pair.Key.Item2) : fontVariants.MinBy(pair => Math.Abs(fontSize - pair.Key.Item2)); string styledContent = style.TextStyling(Internal.Content); // FIXME: this doesn't play well with translatable text. RenderableText renderable = font.ParseFancyText(styledContent, style.TextBaseColor); if (Internal.MaxWidth > 0) @@ -123,56 +117,40 @@ public RenderableText CreateRenderable(UIStyle style) /// If is true, clears the cache. /// Otherwise, recreates all renderable objects in the cache. /// - public void UpdateRenderables() + public void UpdateRenderable(UIStyle style = null) { - if (IsEmpty) - { - Internal.Renderables.Clear(); - } - else - { - Internal.Renderables = ElementInternal.Styles - .Select(style => (style, CreateRenderable(style))) - .ToDictionary(); - Internal.OnRenderablesUpdate?.Invoke(); - } + style ??= Style; + Internal.Renderable = !IsEmpty && style.CanRenderText ? CreateRenderable(style) : RenderableText.Empty; } /// public override void StyleChanged(UIStyle from, UIStyle to) { - if (!IsEmpty && to.CanRenderText && !Internal.Renderables.ContainsKey(to)) - { - Internal.Renderables[to] = CreateRenderable(to); - } - if (Scale != 0 && Internal.Renderables.TryGetValue(to, out RenderableText renderable)) + UpdateRenderable(to); + if (Scale != 0) { - Layout.SetSize(renderable.GetTrueSize(to.TextFont)); + Layout.SetSize(Internal.Renderable.GetTrueSize(to.TextFont)); } } /// public override void ScaleChanged(float from, float to) { - UpdateRenderables(); - if (from == 0 && GetRenderable(Style) is RenderableText renderable) + UpdateRenderable(); + if (from == 0 && !Internal.Renderable.IsEmpty) { - Layout.SetSize(renderable.GetTrueSize(Style.TextFont)); + Layout.SetSize(Internal.Renderable.GetTrueSize(Style.TextFont)); } } - /// Returns the cached object corresponding to the given style, or null if none is present. - /// The UI style to query. - public RenderableText GetRenderable(UIStyle style) => !IsEmpty && Internal.Renderables.TryGetValue(style, out RenderableText renderable) ? renderable : null; - /// public override void Render(double delta, UIStyle style) { - if (GetRenderable(style) is RenderableText renderable) + if (!Internal.Renderable.IsEmpty) { - int trueX = X + Layout.Anchor.AlignmentX.GetPosition(Width, renderable.Width); - int trueY = Y + Layout.Anchor.AlignmentY.GetPosition(Height, renderable.Height); - style.TextFont.DrawFancyText(renderable, new Location(trueX, trueY, 0)); + int trueX = X + Layout.Anchor.AlignmentX.GetPosition(Width, Internal.Renderable.Width); + int trueY = Y + Layout.Anchor.AlignmentY.GetPosition(Height, Internal.Renderable.Height); + style.TextFont.DrawFancyText(Internal.Renderable, new Location(trueX, trueY, 0)); } } diff --git a/FGEGraphics/UISystem/UIParagraph.cs b/FGEGraphics/UISystem/UIParagraph.cs index 59bad954..971857f8 100644 --- a/FGEGraphics/UISystem/UIParagraph.cs +++ b/FGEGraphics/UISystem/UIParagraph.cs @@ -74,11 +74,11 @@ public void UpdateRenderables() List<(FontSet Font, RenderableTextLine Line)> lines = []; foreach (UILabel label in Labels) { - if (label.GetRenderable(label.Style) is not RenderableText renderable) + if (label.Internal.Renderable.IsEmpty) { continue; } - List textLines = [.. renderable.Lines]; + List textLines = [.. label.Internal.Renderable.Lines]; if (lines.Count != 0) { RenderableTextLine combinedLine = new([.. lines[^1].Line.Parts, .. textLines[0].Parts]); From 7d940aa8fa07f68f51cf90b11f259b7b1323ec3f Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Mon, 18 May 2026 18:13:29 -0700 Subject: [PATCH 11/19] dynamic styling --- .../GraphicsHelpers/FontSets/FontSetEngine.cs | 1 + FGEGraphics/UISystem/UI3DSubEngine.cs | 2 +- FGEGraphics/UISystem/UIBox.cs | 2 +- FGEGraphics/UISystem/UIDropdown.cs | 2 +- FGEGraphics/UISystem/UIElement.cs | 6 +- FGEGraphics/UISystem/UIGroup.cs | 2 +- FGEGraphics/UISystem/UIImage.cs | 2 +- FGEGraphics/UISystem/UIInputBox.cs | 2 +- FGEGraphics/UISystem/UIInputLabel.cs | 10 +-- FGEGraphics/UISystem/UIInteractionStyles.cs | 4 +- FGEGraphics/UISystem/UINativeTexture.cs | 2 +- FGEGraphics/UISystem/UINumberInputLabel.cs | 6 +- FGEGraphics/UISystem/UINumberSlider.cs | 4 +- FGEGraphics/UISystem/UIParagraph.cs | 2 +- FGEGraphics/UISystem/UIRenderable.cs | 2 +- FGEGraphics/UISystem/UIScreen.cs | 2 +- FGEGraphics/UISystem/UIScrollGroup.cs | 2 +- FGEGraphics/UISystem/UIStyleValue.cs | 39 +++++++++ FGEGraphics/UISystem/UIStyling.cs | 86 +++++++------------ FGEGraphics/UISystem/UIToggleBox.cs | 4 +- 20 files changed, 99 insertions(+), 83 deletions(-) create mode 100644 FGEGraphics/UISystem/UIStyleValue.cs diff --git a/FGEGraphics/GraphicsHelpers/FontSets/FontSetEngine.cs b/FGEGraphics/GraphicsHelpers/FontSets/FontSetEngine.cs index 2dad67db..4aeb9cf1 100644 --- a/FGEGraphics/GraphicsHelpers/FontSets/FontSetEngine.cs +++ b/FGEGraphics/GraphicsHelpers/FontSets/FontSetEngine.cs @@ -16,6 +16,7 @@ using FGECore.UtilitySystems; using FGEGraphics.GraphicsHelpers.Shaders; using OpenTK.Mathematics; +using FGECore.CoreSystems; namespace FGEGraphics.GraphicsHelpers.FontSets; diff --git a/FGEGraphics/UISystem/UI3DSubEngine.cs b/FGEGraphics/UISystem/UI3DSubEngine.cs index 2f3c29e0..c5760216 100644 --- a/FGEGraphics/UISystem/UI3DSubEngine.cs +++ b/FGEGraphics/UISystem/UI3DSubEngine.cs @@ -33,7 +33,7 @@ public class UI3DSubEngine : UIElement /// Constructs a new 3D sub-engine. /// The layout of the element. /// Whether to have an alpha background. - public UI3DSubEngine(UILayout layout, bool alphaBack) : base(UIStyling.Empty, layout) + public UI3DSubEngine(UILayout layout, bool alphaBack) : base(null, layout) { SubEngine = new GameEngine3D { diff --git a/FGEGraphics/UISystem/UIBox.cs b/FGEGraphics/UISystem/UIBox.cs index e1a4d71d..4fd8b540 100644 --- a/FGEGraphics/UISystem/UIBox.cs +++ b/FGEGraphics/UISystem/UIBox.cs @@ -40,7 +40,7 @@ public class UIBox : UIElement /// Text to display inside the box. public UIBox(UIStyling styling, UILayout layout, string text = null) : base(styling, layout) { - AddChild(Label = new UILabel(text, styling.Bind(this), new UILayout().SetAnchor(UIAnchor.CENTER)) { IsEnabled = false }); + AddChild(Label = new UILabel(text, styling with { Element = this }, new UILayout().SetAnchor(UIAnchor.CENTER)) { IsEnabled = false }); } /// diff --git a/FGEGraphics/UISystem/UIDropdown.cs b/FGEGraphics/UISystem/UIDropdown.cs index 7a13c6e9..c19a20ab 100644 --- a/FGEGraphics/UISystem/UIDropdown.cs +++ b/FGEGraphics/UISystem/UIDropdown.cs @@ -133,7 +133,7 @@ public void DeselectChoice() public void AddChoice(UIElement choice, Func label) { // TODO: configurable appearance - UIStyle containerStyle = Entries.Items.Count % 2 == 0 ? UIStyle.Empty : new UIStyle { BaseColor = new(0, 0, 0, 0.25f) }; + UIStyling containerStyle = Entries.Items.Count % 2 == 0 ? null : new UIStyling { Fill = new Color4F(0, 0, 0, 0.25f) }; UIBox container = new(containerStyle, new UILayout().SetSize(() => Box.Width, () => choice.Height)); choice.Layout.SetAnchor(UIAnchor.TOP_CENTER); container.AddChild(choice); diff --git a/FGEGraphics/UISystem/UIElement.cs b/FGEGraphics/UISystem/UIElement.cs index 49df142e..d1c9d8c4 100644 --- a/FGEGraphics/UISystem/UIElement.cs +++ b/FGEGraphics/UISystem/UIElement.cs @@ -173,9 +173,6 @@ public struct ElementInternalData() /// The current style of this element. public UIStyle Style = UIStyle.Empty; - - /// Styles registered on this element. - //public HashSet Styles = []; } /// Data internal to a instance. @@ -374,7 +371,6 @@ public void SetStyle(UIStyle style) return; } ElementInternal.Style = style; - //ElementInternal.Styles.Add(style); StyleChanged(previousStyle, ElementInternal.Style = style); OnStyleChange?.Invoke(previousStyle, style); } @@ -382,7 +378,7 @@ public void SetStyle(UIStyle style) /// If a is present, attempts to update the current style. public void UpdateStyle() { - SetStyle(Styling.Get(this)); + SetStyle(Styling?.Get(this) ?? UIStyle.Empty); } /// diff --git a/FGEGraphics/UISystem/UIGroup.cs b/FGEGraphics/UISystem/UIGroup.cs index b797f0a8..450ca0df 100644 --- a/FGEGraphics/UISystem/UIGroup.cs +++ b/FGEGraphics/UISystem/UIGroup.cs @@ -22,7 +22,7 @@ public class UIGroup : UIElement /// Constructs a new group. /// The layout of the element. - public UIGroup(UILayout layout) : base(UIStyling.Empty, layout) + public UIGroup(UILayout layout) : base(null, layout) { IsEnabled = false; ScaleSize = false; diff --git a/FGEGraphics/UISystem/UIImage.cs b/FGEGraphics/UISystem/UIImage.cs index e568f0f4..8c705a7c 100644 --- a/FGEGraphics/UISystem/UIImage.cs +++ b/FGEGraphics/UISystem/UIImage.cs @@ -23,7 +23,7 @@ namespace FGEGraphics.UISystem; /// Constructs an image. /// The image to display. /// The layout of the element. -public class UIImage(Texture image, UILayout layout) : UIElement(UIStyling.Empty, layout) +public class UIImage(Texture image, UILayout layout) : UIElement(null, layout) { /// public override string Name => $"Image \"{Image.Name}\""; diff --git a/FGEGraphics/UISystem/UIInputBox.cs b/FGEGraphics/UISystem/UIInputBox.cs index 48ce2018..9e268962 100644 --- a/FGEGraphics/UISystem/UIInputBox.cs +++ b/FGEGraphics/UISystem/UIInputBox.cs @@ -32,7 +32,7 @@ namespace FGEGraphics.UISystem; /// The font to use. /// The position of the element. // TODO: Remove -public class UIInputBox(string text, string info, FontSet fonts, UILayout pos) : UIElement(UIStyling.Empty, pos.Height <= 0 ? pos.SetHeight(fonts.Height) : pos) +public class UIInputBox(string text, string info, FontSet fonts, UILayout pos) : UIElement(null, pos.Height <= 0 ? pos.SetHeight(fonts.Height) : pos) { /// The current text in this input box. public string Text = text; diff --git a/FGEGraphics/UISystem/UIInputLabel.cs b/FGEGraphics/UISystem/UIInputLabel.cs index 5ba534d0..73f499a1 100644 --- a/FGEGraphics/UISystem/UIInputLabel.cs +++ b/FGEGraphics/UISystem/UIInputLabel.cs @@ -47,16 +47,16 @@ public enum EditType // TODO: rethink /// Wraps a instance with logic specific to input labels. /// The base interaction styles. - public struct Styles(UIInteractionStyles styles) + /*public struct Styles(UIInteractionStyles styles) { public UIInteractionStyles BaseStyles = styles; /// The styling logic for an input label. public readonly UIStyle Styling(UIElement element) => element.IsFocused ? styles.Press : styles.Styling(element); - /// Calls . - public static implicit operator UIStyling(Styles styles) => new(styles.Styling); - } + /// Calls . + public static implicit operator UIStylingOld(Styles styles) => new(styles.Styling); + }*/ /// public override string Name => "Input Label"; @@ -118,7 +118,7 @@ public struct InternalData() public UIInputLabel(string placeholderInfo, string defaultText, UIStyling styling, UIStyling inputStyling, UIStyling highlightStyling, UILayout layout) : base(styling, layout) { ScrollGroup = new(layout.Container()) { IsEnabled = false }; - ScrollGroup.AddScrollableChild(PlaceholderInfo = new UILabel(placeholderInfo, styling.Bind(this), new UILayout()) { IsEnabled = false }); + ScrollGroup.AddScrollableChild(PlaceholderInfo = new UILabel(placeholderInfo, styling with { Element = this }, new UILayout()) { IsEnabled = false }); ScrollGroup.AddScrollableChild(Paragraph = new UIInputParagraph(highlightStyling, inputStyling, highlightStyling, new UILayout()) { IsEnabled = false }); AddChild(ScrollGroup); Paragraph.SetContent(defaultText); diff --git a/FGEGraphics/UISystem/UIInteractionStyles.cs b/FGEGraphics/UISystem/UIInteractionStyles.cs index 43420718..b27cfe79 100644 --- a/FGEGraphics/UISystem/UIInteractionStyles.cs +++ b/FGEGraphics/UISystem/UIInteractionStyles.cs @@ -59,6 +59,6 @@ public static UIInteractionStyles Textured(UIStyle baseStyle, TextureEngine text return new(normal, hover, press, disabled); } - /// Calls . - public static implicit operator UIStyling(UIInteractionStyles styles) => new(styles.Styling); + /// Calls . + public static implicit operator UIStylingOld(UIInteractionStyles styles) => new(styles.Styling); } diff --git a/FGEGraphics/UISystem/UINativeTexture.cs b/FGEGraphics/UISystem/UINativeTexture.cs index 5cb30403..88c43186 100644 --- a/FGEGraphics/UISystem/UINativeTexture.cs +++ b/FGEGraphics/UISystem/UINativeTexture.cs @@ -24,7 +24,7 @@ namespace FGEGraphics.UISystem; /// /// The texture to display. /// The layout of the element. -public class UINativeTexture(Func texture, UILayout layout) : UIElement(UIStyling.Empty, layout) +public class UINativeTexture(Func texture, UILayout layout) : UIElement(null, layout) { /// public override string Name => "Native Texture"; diff --git a/FGEGraphics/UISystem/UINumberInputLabel.cs b/FGEGraphics/UISystem/UINumberInputLabel.cs index 951400cc..a3377095 100644 --- a/FGEGraphics/UISystem/UINumberInputLabel.cs +++ b/FGEGraphics/UISystem/UINumberInputLabel.cs @@ -74,13 +74,13 @@ public double Value /// Constructs a number input label. /// Whether the label should be an integer. /// The styling logic of the element. - /// The style of normal input content. - /// The style of highlighted input content. + /// The style of normal input content. + /// The style of highlighted input content. /// The layout of the element. /// The default number value. /// The format string for the label. /// The text to display when the input is empty. - public UINumberInputLabel(bool integer, UIStyling styling, UIStyle inputStyle, UIStyle highlightStyle, UILayout layout, double defaultValue = 0, string format = null, string placeholderInfo = "") : base(placeholderInfo, placeholderInfo.Length == 0 ? defaultValue.ToString(format) : "", styling, inputStyle, highlightStyle, layout) + public UINumberInputLabel(bool integer, UIStyling styling, UIStyling inputStyling, UIStyling highlightStyling, UILayout layout, double defaultValue = 0, string format = null, string placeholderInfo = "") : base(placeholderInfo, placeholderInfo.Length == 0 ? defaultValue.ToString(format) : "", styling, inputStyling, highlightStyling, layout) { Integer = integer; Format = format ?? (integer ? "0" : "0.0"); diff --git a/FGEGraphics/UISystem/UINumberSlider.cs b/FGEGraphics/UISystem/UINumberSlider.cs index 8de79597..011f2c0b 100644 --- a/FGEGraphics/UISystem/UINumberSlider.cs +++ b/FGEGraphics/UISystem/UINumberSlider.cs @@ -69,7 +69,7 @@ public class UINumberSlider : UIElement /// Whether to use integers instead of decimals. /// The clickable styles. /// The layout of the element. - public UINumberSlider(double min, double max, double defaultValue, double interval, bool integer, UIStyling styling, UILayout layout) : base(styling,layout) + public UINumberSlider(double min, double max, double defaultValue, double interval, bool integer, UIStyling styling, UILayout layout) : base(styling, layout) { Integer = integer; Interval = Integer ? Math.Max((int)interval, 1.0) : interval; @@ -82,7 +82,7 @@ public UINumberSlider(double min, double max, double defaultValue, double interv Max = Min + Interval * maxStep; } Value = Math.Clamp(Integer ? (int)Default : Default, Min, Max); // TODO: is this correct? - AddChild(Button = new(UIStyle.Empty, layout.Copy().SetOrigin().SetWidth(layout.Height / 2)) { RenderMode = UIRenderMode.NONE, IsEnabled = false }); + AddChild(Button = new(null, layout.Copy().SetOrigin().SetWidth(layout.Height / 2)) { RenderMode = UIRenderMode.NONE, IsEnabled = false }); Button.Layout.SetX(() => (int)(Progress * Width) - Button.Width / 2); } diff --git a/FGEGraphics/UISystem/UIParagraph.cs b/FGEGraphics/UISystem/UIParagraph.cs index 971857f8..fde9b294 100644 --- a/FGEGraphics/UISystem/UIParagraph.cs +++ b/FGEGraphics/UISystem/UIParagraph.cs @@ -23,7 +23,7 @@ namespace FGEGraphics.UISystem; /// Represents multiple s chained together. /// The layout of the element. -public class UIParagraph(UILayout layout) : UIElement(UIStyle.Empty, layout) +public class UIParagraph(UILayout layout) : UIElement(null, layout) { /// public override string Name => "Paragraph"; diff --git a/FGEGraphics/UISystem/UIRenderable.cs b/FGEGraphics/UISystem/UIRenderable.cs index a2e8fc59..ab9918e1 100644 --- a/FGEGraphics/UISystem/UIRenderable.cs +++ b/FGEGraphics/UISystem/UIRenderable.cs @@ -17,7 +17,7 @@ namespace FGEGraphics.UISystem; /// Represents a simple renderer that can be attached to any element. /// The renderer method. See . -public class UIRenderable(Action renderer) : UIElement(UIStyling.Empty, new UILayout()) +public class UIRenderable(Action renderer) : UIElement(null, new UILayout()) { /// public override string Name => "Renderable"; diff --git a/FGEGraphics/UISystem/UIScreen.cs b/FGEGraphics/UISystem/UIScreen.cs index 133d9cbb..3e0ba68e 100644 --- a/FGEGraphics/UISystem/UIScreen.cs +++ b/FGEGraphics/UISystem/UIScreen.cs @@ -38,7 +38,7 @@ public class UIScreen : UIElement /// Constructs a . /// The client UI view. /// The layout of the element. If null, defaults to a layout covering the parent view. - public UIScreen(ViewUI2D view, UILayout layout = null) : base(UIStyling.Empty, layout) + public UIScreen(ViewUI2D view, UILayout layout = null) : base(null, layout) { View = view; IsEnabled = false; diff --git a/FGEGraphics/UISystem/UIScrollGroup.cs b/FGEGraphics/UISystem/UIScrollGroup.cs index 3e903e98..55b5ec32 100644 --- a/FGEGraphics/UISystem/UIScrollGroup.cs +++ b/FGEGraphics/UISystem/UIScrollGroup.cs @@ -43,7 +43,7 @@ public class UIScrollGroup : UIElement /// Constructs the UI scroll group. /// The layout of the element. - public UIScrollGroup(UILayout layout) : base(UIStyling.Empty, layout) + public UIScrollGroup(UILayout layout) : base(null, layout) { // TODO: Fix scroll bar overlap XAxis = new(this, false); diff --git a/FGEGraphics/UISystem/UIStyleValue.cs b/FGEGraphics/UISystem/UIStyleValue.cs new file mode 100644 index 00000000..bd942b94 --- /dev/null +++ b/FGEGraphics/UISystem/UIStyleValue.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FGEGraphics.UISystem; + +public readonly struct UIStyleValue +{ + public readonly T Constant; + + public readonly Func Dynamic; + + public readonly bool IsEmpty => Constant is null && Dynamic is null; + + public UIStyleValue(T constant) + { + Constant = constant; + } + + public UIStyleValue(Func dynamic) + { + Dynamic = dynamic; + } + + public readonly T Get(UIElement element) => Constant ?? (Dynamic is not null ? Dynamic.Invoke(element) : default); + + public static implicit operator UIStyleValue(T constant) => new(constant); + + public static implicit operator UIStyleValue(Func dynamic) => new(dynamic); + + public static UIStyleValue Interactive(T idle, T hovered, T pressed, T disabled) => new( + element => !element.IsEnabled ? disabled + : element.IsPressed ? pressed + : element.IsHovered ? hovered + : idle + ); +} diff --git a/FGEGraphics/UISystem/UIStyling.cs b/FGEGraphics/UISystem/UIStyling.cs index 4720f9e0..af0ce500 100644 --- a/FGEGraphics/UISystem/UIStyling.cs +++ b/FGEGraphics/UISystem/UIStyling.cs @@ -1,11 +1,7 @@ -// -// This file is part of the Frenetic Game Engine, created by Frenetic LLC. -// This code is Copyright (C) Frenetic LLC under the terms of a strict license. -// See README.md or LICENSE.txt in the FreneticGameEngine source root for the contents of the license. -// If neither of these are available, assume that neither you nor anyone other than the copyright holder -// hold any right or permission to use this software until such time as the official license is identified. -// - +using FGECore.ConsoleHelpers; +using FGECore.MathHelpers; +using FGEGraphics.GraphicsHelpers.FontSets; +using FGEGraphics.GraphicsHelpers.Textures; using System; using System.Collections.Generic; using System.Linq; @@ -14,60 +10,44 @@ namespace FGEGraphics.UISystem; -/// Represents the styling logic for a . -public struct UIStyling +public record UIStyling { - /// Empty styling logic. Resolves to . - public static readonly UIStyling Empty = new((UIStyle)null); + public UIElement Element = null; - /// A constant style. - public UIStyle Constant; + public UIStyleValue Fill = Color4F.Transparent; - /// A dynamic style. If present, updates the relevant every frame. - public Func Dynamic; + public UIStyleValue Texture = default; - /// Whether this style is equivalent to - public readonly bool IsEmpty => Constant is null && Dynamic is null; + public UIStyleValue Stroke = Color4F.Transparent; - /// Constructs styling logic using a constant style. - /// The constant style. - public UIStyling(UIStyle style) - { - Constant = style; - } + public UIStyleValue StrokeWeight = 0; - /// Constructs styling logic using a dynamic style. - /// The dynamic style. - public UIStyling(Func styling) - { - Dynamic = styling; - } + public UIStyleValue Padding = 0; + + public UIStyleValue ShadowSize = 0; + + public UIStyleValue TextFont = default; - /// - /// Returns the style for the specified based on this styling logic. - /// Tries to evaluate then . If neither are present, resolves to . - /// - /// The element to be styled. - public readonly UIStyle Get(UIElement element) => Constant ?? Dynamic?.Invoke(element) ?? UIStyle.Empty; + public UIStyleValue> TextStyling = default; - /// - /// If is present, returns a new instance with the specified bound to the dynamic logic. - /// Otherwise, returns this instance unaltered. - /// - /// The element to bind. - public readonly UIStyling Bind(UIElement element) - { - if (Dynamic is not null) + public UIStyleValue TextBaseColor = TextStyle.Simple; + + public UIStyle Get(UIElement element) { + if (Element is not null) { - Func dynamic = Dynamic; - return new(_ => dynamic(element)); + element = Element; } - return this; + return new() + { + BaseColor = Fill.Get(element), + BaseTexture = Texture.Get(element), + BorderColor = Stroke.Get(element), + BorderThickness = StrokeWeight.Get(element), + Padding = Padding.Get(element), + DropShadowLength = ShadowSize.Get(element), + TextFont = TextFont.Get(element), + TextStyling = TextStyling.Get(element), + TextBaseColor = TextBaseColor.Get(element) + }; } - - /// Calls . - public static implicit operator UIStyling(UIStyle style) => new(style); - - /// Calls . - public static implicit operator UIStyling(Func styling) => new(styling); } diff --git a/FGEGraphics/UISystem/UIToggleBox.cs b/FGEGraphics/UISystem/UIToggleBox.cs index 50e1ead1..10ac3113 100644 --- a/FGEGraphics/UISystem/UIToggleBox.cs +++ b/FGEGraphics/UISystem/UIToggleBox.cs @@ -67,11 +67,11 @@ public void Toggle() /// The styling of the label. /// The anchor to use when positioning the box and the icon in a list. /// A tuple of the toggle box, label, and their list container. - public static (UIToggleBox Box, UILabel Label, UIListGroup List) WithLabel(string text, int spacing, UIStyling styling, UILayout layout, bool toggled = false, UIStyling labelStyling = default, UIAnchor listAnchor = null) + public static (UIToggleBox Box, UILabel Label, UIListGroup List) WithLabel(string text, int spacing, UIStyling styling, UILayout layout, bool toggled = false, UIStyling labelStyling = null, UIAnchor listAnchor = null) { UIToggleBox box = new(styling, layout.Copy().SetOrigin(), toggled); UIListGroup list = new(spacing, layout, vertical: false, anchor: listAnchor ?? UIAnchor.TOP_LEFT); - UILabel label = new(text, labelStyling.IsEmpty ? styling.Bind(box) : labelStyling, new UILayout()); + UILabel label = new(text, labelStyling ?? styling with { Element = box }, new UILayout()); list.AddListItem(box); list.AddListItem(label); return (box, label, list); From c194bc3b85d291b3821ec25d46d6722ab9e74677 Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Mon, 18 May 2026 19:02:59 -0700 Subject: [PATCH 12/19] dynamic styling kind of works! --- FGEGraphics/UISystem/UIBox.cs | 2 +- FGEGraphics/UISystem/UIInputLabel.cs | 2 +- FGEGraphics/UISystem/UILabel.cs | 2 +- FGEGraphics/UISystem/UIStyleValue.cs | 4 ++-- FGEGraphics/UISystem/UIStyling.cs | 2 ++ FGEGraphics/UISystem/UIToggleBox.cs | 2 +- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/FGEGraphics/UISystem/UIBox.cs b/FGEGraphics/UISystem/UIBox.cs index 4fd8b540..fe7a9041 100644 --- a/FGEGraphics/UISystem/UIBox.cs +++ b/FGEGraphics/UISystem/UIBox.cs @@ -40,7 +40,7 @@ public class UIBox : UIElement /// Text to display inside the box. public UIBox(UIStyling styling, UILayout layout, string text = null) : base(styling, layout) { - AddChild(Label = new UILabel(text, styling with { Element = this }, new UILayout().SetAnchor(UIAnchor.CENTER)) { IsEnabled = false }); + AddChild(Label = new UILabel(text, styling?.Bind(this), new UILayout().SetAnchor(UIAnchor.CENTER)) { IsEnabled = false }); } /// diff --git a/FGEGraphics/UISystem/UIInputLabel.cs b/FGEGraphics/UISystem/UIInputLabel.cs index 73f499a1..5577e5af 100644 --- a/FGEGraphics/UISystem/UIInputLabel.cs +++ b/FGEGraphics/UISystem/UIInputLabel.cs @@ -118,7 +118,7 @@ public struct InternalData() public UIInputLabel(string placeholderInfo, string defaultText, UIStyling styling, UIStyling inputStyling, UIStyling highlightStyling, UILayout layout) : base(styling, layout) { ScrollGroup = new(layout.Container()) { IsEnabled = false }; - ScrollGroup.AddScrollableChild(PlaceholderInfo = new UILabel(placeholderInfo, styling with { Element = this }, new UILayout()) { IsEnabled = false }); + ScrollGroup.AddScrollableChild(PlaceholderInfo = new UILabel(placeholderInfo, styling?.Bind(this), new UILayout()) { IsEnabled = false }); ScrollGroup.AddScrollableChild(Paragraph = new UIInputParagraph(highlightStyling, inputStyling, highlightStyling, new UILayout()) { IsEnabled = false }); AddChild(ScrollGroup); Paragraph.SetContent(defaultText); diff --git a/FGEGraphics/UISystem/UILabel.cs b/FGEGraphics/UISystem/UILabel.cs index 5c52074c..8f9becea 100644 --- a/FGEGraphics/UISystem/UILabel.cs +++ b/FGEGraphics/UISystem/UILabel.cs @@ -103,7 +103,7 @@ public RenderableText CreateRenderable(UIStyle style) { return RenderableText.Empty; } - string styledContent = style.TextStyling(Internal.Content); // FIXME: this doesn't play well with translatable text. + string styledContent = style.TextStyling?.Invoke(Internal.Content) ?? Internal.Content; // FIXME: this doesn't play well with translatable text. RenderableText renderable = font.ParseFancyText(styledContent, style.TextBaseColor); if (Internal.MaxWidth > 0) { diff --git a/FGEGraphics/UISystem/UIStyleValue.cs b/FGEGraphics/UISystem/UIStyleValue.cs index bd942b94..c5e21466 100644 --- a/FGEGraphics/UISystem/UIStyleValue.cs +++ b/FGEGraphics/UISystem/UIStyleValue.cs @@ -31,9 +31,9 @@ public UIStyleValue(Func dynamic) public static implicit operator UIStyleValue(Func dynamic) => new(dynamic); public static UIStyleValue Interactive(T idle, T hovered, T pressed, T disabled) => new( - element => !element.IsEnabled ? disabled - : element.IsPressed ? pressed + element => element.IsPressed ? pressed : element.IsHovered ? hovered + : !element.IsEnabled ? disabled : idle ); } diff --git a/FGEGraphics/UISystem/UIStyling.cs b/FGEGraphics/UISystem/UIStyling.cs index af0ce500..412a9559 100644 --- a/FGEGraphics/UISystem/UIStyling.cs +++ b/FGEGraphics/UISystem/UIStyling.cs @@ -50,4 +50,6 @@ public UIStyle Get(UIElement element) { TextBaseColor = TextBaseColor.Get(element) }; } + + public UIStyling Bind(UIElement element) => this with { Element = element }; } diff --git a/FGEGraphics/UISystem/UIToggleBox.cs b/FGEGraphics/UISystem/UIToggleBox.cs index 10ac3113..db910291 100644 --- a/FGEGraphics/UISystem/UIToggleBox.cs +++ b/FGEGraphics/UISystem/UIToggleBox.cs @@ -71,7 +71,7 @@ public static (UIToggleBox Box, UILabel Label, UIListGroup List) WithLabel(strin { UIToggleBox box = new(styling, layout.Copy().SetOrigin(), toggled); UIListGroup list = new(spacing, layout, vertical: false, anchor: listAnchor ?? UIAnchor.TOP_LEFT); - UILabel label = new(text, labelStyling ?? styling with { Element = box }, new UILayout()); + UILabel label = new(text, labelStyling ?? styling?.Bind(box), new UILayout()); list.AddListItem(box); list.AddListItem(label); return (box, label, list); From b1f526a5f9e274d7e7bbaa83c6136bb6667f1544 Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Tue, 26 May 2026 22:09:26 -0700 Subject: [PATCH 13/19] remove dead code --- FGEGraphics/UISystem/UIInputLabel.cs | 14 ----- FGEGraphics/UISystem/UIInteractionStyles.cs | 64 --------------------- FGEGraphics/UISystem/UIStyle.cs | 37 +++--------- 3 files changed, 7 insertions(+), 108 deletions(-) delete mode 100644 FGEGraphics/UISystem/UIInteractionStyles.cs diff --git a/FGEGraphics/UISystem/UIInputLabel.cs b/FGEGraphics/UISystem/UIInputLabel.cs index 5577e5af..a71a689d 100644 --- a/FGEGraphics/UISystem/UIInputLabel.cs +++ b/FGEGraphics/UISystem/UIInputLabel.cs @@ -44,20 +44,6 @@ public enum EditType SUBMIT } - // TODO: rethink - /// Wraps a instance with logic specific to input labels. - /// The base interaction styles. - /*public struct Styles(UIInteractionStyles styles) - { - public UIInteractionStyles BaseStyles = styles; - - /// The styling logic for an input label. - public readonly UIStyle Styling(UIElement element) => element.IsFocused ? styles.Press : styles.Styling(element); - - /// Calls . - public static implicit operator UIStylingOld(Styles styles) => new(styles.Styling); - }*/ - /// public override string Name => "Input Label"; diff --git a/FGEGraphics/UISystem/UIInteractionStyles.cs b/FGEGraphics/UISystem/UIInteractionStyles.cs deleted file mode 100644 index b27cfe79..00000000 --- a/FGEGraphics/UISystem/UIInteractionStyles.cs +++ /dev/null @@ -1,64 +0,0 @@ -// -// This file is part of the Frenetic Game Engine, created by Frenetic LLC. -// This code is Copyright (C) Frenetic LLC under the terms of a strict license. -// See README.md or LICENSE.txt in the FreneticGameEngine source root for the contents of the license. -// If neither of these are available, assume that neither you nor anyone other than the copyright holder -// hold any right or permission to use this software until such time as the official license is identified. -// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using FGEGraphics.GraphicsHelpers.Textures; - -namespace FGEGraphics.UISystem; - -/// Represents styles that display an element's interaction state. -/// The default style to use. -/// The style to use on hover. -/// The style to use on press. -/// The style to use when disabled. -public class UIInteractionStyles(UIStyle normal, UIStyle hover, UIStyle press, UIStyle disabled) -{ - /// Empty interaction styles. - public static readonly UIInteractionStyles Empty = new(null, null, null, null); - - /// The style of an element not being interacted with. - public UIStyle Normal = normal; - - /// The style of an element about to be interacted with. - public UIStyle Hover = hover; - - /// The style of an element being interacted with. - public UIStyle Press = press; - - /// The style of a disabled element. - public UIStyle Disabled = disabled; - - // TODO: should this also be a record - public UIInteractionStyles(UIInteractionStyles styles) : this(styles.Normal, styles.Hover, styles.Press, styles.Disabled) - { } - - /// The styling logic for an interactable element. - public virtual UIStyle Styling(UIElement element) => element.IsPressed ? Press - : element.IsHovered ? Hover - : !element.IsEnabled ? Disabled - : Normal; - - /// Creates interaction styles based on a standard texture set. - /// The base interaction style. - /// The engine to get textures from. - /// The name of the texture set. - public static UIInteractionStyles Textured(UIStyle baseStyle, TextureEngine textures, string textureSet) - { - UIStyle normal = new(baseStyle) { BaseTexture = textures.GetTexture($"{textureSet}_none") }; - UIStyle hover = new(baseStyle) { BaseTexture = textures.GetTexture($"{textureSet}_hover") }; - UIStyle press = new(baseStyle) { BaseTexture = textures.GetTexture($"{textureSet}_press") }; - UIStyle disabled = new(baseStyle) { BaseTexture = textures.GetTexture($"{textureSet}_disabled") }; - return new(normal, hover, press, disabled); - } - - /// Calls . - public static implicit operator UIStylingOld(UIInteractionStyles styles) => new(styles.Styling); -} diff --git a/FGEGraphics/UISystem/UIStyle.cs b/FGEGraphics/UISystem/UIStyle.cs index 9ab65173..1daa27b3 100644 --- a/FGEGraphics/UISystem/UIStyle.cs +++ b/FGEGraphics/UISystem/UIStyle.cs @@ -25,58 +25,35 @@ public record UIStyle public static readonly UIStyle Empty = new() { Name = "Empty" }; /// What base color to use (or for none). - public Color4F BaseColor = Color4F.Transparent; + public Color4F BaseColor; /// What texture to display (or null for none). public Texture BaseTexture; /// What border outline color to use (or for none). - public Color4F BorderColor = Color4F.Transparent; + public Color4F BorderColor; /// How thick the border outline should be (or 0 for none). - public int BorderThickness = 0; + public int BorderThickness; - public int Padding = 0; + public int Padding; /// How big the drop-shadow effect should be (or 0 for none). - public int DropShadowLength = 0; + public int DropShadowLength; /// The text font (or null for none). public FontSet TextFont; // TODO: Does the usage of 'Func' work properly in the context of a 'record' that's going into a Dictionary (ie hashcode/equality checks)? /// The styling effect for text. - public Func TextStyling = str => str; + public Func TextStyling; /// The base color effect for text (consider if unsure). - public string TextBaseColor = TextStyle.Simple; + public string TextBaseColor; /// The name of the element style (for debug info). public string Name; - /// Constructs a default element style. - public UIStyle() - { - } - - // TODO: since this is a record, we can simply do: - // style with { } - // TODO: determine whether UIStyle should stay a record - /// Constructs a new style as a copy of another style. - /// The style to copy. - public UIStyle(UIStyle style) - { - BaseColor = style.BaseColor; - BaseTexture = style.BaseTexture; - BorderColor = style.BorderColor; - BorderThickness = style.BorderThickness; - Padding = style.Padding; - DropShadowLength = style.DropShadowLength; - TextFont = style.TextFont; - TextStyling = style.TextStyling; - TextBaseColor = style.TextBaseColor; - } - public int Inset => BorderThickness + Padding; /// Returns the font height, or 0 if is null. From c3e0a9cd492788c7e055bf8e9564e9da7db79af6 Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Tue, 26 May 2026 22:09:40 -0700 Subject: [PATCH 14/19] fix UIStyleValue resolution --- FGEGraphics/UISystem/UIStyleValue.cs | 2 +- FGEGraphics/UISystem/UIStyling.cs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/FGEGraphics/UISystem/UIStyleValue.cs b/FGEGraphics/UISystem/UIStyleValue.cs index c5e21466..202298ff 100644 --- a/FGEGraphics/UISystem/UIStyleValue.cs +++ b/FGEGraphics/UISystem/UIStyleValue.cs @@ -24,7 +24,7 @@ public UIStyleValue(Func dynamic) Dynamic = dynamic; } - public readonly T Get(UIElement element) => Constant ?? (Dynamic is not null ? Dynamic.Invoke(element) : default); + public readonly T Get(UIElement element) => Dynamic is not null ? Dynamic.Invoke(element) : Constant; public static implicit operator UIStyleValue(T constant) => new(constant); diff --git a/FGEGraphics/UISystem/UIStyling.cs b/FGEGraphics/UISystem/UIStyling.cs index 412a9559..d204df86 100644 --- a/FGEGraphics/UISystem/UIStyling.cs +++ b/FGEGraphics/UISystem/UIStyling.cs @@ -33,10 +33,7 @@ public record UIStyling public UIStyleValue TextBaseColor = TextStyle.Simple; public UIStyle Get(UIElement element) { - if (Element is not null) - { - element = Element; - } + element = Element ?? element; return new() { BaseColor = Fill.Get(element), From 75085bf42bc21454a67c7b94a155cf1eaebf69f5 Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Tue, 26 May 2026 22:12:34 -0700 Subject: [PATCH 15/19] doc UIRenderMode --- FGEGraphics/UISystem/UIRenderMode.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/FGEGraphics/UISystem/UIRenderMode.cs b/FGEGraphics/UISystem/UIRenderMode.cs index 642df3a3..863eba48 100644 --- a/FGEGraphics/UISystem/UIRenderMode.cs +++ b/FGEGraphics/UISystem/UIRenderMode.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - // // This file is part of the Frenetic Game Engine, created by Frenetic LLC. // This code is Copyright (C) Frenetic LLC under the terms of a strict license. @@ -12,11 +6,21 @@ // hold any right or permission to use this software until such time as the official license is identified. // +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + namespace FGEGraphics.UISystem; +/// Represents the way a renders to the screen. public enum UIRenderMode { + /// Render the element and its children. FULL, + /// Skip the element and render only its children. SKIP_SELF, + /// Skip rendering the element and all of its children. NONE } From 9e05b9a42a40ddc21c268980861d41f96b45bdb4 Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Tue, 26 May 2026 22:23:21 -0700 Subject: [PATCH 16/19] rename UIStyle properties, clean docs --- FGEGraphics/UISystem/UIBox.cs | 20 ++++++------ FGEGraphics/UISystem/UIElement.cs | 2 +- FGEGraphics/UISystem/UIInputLabel.cs | 2 +- FGEGraphics/UISystem/UIInputParagraph.cs | 4 +-- FGEGraphics/UISystem/UILabel.cs | 2 +- FGEGraphics/UISystem/UINumberSlider.cs | 4 +-- FGEGraphics/UISystem/UIStyle.cs | 39 +++++++++++------------- FGEGraphics/UISystem/UIStyling.cs | 10 +++--- 8 files changed, 39 insertions(+), 44 deletions(-) diff --git a/FGEGraphics/UISystem/UIBox.cs b/FGEGraphics/UISystem/UIBox.cs index fe7a9041..2394bf59 100644 --- a/FGEGraphics/UISystem/UIBox.cs +++ b/FGEGraphics/UISystem/UIBox.cs @@ -47,30 +47,30 @@ public UIBox(UIStyling styling, UILayout layout, string text = null) : base(styl public override void Render(double delta, UIStyle style) { Vector3 rotation = new(-0.5f, -0.5f, Rotation); - bool any = style.DropShadowLength > 0 || style.BorderColor.A > 0 || style.BaseColor.A > 0; + bool any = style.ShadowSize > 0 || style.Stroke.A > 0 || style.Fill.A > 0; if (any) { View.Engine.Textures.White.Bind(); - if (style.DropShadowLength > 0) + if (style.ShadowSize > 0) { Renderer2D.SetColor(new Color4F(0, 0, 0, 0.5f)); - View.Rendering.RenderRectangle(View.UIContext, X, Y, X + Width + style.DropShadowLength, Y + Height + style.DropShadowLength, rotation); + View.Rendering.RenderRectangle(View.UIContext, X, Y, X + Width + style.ShadowSize, Y + Height + style.ShadowSize, rotation); } - if (style.BorderColor.A > 0 && style.BorderThickness > 0) + if (style.Stroke.A > 0 && style.StrokeWeight > 0) { - Renderer2D.SetColor(style.BorderColor); + Renderer2D.SetColor(style.Stroke); View.Rendering.RenderRectangle(View.UIContext, X, Y, X + Width, Y + Height, rotation); } - if (style.BaseColor.A > 0) + if (style.Fill.A > 0) { - Renderer2D.SetColor(style.BaseColor); - View.Rendering.RenderRectangle(View.UIContext, X + style.BorderThickness, Y + style.BorderThickness, X + Width - style.BorderThickness, Y + Height - style.BorderThickness, rotation); + Renderer2D.SetColor(style.Fill); + View.Rendering.RenderRectangle(View.UIContext, X + style.StrokeWeight, Y + style.StrokeWeight, X + Width - style.StrokeWeight, Y + Height - style.StrokeWeight, rotation); } Renderer2D.SetColor(Color4F.White); } - if (style.BaseTexture is not null) + if (style.Texture is not null) { - style.BaseTexture.Bind(); + style.Texture.Bind(); float ymin = Flip ? Y + Height : Y; float ymax = Flip ? Y : Y + Height; View.Rendering.RenderRectangle(View.UIContext, X, ymin, X + Width, ymax, rotation); diff --git a/FGEGraphics/UISystem/UIElement.cs b/FGEGraphics/UISystem/UIElement.cs index d1c9d8c4..35fe5382 100644 --- a/FGEGraphics/UISystem/UIElement.cs +++ b/FGEGraphics/UISystem/UIElement.cs @@ -33,7 +33,7 @@ public abstract class UIElement /// The parent element, null if this element is the root or hasn't been added as a child. public UIElement Parent; - /// Gets the UI view this element is attached to. + /// The UI view this element is attached to. public ViewUI2D View; /// Styling logic for this element. diff --git a/FGEGraphics/UISystem/UIInputLabel.cs b/FGEGraphics/UISystem/UIInputLabel.cs index a71a689d..bd418ea6 100644 --- a/FGEGraphics/UISystem/UIInputLabel.cs +++ b/FGEGraphics/UISystem/UIInputLabel.cs @@ -156,7 +156,7 @@ public void UpdateScrollGroupY() ScrollGroup.YAxis.Reset(); return; } - int lastLineHeight = Paragraph.InputInternal.LabelRight.Style.FontHeight; + int lastLineHeight = Paragraph.InputInternal.LabelRight.Style.TextFont?.Height ?? 0; ScrollGroup.YAxis.MaxValue = Math.Max((int)Paragraph.Internal.Renderables[^1].YOffset + lastLineHeight - ScrollGroup.Height, 0); ScrollGroup.YAxis.ScrollToPos((int)Paragraph.InputInternal.CursorRenderOffset.Y, (int)Paragraph.InputInternal.CursorRenderOffset.Y + lastLineHeight - ScrollGroup.YAxis.Value); ScrollGroup.XAxis.Clamp(); diff --git a/FGEGraphics/UISystem/UIInputParagraph.cs b/FGEGraphics/UISystem/UIInputParagraph.cs index d8639d89..1cc3f7f0 100644 --- a/FGEGraphics/UISystem/UIInputParagraph.cs +++ b/FGEGraphics/UISystem/UIInputParagraph.cs @@ -188,8 +188,8 @@ public override void Render(double delta, UIStyle style) return; } View.Engine.Textures.White.Bind(); - Renderer2D.SetColor(style.BorderColor); - int lineWidth = style.BorderThickness / 2; + Renderer2D.SetColor(style.Stroke); + int lineWidth = style.StrokeWeight / 2; int lineHeight = style.TextFont.Height; View.Rendering.RenderRectangle(View.UIContext, X + InputInternal.CursorRenderOffset.XF - lineWidth, Y + InputInternal.CursorRenderOffset.YF, X + InputInternal.CursorRenderOffset.XF + lineWidth, Y + InputInternal.CursorRenderOffset.YF + lineHeight); Renderer2D.SetColor(Color4.White); diff --git a/FGEGraphics/UISystem/UILabel.cs b/FGEGraphics/UISystem/UILabel.cs index 8f9becea..14a8a7e3 100644 --- a/FGEGraphics/UISystem/UILabel.cs +++ b/FGEGraphics/UISystem/UILabel.cs @@ -120,7 +120,7 @@ public RenderableText CreateRenderable(UIStyle style) public void UpdateRenderable(UIStyle style = null) { style ??= Style; - Internal.Renderable = !IsEmpty && style.CanRenderText ? CreateRenderable(style) : RenderableText.Empty; + Internal.Renderable = !IsEmpty && style.TextFont is not null ? CreateRenderable(style) : RenderableText.Empty; } /// diff --git a/FGEGraphics/UISystem/UINumberSlider.cs b/FGEGraphics/UISystem/UINumberSlider.cs index 011f2c0b..180bf42b 100644 --- a/FGEGraphics/UISystem/UINumberSlider.cs +++ b/FGEGraphics/UISystem/UINumberSlider.cs @@ -126,8 +126,8 @@ public override void Tick(double delta) public override void Render(double delta, UIStyle style) { View.Engine.Textures.White.Bind(); - Renderer2D.SetColor(style.BorderColor); - int lineWidth = style.BorderThickness / 2; + Renderer2D.SetColor(style.Stroke); + int lineWidth = style.StrokeWeight / 2; int centerY = Y + Height / 2; View.Rendering.RenderRectangle(View.UIContext, X, centerY - lineWidth, X + Width, centerY + lineWidth); if (Interval > 0.0) diff --git a/FGEGraphics/UISystem/UIStyle.cs b/FGEGraphics/UISystem/UIStyle.cs index 1daa27b3..f2a563ce 100644 --- a/FGEGraphics/UISystem/UIStyle.cs +++ b/FGEGraphics/UISystem/UIStyle.cs @@ -24,41 +24,36 @@ public record UIStyle /// An empty element style. public static readonly UIStyle Empty = new() { Name = "Empty" }; - /// What base color to use (or for none). - public Color4F BaseColor; + /// The color to fill an element's interior with. + public Color4F Fill; - /// What texture to display (or null for none). - public Texture BaseTexture; + /// The texture to draw on an element. + public Texture Texture; - /// What border outline color to use (or for none). - public Color4F BorderColor; + /// The color to draw an element's outline with. + public Color4F Stroke; - /// How thick the border outline should be (or 0 for none). - public int BorderThickness; + /// The thickness to draw an element's outline with. + public int StrokeWeight; + /// The distance between an element's outline and its interior content. public int Padding; - /// How big the drop-shadow effect should be (or 0 for none). - public int DropShadowLength; + /// The size of the drop shadow on an element. + public int ShadowSize; - /// The text font (or null for none). + /// The text font. public FontSet TextFont; - // TODO: Does the usage of 'Func' work properly in the context of a 'record' that's going into a Dictionary (ie hashcode/equality checks)? - /// The styling effect for text. + /// The text styling effect. public Func TextStyling; - /// The base color effect for text (consider if unsure). + /// The base color effect for text. public string TextBaseColor; - /// The name of the element style (for debug info). + /// The name of the style (for debug info). public string Name; - public int Inset => BorderThickness + Padding; - - /// Returns the font height, or 0 if is null. - public int FontHeight => TextFont?.Height ?? 0; - - /// Returns whether this style can render text in general. - public bool CanRenderText => TextFont is not null; + /// The distance between an element's boundary and its interior content. + public int Inset => StrokeWeight + Padding; } diff --git a/FGEGraphics/UISystem/UIStyling.cs b/FGEGraphics/UISystem/UIStyling.cs index d204df86..1fd177ed 100644 --- a/FGEGraphics/UISystem/UIStyling.cs +++ b/FGEGraphics/UISystem/UIStyling.cs @@ -36,12 +36,12 @@ public UIStyle Get(UIElement element) { element = Element ?? element; return new() { - BaseColor = Fill.Get(element), - BaseTexture = Texture.Get(element), - BorderColor = Stroke.Get(element), - BorderThickness = StrokeWeight.Get(element), + Fill = Fill.Get(element), + Texture = Texture.Get(element), + Stroke = Stroke.Get(element), + StrokeWeight = StrokeWeight.Get(element), Padding = Padding.Get(element), - DropShadowLength = ShadowSize.Get(element), + ShadowSize = ShadowSize.Get(element), TextFont = TextFont.Get(element), TextStyling = TextStyling.Get(element), TextBaseColor = TextBaseColor.Get(element) From ce1f85b72406c03d69445d385c09eadfba670edd Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Tue, 26 May 2026 22:34:37 -0700 Subject: [PATCH 17/19] doc UIStyleValue --- FGEGraphics/UISystem/UIStyle.cs | 2 +- FGEGraphics/UISystem/UIStyleValue.cs | 26 ++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/FGEGraphics/UISystem/UIStyle.cs b/FGEGraphics/UISystem/UIStyle.cs index f2a563ce..f4b7a74f 100644 --- a/FGEGraphics/UISystem/UIStyle.cs +++ b/FGEGraphics/UISystem/UIStyle.cs @@ -19,7 +19,7 @@ namespace FGEGraphics.UISystem; /// Represents the rendering style of a . -public record UIStyle +public class UIStyle { /// An empty element style. public static readonly UIStyle Empty = new() { Name = "Empty" }; diff --git a/FGEGraphics/UISystem/UIStyleValue.cs b/FGEGraphics/UISystem/UIStyleValue.cs index 202298ff..a603c89a 100644 --- a/FGEGraphics/UISystem/UIStyleValue.cs +++ b/FGEGraphics/UISystem/UIStyleValue.cs @@ -1,3 +1,11 @@ +// +// This file is part of the Frenetic Game Engine, created by Frenetic LLC. +// This code is Copyright (C) Frenetic LLC under the terms of a strict license. +// See README.md or LICENSE.txt in the FreneticGameEngine source root for the contents of the license. +// If neither of these are available, assume that neither you nor anyone other than the copyright holder +// hold any right or permission to use this software until such time as the official license is identified. +// + using System; using System.Collections.Generic; using System.Linq; @@ -6,30 +14,44 @@ namespace FGEGraphics.UISystem; +/// Represents a single value of a object that resolves to a value each frame. public readonly struct UIStyleValue { + /// A constant value. public readonly T Constant; + /// A dynamic style. public readonly Func Dynamic; - public readonly bool IsEmpty => Constant is null && Dynamic is null; - + /// Constructs a constant style value. + /// The constant value. public UIStyleValue(T constant) { Constant = constant; } + /// Constructs a dynamic style value. + /// The dynamic value. public UIStyleValue(Func dynamic) { Dynamic = dynamic; } + /// Returns the style value for the specified . + /// The element to be styled. public readonly T Get(UIElement element) => Dynamic is not null ? Dynamic.Invoke(element) : Constant; + /// Calls . public static implicit operator UIStyleValue(T constant) => new(constant); + /// Calls . public static implicit operator UIStyleValue(Func dynamic) => new(dynamic); + /// Constructs a style value dependent on the interaction state of an element. + /// The value to use when the element is not being interacted with. + /// The value to use when the element is hovered. + /// The value to use when the element is pressed. + /// The value to use when the element is disabled. public static UIStyleValue Interactive(T idle, T hovered, T pressed, T disabled) => new( element => element.IsPressed ? pressed : element.IsHovered ? hovered From b7600c4511a615ac7a084f8c9fed2d5e73dc416d Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Tue, 26 May 2026 22:40:34 -0700 Subject: [PATCH 18/19] doc uistyling --- FGEGraphics/UISystem/UIStyling.cs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/FGEGraphics/UISystem/UIStyling.cs b/FGEGraphics/UISystem/UIStyling.cs index 1fd177ed..6ca4a21b 100644 --- a/FGEGraphics/UISystem/UIStyling.cs +++ b/FGEGraphics/UISystem/UIStyling.cs @@ -1,3 +1,11 @@ +// +// This file is part of the Frenetic Game Engine, created by Frenetic LLC. +// This code is Copyright (C) Frenetic LLC under the terms of a strict license. +// See README.md or LICENSE.txt in the FreneticGameEngine source root for the contents of the license. +// If neither of these are available, assume that neither you nor anyone other than the copyright holder +// hold any right or permission to use this software until such time as the official license is identified. +// + using FGECore.ConsoleHelpers; using FGECore.MathHelpers; using FGEGraphics.GraphicsHelpers.FontSets; @@ -10,29 +18,45 @@ namespace FGEGraphics.UISystem; +/// Represents the styling logic of a . public record UIStyling { + /// + /// The element bound to this style. + /// If present, calls to will use this element rather than the one passed as an argument. + /// public UIElement Element = null; + /// The color to fill an element's interior with. public UIStyleValue Fill = Color4F.Transparent; + /// The texture to draw on an element. public UIStyleValue Texture = default; + /// The color to draw an element's outline with. public UIStyleValue Stroke = Color4F.Transparent; + /// The thickness to draw an element's outline with. public UIStyleValue StrokeWeight = 0; + /// The distance between an element's outline and its interior content. public UIStyleValue Padding = 0; + /// The size of the drop shadow on an element. public UIStyleValue ShadowSize = 0; + /// The text font. public UIStyleValue TextFont = default; + /// The text styling effect. public UIStyleValue> TextStyling = default; + /// The base color effect for text. public UIStyleValue TextBaseColor = TextStyle.Simple; - public UIStyle Get(UIElement element) { + /// Returns a new using style values based on the given . + public UIStyle Get(UIElement element) + { element = Element ?? element; return new() { @@ -48,5 +72,6 @@ public UIStyle Get(UIElement element) { }; } + /// Returns this styling instance with set to . public UIStyling Bind(UIElement element) => this with { Element = element }; } From 390b8360f2d223c7575e161aa1bed05a38cf1dd1 Mon Sep 17 00:00:00 2001 From: Skye Prince Date: Sat, 30 May 2026 21:47:18 -0700 Subject: [PATCH 19/19] small fixes / docs --- FGEGraphics/ClientSystem/ViewUI2D.cs | 1 - FGEGraphics/GraphicsHelpers/FontSets/FontSetEngine.cs | 4 ++++ FGEGraphics/UISystem/UIInputLabel.cs | 2 +- FGEGraphics/UISystem/UILayout.cs | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/FGEGraphics/ClientSystem/ViewUI2D.cs b/FGEGraphics/ClientSystem/ViewUI2D.cs index be5b2b2c..bec939d8 100644 --- a/FGEGraphics/ClientSystem/ViewUI2D.cs +++ b/FGEGraphics/ClientSystem/ViewUI2D.cs @@ -20,7 +20,6 @@ using FGEGraphics.GraphicsHelpers.FontSets; using FGEGraphics.GraphicsHelpers.Shaders; using FGEGraphics.UISystem; -using FreneticUtilities.FreneticExtensions; using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL4; diff --git a/FGEGraphics/GraphicsHelpers/FontSets/FontSetEngine.cs b/FGEGraphics/GraphicsHelpers/FontSets/FontSetEngine.cs index 4aeb9cf1..ede62c49 100644 --- a/FGEGraphics/GraphicsHelpers/FontSets/FontSetEngine.cs +++ b/FGEGraphics/GraphicsHelpers/FontSets/FontSetEngine.cs @@ -45,6 +45,7 @@ public class FontSetEngine(GLFontEngine fontEngine) /// A list of all currently loaded font sets. public Dictionary<(string, int), FontSet> Fonts = []; + /// A cache of font names and sizes to their closest font set. public Dictionary<(string, int), FontSet> ApproximateFonts = []; /// Helper function to get a language data. @@ -127,6 +128,9 @@ public FontSet GetFont(string fontname, int fontsize) return toret; } + /// Returns the closest from the given font name and size. + /// The name of the font. + /// The size of the font. public FontSet GetApproximateFont(string fontname, int fontsize) { if (ApproximateFonts.TryGetValue((fontname, fontsize), out FontSet found)) diff --git a/FGEGraphics/UISystem/UIInputLabel.cs b/FGEGraphics/UISystem/UIInputLabel.cs index bd418ea6..8edb5535 100644 --- a/FGEGraphics/UISystem/UIInputLabel.cs +++ b/FGEGraphics/UISystem/UIInputLabel.cs @@ -172,7 +172,7 @@ public void UpdateScrollGroup() } } - /// Updates the text components based on the cursor positions. + /// Updates the visual state of this label. public void UpdateRenderState() { Paragraph.UpdateRenderState(); diff --git a/FGEGraphics/UISystem/UILayout.cs b/FGEGraphics/UISystem/UILayout.cs index a03c14dd..f93d5d44 100644 --- a/FGEGraphics/UISystem/UILayout.cs +++ b/FGEGraphics/UISystem/UILayout.cs @@ -258,7 +258,7 @@ public UILayout Transpose() return this; } - // TODO: what else to inherit? + /// Returns a new inhabiting the interior space of this one. public UILayout Container() => new UILayout() .SetPosition(() => Element.Style.Inset, () => Element.Style.Inset) .SetSize(() => Width - Element.Style.Inset * 2, () => Height - Element.Style.Inset * 2);