Start of text renderer component

- UC, LC, IC, RC tags
This commit is contained in:
Simon Lübeß
2024-03-14 22:17:57 +01:00
parent a87820c7da
commit e703452b94
6 changed files with 265 additions and 21 deletions
@@ -74,7 +74,11 @@ float4 PS(PS_Input input) : SV_Target0
float screenPxDistance = ScreenPxRange(input.TexCoord) * (sd - 0.5);
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
return float4(input.Color.rgb, opacity * input.Color.a);
clip(opacity - 0.001f);
//return float4(input.Color.rgb, opacity * input.Color.a) * 0.0001f + float4(opacity.xxxx);
return input.Color * opacity; // TODO: This is not quite right // float4(input.Color.rgb, input.Color.a * opacity);
//return float4(input.Color.rgb, opacity * input.Color.a) * 0.0001f + float4(msd, 1.0f);
}
/*
@@ -9,6 +9,7 @@ using GlitchyEngine.Content;
using GlitchyEngine.Scripting;
using GlitchyEngine.Core;
using Mono;
using GlitchyEngine.World.Components;
namespace GlitchyEditor.EditWindows
{
@@ -81,9 +82,12 @@ namespace GlitchyEditor.EditWindows
ShowComponentEditor<SpriteRendererComponent>("Sprite Renderer", entity, => ShowSpriteRendererComponentEditor, => ShowComponentContextMenu<SpriteRendererComponent>);
ShowComponentEditor<CircleRendererComponent>("Circle Renderer", entity, => ShowCircleRendererComponentEditor, => ShowComponentContextMenu<CircleRendererComponent>);
ShowComponentEditor<TextRendererComponent>("Text Renderer", entity, => ShowTextRendererComponentEditor, => ShowComponentContextMenu<TextRendererComponent>);
ShowComponentEditor<MeshRendererComponent>("Mesh Renderer", entity, => ShowMeshRendererComponentEditor, => ShowComponentContextMenu<MeshRendererComponent>);
ShowComponentEditor<LightComponent>("Light", entity, => ShowLightComponentEditor, => ShowComponentContextMenu<LightComponent>);
ShowComponentEditor<MeshComponent>("Mesh", entity, => ShowMeshComponentEditor, => ShowComponentContextMenu<MeshComponent>);
ShowComponentEditor<Rigidbody2DComponent>("Rigidbody 2D", entity, => ShowRigidBody2DComponentEditor, => ShowComponentContextMenu<Rigidbody2DComponent>);
ShowComponentEditor<BoxCollider2DComponent>("Box collider 2D", entity, => ShowBoxCollider2DComponentEditor, => ShowComponentContextMenu<BoxCollider2DComponent>);
ShowComponentEditor<CircleCollider2DComponent>("Circle collider 2D", entity, => ShowCircleCollider2DComponentEditor, => ShowComponentContextMenu<CircleCollider2DComponent>);
@@ -453,6 +457,49 @@ namespace GlitchyEditor.EditWindows
StartNewProperty("Inner Radius");
ImGui.DragFloat("##Inner Radius", &circleRendererComponent.InnerRadius, 0.1f, 0.0f, 1.0f);
}
static int InputTextCallback(ImGui.InputTextCallbackData* data)
{
if (data.EventFlag == .CallbackResize)
{
String str = (String)Internal.UnsafeCastToObject(data.UserData);
Log.EngineLogger.AssertDebug(str.Ptr == data.Buf);
str.PadRight(data.BufTextLen * 2, '\0');
data.Buf = str.Ptr;
return 0;
}
return 0;
}
private static void ShowTextRendererComponentEditor(Entity entity, TextRendererComponent* textRendererComponent)
{
StartNewProperty("Text");
String text = textRendererComponent.[Friend]_text;
if (text == null)
{
text = textRendererComponent.[Friend]_text = new String();
}
text?.EnsureNullTerminator();
if (ImGui.InputTextMultiline("##text", text.Ptr, (uint64)text.Length, .Zero, .CallbackResize, => InputTextCallback, Internal.UnsafeCastToPtr(text)))
{
// To deleting text, ImGui simply puts a null char after the remaining text.
// Search for null char and set the length of the string accordingly.
int length = text.IndexOf('\0');
if (length >= 0)
{
text.Length = length;
}
}
}
private static void ShowMeshRendererComponentEditor(Entity entity, MeshRendererComponent* meshRendererComponent)
{
@@ -844,6 +891,7 @@ namespace GlitchyEditor.EditWindows
ShowComponentButton<CameraComponent>("Camera");
ShowComponentButton<SpriteRendererComponent>("Sprite Renderer");
ShowComponentButton<CircleRendererComponent>("Circle Renderer");
ShowComponentButton<TextRendererComponent>("Text Renderer");
ShowComponentButton<LightComponent>("Light");
ShowComponentButton<Rigidbody2DComponent>("Rigidbody 2D");
ShowComponentButton<BoxCollider2DComponent>("Box collider 2D");
+132 -20
View File
@@ -5,6 +5,7 @@ using GlitchyEngine.Math;
using System.Collections;
using GlitchyEngine.Core;
using GlitchyEngine.Content;
using System.Text;
using static FreeType.HarfBuzz;
using internal GlitchyEngine.Renderer.Text;
@@ -126,7 +127,15 @@ namespace GlitchyEngine.Renderer.Text
case BottomToTop;
}
public static PreparedText PrepareText(Font font, String text, float fontSize, Color fontColor = .White, Color bitmapColor = .White, float lineSpaceScale = 1.0f, TextDirection direction = .LeftToRight)
public enum TextCase
{
case RetainCase;
case UpperCase;
case LowerCase;
case InvertCase;
}
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!();
@@ -143,17 +152,18 @@ namespace GlitchyEngine.Renderer.Text
// Position of the next character on the line
float penPosition = 0;
// TODO: use stringview?
StringView textBuffer = .(text, 0, 0);//new:ScopedAlloc! String();
hb_buffer_t* buf = hb_buffer_create();
List<char32> chars = new:ScopedAlloc! .(text.Length);
int runStart = 0;
//PreparedLine currentLine = new PreparedLine();
hb_buffer_t* buf = hb_buffer_create();
// Stack to keep track of the text direction.
List<TextDirection> textDirectionStack = scope .();
textDirectionStack.Add(direction);
List<TextCase> textCaseStack = scope .();
textCaseStack.Add(.RetainCase);
int movedLines = 0;
Font currentFont = font;
@@ -168,10 +178,9 @@ namespace GlitchyEngine.Renderer.Text
hb_buffer_clear_contents(buf);
hb_buffer_add_utf8(buf, text.Ptr, (.)text.Length, (.)(textBuffer.Ptr - text.Ptr), (.)textBuffer.Length);
textBuffer.Ptr += textBuffer.Length;
textBuffer.Length = 0;
hb_buffer_add_utf32(buf, chars.Ptr, (.)chars.Count, (.)runStart, (.)(chars.Count - runStart));
runStart = chars.Count;
// Set the script, language and direction of the buffer.
// TODO: change direction, script, etc.
@@ -226,6 +235,8 @@ namespace GlitchyEngine.Renderer.Text
penPosition = 0;
}
bool escapeNextChar = false;
for (char32 char in text.DecodedChars)
{
defer
@@ -233,16 +244,21 @@ namespace GlitchyEngine.Renderer.Text
currentIndex = @char.NextIndex;
}
switch(char)
void DoLineBreak()
{
case '\n':
chars.Add('\n');
// Only flush after the first new line
if (movedLines == 0)
FlushShapeBuffer();
// carriage return
movedLines++;
}
switch(char)
{
case '\n':
DoLineBreak();
continue;
case '\u{240}':
FlushShapeBuffer();
@@ -274,6 +290,107 @@ namespace GlitchyEngine.Renderer.Text
textDirectionStack.PopBack();
continue;
case '\\':
if (escapeNextChar)
break;
else
{
escapeNextChar = true;
continue;
}
case '<':
if (escapeNextChar)
break;
// Copy the char enumerator to make a lookahead
String.UTF8Enumerator tagEnumerator = @char;
String tagText = scope .();
int endIndex = -1;
for (char32 tagChar in tagEnumerator)
{
if (tagChar == '>')
{
endIndex = tagEnumerator.NextIndex;
break;
}
else
{
UTF32.Decode(Span<char32>(&tagChar, 1), tagText);
}
}
// It seems that it's just an unescaped <
if (endIndex == -1)
break; // TODO: Probably warn or something
bool isEndTag = tagText.StartsWith('/');
if (isEndTag)
tagText.Remove(0);
switch(tagText)
{
case "b", "bold":
// bold
case "br", "linebreak":
DoLineBreak();
case "u", "underline":
// underlined
case "i", "italic":
// italic
case "small":
case "sub", "subscript":
case "sup", "superscript":
case "s", "strikethrough":
// Text case control
case "lc", "lowercase":
if (isEndTag && textCaseStack.Count > 0)
textCaseStack.PopBack();
else
textCaseStack.Add(.LowerCase);
case "uc", "uppercase":
if (isEndTag && textCaseStack.Count > 0)
textCaseStack.PopBack();
else
textCaseStack.Add(.UpperCase);
case "rc", "retaincase":
if (isEndTag && textCaseStack.Count > 0)
textCaseStack.PopBack();
else
textCaseStack.Add(.RetainCase);
case "ic", "invertcase":
if (isEndTag && textCaseStack.Count > 0)
textCaseStack.PopBack();
else
textCaseStack.Add(.InvertCase);
}
// Skip the tag in the main enumerator
@char.NextIndex = endIndex;
continue;
}
escapeNextChar = false;
switch (textCaseStack.Back)
{
case .RetainCase:
// Nothing to do
case .LowerCase:
char = char.ToLower;
case .UpperCase:
char = char.ToUpper;
case .InvertCase:
if (char.IsUpper)
char = char.ToLower;
else if (char.IsLower)
char = char.ToUpper;
}
// Get the font that can draw the char. Use the given font if no fallback is found (will draw the "missing glyph").
@@ -299,12 +416,7 @@ namespace GlitchyEngine.Renderer.Text
movedLines = 0;
}
//textBuffer.Append(char);
if(textBuffer.Length == 0)
{
textBuffer.Ptr = text.Ptr + currentIndex;
}
textBuffer.Length += @char.NextIndex - currentIndex;
chars.Add(char);
}
FlushShapeBuffer();
@@ -336,7 +448,7 @@ namespace GlitchyEngine.Renderer.Text
Renderer2D.[Friend]s_currentQuadEffect = _msdfEffect;
// TODO: oh no....
// Copy viewProjection from current effect
Matrix viewProjection = lastEffect.Variables["ViewProjection"].[Friend]GetData<Matrix>();
Matrix viewProjection = Renderer2D.[Friend]s_quadBatchEffect.Variables["ViewProjection"].[Friend]GetData<Matrix>();
_msdfEffect.Variables["ViewProjection"].SetData(viewProjection);
// TODO: this doesn't really work with fallback fonts
@@ -0,0 +1,32 @@
using System;
namespace GlitchyEngine.World.Components;
struct TextRendererComponent : IDisposableComponent
{
private String _text;
private bool _isRichText;
public StringView Text
{
get => _text;
set mut
{
if (_text == null)
_text = new String();
_text.Set(value);
}
}
public bool IsRichText
{
get => _isRichText;
set mut => _isRichText = value;
}
public void Dispose()
{
delete _text;
}
}
+21
View File
@@ -3,6 +3,7 @@ using GlitchyEngine.Content;
using GlitchyEngine.Math;
using System;
using GlitchyEngine.World.Components;
using GlitchyEngine.Renderer.Text;
namespace GlitchyEngine.World;
@@ -24,6 +25,10 @@ class SceneRenderer
public RenderTargetGroup CompositeTarget => _compositeTarget;
Font _font ~ _.ReleaseRef();
FontRenderer.PreparedText _smallLinesInfo;
public this()
{
RenderTargetGroupDescription desc = .(100, 100,
@@ -47,6 +52,8 @@ 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.
@@ -204,6 +211,20 @@ class SceneRenderer
Renderer2D.DrawCircle(transform.WorldTransform, circle, entity.Index);
}
for (var (entity, transform, text, editorFlags) in Scene._ecsWorld.Enumerate<TransformComponent, TextRendererComponent, EditorFlagsComponent>())
{
if (editorFlags.Flags.HasFlag(.HideInScene))
continue;
_smallLinesInfo = FontRenderer.PrepareText(_font, text.Text, 24, .Black);
FontRenderer.DrawText(_smallLinesInfo, transform.WorldTransform * Matrix.Scaling(1.0f / 24.0f));
_smallLinesInfo.ReleaseRef();
}
//FontRenderer.DrawText(_smallLinesInfo, 0, 0);
Renderer2D.EndScene();
@@ -249,6 +249,12 @@ class SceneSerializer
{
Serialize.Value(writer, "Flags", component.Flags);
});
SerializeComponent<TextRendererComponent>(writer, entity, "TextRenderer", scope (component) =>
{
Serialize.Value(writer, "IsRichText", component.IsRichText);
Serialize.Value(writer, "Text", component.Text);
});
}
writer.EntryEnd();
@@ -671,6 +677,27 @@ class SceneSerializer
{
Try!(Deserialize.Value(reader, "Flags", out component.Flags));
return .Ok;
}));
case "TextRenderer":
Try!(DeserializeComponent<TextRendererComponent>(reader, entity, scope (component) =>
{
Try!(Deserialize.Value<bool>(reader, "IsRichText", let isRichText));
component.IsRichText = isRichText;
Try!(reader.EntryEnd());
String text = null;
Try!(Deserialize.Value<String>(reader, "Text", out text));
if (text != null)
{
component.Text = text;
delete text;
}
return .Ok;
}));
default: