diff --git a/TestGame/TestGame.cs b/TestGame/TestGame.cs
index 4c46e23..b39bde0 100644
--- a/TestGame/TestGame.cs
+++ b/TestGame/TestGame.cs
@@ -20,7 +20,7 @@ public class TestGame : Game
InitializeSystemsDefault();
_uiSystem = new UISystem(Input);
- _uiSystem.RenderDebugRects = true;
+ _uiSystem.RenderDebugRects = false;
ResourceManager.EnableFileWatching();
@@ -73,7 +73,7 @@ public class TestGame : Game
_uiSystem.SetStyleSheet(_styleSheet);
- var addButton = new Button("", _defaultFontSet);
+ var addButton = new Button("Default button", _defaultFontSet);
var removeButton = new Button("Danger button", _defaultFontSet);
@@ -90,7 +90,7 @@ public class TestGame : Game
{
StyleVariant = "Layer01",
ConfineToContents = true,
- Anchor = Anchor.TopCenter
+ Anchor = Anchor.TopCenter,
};
var inputField = new InputField(string.Empty, _defaultFontSet)
@@ -124,6 +124,8 @@ public class TestGame : Game
// ResourceManager.Reload();
// _particleSystem!.RestartEmitter(_emitterId);
}
+
+ _uiSystem.SetWindowSize(Renderer.WindowSize);
}
protected override void Render(double deltaTime)
diff --git a/Voile/Source/UI/Containers/Container.cs b/Voile/Source/UI/Containers/Container.cs
index cdd2138..54f7d10 100644
--- a/Voile/Source/UI/Containers/Container.cs
+++ b/Voile/Source/UI/Containers/Container.cs
@@ -40,18 +40,20 @@ public abstract class Container : UIElement, IParentableElement
MarkDirty();
}
- protected override void OnUpdate()
+ protected override void OnUpdate(LayoutContext layoutContext)
{
- foreach (var child in _children)
+ for (int i = 0; i < _children.Count; i++)
{
- if (child is not IUpdatableElement updatable) continue;
+ var child = _children[i];
- updatable.Update();
+ if (!child.Visible)
+ continue;
+
+ if (child is IUpdatableElement updatable && updatable.Dirty)
+ updatable.Update(layoutContext);
if (child is IAnchorableElement anchorable)
- {
anchorable.ApplyAnchor(GlobalPosition, Size);
- }
}
Arrange();
@@ -62,15 +64,15 @@ public abstract class Container : UIElement, IParentableElement
}
}
- public override void MarkDirty()
+ public override void MarkDirty(DirtyFlags flags = DirtyFlags.Layout)
{
- base.MarkDirty();
+ base.MarkDirty(flags);
foreach (var child in _children)
{
if (child is not IUpdatableElement updatable) continue;
- updatable.MarkDirty();
+ updatable.MarkDirty(flags);
}
}
@@ -85,6 +87,9 @@ public abstract class Container : UIElement, IParentableElement
///
public void RecalculateSizes()
{
+ if (_children.Count == 0)
+ return;
+
float minX = float.MaxValue;
float minY = float.MaxValue;
float maxX = float.MinValue;
@@ -113,31 +118,45 @@ public abstract class Container : UIElement, IParentableElement
if (finalSize != Size)
{
- Size = finalSize;
+ LayoutSize = finalSize;
}
if (_minimumSize > Size)
{
- Size = _minimumSize;
+ LayoutSize = _minimumSize;
}
}
+ ///
+ /// Adds an to the list of children.
+ ///
+ /// Child to add.
+ ///
public void AddChild(UIElement child)
{
// child.StyleSheetOverride = StyleSheet;
+ if (child.Parent != null)
+ throw new InvalidOperationException("This UIElement already contains a parent.");
+
_children.Add(child);
child.SetParent(this);
MarkDirty();
- Update();
}
+ ///
+ /// Removes an from the list of children.
+ ///
+ /// Child to remove.
+ ///
public void RemoveChild(UIElement child)
{
+ if (child.Parent != this)
+ throw new InvalidOperationException("This UIElement is not a child of this Container.");
+
_children.Remove(child);
MarkDirty();
- Update();
}
protected override void OnRender(RenderSystem renderer, Style style)
diff --git a/Voile/Source/UI/Containers/FillContainer.cs b/Voile/Source/UI/Containers/FillContainer.cs
index b5bcaf3..7b79540 100644
--- a/Voile/Source/UI/Containers/FillContainer.cs
+++ b/Voile/Source/UI/Containers/FillContainer.cs
@@ -1,5 +1,3 @@
-using Voile.Rendering;
-
namespace Voile.UI.Containers;
///
@@ -8,56 +6,18 @@ namespace Voile.UI.Containers;
///
public class FillContainer : Container
{
- public FillContainer()
- {
-
- }
-
- public FillContainer(Rect minimumSize) : base(minimumSize)
- {
-
- }
-
public override void Arrange()
{
-
+ // FillContainer does not position children.
+ // Children handle their own layout or are absolute.
}
- protected override void OnRender(RenderSystem renderer, Style style)
+ protected override void OnUpdate(LayoutContext layout)
{
- base.OnRender(renderer, style);
+ Size = Parent != null
+ ? Parent.Size
+ : new Rect(layout.WindowSize.X, layout.WindowSize.Y);
- Rect parentSize;
-
- if (Parent != null)
- {
- parentSize = Parent.Size;
- }
- else
- {
- var windowSize = renderer.WindowSize;
- var windowRect = new Rect(windowSize.X, windowSize.Y);
-
- parentSize = windowRect;
- }
-
- if (_lastParentSize != parentSize)
- {
- Size = parentSize;
- _lastParentSize = parentSize;
- }
+ base.OnUpdate(layout);
}
-
- protected override void OnUpdate()
- {
- base.OnUpdate();
- Size = _lastParentSize;
-
- if (Children.Count != 0)
- {
- Children[0].Size = Size;
- }
- }
-
- private Rect _lastParentSize = Rect.Zero;
}
\ No newline at end of file
diff --git a/Voile/Source/UI/Containers/FlexContainer.cs b/Voile/Source/UI/Containers/FlexContainer.cs
index 397f068..10a7478 100644
--- a/Voile/Source/UI/Containers/FlexContainer.cs
+++ b/Voile/Source/UI/Containers/FlexContainer.cs
@@ -124,7 +124,7 @@ public class FlexContainer : Container
float justifyOffset = GetJustifyOffset(containerMainSize, lineMainLength, line.Count);
float currentMain = mainPos + justifyOffset;
- float maxLineCross = line.Select(child => GetCrossSize(GetChildSize(child))).Max();
+ float maxLineCross = line.Max(child => GetCrossSize(GetChildSize(child)));
foreach (var child in line)
{
diff --git a/Voile/Source/UI/Containers/MarginContainer.cs b/Voile/Source/UI/Containers/MarginContainer.cs
index 1886d98..16742a3 100644
--- a/Voile/Source/UI/Containers/MarginContainer.cs
+++ b/Voile/Source/UI/Containers/MarginContainer.cs
@@ -21,9 +21,10 @@ public class MarginContainer : Container
Margin = margin;
}
- protected override void OnUpdate()
+ protected override void OnUpdate(LayoutContext layoutContext)
{
- base.OnUpdate();
+ base.OnUpdate(layoutContext);
+
if (Parent == null) return;
if (Size != Parent.Size)
diff --git a/Voile/Source/UI/DirtyFlags.cs b/Voile/Source/UI/DirtyFlags.cs
new file mode 100644
index 0000000..443622b
--- /dev/null
+++ b/Voile/Source/UI/DirtyFlags.cs
@@ -0,0 +1,10 @@
+namespace Voile.UI;
+
+[Flags]
+public enum DirtyFlags
+{
+ None = 0,
+ Layout = 1 << 0,
+ Content = 1 << 1,
+ Style = 1 << 2,
+}
\ No newline at end of file
diff --git a/Voile/Source/UI/IElement.cs b/Voile/Source/UI/IElement.cs
index 9d4a29f..c21407c 100644
--- a/Voile/Source/UI/IElement.cs
+++ b/Voile/Source/UI/IElement.cs
@@ -25,7 +25,7 @@ public interface IElement
public interface ITickableElement
{
///
- /// Excutes unconditionally on every frame engine loop step.
+ /// Executes unconditionally on every frame engine loop step.
///
/// Elapsed delta frame time in seconds.
/// InputSystem that this tickable element should use to poll input events.
@@ -77,11 +77,12 @@ public interface IUpdatableElement
///
/// Update this element.
///
- void Update(float dt = 0.0f);
+ void Update(LayoutContext layoutContext);
///
- /// Marks this element as changed, requiring an update.
+ /// Marks this element as dirty (i.e. requiring an update).
///
- void MarkDirty();
+ /// The parts that were updated, as flags.
+ void MarkDirty(DirtyFlags flags = DirtyFlags.Layout);
}
///
diff --git a/Voile/Source/UI/LayoutContext.cs b/Voile/Source/UI/LayoutContext.cs
new file mode 100644
index 0000000..5da4d69
--- /dev/null
+++ b/Voile/Source/UI/LayoutContext.cs
@@ -0,0 +1,13 @@
+using System.Numerics;
+
+namespace Voile.UI;
+
+public readonly struct LayoutContext
+{
+ public Vector2 WindowSize { get; }
+
+ public LayoutContext(Vector2 windowSize)
+ {
+ WindowSize = windowSize;
+ }
+}
\ No newline at end of file
diff --git a/Voile/Source/UI/UIElement.cs b/Voile/Source/UI/UIElement.cs
index fa15302..29b789c 100644
--- a/Voile/Source/UI/UIElement.cs
+++ b/Voile/Source/UI/UIElement.cs
@@ -55,7 +55,7 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
_size.Width = width;
_size.Height = height;
- MarkDirty();
+ MarkDirty(DirtyFlags.Layout);
}
}
@@ -63,57 +63,112 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
public Anchor Anchor { get; set; } = Anchor.TopLeft;
public abstract Rect MinimumSize { get; }
- public bool Dirty => _dirty;
+ public bool Dirty => _dirty != DirtyFlags.None || _pendingDirty != DirtyFlags.None;
public bool TryGetStyle(StyleSheet styleSheet, [NotNullWhen(true)] out Style? style)
{
return styleSheet.TryGet(StyleName, out style);
}
- public virtual void MarkDirty()
- {
- if (Parent != null && !Parent.Dirty)
- {
- Parent.MarkDirty();
- }
-
- _dirty = true;
- }
+ public virtual void MarkDirty(DirtyFlags flags = DirtyFlags.Layout) => _pendingDirty |= flags;
///
/// Sets a parent element for this .
///
/// Element to parent this to.
+
public void SetParent(UIElement parent)
{
_parent = parent;
- MarkDirty();
+ MarkDirty(DirtyFlags.Layout);
}
- public void Update(float dt = 0.0f)
+ public void Update(LayoutContext layoutContext)
{
- if (!_dirty) return;
- _dirty = false;
+ _dirty |= _pendingDirty;
+ _pendingDirty = DirtyFlags.None;
- if (Size == Rect.Zero)
- Size = MinimumSize;
+ if (_dirty == DirtyFlags.None)
+ return;
- OnUpdate();
+ if (HasDirty(DirtyFlags.Layout))
+ OnLayoutUpdate();
- if (_parent is not null && _parent.Size != Rect.Zero)
- {
- ApplyAnchor(_parent.GlobalPosition, _parent.Size);
- }
+ if (HasDirty(DirtyFlags.Content))
+ OnContentUpdate();
+
+ if (HasDirty(DirtyFlags.Style))
+ OnStyleUpdate();
+
+ _dirty = DirtyFlags.None;
+
+ OnUpdate(layoutContext);
}
+
public void Render(RenderSystem renderer, Style style)
{
RenderStyleBox(renderer, style);
OnRender(renderer, style);
}
+ public void DrawSize(RenderSystem renderer)
+ {
+ renderer.SetTransform(GlobalPosition, Vector2.Zero);
+ renderer.DrawRectangleOutline(new Vector2(Size.Width, Size.Height), Color.Red, 2.0f);
+ }
+
+ ///
+ /// Determines if this contains a point within its confines.
+ ///
+ /// A global position of the point.
+ /// True if the point is inside the widget; otherwise, false.
+ public bool ContainsPoint(Vector2 point)
+ {
+ return point.X >= GlobalPosition.X && point.Y >= GlobalPosition.Y &&
+ point.X <= GlobalPosition.X + Size.Width &&
+ point.Y <= GlobalPosition.Y + Size.Height;
+ }
+
+ ///
+ /// Applies this anchor.
+ ///
+ public virtual void ApplyAnchor(Vector2 parentPosition, Rect parentRect)
+ {
+ LocalPosition = Anchor.Calculate(parentPosition, parentRect, Size) + new Vector2(AnchorOffset.X, AnchorOffset.Y);
+ }
+
+ ///
+ /// Helper method for determining if this has a specific dirty flag.
+ ///
+ ///
+ ///
+ public bool HasDirty(DirtyFlags flags) =>
+ (_dirty & flags) != 0 || (_pendingDirty & flags) != 0;
+
+ ///
+ /// The layout-computed size of this UI element that doesn't trigger
+ /// dirty state propagation.
+ ///
+ ///
+ /// This property is intended to be used exclusively by the layout system
+ /// (e.g. containers during RecalculateSizes).
+ ///
+ /// Unlike the property setter, this property does not call
+ /// MarkDirty, and therefore will not trigger layout invalidation or
+ /// parent propagation.
+ ///
+ protected Rect LayoutSize
+ {
+ get => _size; set => _size = value;
+ }
+
protected abstract void OnRender(RenderSystem renderer, Style style);
- protected abstract void OnUpdate();
+
+ protected virtual void OnLayoutUpdate() { }
+ protected virtual void OnContentUpdate() { }
+ protected virtual void OnStyleUpdate() { }
+ protected abstract void OnUpdate(LayoutContext layoutContext);
///
/// Renders a stylebox from a given style.
@@ -174,32 +229,6 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
}
}
- public void DrawSize(RenderSystem renderer)
- {
- renderer.SetTransform(GlobalPosition, Vector2.Zero);
- renderer.DrawRectangleOutline(new Vector2(Size.Width, Size.Height), Color.Red, 2.0f);
- }
-
- ///
- /// Determines if this contains a point within its confines.
- ///
- /// A global position of the point.
- /// True if the point is inside the widget; otherwise, false.
- public bool ContainsPoint(Vector2 point)
- {
- return point.X >= GlobalPosition.X && point.Y >= GlobalPosition.Y &&
- point.X <= GlobalPosition.X + Size.Width &&
- point.Y <= GlobalPosition.Y + Size.Height;
- }
-
- ///
- /// Applies this anchor.
- ///
- public virtual void ApplyAnchor(Vector2 parentPosition, Rect parentRect)
- {
- LocalPosition = Anchor.Calculate(parentPosition, parentRect, Size) + new Vector2(AnchorOffset.X, AnchorOffset.Y);
- }
-
private string ConstructStyleModifiers(string[]? modifiers)
{
if (modifiers == null)
@@ -225,7 +254,8 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme
return $".{StyleVariant}";
}
- private bool _dirty = true;
+ private DirtyFlags _dirty = DirtyFlags.Layout;
+ private DirtyFlags _pendingDirty = DirtyFlags.None;
private Rect _size = Rect.Zero;
private UIElement? _parent;
diff --git a/Voile/Source/UI/UISystem.cs b/Voile/Source/UI/UISystem.cs
index aa4921f..39f49a4 100644
--- a/Voile/Source/UI/UISystem.cs
+++ b/Voile/Source/UI/UISystem.cs
@@ -1,7 +1,6 @@
using System.Numerics;
using Voile.Input;
using Voile.Rendering;
-using Voile.Resources;
namespace Voile.UI;
@@ -33,10 +32,20 @@ public class UISystem : IUpdatableSystem, IRenderableSystem, IReloadableSystem
{
element.StyleSheetOverride = _styleSheet;
_elements.Add(element);
- _inputElementIndices.Add(element.GlobalPosition, _elements.Count - 1);
}
public void RemoveElement(UIElement element) => _elements.Remove(element);
+ public void SetWindowSize(Vector2 size)
+ {
+ if (_windowSize == size)
+ return;
+
+ _windowSize = size;
+
+ foreach (var element in _elements)
+ element.MarkDirty(DirtyFlags.Layout);
+ }
+
public void Update(double deltaTime)
{
float dt = (float)deltaTime;
@@ -46,7 +55,11 @@ public class UISystem : IUpdatableSystem, IRenderableSystem, IReloadableSystem
foreach (var element in _elements)
{
if (element is not IUpdatableElement updatable) continue;
- updatable.Update();
+
+ if (!updatable.Dirty)
+ continue;
+
+ updatable.Update(new LayoutContext(_windowSize));
}
}
@@ -213,11 +226,10 @@ public class UISystem : IUpdatableSystem, IRenderableSystem, IReloadableSystem
return false;
}
+ private Vector2 _windowSize;
private ResourceRef _styleSheet;
private List _elements = new();
private InputSystem _input;
- private GridSet _inputElementIndices = new();
-
private Vector2 _lastMousePosition = Vector2.Zero;
}
\ No newline at end of file
diff --git a/Voile/Source/UI/Widgets/Button.cs b/Voile/Source/UI/Widgets/Button.cs
index 68c62de..ef92408 100644
--- a/Voile/Source/UI/Widgets/Button.cs
+++ b/Voile/Source/UI/Widgets/Button.cs
@@ -23,7 +23,7 @@ public class Button : Widget
get => _text; set
{
_text = value;
- MarkDirty();
+ MarkDirty(DirtyFlags.Content);
}
}
@@ -57,7 +57,6 @@ public class Button : Widget
FontSet.AddFont(fontOverride);
MarkDirty();
- Update();
}
public Button(string text, FontSet fontSet)
@@ -67,7 +66,6 @@ public class Button : Widget
FontSet = fontSet;
MarkDirty();
- Update();
}
public Button(string text, FontSet fontSet, Action pressedAction)
@@ -78,25 +76,23 @@ public class Button : Widget
FontSet = fontSet;
MarkDirty();
- Update();
}
protected override void OnRender(RenderSystem renderer, Style style)
{
- if (_padding != style.Padding)
- {
- MarkDirty();
- }
-
_padding = style.Padding ?? Voile.Size.Zero;
- var textColor = style.TextColor ?? Color.Black;
- var textPosition = new Vector2(GlobalPosition.X + Padding.Left, GlobalPosition.Y + Padding.Top);
- renderer.SetTransform(textPosition, Vector2.Zero);
+ var color = style.TextColor ?? Color.Black;
- if (_suitableFont.HasValue)
+ var pos = new Vector2(
+ GlobalPosition.X + _padding.Left,
+ GlobalPosition.Y + _padding.Top);
+
+ renderer.SetTransform(pos, Vector2.Zero);
+
+ if (_font.HasValue)
{
- renderer.DrawText(_suitableFont, _text, textColor);
+ renderer.DrawText(_font, _text, color);
}
}
@@ -139,39 +135,55 @@ public class Button : Widget
}
}
- protected override void OnUpdate()
+ protected override void OnUpdate(LayoutContext layoutContext)
{
- ResourceRef fontRef = ResourceRef.Empty();
+ ResolveFont();
- foreach (var c in _text)
+ if (!_font.HasValue)
+ return;
+
+ var font = _font.Value;
+
+ var newSize = font.Measure(_text);
+ _textSize = newSize;
+
+ var final = _textSize + _padding;
+
+ if (_cachedSize.Width == final.Width &&
+ _cachedSize.Height == final.Height)
+ return;
+
+ _cachedSize = final;
+
+ Size = final;
+
+ MarkDirty(DirtyFlags.Layout);
+ }
+
+ private void ResolveFont()
+ {
+ if (_font.HasValue)
+ return;
+
+ var text = _text;
+
+ for (int i = 0; i < text.Length; i++)
{
- if (FontSet.TryGetFontFor(c, out var fallbackFont))
+ if (FontSet.TryGetFontFor(text[i], out var f))
{
- if (fallbackFont != fontRef)
- {
- fontRef = fallbackFont;
- }
+ _font = f;
+ break;
}
}
-
- if (fontRef.HasValue)
- {
- _suitableFont = fontRef;
-
- var font = _suitableFont.Value;
- _textSize = font.Measure(_text);
- }
-
- Size = _padding + _textSize;
}
private Action? _pressedAction;
-
- private ResourceRef _suitableFont = ResourceRef.Empty();
-
+ private Rect _cachedSize = Rect.Zero;
private string _text = "Hello, World!";
private Rect _textSize = Rect.Zero;
+ private ResourceRef _font = ResourceRef.Empty();
+
private Size _padding;
private bool _isHeldDown;
diff --git a/Voile/Source/UI/Widgets/InputField.cs b/Voile/Source/UI/Widgets/InputField.cs
index 886cc85..0777126 100644
--- a/Voile/Source/UI/Widgets/InputField.cs
+++ b/Voile/Source/UI/Widgets/InputField.cs
@@ -27,7 +27,8 @@ public class InputField : Widget, ITickableElement
{
_input = value ?? string.Empty;
_cursor = Math.Clamp(_cursor, 0, _input.Length);
- MarkDirty();
+
+ MarkDirty(DirtyFlags.Content);
}
}
@@ -43,7 +44,6 @@ public class InputField : Widget, ITickableElement
FontSet = fontSet;
MarkDirty();
- Update();
}
public void Tick(float dt, InputSystem input)
@@ -94,7 +94,8 @@ public class InputField : Widget, ITickableElement
{
_input = _input.Insert(_cursor, c.ToString());
_cursor++;
- MarkDirty();
+
+ MarkDirty(DirtyFlags.Content);
}
context.SetHandled();
@@ -149,7 +150,7 @@ public class InputField : Widget, ITickableElement
{
_input = _input.Remove(_cursor - 1, 1);
_cursor--;
- MarkDirty();
+ MarkDirty(DirtyFlags.Content);
}
break;
@@ -157,7 +158,7 @@ public class InputField : Widget, ITickableElement
if (_cursor > 0)
{
_cursor--;
- MarkDirty();
+ MarkDirty(DirtyFlags.Content);
}
break;
@@ -165,7 +166,7 @@ public class InputField : Widget, ITickableElement
if (_cursor < _input.Length)
{
_cursor++;
- MarkDirty();
+ MarkDirty(DirtyFlags.Content);
}
break;
}
@@ -178,95 +179,94 @@ public class InputField : Widget, ITickableElement
protected override void OnRender(RenderSystem renderer, Style style)
{
- if (_padding != style.Padding)
- {
- MarkDirty();
- }
-
_padding = style.Padding ?? Voile.Size.Zero;
var textColor = style.TextColor ?? Color.Black;
var caretColor = style.BorderColor ?? Color.Black;
- var textPosition = new Vector2(GlobalPosition.X + _padding.Left, GlobalPosition.Y + _padding.Top);
- renderer.SetTransform(textPosition, Vector2.Zero);
+ var pos = new Vector2(GlobalPosition.X + _padding.Left,
+ GlobalPosition.Y + _padding.Top);
- // Placeholder
- // TODO: use a color from the style instead of making it less transparent.
- if (string.IsNullOrEmpty(_input) && !string.IsNullOrEmpty(PlaceholderText))
- {
- var placeholderColor = textColor.Lightened(0.5f);
+ renderer.SetTransform(pos, Vector2.Zero);
- if (_suitableFont.HasValue)
- {
- renderer.DrawText(_suitableFont, PlaceholderText, placeholderColor);
- }
- }
- else
- {
- if (_suitableFont.HasValue)
- {
- renderer.DrawText(_suitableFont, _input, textColor);
- }
- }
+ string text = string.IsNullOrEmpty(_input)
+ ? PlaceholderText
+ : _input;
+
+ // TODO: use a placeholder color from the style instead of making it lighter than the original text color.
+ var placeholderColor = textColor.Lightened(0.5f);
+
+ var color = string.IsNullOrEmpty(_input)
+ ? placeholderColor
+ : textColor;
+
+ if (_font.HasValue)
+ renderer.DrawText(_font, text, color);
- // Caret
if (_isFocused && _blink)
{
- var caretX = MeasureTextWidth(_input.AsSpan(0, _cursor));
+ float caretX = GetCaretX(_cursor);
renderer.SetTransform(
- new Vector2(GlobalPosition.X + caretX + _padding.Left, GlobalPosition.Y + _padding.Top),
- Vector2.Zero
- );
+ new Vector2(GlobalPosition.X + _padding.Left + caretX,
+ GlobalPosition.Y + _padding.Top),
+ Vector2.Zero);
- var caretHeight = Math.Max(_placeholderSize.Height, _textSize.Height);
-
- renderer.DrawRectangle(
- new Vector2(1, caretHeight),
- caretColor
- );
+ float h = Math.Max(_textSize.Height, _placeholderSize.Height);
+ renderer.DrawRectangle(new Vector2(1, h), caretColor);
}
}
- protected override void OnUpdate()
+ protected override void OnUpdate(LayoutContext layoutContext)
{
- ResourceRef fontRef = ResourceRef.Empty();
+ if (!_font.HasValue)
+ ResolveFont();
+
+ if (!_font.HasValue)
+ return;
+
+ var font = _font.Value;
+
+ _textSize = font.Measure(_input);
+
+ if (_input.Length == 0)
+ _placeholderSize = font.Measure(PlaceholderText);
+
+ var content = Rect.MaxWidth(_textSize, _placeholderSize);
+
+ float width = content.Width + _padding.Left + _padding.Right;
+ float height = content.Height + _padding.Top + _padding.Bottom;
+
+ if (LayoutSize.Width == width && LayoutSize.Height == height)
+ return;
+
+ LayoutSize = new Rect(width, height);
+
+ MarkDirty(DirtyFlags.Layout);
+ }
+
+ private float GetCaretX(int index)
+ {
+ var span = _input.AsSpan(0, index);
+
+ if (!_font.HasValue)
+ return 0.0f;
+
+ return _font.Value.Measure(span).Width;
+ }
+
+ private void ResolveFont()
+ {
+ if (_font.HasValue)
+ return;
var text = string.IsNullOrEmpty(_input) ? PlaceholderText : _input;
foreach (var c in text)
{
- if (FontSet.TryGetFontFor(c, out var fallbackFont))
- {
- fontRef = fallbackFont;
- }
+ if (FontSet.TryGetFontFor(c, out var f))
+ _font = f;
}
-
- if (fontRef.HasValue)
- {
- _suitableFont = fontRef;
- var font = _suitableFont.Value;
-
- _textSize = font.Measure(_input);
-
- if (string.IsNullOrEmpty(_input))
- _placeholderSize = font.Measure(PlaceholderText);
- }
-
- var size = Rect.MaxWidth(_placeholderSize, _textSize);
-
- Size = _padding + size;
- }
-
- private float MeasureTextWidth(ReadOnlySpan chars)
- {
- if (!_suitableFont.HasValue)
- {
- return 0.0f;
- }
-
- return _suitableFont.Value.Measure(chars).Width;
}
private float _repeatTimer = 0.0f;
@@ -281,7 +281,7 @@ public class InputField : Widget, ITickableElement
private bool _blink = true;
- private ResourceRef _suitableFont = ResourceRef.Empty();
+ private ResourceRef _font = ResourceRef.Empty();
private Size _padding = Voile.Size.Zero;
private Rect _textSize = Rect.Zero;
diff --git a/Voile/Source/UI/Widgets/Label.cs b/Voile/Source/UI/Widgets/Label.cs
index 80a7fcd..36c3681 100644
--- a/Voile/Source/UI/Widgets/Label.cs
+++ b/Voile/Source/UI/Widgets/Label.cs
@@ -32,7 +32,6 @@ public class Label : Widget
FontSet.AddFont(fontOverride);
MarkDirty();
- Update();
}
public Label(string text, FontSet fontSet)
@@ -42,7 +41,6 @@ public class Label : Widget
FontSet = fontSet;
MarkDirty();
- Update();
}
protected override void OnInput(UIInputContext action)
@@ -56,7 +54,7 @@ public class Label : Widget
renderer.DrawText(_suitableFont, _text, style.TextColor ?? Color.Black);
}
- protected override void OnUpdate()
+ protected override void OnUpdate(LayoutContext layoutContext)
{
ResourceRef fontRef = ResourceRef.Empty();
foreach (var c in _text)
diff --git a/Voile/Source/UI/Widgets/RectangleWidget.cs b/Voile/Source/UI/Widgets/RectangleWidget.cs
index d42c157..6f87e4f 100644
--- a/Voile/Source/UI/Widgets/RectangleWidget.cs
+++ b/Voile/Source/UI/Widgets/RectangleWidget.cs
@@ -65,7 +65,7 @@ public class RectangleWidget : Widget
}
}
- protected override void OnUpdate()
+ protected override void OnUpdate(LayoutContext layoutContext)
{
}