diff --git a/Source/Editor/Windows/DebugLogWindow.cs b/Source/Editor/Windows/DebugLogWindow.cs
index 5b5abf1e2..64cb29f88 100644
--- a/Source/Editor/Windows/DebugLogWindow.cs
+++ b/Source/Editor/Windows/DebugLogWindow.cs
@@ -13,6 +13,7 @@ using FlaxEditor.Options;
using FlaxEngine;
using FlaxEngine.Assertions;
using FlaxEngine.GUI;
+using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Windows
@@ -69,6 +70,67 @@ namespace FlaxEditor.Windows
private class LogEntry : Control
{
+ ///
+ /// Text block with only color available, no other styling.
+ ///
+ private struct TextColorBlock
+ {
+ ///
+ /// Color of this block.
+ ///
+ public Color TextColor;
+
+ ///
+ /// Range of the text.
+ ///
+ public TextRange Range;
+
+ ///
+ /// The text location and size.
+ ///
+ public Rectangle Bounds;
+ }
+
+ ///
+ /// Rich text parsing context.
+ ///
+ private struct ColorParseContext
+ {
+ ///
+ /// LogEntry control.
+ ///
+ public LogEntry Control;
+
+ ///
+ /// Caret location for the next text block.
+ ///
+ public Float2 Caret;
+
+ ///
+ /// Current parsed color.
+ ///
+ public Color CurrentColor;
+
+ ///
+ /// Default color, for reverting color on closing tags.
+ ///
+ public Color DefaultColor;
+
+ ///
+ /// Current text font for text processing.
+ ///
+ public Font TextFont;
+
+ ///
+ /// Add text block to the control.
+ ///
+ /// The text block to add.
+ public void AddBlock(ref TextColorBlock block)
+ {
+ Control._textBlocks.Add(block);
+ }
+ }
+
private bool _isRightMouseDown;
///
@@ -82,6 +144,8 @@ namespace FlaxEditor.Windows
public SpriteHandle Icon;
public int LogCount = 1;
+ private readonly List _textBlocks = new List();
+
public LogEntry(DebugLogWindow window, ref LogEntryDescription desc)
: base(0, 0, 120, DefaultHeight)
{
@@ -106,6 +170,126 @@ namespace FlaxEditor.Windows
Icon = _window._iconError;
break;
}
+
+ // Color parsing
+ var style = Style.Current;
+ var color = Group == LogGroup.Error ? _window._colorError : (Group == LogGroup.Warning ? _window._colorWarning : _window._colorInfo);
+
+ HtmlParser parser = new();
+
+ parser.Reset(Desc.Title);
+
+ var context = new ColorParseContext
+ {
+ Control = this,
+ Caret = Float2.Zero,
+ CurrentColor = color,
+ DefaultColor = color,
+ TextFont = style.FontMedium,
+ };
+
+ int pointerPos = 0;
+
+ while (parser.ParseNext(out var tag))
+ {
+ if (tag.Name.ToLower() == "color")
+ {
+ ProcessTextBlock(ref context, pointerPos, tag.StartPosition);
+
+ pointerPos = tag.EndPosition;
+
+ ProcessColorTag(ref context, ref tag);
+ }
+ }
+
+ // Processing leftover text
+ ProcessTextBlock(ref context, pointerPos, Desc.Title.Length);
+ }
+
+ ///
+ /// Processing the text block within given range and adding it to the list.
+ ///
+ /// The parsing context.
+ /// Start of the range.(Character index)
+ /// End of the range.(Character index)
+ private void ProcessTextBlock(ref ColorParseContext context, int startPos, int endPos)
+ {
+ // Text block preset
+ var textBlock = new TextColorBlock()
+ {
+ TextColor = context.CurrentColor,
+ Range = new TextRange(startPos, endPos),
+ Bounds = new Rectangle(context.Caret, Float2.Zero)
+ };
+
+ // Processing the text with selected font. (Handle newlines, text offsets)
+ var lines = context.TextFont.ProcessText(Desc.Title, ref textBlock.Range);
+
+ if (lines == null || lines.Length == 0)
+ {
+ return;
+ }
+ for (int i = 0; i < lines.Length; i++)
+ {
+ ref var line = ref lines[i];
+ textBlock.Range = new TextRange
+ {
+ StartIndex = startPos + line.FirstCharIndex,
+ EndIndex = startPos + line.LastCharIndex + 1
+ };
+
+ // Move to the next line
+ if (i != 0)
+ {
+ context.Caret.Y += line.Size.Y;
+ textBlock.Bounds.X = 0;
+ textBlock.Bounds.Y += line.Size.Y;
+ }
+
+ textBlock.Bounds.X += line.Location.X;
+ textBlock.Bounds.Size = line.Size;
+ context.AddBlock(ref textBlock);
+ }
+
+ // Caret location for the next text block
+ var lastLine = lines[lines.Length - 1];
+ if (lines.Length == 1)
+ {
+ context.Caret.X += lastLine.Size.X;
+ }
+ else
+ {
+ context.Caret.X = lastLine.Size.X;
+ }
+ }
+
+ ///
+ /// Parse color info from the tag and handle closing tags.
+ ///
+ /// The parsing context.
+ /// Tag to process.
+ private static void ProcessColorTag(ref ColorParseContext context, ref HtmlTag tag)
+ {
+ // Closing tag
+ if (tag.IsSlash)
+ {
+ context.CurrentColor = context.DefaultColor;
+ }
+ else
+ {
+ // Parse color
+ if (tag.Attributes.TryGetValue(string.Empty, out string colorText))
+ {
+ if (Color.TryParse(colorText, out Color colorVal))
+ {
+ context.CurrentColor = colorVal;
+ }
+ }
+ else
+ {
+ context.CurrentColor = context.DefaultColor;
+ }
+ }
}
///
@@ -142,16 +326,30 @@ namespace FlaxEditor.Windows
Render2D.DrawSprite(Icon, new Rectangle(8, 0, 32, 32), color);
// Title
- var textRect = new Rectangle(43, 2, clientRect.Width - 40, clientRect.Height - 10);
+ var textLocation = new Float2(43, 2);
Render2D.PushClip(ref clientRect);
bool coloredText = _window._colorDebugLogText;
- if (LogCount == 1)
+
+ // Render text blocks with their colors
+ for (int i = 0; i < _textBlocks.Count; i++)
{
- Render2D.DrawText(style.FontMedium, Desc.Title, textRect, coloredText ? color : style.Foreground);
+ TextColorBlock block = _textBlocks[i];
+ Render2D.DrawText(style.FontMedium, Desc.Title, ref block.Range,
+ coloredText ? block.TextColor : style.Foreground, textLocation + block.Bounds.Location);
}
- else if (LogCount > 1)
+
+ // Adding log counter for collapsed logs
+ if (LogCount > 1)
{
- Render2D.DrawText(style.FontMedium, $"{Desc.Title} ({LogCount})", textRect, coloredText ? color : style.Foreground);
+ Float2 numberLocation = textLocation;
+ if (_textBlocks.Count > 0)
+ {
+ TextColorBlock block = _textBlocks[_textBlocks.Count - 1];
+ numberLocation += block.Bounds.Location;
+ numberLocation.X += block.Bounds.Size.X;
+ }
+
+ Render2D.DrawText(style.FontMedium, $" ({LogCount})", color, numberLocation);
}
Render2D.PopClip();
}