diff --git a/Robust.Client/UserInterface/Controls/RichTextLabel.cs b/Robust.Client/UserInterface/Controls/RichTextLabel.cs index 75594846429..0f6646734e9 100644 --- a/Robust.Client/UserInterface/Controls/RichTextLabel.cs +++ b/Robust.Client/UserInterface/Controls/RichTextLabel.cs @@ -21,6 +21,9 @@ public class RichTextLabel : Control private float _lineHeightScale = 1; private bool _lineHeightOverride; + [ViewVariables] + public Label.AlignMode Align { get; set; } = Label.AlignMode.Left; + [ViewVariables(VVAccess.ReadWrite)] public float LineHeightScale { @@ -105,7 +108,7 @@ protected override Vector2 MeasureOverride(Vector2 availableSize) protected internal override void Draw(DrawingHandleScreen handle) { base.Draw(handle); - _entry?.Draw(_tagManager, handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale, LineHeightScale); + _entry?.Draw(_tagManager, handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale, LineHeightScale, Align); } [Pure] diff --git a/Robust.Client/UserInterface/RichTextEntry.cs b/Robust.Client/UserInterface/RichTextEntry.cs index 0e8898ef075..b94d74f41d6 100644 --- a/Robust.Client/UserInterface/RichTextEntry.cs +++ b/Robust.Client/UserInterface/RichTextEntry.cs @@ -119,7 +119,7 @@ public RichTextEntry Update(MarkupTagManager tagManager, Font defaultFont, float foreach (var node in Message) { nodeIndex++; - var text = ProcessNode(tagManager, node, context); + var text = ProcessNode(tagManager, node, context, _tagsAllowed); if (!context.Font.TryPeek(out var font)) font = defaultFont; @@ -208,22 +208,28 @@ public readonly void Draw( float verticalOffset, MarkupDrawingContext context, float uiScale, - float lineHeightScale = 1) + float lineHeightScale = 1, + Robust.Client.UserInterface.Controls.Label.AlignMode align = Robust.Client.UserInterface.Controls.Label.AlignMode.Left) { context.Clear(); context.Color.Push(_defaultColor); context.Font.Push(defaultFont); + var lineWidths = align == Robust.Client.UserInterface.Controls.Label.AlignMode.Left + ? null + : CalculateLineWidths(in this, tagManager, defaultFont, uiScale); + var globalBreakCounter = 0; var lineBreakIndex = 0; - var baseLine = drawBox.TopLeft + new Vector2(0, defaultFont.GetAscent(uiScale) + verticalOffset); + var currentLine = 0; + var baseLine = drawBox.TopLeft + new Vector2(GetAlignedOffset(currentLine), defaultFont.GetAscent(uiScale) + verticalOffset); var controlYAdvance = 0f; var nodeIndex = -1; foreach (var node in Message) { nodeIndex++; - var text = ProcessNode(tagManager, node, context); + var text = ProcessNode(tagManager, node, context, _tagsAllowed); if (!context.Color.TryPeek(out var color) || !context.Font.TryPeek(out var font)) { color = _defaultColor; @@ -235,7 +241,8 @@ public readonly void Draw( if (lineBreakIndex < LineBreaks.Count && LineBreaks[lineBreakIndex] == globalBreakCounter) { - baseLine = new Vector2(drawBox.Left, baseLine.Y + GetLineHeight(font, uiScale, lineHeightScale) + controlYAdvance); + currentLine += 1; + baseLine = new Vector2(drawBox.Left + GetAlignedOffset(currentLine), baseLine.Y + GetLineHeight(font, uiScale, lineHeightScale) + controlYAdvance); controlYAdvance = 0; lineBreakIndex += 1; } @@ -260,16 +267,86 @@ public readonly void Draw( controlYAdvance = Math.Max(0f, (control.DesiredPixelSize.Y - GetLineHeight(font, uiScale, lineHeightScale)) * invertedScale); baseLine += new Vector2(advanceX, 0); } + + float GetAlignedOffset(int lineIndex) + { + if (lineWidths == null || lineIndex < 0 || lineIndex >= lineWidths.Count) + return 0f; + + var remainingWidth = drawBox.Width - lineWidths[lineIndex]; + if (remainingWidth <= 0) + return 0f; + + return align switch + { + Robust.Client.UserInterface.Controls.Label.AlignMode.Right => remainingWidth, + Robust.Client.UserInterface.Controls.Label.AlignMode.Center or Robust.Client.UserInterface.Controls.Label.AlignMode.Fill => remainingWidth / 2f, + _ => 0f, + }; + } + } + + private static List CalculateLineWidths(in RichTextEntry entry, MarkupTagManager tagManager, Font defaultFont, float uiScale) + { + var widths = new List { 0 }; + var context = new MarkupDrawingContext(); + context.Color.Push(entry._defaultColor); + context.Font.Push(defaultFont); + + var globalBreakCounter = 0; + var lineBreakIndex = 0; + var currentLine = 0; + var nodeIndex = -1; + + foreach (var node in entry.Message) + { + nodeIndex++; + var text = ProcessNode(tagManager, node, context, entry._tagsAllowed); + if (!context.Font.TryPeek(out var font)) + font = defaultFont; + + foreach (var rune in text.EnumerateRunes()) + { + if (lineBreakIndex < entry.LineBreaks.Count && + entry.LineBreaks[lineBreakIndex] == globalBreakCounter) + { + currentLine += 1; + widths.Add(0); + lineBreakIndex += 1; + } + + if (font.TryGetCharMetrics(rune, uiScale, out var metrics)) + widths[currentLine] += metrics.Advance; + + globalBreakCounter += 1; + } + + if (entry.Controls == null || !entry.Controls.TryGetValue(nodeIndex, out var control)) + continue; + + if (lineBreakIndex < entry.LineBreaks.Count && + entry.LineBreaks[lineBreakIndex] == globalBreakCounter) + { + currentLine += 1; + widths.Add(0); + lineBreakIndex += 1; + } + + control.Measure(new Vector2(entry.Width, entry.Height)); + widths[currentLine] += control.DesiredPixelSize.X; + } + + return widths; } - private readonly string ProcessNode(MarkupTagManager tagManager, MarkupNode node, MarkupDrawingContext context) + private static string ProcessNode(MarkupTagManager tagManager, MarkupNode node, MarkupDrawingContext context, Type[]? tagsAllowed) { // If a nodes name is null it's a text node. if (node.Name == null) return node.Value.StringValue ?? ""; //Skip the node if there is no markup tag for it. - if (!tagManager.TryGetMarkupTagHandler(node.Name, _tagsAllowed, out var tag)) + if (!tagManager.TryGetMarkupTagHandler(node.Name, tagsAllowed, out var tag)) return ""; if (!node.Closing)