From bef27762ee714668becaefd6aa18f7f60c61800e Mon Sep 17 00:00:00 2001 From: dnesov Date: Mon, 30 Jun 2025 18:34:04 +0200 Subject: [PATCH] Prepare UIElement and Style for animated style implementation. --- .../Resources/DataReaders/IDataReader.cs | 3 + .../Resources/DataReaders/TomlDataReader.cs | 16 +++++- Voile/Source/UI/Containers/Container.cs | 4 +- Voile/Source/UI/Containers/FillContainer.cs | 4 +- Voile/Source/UI/IElement.cs | 2 +- Voile/Source/UI/Style.cs | 18 ++++++ Voile/Source/UI/StyleAnimator.cs | 56 +++++++++++++++++++ Voile/Source/UI/UIElement.cs | 10 +++- Voile/Source/UI/Widgets/Button.cs | 7 +-- Voile/Source/UI/Widgets/Label.cs | 4 +- Voile/Source/UI/Widgets/RectangleWidget.cs | 2 +- Voile/Source/Utils/MathUtils.cs | 12 ++++ 12 files changed, 119 insertions(+), 19 deletions(-) create mode 100644 Voile/Source/UI/StyleAnimator.cs diff --git a/Voile/Source/Resources/DataReaders/IDataReader.cs b/Voile/Source/Resources/DataReaders/IDataReader.cs index d60748e..6d7942d 100644 --- a/Voile/Source/Resources/DataReaders/IDataReader.cs +++ b/Voile/Source/Resources/DataReaders/IDataReader.cs @@ -65,6 +65,9 @@ namespace Voile.Resources.DataReaders /// Default value in case this getter fails to get data. /// double GetDouble(string key, double defaultValue = 0.0); + + string GetString(string key, string defaultValue); + /// /// Get a Voile.Color from this data getter. /// diff --git a/Voile/Source/Resources/DataReaders/TomlDataReader.cs b/Voile/Source/Resources/DataReaders/TomlDataReader.cs index 8b1089a..aa680cd 100644 --- a/Voile/Source/Resources/DataReaders/TomlDataReader.cs +++ b/Voile/Source/Resources/DataReaders/TomlDataReader.cs @@ -105,6 +105,21 @@ public class TomlDataReader : IStreamDataReader, IDataValidator, IStreamKeyValue public double GetDouble(string key, double defaultValue = 0) => TryGetNode(key, out var node) && node.IsFloat ? node.AsFloat : defaultValue; + public string GetString(string key, string defaultValue) + { + if (!TryGetNode(key, out var node)) + { + return defaultValue; + } + + if (node.IsString) + { + return node.AsString; + } + + return defaultValue; + } + public Color GetColor(string key, Color defaultValue) { if (!TryGetNode(key, out var node)) @@ -225,7 +240,6 @@ public class TomlDataReader : IStreamDataReader, IDataValidator, IStreamKeyValue return false; } - private TomlTable? _table; private bool _valid; diff --git a/Voile/Source/UI/Containers/Container.cs b/Voile/Source/UI/Containers/Container.cs index c3feae8..cdd2138 100644 --- a/Voile/Source/UI/Containers/Container.cs +++ b/Voile/Source/UI/Containers/Container.cs @@ -140,10 +140,8 @@ public abstract class Container : UIElement, IParentableElement Update(); } - public override void Render(RenderSystem renderer, Style style) + protected override void OnRender(RenderSystem renderer, Style style) { - RenderStyleBox(renderer, style); - foreach (var child in Children) { if (child is not IRenderableElement renderable) continue; diff --git a/Voile/Source/UI/Containers/FillContainer.cs b/Voile/Source/UI/Containers/FillContainer.cs index 2365bf6..b5bcaf3 100644 --- a/Voile/Source/UI/Containers/FillContainer.cs +++ b/Voile/Source/UI/Containers/FillContainer.cs @@ -23,9 +23,9 @@ public class FillContainer : Container } - public override void Render(RenderSystem renderer, Style style) + protected override void OnRender(RenderSystem renderer, Style style) { - base.Render(renderer, style); + base.OnRender(renderer, style); Rect parentSize; diff --git a/Voile/Source/UI/IElement.cs b/Voile/Source/UI/IElement.cs index 5b27df9..fc6d87f 100644 --- a/Voile/Source/UI/IElement.cs +++ b/Voile/Source/UI/IElement.cs @@ -63,7 +63,7 @@ public interface IUpdatableElement /// /// Update this element. /// - void Update(); + void Update(float dt = 0.0f); /// /// Marks this element as changed, requiring an update. /// diff --git a/Voile/Source/UI/Style.cs b/Voile/Source/UI/Style.cs index 52cda49..5988c43 100644 --- a/Voile/Source/UI/Style.cs +++ b/Voile/Source/UI/Style.cs @@ -11,6 +11,16 @@ namespace Voile.UI; /// public class Style { + public enum AnimationType + { + Linear, + EaseIn, + EaseOut, + EaseInOut + } + + public float TransitionDuration = 0f; + public AnimationType TransitionType = AnimationType.Linear; public Style() { } @@ -78,6 +88,14 @@ public class StyleSheetLoader : ResourceLoader { var style = new Style(); + string easingName = reader.GetString("TransitionType", "Linear"); + + if (!Enum.TryParse(easingName, true, out var easing)) + easing = Style.AnimationType.Linear; + + style.TransitionType = easing; + + if (reader.HasKey("BackgroundColor")) style.BackgroundColor = reader.GetColor("BackgroundColor", Color.Transparent); diff --git a/Voile/Source/UI/StyleAnimator.cs b/Voile/Source/UI/StyleAnimator.cs new file mode 100644 index 0000000..e6e981c --- /dev/null +++ b/Voile/Source/UI/StyleAnimator.cs @@ -0,0 +1,56 @@ +namespace Voile.UI; + +public class StyleAnimator +{ + public bool IsComplete => _elapsed >= _duration; + + public StyleAnimator(Style from, Style to, float duration) + { + _from = from; + _to = to; + + _duration = duration; + _elapsed = 0f; + } + + public static float Ease(float t, Style.AnimationType type) + { + return type switch + { + Style.AnimationType.Linear => t, + Style.AnimationType.EaseIn => t * t, + Style.AnimationType.EaseOut => t * (2 - t), + Style.AnimationType.EaseInOut => t < 0.5f + ? 2 * t * t + : -1 + (4 - 2 * t) * t, + _ => t + }; + } + + public Style Update(float deltaTime) + { + _elapsed = MathF.Min(_elapsed + deltaTime, _duration); + float t = _duration == 0 ? 1 : _elapsed / _duration; + float easedT = Ease(t, _to.TransitionType); + + return LerpStyle(_from, _to, easedT); + } + + private static Style LerpStyle(Style from, Style to, float t) + { + var result = new Style() + { + BackgroundColor = MathUtils.LerpColor(from.BackgroundColor ?? Color.Transparent, to.BackgroundColor ?? Color.Transparent, t), + TextColor = MathUtils.LerpColor(from.TextColor ?? Color.Black, to.TextColor ?? Color.Black, t), + Padding = MathUtils.LerpSize(from.Padding ?? Size.Zero, to.Padding ?? Size.Zero, t), + BorderColor = MathUtils.LerpColor(from.BorderColor ?? Color.Transparent, to.BorderColor ?? Color.Transparent, t), + BorderSize = MathUtils.LerpSize(from.BorderSize ?? Size.Zero, to.BorderSize ?? Size.Zero, t), + TransitionType = to.TransitionType + }; + + return result; + } + + private Style _from, _to; + private float _duration, _elapsed; +} \ No newline at end of file diff --git a/Voile/Source/UI/UIElement.cs b/Voile/Source/UI/UIElement.cs index 30e973f..810c5a4 100644 --- a/Voile/Source/UI/UIElement.cs +++ b/Voile/Source/UI/UIElement.cs @@ -91,7 +91,7 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme MarkDirty(); } - public void Update() + public void Update(float dt = 0.0f) { if (!_dirty) return; _dirty = false; @@ -107,7 +107,13 @@ public abstract class UIElement : IElement, IRenderableElement, IResizeableEleme } } - public abstract void Render(RenderSystem renderer, Style style); + public void Render(RenderSystem renderer, Style style) + { + RenderStyleBox(renderer, style); + OnRender(renderer, style); + } + + protected abstract void OnRender(RenderSystem renderer, Style style); protected abstract void OnUpdate(); /// diff --git a/Voile/Source/UI/Widgets/Button.cs b/Voile/Source/UI/Widgets/Button.cs index c31b12f..2214c98 100644 --- a/Voile/Source/UI/Widgets/Button.cs +++ b/Voile/Source/UI/Widgets/Button.cs @@ -81,7 +81,7 @@ public class Button : Widget Update(); } - public override void Render(RenderSystem renderer, Style style) + protected override void OnRender(RenderSystem renderer, Style style) { if (_padding != style.Padding) { @@ -91,11 +91,6 @@ public class Button : Widget _padding = style.Padding ?? Voile.Size.Zero; var textColor = style.TextColor ?? Color.Black; - // renderer.SetTransform(GlobalPosition, Vector2.Zero); - // renderer.DrawRectangle(new Vector2(Size.Width, Size.Height), backgroundColor); - - RenderStyleBox(renderer, style); - var textPosition = new Vector2(GlobalPosition.X + Padding.Left, GlobalPosition.Y + Padding.Top); renderer.SetTransform(textPosition, Vector2.Zero); renderer.DrawText(_suitableFont, _text, textColor); diff --git a/Voile/Source/UI/Widgets/Label.cs b/Voile/Source/UI/Widgets/Label.cs index 60f6872..80a7fcd 100644 --- a/Voile/Source/UI/Widgets/Label.cs +++ b/Voile/Source/UI/Widgets/Label.cs @@ -50,10 +50,8 @@ public class Label : Widget } - public override void Render(RenderSystem renderer, Style style) + protected override void OnRender(RenderSystem renderer, Style style) { - RenderStyleBox(renderer, style); - renderer.SetTransform(GlobalPosition, Vector2.Zero); renderer.DrawText(_suitableFont, _text, style.TextColor ?? Color.Black); } diff --git a/Voile/Source/UI/Widgets/RectangleWidget.cs b/Voile/Source/UI/Widgets/RectangleWidget.cs index b78cbfd..d42c157 100644 --- a/Voile/Source/UI/Widgets/RectangleWidget.cs +++ b/Voile/Source/UI/Widgets/RectangleWidget.cs @@ -20,7 +20,7 @@ public class RectangleWidget : Widget _hoverColor = color.Lightened(0.25f); } - public override void Render(RenderSystem renderer, Style style) + protected override void OnRender(RenderSystem renderer, Style style) { renderer.SetTransform(GlobalPosition, Vector2.Zero); renderer.DrawRectangle(new Vector2(Size.Width, Size.Height), Color); diff --git a/Voile/Source/Utils/MathUtils.cs b/Voile/Source/Utils/MathUtils.cs index cbb7ccc..e36b0d6 100644 --- a/Voile/Source/Utils/MathUtils.cs +++ b/Voile/Source/Utils/MathUtils.cs @@ -15,6 +15,18 @@ namespace Voile [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static float Lerp(float a, float b, float t) => t <= 0f ? a : t >= 1f ? b : LerpUnclamped(a, b, t); + public static Size LerpSize(Size a, Size b, float t) + { + t = Math.Clamp(t, 0f, 1f); + + float left = Lerp(a.Left, b.Left, t); + float right = Lerp(a.Right, b.Right, t); + float top = Lerp(a.Top, b.Top, t); + float bottom = Lerp(a.Bottom, b.Bottom, t); + + return new Size(left, right, top, bottom); + } + public static Color LerpColor(Color colorA, Color colorB, float t) { t = Math.Clamp(t, 0f, 1f);