diff --git a/TestGame/TestGame.cs b/TestGame/TestGame.cs
index b39bde0..dff9036 100644
--- a/TestGame/TestGame.cs
+++ b/TestGame/TestGame.cs
@@ -98,39 +98,33 @@ public class TestGame : Game
PlaceholderText = "Hello, World!"
};
- c.AddChild(addButton);
- c.AddChild(removeButton);
- c.AddChild(outlineButton);
- c.AddChild(linkButton);
- c.AddChild(inputField);
+ // c.AddChild(addButton);
+ // c.AddChild(removeButton);
+ // c.AddChild(outlineButton);
+ // c.AddChild(linkButton);
+ // c.AddChild(inputField);
- var vc = new VerticalContainer(0.0f);
- vc.AddChild(c);
+ _label = new Label("What the heck??? Word wrapping!!! That's crazy... Noooo wayyy Before GTA 6 too!!!\nnewline :)", _defaultFontSet)
+ {
+ Size = new Rect(256.0f, 128.0f),
+ };
- var f = new MarginContainer(new Size(0.0f));
- f.AddChild(_container);
-
- vc.AddChild(f);
-
- _rootFill.AddChild(vc);
- _uiSystem.AddElement(_rootFill);
+ // _rootFill.AddChild(_label);
+ _uiSystem.AddElement(_label);
}
protected override void Update(double deltaTime)
{
- if (Input.IsActionPressed("reload"))
- {
- // ResourceManager.Reload();
- // _particleSystem!.RestartEmitter(_emitterId);
- }
-
_uiSystem.SetWindowSize(Renderer.WindowSize);
+
+ var mousePos = Input.GetMousePosition();
+ _label.Size = new Rect(mousePos.X, mousePos.Y);
}
protected override void Render(double deltaTime)
{
- Renderer.ClearBackground(Color.Black);
+ Renderer.ClearBackground(Color.White);
// foreach (var emitter in _particleSystem!.Emitters)
// {
@@ -139,6 +133,8 @@ public class TestGame : Game
Renderer.ResetTransform();
_uiSystem.Render(Renderer);
+ Renderer.SetTransform(_label.GlobalPosition, Vector2.Zero);
+ Renderer.DrawRectangleOutline(_label.Size, Color.Red, 2);
}
private void DrawEmitter(ParticleEmitter emitter)
diff --git a/Voile/Source/Rendering/RaylibRenderSystem.cs b/Voile/Source/Rendering/RaylibRenderSystem.cs
index 068e5cf..76524c7 100644
--- a/Voile/Source/Rendering/RaylibRenderSystem.cs
+++ b/Voile/Source/Rendering/RaylibRenderSystem.cs
@@ -247,9 +247,7 @@ namespace Voile.Rendering
var font = fontResource.Value;
if (font.Handle == -1)
- {
LoadFont(font);
- }
if (font.Dirty && font.Handle != -1)
{
@@ -259,26 +257,56 @@ namespace Voile.Rendering
var rayFont = _fontPool[font.Handle];
- float x = transformPosition.X;
- float y = transformPosition.Y;
+ var layout = font.Layout(text, transformPosition);
- for (int i = 0; i < text.Length; i++)
+ foreach (var line in layout.Lines)
{
- char c = text[i];
+ foreach (var run in line.Runs)
+ {
+ Raylib.DrawTextCodepoint(
+ rayFont,
+ run.Character,
+ run.Position,
+ font.Size,
+ VoileColorToRaylibColor(color)
+ );
+ }
+ }
+ }
- if (i > 0)
- x += font.GetKerning(text[i - 1], c);
+ ///
+ /// Draws the text using a pre-computed text layout.
+ ///
+ /// Rasterized font.
+ /// to draw.
+ /// Color of the text.
+ public void DrawText(ResourceRef fontResource, TextLayout layout, Color color)
+ {
+ var font = fontResource.Value;
- Raylib.DrawTextCodepoint(
- rayFont,
- c,
- new Vector2(x, y),
- font.Size,
- VoileColorToRaylibColor(color)
- );
+ if (font.Handle == -1)
+ LoadFont(font);
- var glyph = font.GetGlyph(c);
- x += glyph.Advance * font.SpacingScale + font.LetterSpacing;
+ if (font.Dirty && font.Handle != -1)
+ {
+ UnloadFont(font);
+ LoadFont(font);
+ }
+
+ var rayFont = _fontPool[font.Handle];
+
+ foreach (var line in layout.Lines)
+ {
+ foreach (var run in line.Runs)
+ {
+ Raylib.DrawTextCodepoint(
+ rayFont,
+ run.Character,
+ run.Position,
+ font.Size,
+ VoileColorToRaylibColor(color)
+ );
+ }
}
}
diff --git a/Voile/Source/Rendering/RenderSystem.cs b/Voile/Source/Rendering/RenderSystem.cs
index 8da1b64..84b7af0 100644
--- a/Voile/Source/Rendering/RenderSystem.cs
+++ b/Voile/Source/Rendering/RenderSystem.cs
@@ -184,8 +184,12 @@ namespace Voile.Rendering
/// Fill color.
public abstract void DrawRectangle(Vector2 size, Color color);
+ public void DrawRectangle(Rect rect, Color color) => DrawRectangle(new Vector2(rect.Width, rect.Height), color);
+
public abstract void DrawRectangleOutline(Vector2 size, Color color, float outlineWidth = 1.0f);
+ public void DrawRectangleOutline(Rect rect, Color color, float outlineWidth = 1.0f) => DrawRectangleOutline(new Vector2(rect.Width, rect.Height), color, outlineWidth);
+
///
/// Draws a debug text with a default font.
///
diff --git a/Voile/Source/Resources/Font.cs b/Voile/Source/Resources/Font.cs
index bd7dc87..4afa634 100644
--- a/Voile/Source/Resources/Font.cs
+++ b/Voile/Source/Resources/Font.cs
@@ -22,6 +22,44 @@ public struct Glyph
public Glyph() { }
}
+public struct GlyphRun
+{
+ public char Character;
+ public Vector2 Position;
+
+ public GlyphRun(char character, Vector2 position)
+ {
+ Character = character;
+ Position = position;
+ }
+}
+
+public struct TextLine
+{
+ public List Runs;
+
+ public float Width;
+ public float Height;
+ public float Ascent;
+ public float Descent;
+
+ public TextLine(List runs)
+ {
+ Runs = runs;
+ Width = 0;
+ Height = 0;
+ Ascent = 0;
+ Descent = 0;
+ }
+}
+
+public class TextLayout
+{
+ public List Lines = new();
+
+ public Rect Size = Rect.Zero;
+}
+
///
/// Represents font data.
///
@@ -76,6 +114,101 @@ public class Font : Resource, IUpdatableResource, IDisposable
}
}
+ public TextLayout Layout(string text, Vector2 origin) => Layout(text.AsSpan(), origin);
+
+ public TextLayout Layout(ReadOnlySpan chars, Vector2 origin, float maxWidth = float.MaxValue)
+ {
+ var layout = new TextLayout();
+
+ float startX = origin.X;
+ float x = startX;
+ float y = origin.Y;
+
+ float lineHeight = Size;
+
+ var currentLine = new List();
+
+ float lineAscent = 0;
+ float lineDescent = 0;
+
+ float lineWidth = 0;
+
+ char prev = '\0';
+
+ void BreakLine()
+ {
+ layout.Lines.Add(new TextLine(currentLine)
+ {
+ Width = lineWidth,
+ Height = lineHeight,
+ Ascent = lineAscent,
+ Descent = lineDescent
+ });
+
+ layout.Size.Width = Math.Max(layout.Size.Width, lineWidth);
+
+ currentLine = new List();
+ x = startX;
+ y += lineHeight;
+
+ lineWidth = 0;
+ lineAscent = 0;
+ lineDescent = 0;
+
+ prev = '\0';
+ }
+
+ for (int i = 0; i < chars.Length; i++)
+ {
+ char c = chars[i];
+
+ if (c == '\n')
+ {
+ BreakLine();
+ continue;
+ }
+
+ var glyph = GetGlyph(c);
+
+ float advance = 0;
+
+ if (prev != '\0')
+ advance += GetKerning(prev, c);
+
+ advance += glyph.Advance * SpacingScale + LetterSpacing;
+
+ if (maxWidth > 0 && lineWidth + advance > maxWidth)
+ {
+ BreakLine();
+ }
+
+ if (prev != '\0')
+ {
+ float kerning = GetKerning(prev, c);
+ x += kerning;
+ }
+
+ var pos = new Vector2(x, y);
+
+ currentLine.Add(new GlyphRun(c, pos));
+
+ x += glyph.Advance * SpacingScale + LetterSpacing;
+ lineWidth += advance;
+
+ lineAscent = Math.Max(lineAscent, glyph.Bearing.Y);
+ lineDescent = Math.Max(lineDescent, glyph.Height - glyph.Bearing.Y);
+
+ prev = c;
+ }
+
+ if (currentLine.Count > 0)
+ BreakLine();
+
+ layout.Size.Height = layout.Lines.Count * lineHeight;
+
+ return layout;
+ }
+
///
/// Measures a given string using the font metrics.
///
@@ -86,44 +219,10 @@ public class Font : Resource, IUpdatableResource, IDisposable
return Measure(text.AsSpan());
}
- ///
- /// Measures a given of characters.
- ///
- ///
- /// A with the sizes of a given text using this font.
public Rect Measure(ReadOnlySpan chars)
{
- if (chars.Length == 0)
- return Rect.Zero;
-
- float totalWidth = 0;
- float maxAscent = 0;
- float maxDescent = 0;
-
- for (int i = 0; i < chars.Length; i++)
- {
- char c = chars[i];
- Glyph glyph = GetGlyph(c);
-
- totalWidth += glyph.Advance * SpacingScale + LetterSpacing;
-
- float ascent = glyph.Bearing.Y;
- float descent = glyph.Height - glyph.Bearing.Y;
-
- if (ascent > maxAscent)
- maxAscent = ascent;
- if (descent > maxDescent)
- maxDescent = descent;
-
- if (i > 0)
- {
- char prevChar = chars[i - 1];
- totalWidth += GetKerning(prevChar, c);
- }
- }
-
- float totalHeight = Size;
- return new Rect(totalWidth, totalHeight);
+ var layout = Layout(chars, Vector2.Zero);
+ return layout.Size;
}
public int GetKerning(char left, char right)
diff --git a/Voile/Source/UI/Containers/Container.cs b/Voile/Source/UI/Containers/Container.cs
index cd1b3e6..ec071e9 100644
--- a/Voile/Source/UI/Containers/Container.cs
+++ b/Voile/Source/UI/Containers/Container.cs
@@ -49,7 +49,7 @@ public abstract class Container : UIElement, IParentableElement
if (!child.Visible)
continue;
- if (child is IUpdatableElement updatable && updatable.Dirty)
+ if (child is IUpdatableElement updatable)
updatable.Update(layoutContext);
if (child is IAnchorableElement anchorable)
diff --git a/Voile/Source/UI/Widgets/Label.cs b/Voile/Source/UI/Widgets/Label.cs
index 36c3681..e8b55d2 100644
--- a/Voile/Source/UI/Widgets/Label.cs
+++ b/Voile/Source/UI/Widgets/Label.cs
@@ -7,7 +7,7 @@ namespace Voile.UI.Widgets;
public class Label : Widget
{
- public override Rect MinimumSize => _textSize;
+ public override Rect MinimumSize => Rect.Zero;
public string Text
{
@@ -50,34 +50,43 @@ public class Label : Widget
protected override void OnRender(RenderSystem renderer, Style style)
{
- renderer.SetTransform(GlobalPosition, Vector2.Zero);
- renderer.DrawText(_suitableFont, _text, style.TextColor ?? Color.Black);
+ if (!_font.HasValue)
+ return;
+
+ if (renderer is not RaylibRenderSystem rayRenderer) return; // TODO: Do NOT rely on RaylibRenderSystem check here. Very bad.
+ rayRenderer.DrawText(_font, _layout, style.TextColor ?? Color.Black);
}
protected override void OnUpdate(LayoutContext layoutContext)
{
- ResourceRef fontRef = ResourceRef.Empty();
- foreach (var c in _text)
- {
- if (FontSet.TryGetFontFor(c, out var fallbackFont))
- {
- if (fallbackFont != fontRef)
- {
- fontRef = fallbackFont;
- }
- }
- }
+ ResolveFont();
- _suitableFont = fontRef;
+ if (!_font.HasValue)
+ return;
- var font = _suitableFont.Value;
- _textSize = font.Measure(_text);
-
- Size = _textSize;
+ _layout = _font.Value.Layout(_text, GlobalPosition, Size.Width);
}
- private ResourceRef _suitableFont = ResourceRef.Empty();
+ private void ResolveFont()
+ {
+ if (_font.HasValue)
+ return;
+
+ var text = _text;
+
+ for (int i = 0; i < text.Length; i++)
+ {
+ if (FontSet.TryGetFontFor(text[i], out var f))
+ {
+ _font = f;
+ break;
+ }
+ }
+ }
+
+ private ResourceRef _font = ResourceRef.Empty();
private string _text = "Hello, World!";
- private Rect _textSize = Rect.Zero;
+
+ private TextLayout _layout = new();
}
\ No newline at end of file