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 ecee5dad..ede62c49 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; @@ -44,6 +45,9 @@ 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. public Func GetLanguageHelper; @@ -123,4 +127,24 @@ public FontSet GetFont(string fontname, int fontsize) Fonts.Add((toret.Name, fontsize), toret); 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)) + { + 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/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..2394bf59 100644 --- a/FGEGraphics/UISystem/UIBox.cs +++ b/FGEGraphics/UISystem/UIBox.cs @@ -40,37 +40,37 @@ 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?.Bind(this), new UILayout().SetAnchor(UIAnchor.CENTER)) { IsEnabled = false }); } /// 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/UIDropdown.cs b/FGEGraphics/UISystem/UIDropdown.cs index 7f3660dc..c19a20ab 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.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; @@ -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 b60b6871..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. @@ -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; @@ -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. @@ -373,7 +370,7 @@ public void SetStyle(UIStyle style) { return; } - ElementInternal.Styles.Add(style); + ElementInternal.Style = style; StyleChanged(previousStyle, ElementInternal.Style = style); OnStyleChange?.Invoke(previousStyle, style); } @@ -381,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); } /// @@ -637,13 +634,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); @@ -791,11 +794,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/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 4bbd0a90..8edb5535 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 @@ -44,26 +44,12 @@ public enum EditType SUBMIT } - /// Wraps a instance with logic specific to input labels. - /// The base interaction styles. - 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 . - public static implicit operator UIStyling(Styles styles) => new(styles.Styling); - } - /// public override string Name => "Input Label"; /// 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; @@ -101,15 +87,9 @@ 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() { - /// 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; } @@ -121,32 +101,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. - /// 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) : 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, scrollBarStyles ?? UIStyling.Empty, scrollBarWidth, !maxWidth && scrollBarX, scrollBarY, scrollBarXAnchor, scrollBarYAnchor) { 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 = new(layout.Container()) { 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); } @@ -161,7 +121,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,9 +130,9 @@ public override void Unfocused() Paragraph.SetCursorPosition(0); Paragraph.RenderCursor = false; Paragraph.UpdateRenderState(); - ScrollGroup.ScrollX.Reset(); - ScrollGroup.ScrollY.Reset(); - PlaceholderInfo.RenderSelf = Content.Length == 0; + ScrollGroup.XAxis.Reset(); + ScrollGroup.YAxis.Reset(); + PlaceholderInfo.RenderMode = Content.Length == 0 ? UIRenderMode.FULL : UIRenderMode.NONE; } // FIXME: Paragraph.Width still retains last value when deleting all, incorrect MaxValue calculation @@ -183,9 +143,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 - ScrollGroup.Width, 0); + ScrollGroup.XAxis.ScrollToPos((int)Paragraph.InputInternal.CursorRenderOffset.X, (int)Paragraph.InputInternal.CursorRenderOffset.X - ScrollGroup.XAxis.Value); + ScrollGroup.XAxis.Clamp(); } /// Updates the vertical scroll values based on the text height and cursor position. @@ -193,13 +153,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(); + 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(); } /// Updates the values. @@ -212,12 +172,12 @@ public void UpdateScrollGroup() } } - /// Updates the text components based on the cursor positions. + /// Updates the visual state of this label. 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. @@ -365,7 +325,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/UIInputParagraph.cs b/FGEGraphics/UISystem/UIInputParagraph.cs index ce58a22a..1cc3f7f0 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 . @@ -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/UIInteractionStyles.cs b/FGEGraphics/UISystem/UIInteractionStyles.cs deleted file mode 100644 index 7134efa2..00000000 --- a/FGEGraphics/UISystem/UIInteractionStyles.cs +++ /dev/null @@ -1,60 +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; - - /// 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 UIStyling(UIInteractionStyles styles) => new(styles.Styling); -} diff --git a/FGEGraphics/UISystem/UILabel.cs b/FGEGraphics/UISystem/UILabel.cs index c50a37c1..14a8a7e3 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,15 +98,12 @@ 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. + 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) { @@ -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.TextFont is not null ? 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)); } } @@ -187,7 +165,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..f93d5d44 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,20 @@ 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; + } + + /// 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); /// Gets the relative X value. public int X => Internal.X.Get() + (Element.Parent != null ? Anchor.GetX(Element) : 0); 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 9b5365c2..a3377095 100644 --- a/FGEGraphics/UISystem/UINumberInputLabel.cs +++ b/FGEGraphics/UISystem/UINumberInputLabel.cs @@ -74,19 +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. - /// 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, 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 f223e375..180bf42b 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.AtOrigin().SetWidth(layout.Height / 2)) { RenderSelf = false, 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); } @@ -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/UIParagraph.cs b/FGEGraphics/UISystem/UIParagraph.cs index 8a168bb9..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"; @@ -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; } @@ -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]); diff --git a/FGEGraphics/UISystem/UIRenderMode.cs b/FGEGraphics/UISystem/UIRenderMode.cs new file mode 100644 index 00000000..863eba48 --- /dev/null +++ b/FGEGraphics/UISystem/UIRenderMode.cs @@ -0,0 +1,26 @@ +// +// 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 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 +} 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 d15b3486..55b5ec32 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(null, 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.Container())); + AddChild(ScrollableLayer = new UIScissorGroup(layout.Container())); } /// 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,34 @@ 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) + /// 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. public int Value = 0; @@ -146,42 +131,53 @@ 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; + + /// 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 (!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) { - layout.SetY(() => BarPosition).SetHeight(() => BarLength).SetWidth(() => (int)(width * ScrollBar.Scale)); + throw new Exception("TODO"); } - else + 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.SetX(() => BarPosition).SetWidth(() => BarLength).SetHeight(() => (int)(width * ScrollBar.Scale)); + layout.Transpose(); } ScrollBar = new(styling, layout) { ScaleSize = false }; + ScrollGroup.ScrollBarLayer.AddChild(ScrollBar); + } + + /// Removes the from the parent . + public void RemoveScrollBar() + { + ScrollGroup.ScrollBarLayer.RemoveChild(ScrollBar); + ScrollBar = null; } + // TODO: why is this needed?? /// Sets the and to 0. public void Reset() { @@ -198,6 +194,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 +204,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 +222,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(); } diff --git a/FGEGraphics/UISystem/UIStyle.cs b/FGEGraphics/UISystem/UIStyle.cs index 2a9f9b82..f4b7a74f 100644 --- a/FGEGraphics/UISystem/UIStyle.cs +++ b/FGEGraphics/UISystem/UIStyle.cs @@ -19,61 +19,41 @@ 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" }; - /// What base color to use (or for none). - public Color4F BaseColor = Color4F.Transparent; + /// 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 = Color4F.Transparent; + /// 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 = 0; + /// The thickness to draw an element's outline with. + public int StrokeWeight; - /// How big the drop-shadow effect should be (or 0 for none). - public int DropShadowLength = 0; + /// The distance between an element's outline and its interior content. + public int Padding; - /// The text font (or null for none). + /// The size of the drop shadow on an element. + public int ShadowSize; + + /// 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. - public Func TextStyling = str => str; + /// The text styling effect. + public Func TextStyling; - /// The base color effect for text (consider if unsure). - public string TextBaseColor = TextStyle.Simple; + /// 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; - /// Constructs a default element style. - public UIStyle() - { - } - - /// 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; - DropShadowLength = style.DropShadowLength; - TextFont = style.TextFont; - TextStyling = style.TextStyling; - TextBaseColor = style.TextBaseColor; - } - - /// 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/UIStyleValue.cs b/FGEGraphics/UISystem/UIStyleValue.cs new file mode 100644 index 00000000..a603c89a --- /dev/null +++ b/FGEGraphics/UISystem/UIStyleValue.cs @@ -0,0 +1,61 @@ +// +// 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 System.Threading.Tasks; + +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; + + /// 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 + : !element.IsEnabled ? disabled + : idle + ); +} diff --git a/FGEGraphics/UISystem/UIStyling.cs b/FGEGraphics/UISystem/UIStyling.cs index 4720f9e0..6ca4a21b 100644 --- a/FGEGraphics/UISystem/UIStyling.cs +++ b/FGEGraphics/UISystem/UIStyling.cs @@ -6,6 +6,10 @@ // 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 +18,60 @@ namespace FGEGraphics.UISystem; -/// Represents the styling logic for a . -public struct UIStyling +/// Represents the styling logic of a . +public record UIStyling { - /// Empty styling logic. Resolves to . - public static readonly UIStyling Empty = new((UIStyle)null); + /// + /// 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; - /// A constant style. - public UIStyle Constant; + /// The color to fill an element's interior with. + public UIStyleValue Fill = Color4F.Transparent; - /// A dynamic style. If present, updates the relevant every frame. - public Func Dynamic; + /// The texture to draw on an element. + public UIStyleValue Texture = default; - /// Whether this style is equivalent to - public readonly bool IsEmpty => Constant is null && Dynamic is null; + /// The color to draw an element's outline with. + public UIStyleValue Stroke = Color4F.Transparent; - /// Constructs styling logic using a constant style. - /// The constant style. - public UIStyling(UIStyle style) - { - Constant = style; - } + /// The thickness to draw an element's outline with. + public UIStyleValue StrokeWeight = 0; - /// Constructs styling logic using a dynamic style. - /// The dynamic style. - public UIStyling(Func styling) - { - Dynamic = styling; - } + /// The distance between an element's outline and its interior content. + public UIStyleValue Padding = 0; - /// - /// 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; + /// The size of the drop shadow on an element. + public UIStyleValue ShadowSize = 0; - /// - /// 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) + /// 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; + + /// Returns a new using style values based on the given . + public UIStyle Get(UIElement element) { - if (Dynamic is not null) + element = Element ?? element; + return new() { - Func dynamic = Dynamic; - return new(_ => dynamic(element)); - } - return this; + Fill = Fill.Get(element), + Texture = Texture.Get(element), + Stroke = Stroke.Get(element), + StrokeWeight = StrokeWeight.Get(element), + Padding = Padding.Get(element), + ShadowSize = 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); + /// Returns this styling instance with set to . + public UIStyling Bind(UIElement element) => this with { Element = element }; } diff --git a/FGEGraphics/UISystem/UIToggleBox.cs b/FGEGraphics/UISystem/UIToggleBox.cs index ac2cb157..db910291 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.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()); + UILabel label = new(text, labelStyling ?? styling?.Bind(box), new UILayout()); list.AddListItem(box); list.AddListItem(label); return (box, label, list);