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(); }