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)
|
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");
|
StartNewProperty("Text");
|
||||||
|
|
||||||
String text = textRendererComponent.[Friend]_text;
|
String text = textRendererComponent.[Friend]_text;
|
||||||
@@ -498,6 +510,8 @@ namespace GlitchyEditor.EditWindows
|
|||||||
{
|
{
|
||||||
text.Length = length;
|
text.Length = length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textRendererComponent.NeedsRebuild = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,49 @@ namespace GlitchyEngine.Renderer.Text
|
|||||||
case InvertCase;
|
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)
|
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!();
|
Debug.Profiler.ProfileRendererFunction!();
|
||||||
@@ -158,11 +201,11 @@ namespace GlitchyEngine.Renderer.Text
|
|||||||
hb_buffer_t* buf = hb_buffer_create();
|
hb_buffer_t* buf = hb_buffer_create();
|
||||||
|
|
||||||
// Stack to keep track of the text direction.
|
// Stack to keep track of the text direction.
|
||||||
List<TextDirection> textDirectionStack = scope .();
|
StyleStack<TextDirection> textDirectionStack = scope .(direction);
|
||||||
textDirectionStack.Add(direction);
|
|
||||||
|
|
||||||
List<TextCase> textCaseStack = scope .();
|
StyleStack<TextCase> textCaseStack = scope .(.RetainCase);
|
||||||
textCaseStack.Add(.RetainCase);
|
|
||||||
|
StyleStack<bool> richTextStack = scope .(true);
|
||||||
|
|
||||||
int movedLines = 0;
|
int movedLines = 0;
|
||||||
|
|
||||||
@@ -185,7 +228,7 @@ namespace GlitchyEngine.Renderer.Text
|
|||||||
// Set the script, language and direction of the buffer.
|
// Set the script, language and direction of the buffer.
|
||||||
// TODO: change direction, script, etc.
|
// TODO: change direction, script, etc.
|
||||||
|
|
||||||
TextDirection direction = textDirectionStack.Back;
|
TextDirection direction = textDirectionStack.CurrentValue();
|
||||||
|
|
||||||
switch (direction)
|
switch (direction)
|
||||||
{
|
{
|
||||||
@@ -237,7 +280,7 @@ namespace GlitchyEngine.Renderer.Text
|
|||||||
|
|
||||||
bool escapeNextChar = false;
|
bool escapeNextChar = false;
|
||||||
|
|
||||||
for (char32 char in text.DecodedChars)
|
mainEnumerator: for (char32 char in text.DecodedChars)
|
||||||
{
|
{
|
||||||
defer
|
defer
|
||||||
{
|
{
|
||||||
@@ -271,14 +314,14 @@ namespace GlitchyEngine.Renderer.Text
|
|||||||
// LEFT-TO-RIGHT ISOLATE
|
// LEFT-TO-RIGHT ISOLATE
|
||||||
FlushShapeBuffer();
|
FlushShapeBuffer();
|
||||||
|
|
||||||
textDirectionStack.Add(.RightToLeft);
|
textDirectionStack.Push(.RightToLeft);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
case '\u{2067}':
|
case '\u{2067}':
|
||||||
// RIGHT-TO-LEFT ISOLATE
|
// RIGHT-TO-LEFT ISOLATE
|
||||||
FlushShapeBuffer();
|
FlushShapeBuffer();
|
||||||
|
|
||||||
textDirectionStack.Add(.RightToLeft);
|
textDirectionStack.Push(.RightToLeft);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
// TODO: FIRST STRONG ISOLATE (U+2068)
|
// TODO: FIRST STRONG ISOLATE (U+2068)
|
||||||
@@ -286,8 +329,7 @@ namespace GlitchyEngine.Renderer.Text
|
|||||||
// POP DIRECTIONAL ISOLATE
|
// POP DIRECTIONAL ISOLATE
|
||||||
FlushShapeBuffer();
|
FlushShapeBuffer();
|
||||||
|
|
||||||
if (textDirectionStack.Count > 1)
|
textDirectionStack.Pop();
|
||||||
textDirectionStack.PopBack();
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
case '\\':
|
case '\\':
|
||||||
@@ -331,6 +373,30 @@ namespace GlitchyEngine.Renderer.Text
|
|||||||
if (isEndTag)
|
if (isEndTag)
|
||||||
tagText.Remove(0);
|
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)
|
switch(tagText)
|
||||||
{
|
{
|
||||||
case "b", "bold":
|
case "b", "bold":
|
||||||
@@ -348,37 +414,24 @@ namespace GlitchyEngine.Renderer.Text
|
|||||||
|
|
||||||
// Text case control
|
// Text case control
|
||||||
case "lc", "lowercase":
|
case "lc", "lowercase":
|
||||||
if (isEndTag && textCaseStack.Count > 0)
|
textCaseStack.PushButPopIfTrue(.LowerCase, isEndTag);
|
||||||
textCaseStack.PopBack();
|
|
||||||
else
|
|
||||||
textCaseStack.Add(.LowerCase);
|
|
||||||
case "uc", "uppercase":
|
case "uc", "uppercase":
|
||||||
if (isEndTag && textCaseStack.Count > 0)
|
if (isEndTag)
|
||||||
textCaseStack.PopBack();
|
textCaseStack.PushButPopIfTrue(.UpperCase, isEndTag);
|
||||||
else
|
|
||||||
textCaseStack.Add(.UpperCase);
|
|
||||||
case "rc", "retaincase":
|
case "rc", "retaincase":
|
||||||
if (isEndTag && textCaseStack.Count > 0)
|
textCaseStack.PushButPopIfTrue(.RetainCase, isEndTag);
|
||||||
textCaseStack.PopBack();
|
|
||||||
else
|
|
||||||
textCaseStack.Add(.RetainCase);
|
|
||||||
case "ic", "invertcase":
|
case "ic", "invertcase":
|
||||||
if (isEndTag && textCaseStack.Count > 0)
|
textCaseStack.PushButPopIfTrue(.InvertCase, isEndTag);
|
||||||
textCaseStack.PopBack();
|
|
||||||
else
|
|
||||||
textCaseStack.Add(.InvertCase);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip the tag in the main enumerator
|
// Skip the tag in the main enumerator
|
||||||
@char.NextIndex = endIndex;
|
@char.NextIndex = endIndex;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
escapeNextChar = false;
|
escapeNextChar = false;
|
||||||
|
|
||||||
switch (textCaseStack.Back)
|
switch (textCaseStack.CurrentValue())
|
||||||
{
|
{
|
||||||
case .RetainCase:
|
case .RetainCase:
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using static GlitchyEngine.Renderer.Text.FontRenderer;
|
||||||
namespace GlitchyEngine.World.Components;
|
namespace GlitchyEngine.World.Components;
|
||||||
|
|
||||||
|
enum TextRendererFlags
|
||||||
|
{
|
||||||
|
IsRichText = 1,
|
||||||
|
NeedsRebuild = 2
|
||||||
|
}
|
||||||
|
|
||||||
struct TextRendererComponent : IDisposableComponent
|
struct TextRendererComponent : IDisposableComponent
|
||||||
{
|
{
|
||||||
private String _text;
|
private String _text;
|
||||||
|
|
||||||
|
private PreparedText _preparedText;
|
||||||
|
|
||||||
private bool _isRichText;
|
private TextRendererFlags _flags;
|
||||||
|
|
||||||
public StringView Text
|
public StringView Text
|
||||||
{
|
{
|
||||||
@@ -19,14 +28,27 @@ struct TextRendererComponent : IDisposableComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PreparedText PreparedText
|
||||||
|
{
|
||||||
|
get => _preparedText;
|
||||||
|
set mut => SetReference!(_preparedText, value);
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsRichText
|
public bool IsRichText
|
||||||
{
|
{
|
||||||
get => _isRichText;
|
get => _flags.HasFlag(.IsRichText);
|
||||||
set mut => _isRichText = value;
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
delete _text;
|
delete _text;
|
||||||
|
_preparedText?.ReleaseRef();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ using GlitchyEngine.Math;
|
|||||||
using GlitchyEngine.Scripting.Classes;
|
using GlitchyEngine.Scripting.Classes;
|
||||||
using GlitchyEngine.Serialization;
|
using GlitchyEngine.Serialization;
|
||||||
using GlitchyEngine.World.Components;
|
using GlitchyEngine.World.Components;
|
||||||
|
using GlitchyEngine.Renderer.Text;
|
||||||
|
using static GlitchyEngine.Renderer.Text.FontRenderer;
|
||||||
|
|
||||||
namespace GlitchyEngine.World
|
namespace GlitchyEngine.World
|
||||||
{
|
{
|
||||||
@@ -55,6 +57,8 @@ namespace GlitchyEngine.World
|
|||||||
|
|
||||||
cameraEntity
|
cameraEntity
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Font _font ~ _.ReleaseRef();
|
||||||
|
|
||||||
/// Gets or sets the name of the scene.
|
/// Gets or sets the name of the scene.
|
||||||
public StringView Name
|
public StringView Name
|
||||||
@@ -117,6 +121,8 @@ namespace GlitchyEngine.World
|
|||||||
scene.InitPolygonCollider2D(e, collider);
|
scene.InitPolygonCollider2D(e, collider);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_font = new Font(@"C:\Windows\Fonts\arial.ttf", 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ~this()
|
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)
|
public void Update(GameTime gameTime, UpdateMode mode)
|
||||||
{
|
{
|
||||||
Debug.Profiler.ProfileRendererFunction!();
|
Debug.Profiler.ProfileRendererFunction!();
|
||||||
@@ -807,6 +829,8 @@ namespace GlitchyEngine.World
|
|||||||
|
|
||||||
_destroyQueue.Clear();
|
_destroyQueue.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BuildTexts();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new Entity with the given name.
|
/// Creates a new Entity with the given name.
|
||||||
|
|||||||
@@ -25,10 +25,6 @@ class SceneRenderer
|
|||||||
|
|
||||||
public RenderTargetGroup CompositeTarget => _compositeTarget;
|
public RenderTargetGroup CompositeTarget => _compositeTarget;
|
||||||
|
|
||||||
Font _font ~ _.ReleaseRef();
|
|
||||||
|
|
||||||
FontRenderer.PreparedText _smallLinesInfo;
|
|
||||||
|
|
||||||
public this()
|
public this()
|
||||||
{
|
{
|
||||||
RenderTargetGroupDescription desc = .(100, 100,
|
RenderTargetGroupDescription desc = .(100, 100,
|
||||||
@@ -52,8 +48,6 @@ class SceneRenderer
|
|||||||
_cameraTarget.[Friend]Identifier = "Camera Target";
|
_cameraTarget.[Friend]Identifier = "Camera Target";
|
||||||
|
|
||||||
_gammaCorrectEffect = Content.LoadAsset("Resources/Shaders/GammaCorrect.hlsl");
|
_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.
|
/// 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>())
|
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;
|
continue;
|
||||||
|
|
||||||
_smallLinesInfo = FontRenderer.PrepareText(_font, text.Text, 24, .Black);
|
FontRenderer.DrawText(text.PreparedText, transform.WorldTransform * Matrix.Scaling(1.0f / 24.0f));
|
||||||
|
|
||||||
FontRenderer.DrawText(_smallLinesInfo, transform.WorldTransform * Matrix.Scaling(1.0f / 24.0f));
|
|
||||||
|
|
||||||
_smallLinesInfo.ReleaseRef();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//FontRenderer.DrawText(_smallLinesInfo, 0, 0);
|
//FontRenderer.DrawText(_smallLinesInfo, 0, 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user