mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
FontRenderer: Basic Rich Text processing and lazy text building
This commit is contained in:
@@ -477,6 +477,18 @@ namespace GlitchyEditor.EditWindows
|
||||
|
||||
private static void ShowTextRendererComponentEditor(Entity entity, TextRendererComponent* textRendererComponent)
|
||||
{
|
||||
StartNewProperty("Rich text");
|
||||
|
||||
ImGui.AttachTooltip("If checked, the text will be interpreted as rich text. This means, that you can use tags to change the style of the text.");
|
||||
|
||||
bool isRichText = textRendererComponent.IsRichText;
|
||||
|
||||
if (ImGui.Checkbox("##rich_text", &isRichText))
|
||||
{
|
||||
textRendererComponent.IsRichText = isRichText;
|
||||
textRendererComponent.NeedsRebuild = true;
|
||||
}
|
||||
|
||||
StartNewProperty("Text");
|
||||
|
||||
String text = textRendererComponent.[Friend]_text;
|
||||
@@ -498,6 +510,8 @@ namespace GlitchyEditor.EditWindows
|
||||
{
|
||||
text.Length = length;
|
||||
}
|
||||
|
||||
textRendererComponent.NeedsRebuild = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,6 +135,49 @@ namespace GlitchyEngine.Renderer.Text
|
||||
case InvertCase;
|
||||
}
|
||||
|
||||
class StyleStack<T>
|
||||
{
|
||||
private append List<T> _stack = .();
|
||||
|
||||
public this(T bottomValue)
|
||||
{
|
||||
_stack.Add(bottomValue);
|
||||
}
|
||||
|
||||
public void Push(T value)
|
||||
{
|
||||
_stack.Add(value);
|
||||
}
|
||||
|
||||
public T CurrentValue()
|
||||
{
|
||||
return _stack.Back;
|
||||
}
|
||||
|
||||
public T Pop()
|
||||
{
|
||||
if (_stack.Count == 1)
|
||||
return _stack[0];
|
||||
|
||||
return _stack.PopBack();
|
||||
}
|
||||
|
||||
/// If popValue is true, the given value will not be pushed and the current top of the stack will be poped.
|
||||
/// If popValaue is false, the given value will be pushed and returned.
|
||||
public T PushButPopIfTrue(T value, bool popValue)
|
||||
{
|
||||
if (popValue)
|
||||
{
|
||||
return Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
Push(value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static PreparedText PrepareText(Font font, StringView text, float fontSize, Color fontColor = .White, Color bitmapColor = .White, float lineSpaceScale = 1.0f, TextDirection direction = .LeftToRight)
|
||||
{
|
||||
Debug.Profiler.ProfileRendererFunction!();
|
||||
@@ -158,11 +201,11 @@ namespace GlitchyEngine.Renderer.Text
|
||||
hb_buffer_t* buf = hb_buffer_create();
|
||||
|
||||
// Stack to keep track of the text direction.
|
||||
List<TextDirection> textDirectionStack = scope .();
|
||||
textDirectionStack.Add(direction);
|
||||
StyleStack<TextDirection> textDirectionStack = scope .(direction);
|
||||
|
||||
List<TextCase> textCaseStack = scope .();
|
||||
textCaseStack.Add(.RetainCase);
|
||||
StyleStack<TextCase> textCaseStack = scope .(.RetainCase);
|
||||
|
||||
StyleStack<bool> richTextStack = scope .(true);
|
||||
|
||||
int movedLines = 0;
|
||||
|
||||
@@ -185,7 +228,7 @@ namespace GlitchyEngine.Renderer.Text
|
||||
// Set the script, language and direction of the buffer.
|
||||
// TODO: change direction, script, etc.
|
||||
|
||||
TextDirection direction = textDirectionStack.Back;
|
||||
TextDirection direction = textDirectionStack.CurrentValue();
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
@@ -237,7 +280,7 @@ namespace GlitchyEngine.Renderer.Text
|
||||
|
||||
bool escapeNextChar = false;
|
||||
|
||||
for (char32 char in text.DecodedChars)
|
||||
mainEnumerator: for (char32 char in text.DecodedChars)
|
||||
{
|
||||
defer
|
||||
{
|
||||
@@ -271,14 +314,14 @@ namespace GlitchyEngine.Renderer.Text
|
||||
// LEFT-TO-RIGHT ISOLATE
|
||||
FlushShapeBuffer();
|
||||
|
||||
textDirectionStack.Add(.RightToLeft);
|
||||
textDirectionStack.Push(.RightToLeft);
|
||||
|
||||
continue;
|
||||
case '\u{2067}':
|
||||
// RIGHT-TO-LEFT ISOLATE
|
||||
FlushShapeBuffer();
|
||||
|
||||
textDirectionStack.Add(.RightToLeft);
|
||||
textDirectionStack.Push(.RightToLeft);
|
||||
|
||||
continue;
|
||||
// TODO: FIRST STRONG ISOLATE (U+2068)
|
||||
@@ -286,8 +329,7 @@ namespace GlitchyEngine.Renderer.Text
|
||||
// POP DIRECTIONAL ISOLATE
|
||||
FlushShapeBuffer();
|
||||
|
||||
if (textDirectionStack.Count > 1)
|
||||
textDirectionStack.PopBack();
|
||||
textDirectionStack.Pop();
|
||||
|
||||
continue;
|
||||
case '\\':
|
||||
@@ -331,6 +373,30 @@ namespace GlitchyEngine.Renderer.Text
|
||||
if (isEndTag)
|
||||
tagText.Remove(0);
|
||||
|
||||
tagText.ToLower();
|
||||
|
||||
if (tagText == "rt" || tagText == "richtext")
|
||||
{
|
||||
richTextStack.PushButPopIfTrue(true, isEndTag);
|
||||
|
||||
// Skip the tag in the main enumerator
|
||||
@char.NextIndex = endIndex;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tagText == "pt" || tagText == "plaintext")
|
||||
{
|
||||
richTextStack.PushButPopIfTrue(false, isEndTag);
|
||||
|
||||
// Skip the tag in the main enumerator
|
||||
@char.NextIndex = endIndex;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only process the other tags if we currently use rich text processing
|
||||
if (!richTextStack.CurrentValue())
|
||||
break;
|
||||
|
||||
switch(tagText)
|
||||
{
|
||||
case "b", "bold":
|
||||
@@ -348,37 +414,24 @@ namespace GlitchyEngine.Renderer.Text
|
||||
|
||||
// Text case control
|
||||
case "lc", "lowercase":
|
||||
if (isEndTag && textCaseStack.Count > 0)
|
||||
textCaseStack.PopBack();
|
||||
else
|
||||
textCaseStack.Add(.LowerCase);
|
||||
textCaseStack.PushButPopIfTrue(.LowerCase, isEndTag);
|
||||
case "uc", "uppercase":
|
||||
if (isEndTag && textCaseStack.Count > 0)
|
||||
textCaseStack.PopBack();
|
||||
else
|
||||
textCaseStack.Add(.UpperCase);
|
||||
if (isEndTag)
|
||||
textCaseStack.PushButPopIfTrue(.UpperCase, isEndTag);
|
||||
case "rc", "retaincase":
|
||||
if (isEndTag && textCaseStack.Count > 0)
|
||||
textCaseStack.PopBack();
|
||||
else
|
||||
textCaseStack.Add(.RetainCase);
|
||||
textCaseStack.PushButPopIfTrue(.RetainCase, isEndTag);
|
||||
case "ic", "invertcase":
|
||||
if (isEndTag && textCaseStack.Count > 0)
|
||||
textCaseStack.PopBack();
|
||||
else
|
||||
textCaseStack.Add(.InvertCase);
|
||||
|
||||
textCaseStack.PushButPopIfTrue(.InvertCase, isEndTag);
|
||||
}
|
||||
|
||||
|
||||
// Skip the tag in the main enumerator
|
||||
@char.NextIndex = endIndex;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
escapeNextChar = false;
|
||||
|
||||
switch (textCaseStack.Back)
|
||||
switch (textCaseStack.CurrentValue())
|
||||
{
|
||||
case .RetainCase:
|
||||
// Nothing to do
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
using System;
|
||||
using static GlitchyEngine.Renderer.Text.FontRenderer;
|
||||
namespace GlitchyEngine.World.Components;
|
||||
|
||||
enum TextRendererFlags
|
||||
{
|
||||
IsRichText = 1,
|
||||
NeedsRebuild = 2
|
||||
}
|
||||
|
||||
struct TextRendererComponent : IDisposableComponent
|
||||
{
|
||||
private String _text;
|
||||
|
||||
private PreparedText _preparedText;
|
||||
|
||||
private bool _isRichText;
|
||||
private TextRendererFlags _flags;
|
||||
|
||||
public StringView Text
|
||||
{
|
||||
@@ -19,14 +28,27 @@ struct TextRendererComponent : IDisposableComponent
|
||||
}
|
||||
}
|
||||
|
||||
public PreparedText PreparedText
|
||||
{
|
||||
get => _preparedText;
|
||||
set mut => SetReference!(_preparedText, value);
|
||||
}
|
||||
|
||||
public bool IsRichText
|
||||
{
|
||||
get => _isRichText;
|
||||
set mut => _isRichText = value;
|
||||
get => _flags.HasFlag(.IsRichText);
|
||||
set mut => Enum.SetFlagConditionally(ref _flags, .IsRichText, value);
|
||||
}
|
||||
|
||||
public bool NeedsRebuild
|
||||
{
|
||||
get => _flags.HasFlag(.NeedsRebuild);
|
||||
set mut => Enum.SetFlagConditionally(ref _flags, .NeedsRebuild, value);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
delete _text;
|
||||
_preparedText?.ReleaseRef();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ using GlitchyEngine.Math;
|
||||
using GlitchyEngine.Scripting.Classes;
|
||||
using GlitchyEngine.Serialization;
|
||||
using GlitchyEngine.World.Components;
|
||||
using GlitchyEngine.Renderer.Text;
|
||||
using static GlitchyEngine.Renderer.Text.FontRenderer;
|
||||
|
||||
namespace GlitchyEngine.World
|
||||
{
|
||||
@@ -55,6 +57,8 @@ namespace GlitchyEngine.World
|
||||
|
||||
cameraEntity
|
||||
};
|
||||
|
||||
Font _font ~ _.ReleaseRef();
|
||||
|
||||
/// Gets or sets the name of the scene.
|
||||
public StringView Name
|
||||
@@ -117,6 +121,8 @@ namespace GlitchyEngine.World
|
||||
scene.InitPolygonCollider2D(e, collider);
|
||||
}
|
||||
});
|
||||
|
||||
_font = new Font(@"C:\Windows\Fonts\arial.ttf", 24);
|
||||
}
|
||||
|
||||
public ~this()
|
||||
@@ -768,6 +774,22 @@ namespace GlitchyEngine.World
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildTexts()
|
||||
{
|
||||
for (let (entity, textRenderer) in _ecsWorld.Enumerate<TextRendererComponent>())
|
||||
{
|
||||
if (textRenderer.NeedsRebuild || textRenderer.PreparedText == null)
|
||||
{
|
||||
using (PreparedText preparedText = FontRenderer.PrepareText(_font, textRenderer.Text, 24, .Black))
|
||||
{
|
||||
textRenderer.PreparedText = preparedText;
|
||||
}
|
||||
|
||||
textRenderer.NeedsRebuild = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(GameTime gameTime, UpdateMode mode)
|
||||
{
|
||||
Debug.Profiler.ProfileRendererFunction!();
|
||||
@@ -807,6 +829,8 @@ namespace GlitchyEngine.World
|
||||
|
||||
_destroyQueue.Clear();
|
||||
}
|
||||
|
||||
BuildTexts();
|
||||
}
|
||||
|
||||
/// Creates a new Entity with the given name.
|
||||
|
||||
@@ -25,10 +25,6 @@ class SceneRenderer
|
||||
|
||||
public RenderTargetGroup CompositeTarget => _compositeTarget;
|
||||
|
||||
Font _font ~ _.ReleaseRef();
|
||||
|
||||
FontRenderer.PreparedText _smallLinesInfo;
|
||||
|
||||
public this()
|
||||
{
|
||||
RenderTargetGroupDescription desc = .(100, 100,
|
||||
@@ -52,8 +48,6 @@ class SceneRenderer
|
||||
_cameraTarget.[Friend]Identifier = "Camera Target";
|
||||
|
||||
_gammaCorrectEffect = Content.LoadAsset("Resources/Shaders/GammaCorrect.hlsl");
|
||||
|
||||
_font = new Font(@"C:\Windows\Fonts\arial.ttf", 24);
|
||||
}
|
||||
|
||||
/// Sets the size of the viewport into which the scene will be rendered.
|
||||
@@ -214,14 +208,10 @@ class SceneRenderer
|
||||
|
||||
for (var (entity, transform, text, editorFlags) in Scene._ecsWorld.Enumerate<TransformComponent, TextRendererComponent, EditorFlagsComponent>())
|
||||
{
|
||||
if (editorFlags.Flags.HasFlag(.HideInScene))
|
||||
if (editorFlags.Flags.HasFlag(.HideInScene) || text.PreparedText == null)
|
||||
continue;
|
||||
|
||||
_smallLinesInfo = FontRenderer.PrepareText(_font, text.Text, 24, .Black);
|
||||
|
||||
FontRenderer.DrawText(_smallLinesInfo, transform.WorldTransform * Matrix.Scaling(1.0f / 24.0f));
|
||||
|
||||
_smallLinesInfo.ReleaseRef();
|
||||
FontRenderer.DrawText(text.PreparedText, transform.WorldTransform * Matrix.Scaling(1.0f / 24.0f));
|
||||
}
|
||||
|
||||
//FontRenderer.DrawText(_smallLinesInfo, 0, 0);
|
||||
|
||||
Reference in New Issue
Block a user