From 4371067120c3fdb530ca970b85b465bdce6c51f3 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Tue, 9 Nov 2021 12:13:12 -0500
Subject: [PATCH 01/44] Shared/Utility: Define new FormattedMessage core types
---
Robust.Shared/Utility/FormattedMessage.cs | 267 ++++++++++------------
1 file changed, 123 insertions(+), 144 deletions(-)
diff --git a/Robust.Shared/Utility/FormattedMessage.cs b/Robust.Shared/Utility/FormattedMessage.cs
index b67ca6866aa..59358ac7502 100644
--- a/Robust.Shared/Utility/FormattedMessage.cs
+++ b/Robust.Shared/Utility/FormattedMessage.cs
@@ -8,186 +8,165 @@
namespace Robust.Shared.Utility
{
- ///
- /// Represents a formatted message in the form of a list of "tags".
- /// Does not do any concrete formatting, simply useful as an API surface.
- ///
- [PublicAPI]
[Serializable, NetSerializable]
- public sealed partial class FormattedMessage
+ public struct Section
{
- public TagList Tags => new(_tags);
- private readonly List _tags;
-
- public FormattedMessage()
- {
- _tags = new List();
- }
-
- public FormattedMessage(int capacity)
- {
- _tags = new List(capacity);
- }
-
- public static FormattedMessage FromMarkup(string markup)
- {
- var msg = new FormattedMessage();
- msg.AddMarkup(markup);
- return msg;
- }
-
- public static FormattedMessage FromMarkupPermissive(string markup)
- {
- var msg = new FormattedMessage();
- msg.AddMarkupPermissive(markup);
- return msg;
- }
-
- ///
- /// Escape a string of text to be able to be formatted into markup.
- ///
- public static string EscapeText(string text)
- {
- return text.Replace("\\", "\\\\").Replace("[", "\\[");
- }
-
- ///
- /// Remove all markup, leaving only the basic text content behind.
- ///
- public static string RemoveMarkup(string text)
- {
- return FromMarkup(text).ToString();
- }
+ public FontStyle Style;
+ public FontSize Size;
+ public TextAlign Alignment;
+ public int Color;
+ public MetaFlags Meta;
+ public string Content;
+ }
- ///
- /// Create a new FormattedMessage by copying another one.
- ///
- /// The message to copy.
- public FormattedMessage(FormattedMessage toCopy)
- {
- _tags = toCopy._tags.ShallowClone();
- }
+ [Flags]
+ public enum MetaFlags : byte
+ {
+ Default = 0,
+ Localized = 1,
+ // All other values are reserved.
+ }
- public void AddText(string text)
- {
- _tags.Add(new TagText(text));
- }
+ [Flags]
+ public enum FontStyle : byte
+ {
+ // Single-font styles
+ Normal = 0b0000_0000,
+ Bold = 0b0000_0001,
+ Italic = 0b0000_0010,
+ Monospace = 0b0000_0100,
+ BoldItalic = Bold | Italic,
- public void PushColor(Color color)
- {
- _tags.Add(new TagColor(color));
- }
+ // Escape value
+ Special = 0b1000_0000,
- public void PushNewline()
- {
- AddText("\n");
- }
+ // The lower four bits are available for styles to specify.
+ Standard = 0b0100_0000 | Special,
- public void Pop()
- {
- _tags.Add(new TagPop());
- }
+ // All values not otherwise specified are reserved.
+ }
- public void AddMessage(FormattedMessage other)
- {
- _tags.AddRange(other.Tags);
- }
+ [Flags]
+ public enum FontSize : ushort
+ {
+ // Format (Standard): 0bSNNN_NNNN_NNNN_NNNN
+ // S: Special flag.
+ // N (where S == 0): Font size. Unsigned.
+ // N (where S == 1): Special operation; see below.
+
+ // Flag to indicate the TagFontSize is "special".
+ // All values not specified are reserved.
+ // General Format: 0b1PPP_AAAA_AAAA_AAAA
+ // P: Operation.
+ // A: Arguments.
+ Special = 0b1000_0000_0000_0000,
+
+ // RELative Plus.
+ // Format: 0b1100_NNNN_NNNN_NNNN
+ // N: Addend to the previous font size. Unsigned.
+ RelPlus = 0b0100_0000_0000_0000 | Special,
+
+ // RELative Minus.
+ // Format: 0b1010_NNNN_NNNN_NNNN
+ // N: Subtrahend to the previous font size. Unsigned.
+ RelMinus = 0b0010_0000_0000_0000 | Special,
+
+ // Selects a font size from the stylesheet.
+ // Format: 0b1110_NNNN_NNNN_NNNN
+ // N: The identifier of the preset font size.
+ Standard = 0b0110_0000_0000_0000 | Special,
+ }
- public void Clear()
- {
- _tags.Clear();
- }
+ public enum TextAlign : byte
+ {
+ // Format: 0bHHHH_VVVV
+ // H: Horizontal alignment
+ // V: Vertical alignment.
+ // All values not specified are reserved.
+
+ // This seems dumb to point out, but ok
+ Default = Baseline | Left,
+
+ // Vertical alignment
+ Baseline = 0x00,
+ Top = 0x01,
+ Bottom = 0x02,
+ Superscript = 0x03,
+ Subscript = 0x04,
+
+ // Horizontal alignment
+ Left = 0x00,
+ Right = 0x10,
+ Center = 0x20,
+ Justify = 0x30,
+ }
- /// The string without markup tags.
+ [PublicAPI]
+ [Serializable, NetSerializable]
+ public sealed record FormattedMessage(Section[] Sections)
+ {
public override string ToString()
{
- var builder = new StringBuilder();
- foreach (var tag in _tags)
- {
- if (tag is not TagText text)
- {
- continue;
- }
-
- builder.Append(text.Text);
- }
+ var sb = new StringBuilder();
+ foreach (var i in Sections)
+ sb.Append(i.Content);
- return builder.ToString();
+ return sb.ToString();
}
- /// The string without filtering out markup tags.
- public string ToMarkup()
+ public class Builder
{
- var builder = new StringBuilder();
- foreach (var tag in _tags)
- {
- builder.Append(tag);
- }
+ private bool _dirty = false;
+ private int _idx = 0;
+ private StringBuilder _sb = new();
+ private List _work = new();
- return builder.ToString();
- }
-
- [Serializable, NetSerializable]
- public abstract record Tag
- {
- }
-
- [Serializable, NetSerializable]
- public sealed record TagText(string Text) : Tag
- {
- public override string ToString()
+ public void Clear()
{
- return Text;
+ _idx = 0;
+ _work = new();
+ _sb = _sb.Clear();
}
- }
- [Serializable, NetSerializable]
- public sealed record TagColor(Color Color) : Tag
- {
- public override string ToString()
+ public void AddText(string text)
{
- return $"[color={Color.ToHex()}]";
+ _dirty = true;
+ _sb.Append(text);
}
- }
- [Serializable, NetSerializable]
- public sealed record TagPop : Tag
- {
- public static readonly TagPop Instance = new();
-
- public override string ToString()
+ public void PushColor(Color color)
{
- return $"[/color]";
+ flushWork();
+ _idx++;
+ var last = _work[_work.Count - 1];
+ last.Color = color.ToArgb();
+ _work[_work.Count - 1] = last;
}
- }
-
- public readonly struct TagList : IReadOnlyList
- {
- private readonly List _tags;
- public TagList(List tags)
+ public void PushNewline()
{
- _tags = tags;
+ _dirty = true;
+ _sb.Append('\n');
}
- public List.Enumerator GetEnumerator()
+ public void Pop()
{
- return _tags.GetEnumerator();
+ flushWork();
+ _idx--;
}
- IEnumerator IEnumerable.GetEnumerator()
+ public void flushWork()
{
- return _tags.GetEnumerator();
- }
+ if (!_dirty)
+ return;
- IEnumerator IEnumerable.GetEnumerator()
- {
- return _tags.GetEnumerator();
+ var last = _work[_work.Count - 1];
+ last.Content = _sb.ToString();
+ _sb = _sb.Clear();
+ _work.Add(_work[_idx]);
}
- public int Count => _tags.Count;
-
- public Tag this[int index] => _tags[index];
+ public FormattedMessage Build() => new FormattedMessage(_work.ToArray());
}
}
}
From 29eff23643cba228ab5a0bf1959ea9710dce4aa3 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Tue, 9 Nov 2021 12:15:30 -0500
Subject: [PATCH 02/44] Shared/Utility: Move MarkupParser to a new namespace,
update to new FormattedText
---
.../Basic.cs} | 66 +++++++++++++------
1 file changed, 46 insertions(+), 20 deletions(-)
rename Robust.Shared/Utility/{FormattedMessage.MarkupParser.cs => Markup/Basic.cs} (56%)
diff --git a/Robust.Shared/Utility/FormattedMessage.MarkupParser.cs b/Robust.Shared/Utility/Markup/Basic.cs
similarity index 56%
rename from Robust.Shared/Utility/FormattedMessage.MarkupParser.cs
rename to Robust.Shared/Utility/Markup/Basic.cs
index 0ab2b7f74e8..8377e822f46 100644
--- a/Robust.Shared/Utility/FormattedMessage.MarkupParser.cs
+++ b/Robust.Shared/Utility/Markup/Basic.cs
@@ -1,13 +1,22 @@
+using System;
using System.Collections.Generic;
using Pidgin;
using Robust.Shared.Maths;
+using static Robust.Shared.Utility.FormattedMessage;
using static Pidgin.Parser;
using static Pidgin.Parser;
-namespace Robust.Shared.Utility
+namespace Robust.Shared.Utility.Markup
{
- public partial class FormattedMessage
+ public class Basic
{
+ internal record tag;
+ internal record tagText(string text) : tag;
+ internal record tagColor(Color color) : tag;
+ internal record tagPop() : tag;
+
+ private List _tags = new();
+
// wtf I love parser combinators now.
private const char TagBegin = '[';
private const char TagEnd = ']';
@@ -18,46 +27,45 @@ public partial class FormattedMessage
Char(TagBegin),
Char(TagEnd)));
- private static readonly Parser ParseTagText =
+ private static readonly Parser ParseTagText =
ParseEscapeSequence.Or(Token(c => c != TagBegin && c != '\\'))
.AtLeastOnceString()
- .Select(s => new TagText(s));
+ .Select(s => new tagText(s));
- private static readonly Parser ParseTagColor =
+ private static readonly Parser ParseTagColor =
String("color")
.Then(Char('='))
.Then(Token(ValidColorNameContents).AtLeastOnceString()
.Select(s =>
{
if (Color.TryFromName(s, out var color))
- {
- return new TagColor(color);
- }
- return new TagColor(Color.FromHex(s));
+ return new tagColor(color);
+
+ return new tagColor(Color.FromHex(s));
}));
- private static readonly Parser ParseTagPop =
+ private static readonly Parser ParseTagPop =
Char('/')
.Then(String("color"))
- .ThenReturn(TagPop.Instance);
+ .ThenReturn(new tagPop());
- private static readonly Parser ParseTagContents =
- ParseTagColor.Cast().Or(ParseTagPop.Cast());
+ private static readonly Parser ParseTagContents =
+ ParseTagColor.Cast().Or(ParseTagPop.Cast());
- private static readonly Parser ParseEnclosedTag =
+ private static readonly Parser ParseEnclosedTag =
ParseTagContents.Between(Char(TagBegin), Char(TagEnd));
- private static readonly Parser ParseTagOrFallBack =
+ private static readonly Parser ParseTagOrFallBack =
Try(ParseEnclosedTag)
// If we couldn't parse a tag then parse the [ of the start of the tag
// so the rest is recognized as text.
- .Or(Char(TagBegin).ThenReturn(new TagText("[")));
+ .Or(Char(TagBegin).ThenReturn(new tagText("[")));
- private static readonly Parser> Parse =
- ParseTagText.Cast().Or(ParseEnclosedTag).Many();
+ private static readonly Parser> Parse =
+ ParseTagText.Cast().Or(ParseEnclosedTag).Many();
- private static readonly Parser> ParsePermissive =
- ParseTagText.Cast().Or(ParseTagOrFallBack).Many();
+ private static readonly Parser> ParsePermissive =
+ ParseTagText.Cast().Or(ParseTagOrFallBack).Many();
public static bool ValidMarkup(string markup)
{
@@ -102,5 +110,23 @@ private static bool ValidColorNameContents(char c)
return false;
}
+
+
+ public FormattedMessage Render()
+ {
+ var b = new FormattedMessage.Builder();
+
+ foreach (var t in _tags)
+ {
+ switch (t)
+ {
+ case tagText txt: b.AddText(txt.text); break;
+ case tagColor col: b.PushColor(col.color); break;
+ case tagPop: b.Pop(); break;
+ }
+ }
+
+ return b.Build();
+ }
}
}
From c69294bbe0a47d9ca5ccbe9f7ef29d106e5ceba5 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Tue, 9 Nov 2021 12:17:22 -0500
Subject: [PATCH 03/44] Shared/Serialization: Temporary fix for
FormattedMessageSerializer
---
.../Implementations/FormattedMessageSerializer.cs | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/Robust.Shared/Serialization/TypeSerializers/Implementations/FormattedMessageSerializer.cs b/Robust.Shared/Serialization/TypeSerializers/Implementations/FormattedMessageSerializer.cs
index 70f11b49548..567c10d7bf9 100644
--- a/Robust.Shared/Serialization/TypeSerializers/Implementations/FormattedMessageSerializer.cs
+++ b/Robust.Shared/Serialization/TypeSerializers/Implementations/FormattedMessageSerializer.cs
@@ -8,6 +8,7 @@
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
using Robust.Shared.Utility;
+using Robust.Shared.Utility.Markup;
namespace Robust.Shared.Serialization.TypeSerializers.Implementations
{
@@ -18,14 +19,16 @@ public DeserializationResult Read(ISerializationManager serializationManager,
ValueDataNode node, IDependencyCollection dependencies, bool skipHook,
ISerializationContext? context = null)
{
- return new DeserializedValue(FormattedMessage.FromMarkup(node.Value));
+ var bParser = new Basic();
+ bParser.AddMarkup(node.Value);
+ return new DeserializedValue(bParser.Render());
}
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies,
ISerializationContext? context = null)
{
- return FormattedMessage.ValidMarkup(node.Value)
+ return Basic.ValidMarkup(node.Value)
? new ValidatedValueNode(node)
: new ErrorNode(node, "Invalid markup in FormattedMessage.");
}
@@ -41,7 +44,8 @@ public DataNode Write(ISerializationManager serializationManager, FormattedMessa
public FormattedMessage Copy(ISerializationManager serializationManager, FormattedMessage source,
FormattedMessage target, bool skipHook, ISerializationContext? context = null)
{
- return new(source);
+ // based value types
+ return source;
}
}
}
From 7a03e5b8caac6b1f24f473e8e97d8dbfc3c2d20e Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Tue, 9 Nov 2021 12:17:58 -0500
Subject: [PATCH 04/44] Scripting/ScriptInstanceShared: Move to new
FormattedMessage.Builder
---
Robust.Shared.Scripting/ScriptInstanceShared.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Robust.Shared.Scripting/ScriptInstanceShared.cs b/Robust.Shared.Scripting/ScriptInstanceShared.cs
index f214024613a..8f281d621b8 100644
--- a/Robust.Shared.Scripting/ScriptInstanceShared.cs
+++ b/Robust.Shared.Scripting/ScriptInstanceShared.cs
@@ -77,7 +77,7 @@ static ScriptInstanceShared()
"var x = 5 + 5; var y = (object) \"foobar\"; void Foo(object a) { } Foo(y); Foo(x)";
var script = await CSharpScript.RunAsync(code);
- var msg = new FormattedMessage();
+ var msg = new FormattedMessage.Builder();
// Even run the syntax highlighter!
AddWithSyntaxHighlighting(script.Script, msg, code, new AdhocWorkspace());
});
@@ -101,7 +101,7 @@ public static bool HasReturnValue(Script script)
return _getDiagnosticArguments(diag);
}
- public static void AddWithSyntaxHighlighting(Script script, FormattedMessage msg, string code,
+ public static void AddWithSyntaxHighlighting(Script script, FormattedMessage.Builder msg, string code,
Workspace workspace)
{
var compilation = script.GetCompilation();
From 5b23a74d0decf0fd1c9435b8ffd85b78f3f8e342 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Tue, 9 Nov 2021 12:31:09 -0500
Subject: [PATCH 05/44] Shared/Utility: Add a FormattedMessage loader to the
.Builder
---
Robust.Shared/Utility/FormattedMessage.cs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Robust.Shared/Utility/FormattedMessage.cs b/Robust.Shared/Utility/FormattedMessage.cs
index 59358ac7502..ce10765ba1f 100644
--- a/Robust.Shared/Utility/FormattedMessage.cs
+++ b/Robust.Shared/Utility/FormattedMessage.cs
@@ -121,6 +121,12 @@ public class Builder
private StringBuilder _sb = new();
private List _work = new();
+ public static Builder FromFormattedText(FormattedMessage orig) => new ()
+ {
+ _idx = orig.Sections.Length - 1,
+ _work = new List(orig.Sections),
+ };
+
public void Clear()
{
_idx = 0;
From cf1739cedf670ad8a6c4d6a93dbab57fa74c41b1 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Tue, 9 Nov 2021 13:21:59 -0500
Subject: [PATCH 06/44] Server/Scripting: Port SciptHost to
FormattedMessage.Builder
---
Robust.Server/Scripting/ScriptHost.cs | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/Robust.Server/Scripting/ScriptHost.cs b/Robust.Server/Scripting/ScriptHost.cs
index 3057c437b85..feb90da11c2 100644
--- a/Robust.Server/Scripting/ScriptHost.cs
+++ b/Robust.Server/Scripting/ScriptHost.cs
@@ -184,12 +184,12 @@ private async void ReceiveScriptEval(MsgScriptEval message)
newScript.Compile();
// Echo entered script.
- var echoMessage = new FormattedMessage();
+ var echoMessage = new FormattedMessage.Builder();
ScriptInstanceShared.AddWithSyntaxHighlighting(newScript, echoMessage, code, instance.HighlightWorkspace);
- replyMessage.Echo = echoMessage;
+ replyMessage.Echo = echoMessage.Build();
- var msg = new FormattedMessage();
+ var msg = new FormattedMessage.Builder();
try
{
@@ -215,7 +215,7 @@ private async void ReceiveScriptEval(MsgScriptEval message)
PromptAutoImports(e.Diagnostics, code, msg, instance);
- replyMessage.Response = msg;
+ replyMessage.Response = msg.Build();
_netManager.ServerSendMessage(replyMessage, message.MsgChannel);
return;
}
@@ -240,7 +240,7 @@ private async void ReceiveScriptEval(MsgScriptEval message)
msg.AddText(ScriptInstanceShared.SafeFormat(instance.State.ReturnValue));
}
- replyMessage.Response = msg;
+ replyMessage.Response = msg.Build();
_netManager.ServerSendMessage(replyMessage, message.MsgChannel);
}
@@ -316,7 +316,7 @@ private async void ReceiveScriptCompletion(MsgScriptCompletion message)
private void PromptAutoImports(
IEnumerable diags,
string code,
- FormattedMessage output,
+ FormattedMessage.Builder output,
ScriptInstance instance)
{
if (!ScriptInstanceShared.CalcAutoImports(_reflectionManager, diags, out var found))
From 5ba079fd0ddcd217f7fa5a8a8a22095a1372f6a1 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Thu, 18 Nov 2021 12:20:40 -0500
Subject: [PATCH 07/44] UserInterface/RichTextEntry: NOP out almost everything
not gonna bother fixing it until more groundwork is laid
---
Robust.Client/UserInterface/RichTextEntry.cs | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/Robust.Client/UserInterface/RichTextEntry.cs b/Robust.Client/UserInterface/RichTextEntry.cs
index 44490c1934d..a825d77cdef 100644
--- a/Robust.Client/UserInterface/RichTextEntry.cs
+++ b/Robust.Client/UserInterface/RichTextEntry.cs
@@ -15,9 +15,6 @@ namespace Robust.Client.UserInterface
///
internal struct RichTextEntry
{
- private static readonly FormattedMessage.TagColor TagBaseColor
- = new(new Color(200, 200, 200));
-
public readonly FormattedMessage Message;
///
@@ -51,6 +48,7 @@ public RichTextEntry(FormattedMessage message)
///
public void Update(Font font, float maxSizeX, float uiScale)
{
+ #if false
// This method is gonna suck due to complexity.
// Bear with me here.
// I am so deeply sorry for the person adding stuff to this in the future.
@@ -206,6 +204,7 @@ public void Update(Font font, float maxSizeX, float uiScale)
}
Width = (int) maxUsedWidth;
+ #endif
}
public void Draw(
@@ -213,11 +212,9 @@ public void Draw(
Font font,
UIBox2 drawBox,
float verticalOffset,
- // A stack for format tags.
- // This stack contains the format tag to RETURN TO when popped off.
- // So when a new color tag gets hit this stack gets the previous color pushed on.
- Stack formatStack, float uiScale)
+ float uiScale)
{
+ #if false
// The tag currently doing color.
var currentColorTag = TagBaseColor;
@@ -267,6 +264,7 @@ public void Draw(
}
}
}
+ #endif
}
[Pure]
From ec66b71603c122194f4e454f9fbd2aadd91fc0d5 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Thu, 18 Nov 2021 12:20:40 -0500
Subject: [PATCH 08/44] Shared/Utility: Expand Utility.Extensions a bit
strictly for pesonal reasons
---
Robust.Shared/Utility/CollectionExtensions.cs | 2 +-
Robust.Shared/Utility/FormattedMessage.cs | 8 ++++++++
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/Robust.Shared/Utility/CollectionExtensions.cs b/Robust.Shared/Utility/CollectionExtensions.cs
index e84d0a907a2..2cfa47def7f 100644
--- a/Robust.Shared/Utility/CollectionExtensions.cs
+++ b/Robust.Shared/Utility/CollectionExtensions.cs
@@ -6,7 +6,7 @@
namespace Robust.Shared.Utility
{
- public static class Extensions
+ public static partial class Extensions
{
public static IList Clone(this IList listToClone) where T : ICloneable
{
diff --git a/Robust.Shared/Utility/FormattedMessage.cs b/Robust.Shared/Utility/FormattedMessage.cs
index ce10765ba1f..a3971c4c68d 100644
--- a/Robust.Shared/Utility/FormattedMessage.cs
+++ b/Robust.Shared/Utility/FormattedMessage.cs
@@ -101,6 +101,12 @@ public enum TextAlign : byte
Justify = 0x30,
}
+ public static partial class Extensions
+ {
+ public static TextAlign Vertical (this TextAlign value) => (TextAlign)((byte) value & 0x0F);
+ public static TextAlign Horizontal (this TextAlign value) => (TextAlign)((byte) value & 0xF0);
+ }
+
[PublicAPI]
[Serializable, NetSerializable]
public sealed record FormattedMessage(Section[] Sections)
@@ -114,6 +120,8 @@ public override string ToString()
return sb.ToString();
}
+ // are you a construction worker?
+ // cuz you buildin
public class Builder
{
private bool _dirty = false;
From 730628f06ba2d7716a1fab9ec0f23a369fca2865 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Thu, 18 Nov 2021 12:20:40 -0500
Subject: [PATCH 09/44] Client/UserInterface: Add the base TextLayout engine
---
Robust.Client/UserInterface/TextLayout.cs | 419 ++++++++++++++++++++++
1 file changed, 419 insertions(+)
create mode 100644 Robust.Client/UserInterface/TextLayout.cs
diff --git a/Robust.Client/UserInterface/TextLayout.cs b/Robust.Client/UserInterface/TextLayout.cs
new file mode 100644
index 00000000000..e53333b8c53
--- /dev/null
+++ b/Robust.Client/UserInterface/TextLayout.cs
@@ -0,0 +1,419 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Text;
+using System.Text.Unicode;
+using JetBrains.Annotations;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.Log;
+using Robust.Shared.Maths;
+using Robust.Shared.Utility;
+
+namespace Robust.Client.UserInterface
+{
+ using FontBundle = Dictionary;
+
+ public static class TextLayout
+ {
+ ///
+ /// An Offset is a simplified instruction for rendering a text block.
+ ///
+ ///
+ ///
+ /// Pseudocode for rendering:
+ ///
+ /// (int x, int y) topLeft = (10, 20);
+ /// foreach (var r in Lines)
+ /// {
+ /// var section = Message.Sections[section];
+ /// var font = style.FontBundle[section.Style];
+ /// font.DrawAt(
+ /// text=section.Content.Substring(charOffs, length),
+ /// x=topLeft.x + r.x,
+ /// y=topLeft.y + r.y,
+ /// color=new Color(section.Color)
+ /// )
+ /// }
+ ///
+ ///
+ ///
+ ///
+ /// The index of the backing store (usually a ) in the
+ /// container (usually a ) to which the Offset belongs.
+ ///
+ /// The byte offset in to the to render.
+ /// The number of bytes after to render.
+ /// The offset from the base position's x coordinate to render this chunk of text.
+ /// The offset from the base position's y coordinate to render this chunk of text.
+ public class Offset
+ {
+ public Offset()
+ {
+ }
+
+ public Offset(Offset o)
+ {
+ section = o.section;
+ charOffs = o.charOffs;
+ length = o.length;
+ x = o.x;
+ y = o.y;
+ }
+
+ public int section;
+ public int charOffs;
+ public int length;
+ public int x;
+ public int y;
+ }
+
+ public class RichTextBlock
+ {
+ public Offset[][] Lines;
+ public int Height;
+ public int Width;
+ public FormattedMessage Message;
+ }
+
+ public enum WordType : byte
+ {
+ Normal,
+ Space,
+ LineBreak,
+ }
+
+ public sealed class Word : Offset
+ {
+ public Word()
+ {
+ }
+
+ public Word(Word o) : base(o)
+ {
+ h = o.h;
+ w = o.w;
+ spw = o.spw;
+ wt = o.wt;
+ }
+ public int h;
+ public int w;
+ public int spw;
+ public WordType wt;
+ }
+
+ public static RichTextBlock Layout(
+ ISectionable text,
+ int w,
+ FontBundle fonts,
+ float scale = 1.0f,
+ int lineSpacing = 0,
+ int wordSpacing = 0,
+ int runeSpacing = 0,
+ LayoutOptions options = LayoutOptions.Default
+ ) => Layout(
+ text,
+ Split(text, fonts, scale, wordSpacing, runeSpacing, options),
+ w,
+ fonts,
+ scale,
+ lineSpacing, wordSpacing,
+ options
+ );
+
+ // Actually produce the layout data.
+ // The algorithm is basically ripped from CSS Flexbox.
+ //
+ // 1. Add up all the space each word takes
+ // 2. Subtract that from the line width (w)
+ // 3. Save that as the free space (fs)
+ // 4. Add up each gap's priority value (Σpri)
+ // 5. Assign each gap a final priority (fp) of ((priMax - pri) / Σpri)
+ // 6. That space has (fp*fs) pixels.
+ public static RichTextBlock Layout(
+ ISectionable src,
+ ImmutableArray text,
+ int w,
+ FontBundle fonts,
+ float scale = 1.0f,
+ int lineSpacing = 0,
+ int wordSpacing = 0,
+ LayoutOptions options = LayoutOptions.Default
+ )
+ {
+ var lw = new WorkQueue<(
+ List wds,
+ List gaps,
+ int lnrem,
+ int sptot,
+ int maxPri,
+ int tPri
+ )>();
+
+ var lastAlign = TextAlign.Left;
+
+ // Calculate line boundaries
+ foreach (var wd in text)
+ {
+ var hz = src[wd.section].Alignment.Horizontal();
+ (int gW, int adv) = TransitionWeights(lastAlign, hz);
+ lastAlign = hz;
+
+ lw.Work.gaps.Add(gW+lw.Work.maxPri);
+ lw.Work.tPri += gW+lw.Work.maxPri;
+ lw.Work.maxPri += adv;
+
+ if (lw.Work.lnrem < wd.w)
+ {
+ lw.Flush();
+ lw.Work.lnrem = w;
+ lw.Work.maxPri = 1;
+ }
+
+ lw.Work.sptot += wd.spw;
+ lw.Work.lnrem -= wd.w + wd.spw;
+ lw.Work.wds.Add(wd);
+ }
+ lw.Flush(true);
+
+ int py=0;
+ foreach ((var ln, var gaps, var lnrem, var sptot, var maxPri, var tPri) in lw.Done)
+ {
+ int px=0;
+ int lh=0;
+ var spDist = new int[gaps.Count];
+ for (int i = 0; i < gaps.Count; i++)
+ {
+ spDist[i] = (int) (((float) gaps[i] / (float) tPri) * (float) sptot);
+ }
+
+ int prevasc=0, prevdesc=0;
+ for (int i = 0; i < ln.Count; i++)
+ {
+ var ss = src[ln[i].section];
+ var sf = fonts[ss.Style, ];
+ var asc = sf.GetAscent(scale);
+ var desc = sf.GetDescent(scale);
+ px += spDist[i] + ln[i].w;
+ ln[i].x = px;
+ lh = Math.Max(lh, ln[i].h);
+ ln[i].y = src[ln[i].section].Alignment.Vertical() switch {
+ TextAlign.Baseline => 0,
+ TextAlign.Bottom => -(desc - prevdesc), // Scoot it up by the descent
+ TextAlign.Top => (asc - prevasc),
+ TextAlign.Subscript => -ln[i].h / 8, // Technically these should be derived from the font data,
+ TextAlign.Superscript => ln[i].h / 4, // but I'm not gonna bother figuring out how to pull it from them.
+ _ => 0,
+ };
+ prevasc = asc;
+ prevdesc = desc;
+ }
+ py += lineSpacing + lh;
+ }
+ }
+
+ private static (int gapPri, int adv) TransitionWeights (TextAlign l, TextAlign r)
+ {
+ l = l.Horizontal();
+ r = r.Horizontal();
+
+ // Technically these could be slimmed down, but it's as much to help explain the system
+ // as it is to implement it.
+
+ // p (aka gapPri) is how high up the food chain each gap should be.
+ // _LOWER_ p means more (since we do first-come first-serve).
+
+ // a (aka adv) is how much we increment the gapPri counter, meaning how much less important
+ // future alignment changes are.
+
+ // Left alignment.
+ (int p, int a) la = (l, r) switch {
+ ( TextAlign.Left, TextAlign.Left) => (0, 0), // Left alignment doesn't care about inter-word spacing
+ ( _, TextAlign.Left) => (0, 0), // or anything that comes before it,
+ ( TextAlign.Left, _) => (1, 1), // only what comes after it.
+ ( _, _) => (0, 0)
+ };
+
+ // Right alignment
+ (int p, int a) ra = (l, r) switch {
+ ( TextAlign.Right, TextAlign.Right) => (0, 0), // Right alignment also does not care about inter-word spacing,
+ ( _, TextAlign.Right) => (1, 1), // but it does care what comes before it,
+ ( TextAlign.Right, _) => (0, 0), // but not after.
+ ( _, _) => (0, 0)
+ };
+
+ // Centering
+ (int p, int a) ca = (l, r) switch {
+ ( TextAlign.Center, TextAlign.Center) => (0, 0), // Centering still doesn't care about inter-word spacing,
+ ( _, TextAlign.Center) => (1, 0), // but it cares about both what comes before it,
+ ( TextAlign.Center, _) => (1, 1), // and what comes after it.
+ ( _, _) => (0, 0)
+ };
+
+ // Justifying
+ (int p, int a) ja = (l, r) switch {
+ (TextAlign.Justify, TextAlign.Justify) => (1, 0), // Justification cares about inter-word spacing.
+ ( _, TextAlign.Justify) => (0, 1), // And (sort of) what comes before it.
+ ( _, _) => (0, 0)
+ };
+
+ return new
+ (
+ la.p + ra.p + ca.p + ja.p,
+ la.a + ra.a + ca.a + ja.a
+ );
+ }
+
+ public interface ISectionable
+ {
+ Section this[int i] { get; }
+ int Length { get; }
+ }
+
+ // Split creates a list of words broken based on their boundaries.
+ // Users are encouraged to reuse this for as long as it accurately reflects
+ // the content they're trying to display.
+ public static ImmutableArray Split(
+ ISectionable text,
+ FontBundle fonts,
+ float scale,
+ int wordSpacing,
+ int runeSpacing,
+ LayoutOptions options = LayoutOptions.Default
+ )
+ {
+ var nofb = options.HasFlag(LayoutOptions.NoFallback);
+
+ var s=0;
+ var lsbo=0;
+ var sbo=0;
+ var wq = new WorkQueue(
+ w =>
+ {
+ var len = lsbo-sbo;
+ lsbo = sbo;
+ sbo = 0;
+ return new(w) { length=len };
+ },
+ () => new Word() { section=s, charOffs=sbo },
+ w => w.length > 0
+ );
+
+ for (s = 0; s < text.Length; s++)
+ {
+ var sec = text[s];
+ sbo = 0;
+ var fnt = fonts[sec.Style, sec.Size];
+
+ foreach (var r in sec.Content.EnumerateRunes())
+ {
+ if (r == (Rune) '\n')
+ {
+ wq.Flush();
+ wq.Work.wt = WordType.LineBreak;
+ }
+ else if (Rune.IsSeparator(r))
+ {
+ if (wq.Work.wt != WordType.Space)
+ {
+ wq.Work.w += wordSpacing;
+ wq.Flush();
+ wq.Work.wt = WordType.Space;
+ }
+ }
+ else if (wq.Work.wt != WordType.Normal)
+ wq.Flush();
+
+ sbo += r.Utf16SequenceLength;
+ var cm = fnt.GetCharMetrics(r, scale, !nofb);
+
+ if (!cm.HasValue)
+ {
+ if (nofb)
+ continue;
+ else
+ throw new Exception("unable to get character metrics");
+ }
+
+ // This may be less-than-optimal, since we're ignoring anything below the origin.
+ wq.Work.h = Math.Max(wq.Work.h, cm.Value.BearingY);
+ wq.Work.w += cm.Value.Advance;
+ if (wq.Work.wt == WordType.Normal)
+ wq.Work.spw = runeSpacing;
+ }
+ }
+
+ wq.Flush(true);
+
+ return wq.Done.ToImmutableArray();
+ }
+
+ [Flags]
+ public enum LayoutOptions : byte
+ {
+ Default = 0b0000_0000,
+
+ // Measure the actual height of runes to space lines.
+ UseRenderTop = 0b0000_0001,
+
+ // NoFallback disables the use of the Fallback character.
+ NoFallback = 0b0000_0010,
+ }
+
+ // WorkQueue is probably a misnomer. All it does is streamline a pattern I ended up using
+ // repeatedly where I'd have a list of something and a WIP, then I'd flush the WIP in to
+ // the list.
+ private class WorkQueue
+ where TIn : new()
+ {
+ // _blank creates a new T if _refresh says it needs to.
+ private Func _blank = () => new TIn();
+
+ private Func _check = _ => true;
+
+ private Func _conv;
+
+ public List Done = new();
+ public TIn Work;
+
+ public WorkQueue(
+ Func conv,
+ Func? blank = default,
+ Func? check = default
+ )
+ {
+ _conv = conv;
+
+ if (blank is not null)
+ _blank = blank;
+
+ if (check is not null)
+ _check = check;
+
+ Work = _blank.Invoke();
+ }
+
+ public void Flush(bool force = false)
+ {
+ if (_check.Invoke(Work) || force)
+ {
+ Done.Add(_conv(Work));
+ Work = _blank.Invoke();
+ }
+ }
+ }
+
+ private class WorkQueue : WorkQueue
+ where T : new()
+ {
+ private static Func __conv = i => i;
+ public WorkQueue(
+ Func? conv = default,
+ Func? blank = default,
+ Func? check = default
+ ) : base(conv ?? __conv, blank, check)
+ {
+ }
+ }
+ }
+}
From 3f18ec3320687fcf0223f15f7595e7ac3bdf4bee Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Thu, 25 Nov 2021 02:16:42 -0500
Subject: [PATCH 10/44] Client/Graphics: Add a Font Library manager
---
Robust.Client/Graphics/FontLibrary.cs | 151 ++++++++++++++++++++++++++
1 file changed, 151 insertions(+)
create mode 100644 Robust.Client/Graphics/FontLibrary.cs
diff --git a/Robust.Client/Graphics/FontLibrary.cs b/Robust.Client/Graphics/FontLibrary.cs
new file mode 100644
index 00000000000..e793a2b6f94
--- /dev/null
+++ b/Robust.Client/Graphics/FontLibrary.cs
@@ -0,0 +1,151 @@
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using Robust.Shared.Utility;
+using Robust.Client.ResourceManagement;
+namespace Robust.Client.Graphics;
+
+///
+/// Stores a single style (Bold, Italic, Monospace), or any combination thereof.
+///
+record FontVariant (FontStyle Style, FontResource[] Resource)
+{
+ public Font ToFont(byte size)
+ {
+ if (Resource.Length == 1)
+ return new VectorFont(Resource[0], size);
+
+ var fs = new Font[Resource.Length];
+ for (var i = 0; i < Resource.Length; i++)
+ fs[i] = new VectorFont(Resource[i], size);
+
+ return new StackedFont(fs);
+ }
+};
+
+///
+/// Manages font-based bookkeeping across a single stylesheet.
+///
+interface IFontLibrary
+{
+ /// Associates a name to a set of font resources.
+ void AddFont(string name, params FontVariant[] variants);
+
+ /// Sets a standard size which can be reused across the Font Library.
+ void SetStandardSize(ushort number, byte size);
+
+ /// Sets a standard style which can be reused across the Font Library.
+ void SetStandardStyle(ushort number, string name, FontStyle style);
+
+ ///
+ /// Returns a fancy handle in to the library.
+ /// The handle keeps track of relative changes to and .
+ ///
+ IFontLibrarian StartFont(string id, FontStyle fst, FontSize fsz);
+}
+
+///
+/// Acts as a handle in to an .
+///
+interface IFontLibrarian
+{
+ Font Current { get; }
+ Font Update(FontStyle fst, FontSize fsz);
+}
+
+class FontLibrary : IFontLibrary
+{
+ private Dictionary _styles = new();
+ private Dictionary _standardSt = new();
+ private Dictionary _standardSz = new();
+
+ void IFontLibrary.AddFont(string name, params FontVariant[] variants) =>
+ _styles[name] = variants;
+
+ IFontLibrarian IFontLibrary.StartFont(string id, FontStyle fst, FontSize fsz) =>
+ new FontLibrarian(this, id, fst, fsz);
+
+ void IFontLibrary.SetStandardStyle(ushort number, string name, FontStyle style) =>
+ _standardSt[(FontStyle) number | FontStyle.Standard] = (name, style);
+
+ void IFontLibrary.SetStandardSize(ushort number, byte size) =>
+ _standardSz[(FontSize) number | FontSize.Standard] = size;
+
+ private FontVariant lookup(string id, FontStyle fst)
+ {
+ if (fst.HasFlag(FontStyle.Standard))
+ (id, fst) = _standardSt[fst];
+
+ FontVariant? winner = default;
+ foreach (var vr in _styles[id])
+ {
+ var winfst = winner?.Style ?? ((FontStyle) 0);
+
+ // Since the "style" flags are a bitfield, we can just see which one has more bits.
+ // More bits == closer to the desired font style. Free fallback!
+ if (BitOperations.PopCount((ulong) (vr.Style & fst)) > BitOperations.PopCount((ulong) (winfst & fst)))
+ winner = vr;
+ }
+
+ if (winner is null)
+ throw new Exception("no matching font style");
+
+ return winner;
+ }
+
+ private byte lookupSz(FontSize sz)
+ {
+ if (sz.HasFlag(FontSize.RelMinus) || sz.HasFlag(FontSize.RelPlus))
+ throw new Exception("can't look up a relative font through a library; get a Librarian first");
+
+ if (sz.HasFlag(FontSize.Standard))
+ return _standardSz[sz];
+
+ return (byte) sz;
+ }
+
+ class FontLibrarian : IFontLibrarian
+ {
+ public Font Current => _current;
+ private Font _current;
+
+ private FontLibrary _lib;
+ private string _id;
+ private FontStyle _fst;
+ private FontSize _fsz;
+
+ public FontLibrarian(FontLibrary lib, string id, FontStyle fst, FontSize fsz)
+ {
+ _id = id;
+ _fst = fst;
+ _fsz = fsz;
+ _lib = lib;
+
+ // Actual font entry
+ var f = lib.lookup(id, fst);
+
+ // Real size
+ var rsz = (byte) lib.lookupSz(fsz);
+ _current = f.ToFont(rsz);
+ }
+
+ Font IFontLibrarian.Update(FontStyle fst, FontSize fsz)
+ {
+ var f = _lib.lookup(_id, fst);
+
+ byte rsz = (byte) _fsz;
+ var msk = (byte) fsz & 0b0000_1111;
+ if (fsz.HasFlag(FontSize.Standard))
+ rsz = _lib.lookupSz(fsz);
+ else if (fsz.HasFlag(FontSize.RelPlus))
+ rsz = (byte) (((byte) _fsz) + msk);
+ else if (fsz.HasFlag(FontSize.RelMinus))
+ rsz = (byte) (((byte) _fsz) - msk);
+
+ _fsz = (FontSize) rsz;
+ _fst = fst;
+
+ return _current = f.ToFont((byte) rsz);
+ }
+ }
+}
From 58ea2381ed4621c1b9a3615d006957549ddd9d71 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Thu, 25 Nov 2021 03:41:57 -0500
Subject: [PATCH 11/44] Graphics/TextLayout: Finish up implementing the
TextLayout engine
---
Robust.Client/Graphics/FontLibrary.cs | 31 ++++++++++--
Robust.Client/UserInterface/TextLayout.cs | 59 ++++++++++-------------
2 files changed, 53 insertions(+), 37 deletions(-)
diff --git a/Robust.Client/Graphics/FontLibrary.cs b/Robust.Client/Graphics/FontLibrary.cs
index e793a2b6f94..1f5256d537c 100644
--- a/Robust.Client/Graphics/FontLibrary.cs
+++ b/Robust.Client/Graphics/FontLibrary.cs
@@ -8,7 +8,7 @@ namespace Robust.Client.Graphics;
///
/// Stores a single style (Bold, Italic, Monospace), or any combination thereof.
///
-record FontVariant (FontStyle Style, FontResource[] Resource)
+public record FontVariant (FontStyle Style, FontResource[] Resource)
{
public Font ToFont(byte size)
{
@@ -23,11 +23,20 @@ public Font ToFont(byte size)
}
};
+public record FontClass
+(
+ string Id,
+ FontStyle Style,
+ FontSize Size
+);
+
///
/// Manages font-based bookkeeping across a single stylesheet.
///
-interface IFontLibrary
+public interface IFontLibrary
{
+ FontClass Default { get; }
+
/// Associates a name to a set of font resources.
void AddFont(string name, params FontVariant[] variants);
@@ -42,19 +51,33 @@ interface IFontLibrary
/// The handle keeps track of relative changes to and .
///
IFontLibrarian StartFont(string id, FontStyle fst, FontSize fsz);
+
+ IFontLibrarian StartFont(FontClass? fclass = default) =>
+ StartFont(
+ (fclass ?? Default).Id,
+ (fclass ?? Default).Style,
+ (fclass ?? Default).Size
+ );
}
///
/// Acts as a handle in to an .
///
-interface IFontLibrarian
+public interface IFontLibrarian
{
Font Current { get; }
Font Update(FontStyle fst, FontSize fsz);
}
-class FontLibrary : IFontLibrary
+public class FontLibrary : IFontLibrary
{
+ public FontClass Default { get; set; }
+
+ public FontLibrary(FontClass def)
+ {
+ Default = def;
+ }
+
private Dictionary _styles = new();
private Dictionary _standardSt = new();
private Dictionary _standardSz = new();
diff --git a/Robust.Client/UserInterface/TextLayout.cs b/Robust.Client/UserInterface/TextLayout.cs
index e53333b8c53..c8534348f09 100644
--- a/Robust.Client/UserInterface/TextLayout.cs
+++ b/Robust.Client/UserInterface/TextLayout.cs
@@ -1,19 +1,13 @@
using System;
+using System.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
-using System.Text.Unicode;
-using JetBrains.Annotations;
using Robust.Client.Graphics;
-using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Log;
-using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface
{
- using FontBundle = Dictionary;
-
public static class TextLayout
{
///
@@ -24,10 +18,11 @@ public static class TextLayout
/// Pseudocode for rendering:
///
/// (int x, int y) topLeft = (10, 20);
- /// foreach (var r in Lines)
+ /// var libn = style.FontLib.StartFont(defaultFontID, defaultFontStyle, defaultFontSize);
+ /// foreach (var r in returnedWords)
/// {
/// var section = Message.Sections[section];
- /// var font = style.FontBundle[section.Style];
+ /// var font = libn.Update(section.Style, section.Size);
/// font.DrawAt(
/// text=section.Content.Substring(charOffs, length),
/// x=topLeft.x + r.x,
@@ -68,14 +63,6 @@ public Offset(Offset o)
public int y;
}
- public class RichTextBlock
- {
- public Offset[][] Lines;
- public int Height;
- public int Width;
- public FormattedMessage Message;
- }
-
public enum WordType : byte
{
Normal,
@@ -102,22 +89,30 @@ public Word(Word o) : base(o)
public WordType wt;
}
- public static RichTextBlock Layout(
+ public interface ISectionable
+ {
+ Section this[int i] { get; }
+ int Length { get; }
+ }
+
+ public static ImmutableArray> Layout(
ISectionable text,
int w,
- FontBundle fonts,
+ IFontLibrary fonts,
float scale = 1.0f,
int lineSpacing = 0,
int wordSpacing = 0,
int runeSpacing = 0,
+ FontClass? fclass = default,
LayoutOptions options = LayoutOptions.Default
) => Layout(
text,
- Split(text, fonts, scale, wordSpacing, runeSpacing, options),
+ Split(text, fonts, scale, wordSpacing, runeSpacing, fclass, options),
w,
fonts,
scale,
lineSpacing, wordSpacing,
+ fclass,
options
);
@@ -130,14 +125,15 @@ public static RichTextBlock Layout(
// 4. Add up each gap's priority value (Σpri)
// 5. Assign each gap a final priority (fp) of ((priMax - pri) / Σpri)
// 6. That space has (fp*fs) pixels.
- public static RichTextBlock Layout(
+ public static ImmutableArray> Layout(
ISectionable src,
ImmutableArray text,
int w,
- FontBundle fonts,
+ IFontLibrary fonts,
float scale = 1.0f,
int lineSpacing = 0,
int wordSpacing = 0,
+ FontClass? fclass = default,
LayoutOptions options = LayoutOptions.Default
)
{
@@ -176,6 +172,7 @@ int tPri
}
lw.Flush(true);
+ var flib = fonts.StartFont(fclass);
int py=0;
foreach ((var ln, var gaps, var lnrem, var sptot, var maxPri, var tPri) in lw.Done)
{
@@ -183,15 +180,13 @@ int tPri
int lh=0;
var spDist = new int[gaps.Count];
for (int i = 0; i < gaps.Count; i++)
- {
spDist[i] = (int) (((float) gaps[i] / (float) tPri) * (float) sptot);
- }
int prevasc=0, prevdesc=0;
for (int i = 0; i < ln.Count; i++)
{
var ss = src[ln[i].section];
- var sf = fonts[ss.Style, ];
+ var sf = flib.Update(ss.Style, ss.Size);
var asc = sf.GetAscent(scale);
var desc = sf.GetDescent(scale);
px += spDist[i] + ln[i].w;
@@ -210,6 +205,8 @@ int tPri
}
py += lineSpacing + lh;
}
+
+ return lw.Done.Select(l => l.wds.ToImmutableArray()).ToImmutableArray();
}
private static (int gapPri, int adv) TransitionWeights (TextAlign l, TextAlign r)
@@ -264,21 +261,16 @@ private static (int gapPri, int adv) TransitionWeights (TextAlign l, TextAlign r
);
}
- public interface ISectionable
- {
- Section this[int i] { get; }
- int Length { get; }
- }
-
// Split creates a list of words broken based on their boundaries.
// Users are encouraged to reuse this for as long as it accurately reflects
// the content they're trying to display.
public static ImmutableArray Split(
ISectionable text,
- FontBundle fonts,
+ IFontLibrary fonts,
float scale,
int wordSpacing,
int runeSpacing,
+ FontClass? fclass,
LayoutOptions options = LayoutOptions.Default
)
{
@@ -299,11 +291,12 @@ public static ImmutableArray Split(
w => w.length > 0
);
+ var flib = fonts.StartFont(fclass);
for (s = 0; s < text.Length; s++)
{
var sec = text[s];
sbo = 0;
- var fnt = fonts[sec.Style, sec.Size];
+ var fnt = flib.Update(sec.Style, sec.Size);
foreach (var r in sec.Content.EnumerateRunes())
{
From e230c8533f1871c539bea90d305e267926132b37 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Thu, 25 Nov 2021 03:41:57 -0500
Subject: [PATCH 12/44] Utility/FormattedMessage: Add yet another hack to keep
the serializer in service
---
Robust.Shared/Utility/FormattedMessage.cs | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/Robust.Shared/Utility/FormattedMessage.cs b/Robust.Shared/Utility/FormattedMessage.cs
index a3971c4c68d..68458ecdd80 100644
--- a/Robust.Shared/Utility/FormattedMessage.cs
+++ b/Robust.Shared/Utility/FormattedMessage.cs
@@ -120,6 +120,20 @@ public override string ToString()
return sb.ToString();
}
+ // I don't wanna fix the serializer yet.
+ public string ToMarkup()
+ {
+ var sb = new StringBuilder();
+ foreach (var i in Sections)
+ {
+ sb.AppendFormat("[color=#{0:X}]", i.Color);
+ sb.Append(i.Content);
+ sb.Append("[/color]");
+ }
+
+ return sb.ToString();
+ }
+
// are you a construction worker?
// cuz you buildin
public class Builder
From 5d9b84512ba1fcd3f0b9afacf0d0b80792fb1dca Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Thu, 25 Nov 2021 23:17:52 -0500
Subject: [PATCH 13/44] Commands/Debug: Use FormattedMessage.Builder
---
Robust.Client/Console/Commands/Debug.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Robust.Client/Console/Commands/Debug.cs b/Robust.Client/Console/Commands/Debug.cs
index 96704f997a6..d231d9e9284 100644
--- a/Robust.Client/Console/Commands/Debug.cs
+++ b/Robust.Client/Console/Commands/Debug.cs
@@ -560,12 +560,12 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
vBox.AddChild(tree);
var rich = new RichTextLabel();
- var message = new FormattedMessage();
+ var message = new FormattedMessage.Builder();
message.AddText("Foo\n");
message.PushColor(Color.Red);
message.AddText("Bar");
message.Pop();
- rich.SetMessage(message);
+ rich.SetMessage(message.Build());
vBox.AddChild(rich);
var itemList = new ItemList();
From 8547efa6903eb3d35db3a7c47bf00fe89cc89253 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Thu, 25 Nov 2021 23:22:18 -0500
Subject: [PATCH 14/44] Console/Completions: Use FormattedMessage.Builder
---
Robust.Client/Console/Completions.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Robust.Client/Console/Completions.cs b/Robust.Client/Console/Completions.cs
index 6bc400162e1..bc601c075e8 100644
--- a/Robust.Client/Console/Completions.cs
+++ b/Robust.Client/Console/Completions.cs
@@ -83,7 +83,7 @@ public Entry(LiteResult result)
{
MouseFilter = MouseFilterMode.Stop;
Result = result;
- var compl = new FormattedMessage();
+ var compl = new FormattedMessage.Builder();
var dim = Color.FromHsl((0f, 0f, 0.8f, 1f));
// warning: ew ahead
@@ -120,7 +120,7 @@ public Entry(LiteResult result)
compl.PushColor(Color.LightSlateGray);
compl.AddText(Result.InlineDescription);
}
- SetMessage(compl);
+ SetMessage(compl.Build());
}
}
}
From 8f0ae95644fb0da026001a0370b25daaf22e310d Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Thu, 25 Nov 2021 23:33:47 -0500
Subject: [PATCH 15/44] Utility/FormattedMessage: Add `AddMessage` methods
---
Robust.Shared/Utility/FormattedMessage.cs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Robust.Shared/Utility/FormattedMessage.cs b/Robust.Shared/Utility/FormattedMessage.cs
index 68458ecdd80..5bd86c54a38 100644
--- a/Robust.Shared/Utility/FormattedMessage.cs
+++ b/Robust.Shared/Utility/FormattedMessage.cs
@@ -171,6 +171,12 @@ public void PushColor(Color color)
_work[_work.Count - 1] = last;
}
+ public void AddMessage(FormattedMessage other) =>
+ _work.AddRange(other.Sections);
+
+ public void AddMessage(FormattedMessage.Builder other) =>
+ _work.AddRange(other._work);
+
public void PushNewline()
{
_dirty = true;
From 21b0058ded18032ebec78dd643dbed9c1b9cfd38 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Thu, 25 Nov 2021 23:34:28 -0500
Subject: [PATCH 16/44] Console/ScriptConsole: Use FormattedMessage.Builder
---
.../Console/ScriptClient.ScriptConsoleServer.cs | 4 ++--
Robust.Client/Console/ScriptConsoleClient.cs | 16 ++++++++--------
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/Robust.Client/Console/ScriptClient.ScriptConsoleServer.cs b/Robust.Client/Console/ScriptClient.ScriptConsoleServer.cs
index c5132561e08..73e0031501a 100644
--- a/Robust.Client/Console/ScriptClient.ScriptConsoleServer.cs
+++ b/Robust.Client/Console/ScriptClient.ScriptConsoleServer.cs
@@ -95,11 +95,11 @@ public void ReceiveResponse(MsgScriptResponse response)
_linesEntered = 0;
// Echo entered script.
- var echoMessage = new FormattedMessage();
+ var echoMessage = new FormattedMessage.Builder();
echoMessage.PushColor(Color.FromHex("#D4D4D4"));
echoMessage.AddText("> ");
echoMessage.AddMessage(response.Echo);
- OutputPanel.AddMessage(echoMessage);
+ OutputPanel.AddMessage(echoMessage.Build());
OutputPanel.AddMessage(response.Response);
diff --git a/Robust.Client/Console/ScriptConsoleClient.cs b/Robust.Client/Console/ScriptConsoleClient.cs
index 5e6d5366114..62df23c189f 100644
--- a/Robust.Client/Console/ScriptConsoleClient.cs
+++ b/Robust.Client/Console/ScriptConsoleClient.cs
@@ -128,12 +128,12 @@ protected override async void Run()
newScript.Compile();
// Echo entered script.
- var echoMessage = new FormattedMessage();
+ var echoMessage = new FormattedMessage.Builder();
echoMessage.PushColor(Color.FromHex("#D4D4D4"));
echoMessage.AddText("> ");
ScriptInstanceShared.AddWithSyntaxHighlighting(newScript, echoMessage, code, _highlightWorkspace);
- OutputPanel.AddMessage(echoMessage);
+ OutputPanel.AddMessage(echoMessage.Build());
try
{
@@ -148,7 +148,7 @@ protected override async void Run()
}
catch (CompilationErrorException e)
{
- var msg = new FormattedMessage();
+ var msg = new FormattedMessage.Builder();
msg.PushColor(Color.Crimson);
@@ -158,7 +158,7 @@ protected override async void Run()
msg.AddText("\n");
}
- OutputPanel.AddMessage(msg);
+ OutputPanel.AddMessage(msg.Build());
OutputPanel.AddText(">");
PromptAutoImports(e.Diagnostics, code);
@@ -167,16 +167,16 @@ protected override async void Run()
if (_state.Exception != null)
{
- var msg = new FormattedMessage();
+ var msg = new FormattedMessage.Builder();
msg.PushColor(Color.Crimson);
msg.AddText(CSharpObjectFormatter.Instance.FormatException(_state.Exception));
- OutputPanel.AddMessage(msg);
+ OutputPanel.AddMessage(msg.Build());
}
else if (ScriptInstanceShared.HasReturnValue(newScript))
{
- var msg = new FormattedMessage();
+ var msg = new FormattedMessage.Builder();
msg.AddText(ScriptInstanceShared.SafeFormat(_state.ReturnValue));
- OutputPanel.AddMessage(msg);
+ OutputPanel.AddMessage(msg.Build());
}
OutputPanel.AddText(">");
From 7049ade6e522e115ee8fcf79c792980eb95f363d Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Thu, 25 Nov 2021 23:36:41 -0500
Subject: [PATCH 17/44] Client/Log: Use FormattedMessage.Builder
---
Robust.Client/Log/DebugConsoleLogHandler.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Robust.Client/Log/DebugConsoleLogHandler.cs b/Robust.Client/Log/DebugConsoleLogHandler.cs
index 060c47de778..0ed80a1b893 100644
--- a/Robust.Client/Log/DebugConsoleLogHandler.cs
+++ b/Robust.Client/Log/DebugConsoleLogHandler.cs
@@ -23,7 +23,7 @@ public void Log(string sawmillName, LogEvent message)
if (sawmillName == "CON")
return;
- var formatted = new FormattedMessage(8);
+ var formatted = new FormattedMessage.Builder();
var robustLevel = message.Level.ToRobust();
formatted.PushColor(Color.DarkGray);
formatted.AddText("[");
@@ -38,7 +38,7 @@ public void Log(string sawmillName, LogEvent message)
formatted.AddText("\n");
formatted.AddText(message.Exception.ToString());
}
- Console.AddFormattedLine(formatted);
+ Console.AddFormattedLine(formatted.Build());
}
private static Color LogLevelToColor(LogLevel level)
From e5401c548d8484146d8eee5f8de635c263dcdf94 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Thu, 25 Nov 2021 23:38:18 -0500
Subject: [PATCH 18/44] CustomControls/DebugConsole: Use
FormattedMessage.Builder
---
.../UserInterface/CustomControls/DebugConsole.xaml.cs | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml.cs b/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml.cs
index 85549c70e43..f1192848349 100644
--- a/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml.cs
+++ b/Robust.Client/UserInterface/CustomControls/DebugConsole.xaml.cs
@@ -6,7 +6,6 @@
using System.Threading.Tasks;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
-using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.ContentPack;
@@ -132,11 +131,11 @@ private void OnHistoryChanged()
public void AddLine(string text, Color color)
{
- var formatted = new FormattedMessage(3);
+ var formatted = new FormattedMessage.Builder();
formatted.PushColor(color);
formatted.AddText(text);
formatted.Pop();
- AddFormattedLine(formatted);
+ AddFormattedLine(formatted.Build());
}
public void AddLine(string text)
From 2b1a23bac0932c33322fd27a9ca789ce7a7945a6 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Thu, 25 Nov 2021 23:40:33 -0500
Subject: [PATCH 19/44] Controls/OutputPanel: Use FormattedMessage.Builder, NOP
`Draw` pending rewrite
---
Robust.Client/UserInterface/Controls/OutputPanel.cs | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/Robust.Client/UserInterface/Controls/OutputPanel.cs b/Robust.Client/UserInterface/Controls/OutputPanel.cs
index 0fc5d4ddd3b..adce1c3ef01 100644
--- a/Robust.Client/UserInterface/Controls/OutputPanel.cs
+++ b/Robust.Client/UserInterface/Controls/OutputPanel.cs
@@ -74,9 +74,9 @@ public void RemoveEntry(Index index)
public void AddText(string text)
{
- var msg = new FormattedMessage();
+ var msg = new FormattedMessage.Builder();
msg.AddText(text);
- AddMessage(msg);
+ AddMessage(msg.Build());
}
public void AddMessage(FormattedMessage message)
@@ -121,6 +121,7 @@ protected internal override void Draw(DrawingHandleScreen handle)
var entryOffset = -_scrollBar.Value;
+#if false
// A stack for format tags.
// This stack contains the format tag to RETURN TO when popped off.
// So when a new color tag gets hit this stack gets the previous color pushed on.
@@ -143,6 +144,7 @@ protected internal override void Draw(DrawingHandleScreen handle)
entryOffset += entry.Height + font.GetLineSeparation(UIScale);
}
+#endif
}
protected internal override void MouseWheel(GUIMouseWheelEventArgs args)
From 6de5ad49937de47dabed1210f099549c861b3f4d Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Thu, 25 Nov 2021 23:42:13 -0500
Subject: [PATCH 20/44] Controls/RichTextLabel: Use FormattedMessage.Builder,
NOP `Draw` pending rewrite
---
Robust.Client/UserInterface/Controls/RichTextLabel.cs | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/Robust.Client/UserInterface/Controls/RichTextLabel.cs b/Robust.Client/UserInterface/Controls/RichTextLabel.cs
index cb05077b5f6..13adc9f031c 100644
--- a/Robust.Client/UserInterface/Controls/RichTextLabel.cs
+++ b/Robust.Client/UserInterface/Controls/RichTextLabel.cs
@@ -20,9 +20,9 @@ public void SetMessage(FormattedMessage message)
public void SetMessage(string message)
{
- var msg = new FormattedMessage();
+ var msg = new FormattedMessage.Builder();
msg.AddText(message);
- SetMessage(msg);
+ SetMessage(msg.Build());
}
protected override Vector2 MeasureOverride(Vector2 availableSize)
@@ -47,7 +47,9 @@ protected internal override void Draw(DrawingHandleScreen handle)
return;
}
+#if false
_entry.Draw(handle, _getFont(), SizeBox, 0, new Stack(), UIScale);
+#endif
}
[Pure]
From 90f042aa368bdfabc0035a3ce644650b4b33b0f2 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Fri, 26 Nov 2021 00:05:34 -0500
Subject: [PATCH 21/44] UnitTesting: Update FormattedMessage/Markup Tests
They will NOT pass yet, but I don't care; it compiles.
---
.../FormattedMessageSerializerTest.cs | 6 +-
.../Shared/Utility/FormattedMessage_Test.cs | 69 ++++++++++---------
2 files changed, 41 insertions(+), 34 deletions(-)
diff --git a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/FormattedMessageSerializerTest.cs b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/FormattedMessageSerializerTest.cs
index 88f300b5037..48fd3ca7f33 100644
--- a/Robust.UnitTesting/Shared/Serialization/TypeSerializers/FormattedMessageSerializerTest.cs
+++ b/Robust.UnitTesting/Shared/Serialization/TypeSerializers/FormattedMessageSerializerTest.cs
@@ -3,6 +3,7 @@
using Robust.Shared.Serialization.Markdown.Value;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
using Robust.Shared.Utility;
+using Robust.Shared.Utility.Markup;
// ReSharper disable AccessToStaticMemberViaDerivedType
@@ -17,8 +18,9 @@ public class FormattedMessageSerializerTest : SerializationTest
[TestCase("[color=#FF0000FF]message[/color]")]
public void SerializationTest(string text)
{
- var message = FormattedMessage.FromMarkup(text);
- var node = Serialization.WriteValueAs(message);
+ var message = new Basic();
+ message.AddMarkup(text);
+ var node = Serialization.WriteValueAs(message.Render());
Assert.That(node.Value, Is.EqualTo(text));
}
diff --git a/Robust.UnitTesting/Shared/Utility/FormattedMessage_Test.cs b/Robust.UnitTesting/Shared/Utility/FormattedMessage_Test.cs
index 1e2ee828a28..d796afc3709 100644
--- a/Robust.UnitTesting/Shared/Utility/FormattedMessage_Test.cs
+++ b/Robust.UnitTesting/Shared/Utility/FormattedMessage_Test.cs
@@ -1,43 +1,55 @@
-using System.Linq;
using NUnit.Framework;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
+using Robust.Shared.Utility.Markup;
namespace Robust.UnitTesting.Shared.Utility
{
[Parallelizable(ParallelScope.All)]
[TestFixture]
- [TestOf(typeof(FormattedMessage))]
- public class FormattedMessage_Test
+ [TestOf(typeof(Basic))]
+ public class MarkupBasic_Test
{
[Test]
public static void TestParseMarkup()
{
- var msg = FormattedMessage.FromMarkup("foo[color=#aabbcc]bar[/color]baz");
+ var msg = new Basic();
+ msg.AddMarkup("foo[color=#aabbcc]bar[/color]baz");
- Assert.That(msg.Tags, NUnit.Framework.Is.EquivalentTo(new FormattedMessage.Tag[]
+ Assert.That(msg.Render(), NUnit.Framework.Is.EquivalentTo(new FormattedMessage(new[]
{
- new FormattedMessage.TagText("foo"),
- new FormattedMessage.TagColor(Color.FromHex("#aabbcc")),
- new FormattedMessage.TagText("bar"),
- FormattedMessage.TagPop.Instance,
- new FormattedMessage.TagText("baz")
- }));
+ new Section {
+ Content="foo"
+ },
+ new Section {
+ Content="bar",
+ Color=0xAABBCC,
+ },
+ new Section {
+ Content="baz"
+ }
+ }).Sections));
}
[Test]
public static void TestParseMarkupColorName()
{
- var msg = FormattedMessage.FromMarkup("foo[color=orange]bar[/color]baz");
+ var msg = new Basic();
+ msg.AddMarkup("foo[color=orange]bar[/color]baz");
- Assert.That(msg.Tags, NUnit.Framework.Is.EquivalentTo(new FormattedMessage.Tag[]
+ Assert.That(msg.Render(), NUnit.Framework.Is.EquivalentTo(new FormattedMessage(new[]
{
- new FormattedMessage.TagText("foo"),
- new FormattedMessage.TagColor(Color.Orange),
- new FormattedMessage.TagText("bar"),
- FormattedMessage.TagPop.Instance,
- new FormattedMessage.TagText("baz")
- }));
+ new Section {
+ Content="foo"
+ },
+ new Section {
+ Content="bar",
+ Color=Color.Orange.ToArgb(),
+ },
+ new Section {
+ Content="baz"
+ }
+ }).Sections));
}
[Test]
@@ -46,30 +58,23 @@ public static void TestParseMarkupColorName()
[TestCase("foo[stinky] bar")]
public static void TestParsePermissiveMarkup(string text)
{
- var msg = FormattedMessage.FromMarkupPermissive(text);
+ var msg = new Basic();
+ msg.AddMarkupPermissive(text);
Assert.That(
- string.Join("", msg.Tags.Cast().Select(p => p.Text)),
+ msg.Render().ToString(),
NUnit.Framework.Is.EqualTo(text));
}
- [Test]
- [TestCase("Foo", ExpectedResult = "Foo")]
- [TestCase("[color=red]Foo[/color]", ExpectedResult = "Foo")]
- [TestCase("[color=red]Foo[/color]bar", ExpectedResult = "Foobar")]
- public string TestRemoveMarkup(string test)
- {
- return FormattedMessage.RemoveMarkup(test);
- }
-
[Test]
[TestCase("Foo")]
[TestCase("[color=#FF000000]Foo[/color]")]
[TestCase("[color=#00FF00FF]Foo[/color]bar")]
public static void TestToMarkup(string text)
{
- var message = FormattedMessage.FromMarkup(text);
- Assert.That(message.ToMarkup(), NUnit.Framework.Is.EqualTo(text));
+ var message = new Basic();
+ message.AddMarkup(text);
+ Assert.That(message.Render().ToMarkup(), NUnit.Framework.Is.EqualTo(text));
}
}
}
From 3881489e9bacd121da2096a213e9fd8bc5683497 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Fri, 26 Nov 2021 00:25:34 -0500
Subject: [PATCH 22/44] Utility/FormattedMessage: Fix some off-by-one Builder
bugs
---
Robust.Shared/Utility/FormattedMessage.cs | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/Robust.Shared/Utility/FormattedMessage.cs b/Robust.Shared/Utility/FormattedMessage.cs
index 5bd86c54a38..d75b52815fe 100644
--- a/Robust.Shared/Utility/FormattedMessage.cs
+++ b/Robust.Shared/Utility/FormattedMessage.cs
@@ -141,18 +141,22 @@ public class Builder
private bool _dirty = false;
private int _idx = 0;
private StringBuilder _sb = new();
- private List _work = new();
+ private List _work = new() {
+ new Section()
+ };
public static Builder FromFormattedText(FormattedMessage orig) => new ()
{
- _idx = orig.Sections.Length - 1,
+ _idx = orig.Sections.Length < 0 ? orig.Sections.Length - 1 : 0,
_work = new List(orig.Sections),
};
public void Clear()
{
_idx = 0;
- _work = new();
+ _work = new() {
+ new Section()
+ };
_sb = _sb.Clear();
}
@@ -166,9 +170,10 @@ public void PushColor(Color color)
{
flushWork();
_idx++;
- var last = _work[_work.Count - 1];
+ var lidx = _work.Count > 0 ? _work.Count - 1 : 0;
+ var last = _work[lidx];
last.Color = color.ToArgb();
- _work[_work.Count - 1] = last;
+ _work[lidx] = last;
}
public void AddMessage(FormattedMessage other) =>
@@ -186,7 +191,7 @@ public void PushNewline()
public void Pop()
{
flushWork();
- _idx--;
+ _idx = _idx < 0 ? _idx-1 : 0;
}
public void flushWork()
@@ -194,10 +199,11 @@ public void flushWork()
if (!_dirty)
return;
- var last = _work[_work.Count - 1];
+ var lidx = _work.Count > 0 ? _work.Count - 1 : 0;
+ var last = _work[lidx];
last.Content = _sb.ToString();
_sb = _sb.Clear();
- _work.Add(_work[_idx]);
+ _work.Add(last);
}
public FormattedMessage Build() => new FormattedMessage(_work.ToArray());
From 9accc731ef9ccde31fad4cc958c471e6ab955f12 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Fri, 26 Nov 2021 04:14:55 -0500
Subject: [PATCH 23/44] Utility/FormattedMessage: Continue cleanup, test
compliance
---
Robust.Shared/Utility/FormattedMessage.cs | 88 +++++++++++++++----
.../Shared/Utility/FormattedMessage_Test.cs | 34 +++----
2 files changed, 83 insertions(+), 39 deletions(-)
diff --git a/Robust.Shared/Utility/FormattedMessage.cs b/Robust.Shared/Utility/FormattedMessage.cs
index d75b52815fe..f941a1bd652 100644
--- a/Robust.Shared/Utility/FormattedMessage.cs
+++ b/Robust.Shared/Utility/FormattedMessage.cs
@@ -1,5 +1,5 @@
using System;
-using System.Collections;
+using System.Linq;
using System.Collections.Generic;
using System.Text;
using JetBrains.Annotations;
@@ -9,14 +9,14 @@
namespace Robust.Shared.Utility
{
[Serializable, NetSerializable]
- public struct Section
+ public record struct Section
{
public FontStyle Style;
public FontSize Size;
public TextAlign Alignment;
public int Color;
public MetaFlags Meta;
- public string Content;
+ public string Content = string.Empty;
}
[Flags]
@@ -123,12 +123,24 @@ public override string ToString()
// I don't wanna fix the serializer yet.
public string ToMarkup()
{
+ #warning FormattedMessage.ToMarkup is still lossy.
var sb = new StringBuilder();
foreach (var i in Sections)
{
- sb.AppendFormat("[color=#{0:X}]", i.Color);
+ if (i.Content.Length == 0)
+ continue;
+
+ if (i.Color != default)
+ sb.AppendFormat("[color=#{0:X8}]",
+ // Bit twiddling to swap AARRGGBB to RRGGBBAA
+ ((i.Color << 8) & 0xFF_FF_FF_00) | // Drop alpha from the front
+ ((i.Color & 0xFF_00_00_00) >> 24) // Shuffle it to the back
+ );
+
sb.Append(i.Content);
- sb.Append("[/color]");
+
+ if (i.Color != default)
+ sb.Append("[/color]");
}
return sb.ToString();
@@ -136,23 +148,40 @@ public string ToMarkup()
// are you a construction worker?
// cuz you buildin
+ [Obsolete("Construct FormattedMessage Sections manually.")]
public class Builder
{
+ // _dirty signals that _sb has content that needs flushing to _work
private bool _dirty = false;
+
+ // We fake a stack by keeping an index in to the work list.
+ // Since each Section contains all its styling info, we can "pop" the stack by
+ // using the (unchanged) Section before it.
private int _idx = 0;
private StringBuilder _sb = new();
+
+ // _work starts out with a dummy item because otherwise we break the assumption that
+ // _idx will always refer to *something* in _work.
private List _work = new() {
new Section()
};
public static Builder FromFormattedText(FormattedMessage orig) => new ()
{
+ // Again, we always need at least one _work item, so if the FormattedMessage
+ // is empty, we'll forge one.
_idx = orig.Sections.Length < 0 ? orig.Sections.Length - 1 : 0,
- _work = new List(orig.Sections),
+ _work = new List(
+ orig.Sections.Length == 0 ?
+ new [] { new Section() }
+ : orig.Sections
+ ),
};
+ // hmm what could this do
public void Clear()
{
+ _dirty = false;
_idx = 0;
_work = new() {
new Section()
@@ -160,53 +189,82 @@ public void Clear()
_sb = _sb.Clear();
}
+ // Since we don't change any styling, we don't need to add a full Section.
+ // In these cases, we add it to the StringBuilder, and wait until styling IS changed,
+ // or we Render().
public void AddText(string text)
{
_dirty = true;
_sb.Append(text);
}
+ // PushColor changes the styling, so we need to submit any text we had waiting, then
+ // add a new empty Section with the new color.
public void PushColor(Color color)
{
flushWork();
- _idx++;
- var lidx = _work.Count > 0 ? _work.Count - 1 : 0;
- var last = _work[lidx];
+
+ var last = _work[_idx];
last.Color = color.ToArgb();
- _work[lidx] = last;
+ _work.Add(last);
+ _idx = _work.Count - 1;
}
+ // These next two are probably wildly bugged, since they'll include the other sections
+ // wholesale, and the entire fake-stack facade breaks down, since there's no way for the
+ // new stuff to inherit the previous style, and we don't know what parts of the style are
+ // actually set, and what parts are just default values.
+
+ // TODO: move _idx?
public void AddMessage(FormattedMessage other) =>
_work.AddRange(other.Sections);
+ // TODO: See above
public void AddMessage(FormattedMessage.Builder other) =>
_work.AddRange(other._work);
+ // I wish I understood why this was needed...
+ // Did people not know you could AddText("\n")?
public void PushNewline()
{
_dirty = true;
_sb.Append('\n');
}
+ // Flush any text we've got for the current style,
+ // then roll back to the style before this one.
public void Pop()
{
flushWork();
- _idx = _idx < 0 ? _idx-1 : 0;
+ // Go back one (or stay at the start)
+ _idx = (_idx > 0) ? (_idx - 1) : 0;
}
public void flushWork()
{
+ // Nothing changed? Great.
if (!_dirty)
return;
- var lidx = _work.Count > 0 ? _work.Count - 1 : 0;
- var last = _work[lidx];
+ // Get the last tag (for the style)...
+ var last = _work[_idx];
+ // ...and set the content to the current buffer
last.Content = _sb.ToString();
- _sb = _sb.Clear();
_work.Add(last);
+
+ // Clean up
+ _sb = _sb.Clear();
+ _dirty = false;
}
- public FormattedMessage Build() => new FormattedMessage(_work.ToArray());
+ public FormattedMessage Build()
+ {
+ flushWork();
+ return new FormattedMessage(_work
+ .GetRange(1, _work.Count - 1) // Drop the placeholder
+ .Where(e => e.Content.Length != 0) // and any blanks (which can happen from pushing colors and such)
+ .ToArray());
+ }
}
}
}
diff --git a/Robust.UnitTesting/Shared/Utility/FormattedMessage_Test.cs b/Robust.UnitTesting/Shared/Utility/FormattedMessage_Test.cs
index d796afc3709..f7bd0970567 100644
--- a/Robust.UnitTesting/Shared/Utility/FormattedMessage_Test.cs
+++ b/Robust.UnitTesting/Shared/Utility/FormattedMessage_Test.cs
@@ -16,19 +16,12 @@ public static void TestParseMarkup()
var msg = new Basic();
msg.AddMarkup("foo[color=#aabbcc]bar[/color]baz");
- Assert.That(msg.Render(), NUnit.Framework.Is.EquivalentTo(new FormattedMessage(new[]
+ Assert.That(msg.Render().Sections, NUnit.Framework.Is.EquivalentTo(new[]
{
- new Section {
- Content="foo"
- },
- new Section {
- Content="bar",
- Color=0xAABBCC,
- },
- new Section {
- Content="baz"
- }
- }).Sections));
+ new Section { Content="foo" },
+ new Section { Content="bar", Color=unchecked ((int) 0xFFAABBCC) },
+ new Section { Content="baz" }
+ }));
}
[Test]
@@ -37,19 +30,12 @@ public static void TestParseMarkupColorName()
var msg = new Basic();
msg.AddMarkup("foo[color=orange]bar[/color]baz");
- Assert.That(msg.Render(), NUnit.Framework.Is.EquivalentTo(new FormattedMessage(new[]
+ Assert.That(msg.Render().Sections, NUnit.Framework.Is.EquivalentTo(new[]
{
- new Section {
- Content="foo"
- },
- new Section {
- Content="bar",
- Color=Color.Orange.ToArgb(),
- },
- new Section {
- Content="baz"
- }
- }).Sections));
+ new Section { Content="foo" },
+ new Section { Content="bar", Color=Color.Orange.ToArgb() },
+ new Section { Content="baz" }
+ }));
}
[Test]
From 1d31ebd2592fafbfa66b51d2727a77dcb51f9650 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Fri, 26 Nov 2021 04:57:00 -0500
Subject: [PATCH 24/44] Utility/FormattedMessage: Work around
https://github.com/dotnet/roslyn/issues/57870
---
Robust.Shared/Utility/FormattedMessage.cs | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/Robust.Shared/Utility/FormattedMessage.cs b/Robust.Shared/Utility/FormattedMessage.cs
index f941a1bd652..5401e9541fd 100644
--- a/Robust.Shared/Utility/FormattedMessage.cs
+++ b/Robust.Shared/Utility/FormattedMessage.cs
@@ -11,11 +11,11 @@ namespace Robust.Shared.Utility
[Serializable, NetSerializable]
public record struct Section
{
- public FontStyle Style;
- public FontSize Size;
- public TextAlign Alignment;
- public int Color;
- public MetaFlags Meta;
+ public FontStyle Style = default;
+ public FontSize Size = default;
+ public TextAlign Alignment = default;
+ public int Color = default;
+ public MetaFlags Meta = default;
public string Content = string.Empty;
}
From b5dfd28c952eab4cb531f65d0f6c0756f96dea26 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Fri, 26 Nov 2021 05:31:07 -0500
Subject: [PATCH 25/44] Utility/FormattedMessage: Move ISectionable from
TextLayout, implement it for FormattedMessage
---
Robust.Client/UserInterface/TextLayout.cs | 6 ------
Robust.Shared/Utility/FormattedMessage.cs | 12 +++++++++++-
2 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/Robust.Client/UserInterface/TextLayout.cs b/Robust.Client/UserInterface/TextLayout.cs
index c8534348f09..cc429b85f70 100644
--- a/Robust.Client/UserInterface/TextLayout.cs
+++ b/Robust.Client/UserInterface/TextLayout.cs
@@ -89,12 +89,6 @@ public Word(Word o) : base(o)
public WordType wt;
}
- public interface ISectionable
- {
- Section this[int i] { get; }
- int Length { get; }
- }
-
public static ImmutableArray> Layout(
ISectionable text,
int w,
diff --git a/Robust.Shared/Utility/FormattedMessage.cs b/Robust.Shared/Utility/FormattedMessage.cs
index 5401e9541fd..9007ee81b48 100644
--- a/Robust.Shared/Utility/FormattedMessage.cs
+++ b/Robust.Shared/Utility/FormattedMessage.cs
@@ -8,6 +8,12 @@
namespace Robust.Shared.Utility
{
+ public interface ISectionable
+ {
+ Section this[int i] { get; }
+ int Length { get; }
+ }
+
[Serializable, NetSerializable]
public record struct Section
{
@@ -109,7 +115,7 @@ public static partial class Extensions
[PublicAPI]
[Serializable, NetSerializable]
- public sealed record FormattedMessage(Section[] Sections)
+ public sealed record FormattedMessage(Section[] Sections) : ISectionable
{
public override string ToString()
{
@@ -146,6 +152,10 @@ public string ToMarkup()
return sb.ToString();
}
+ // Implements Robust.Client.UserInterface.TextLayout.ISectionable
+ public Section this[int i] { get => Sections[i]; }
+ public int Length { get => Sections.Length; }
+
// are you a construction worker?
// cuz you buildin
[Obsolete("Construct FormattedMessage Sections manually.")]
From 5caa72fa3434fc17c4517c549f61b039b2a93d6f Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Fri, 26 Nov 2021 07:25:47 -0500
Subject: [PATCH 26/44] UserInterface/TextLayout: Add a `postcreate` function
to set up new `TIn`s
Apparently Roslyn isn't big-brained enough to understand that a
closure of type `Func` means that `var n = new(); return n;` requires
`new()` to return a `T`.
Ironically, it's significantly less cbt to add this than to convert
that big tuple in `Layout` in to a class or struct of some sort
(to initialize the `List<>`s).
---
Robust.Client/UserInterface/TextLayout.cs | 24 +++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/Robust.Client/UserInterface/TextLayout.cs b/Robust.Client/UserInterface/TextLayout.cs
index cc429b85f70..725b18a96e7 100644
--- a/Robust.Client/UserInterface/TextLayout.cs
+++ b/Robust.Client/UserInterface/TextLayout.cs
@@ -138,7 +138,11 @@ public static ImmutableArray> Layout(
int sptot,
int maxPri,
int tPri
- )>();
+ )>(postcreate: i => i with
+ {
+ wds = new List(),
+ gaps = new List()
+ });
var lastAlign = TextAlign.Left;
@@ -355,18 +359,21 @@ private class WorkQueue
{
// _blank creates a new T if _refresh says it needs to.
private Func _blank = () => new TIn();
+ private Func? _postcr;
private Func _check = _ => true;
private Func _conv;
+
public List Done = new();
public TIn Work;
public WorkQueue(
Func conv,
Func? blank = default,
- Func? check = default
+ Func? check = default,
+ Func? postcreate = default
)
{
_conv = conv;
@@ -377,7 +384,13 @@ public WorkQueue(
if (check is not null)
_check = check;
+ if (postcreate is not null)
+ _postcr = postcreate;
+
Work = _blank.Invoke();
+
+ if (_postcr is not null)
+ Work = _postcr.Invoke(Work);
}
public void Flush(bool force = false)
@@ -386,6 +399,8 @@ public void Flush(bool force = false)
{
Done.Add(_conv(Work));
Work = _blank.Invoke();
+ if (_postcr is not null)
+ Work = _postcr.Invoke(Work);
}
}
}
@@ -397,8 +412,9 @@ private class WorkQueue : WorkQueue
public WorkQueue(
Func? conv = default,
Func? blank = default,
- Func? check = default
- ) : base(conv ?? __conv, blank, check)
+ Func? check = default,
+ Func? postcreate = default
+ ) : base(conv ?? __conv, blank, check, postcreate)
{
}
}
From e3b154efc48c7dc902a9faf6dad92114a00a695a Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Fri, 26 Nov 2021 07:30:48 -0500
Subject: [PATCH 27/44] UserInterface/TextLayout: Throw if `Meta` isn't
recognized
TODO warning go brrr
---
Robust.Client/UserInterface/TextLayout.cs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Robust.Client/UserInterface/TextLayout.cs b/Robust.Client/UserInterface/TextLayout.cs
index 725b18a96e7..f5ce7e4b07c 100644
--- a/Robust.Client/UserInterface/TextLayout.cs
+++ b/Robust.Client/UserInterface/TextLayout.cs
@@ -293,6 +293,11 @@ public static ImmutableArray Split(
for (s = 0; s < text.Length; s++)
{
var sec = text[s];
+
+ #warning Meta.Localized not yet implemented
+ if (sec.Meta != default)
+ throw new Exception("Text section with unknown or unimplemented Meta flag");
+
sbo = 0;
var fnt = flib.Update(sec.Style, sec.Size);
From a3f9e072bcc8dff59e8bf8c45eed2fba84081317 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Fri, 26 Nov 2021 07:32:52 -0500
Subject: [PATCH 28/44] Graphics/FontLibrary: Add a `DummyVariant`
---
Robust.Client/Graphics/FontLibrary.cs | 9 +++++++--
Robust.Client/UserInterface/TextLayout.cs | 2 ++
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/Robust.Client/Graphics/FontLibrary.cs b/Robust.Client/Graphics/FontLibrary.cs
index 1f5256d537c..76d03e9a6fa 100644
--- a/Robust.Client/Graphics/FontLibrary.cs
+++ b/Robust.Client/Graphics/FontLibrary.cs
@@ -10,7 +10,7 @@ namespace Robust.Client.Graphics;
///
public record FontVariant (FontStyle Style, FontResource[] Resource)
{
- public Font ToFont(byte size)
+ public virtual Font ToFont(byte size)
{
if (Resource.Length == 1)
return new VectorFont(Resource[0], size);
@@ -23,6 +23,11 @@ public Font ToFont(byte size)
}
};
+internal record DummyVariant(FontStyle fs) : FontVariant(fs, new FontResource[0])
+{
+ public override Font ToFont(byte size) => new DummyFont();
+};
+
public record FontClass
(
string Id,
@@ -106,7 +111,7 @@ private FontVariant lookup(string id, FontStyle fst)
// Since the "style" flags are a bitfield, we can just see which one has more bits.
// More bits == closer to the desired font style. Free fallback!
- if (BitOperations.PopCount((ulong) (vr.Style & fst)) > BitOperations.PopCount((ulong) (winfst & fst)))
+ if (BitOperations.PopCount((ulong) (vr.Style & fst)) >= BitOperations.PopCount((ulong) (winfst & fst)))
winner = vr;
}
diff --git a/Robust.Client/UserInterface/TextLayout.cs b/Robust.Client/UserInterface/TextLayout.cs
index f5ce7e4b07c..a3d1e81171d 100644
--- a/Robust.Client/UserInterface/TextLayout.cs
+++ b/Robust.Client/UserInterface/TextLayout.cs
@@ -327,6 +327,8 @@ public static ImmutableArray Split(
{
if (nofb)
continue;
+ else if (fnt is DummyFont)
+ cm = new CharMetrics();
else
throw new Exception("unable to get character metrics");
}
From e8ed5416bb8431a1000d02c753efbcfb3609d825 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Fri, 26 Nov 2021 07:36:57 -0500
Subject: [PATCH 29/44] UserInterface/UITheme: Move to FontLibraries
---
Robust.Client/UserInterface/UITheme.cs | 28 ++++++++++++++++++++++----
1 file changed, 24 insertions(+), 4 deletions(-)
diff --git a/Robust.Client/UserInterface/UITheme.cs b/Robust.Client/UserInterface/UITheme.cs
index def3abe8616..77a0de65eb6 100644
--- a/Robust.Client/UserInterface/UITheme.cs
+++ b/Robust.Client/UserInterface/UITheme.cs
@@ -10,19 +10,39 @@ namespace Robust.Client.UserInterface
///
public abstract class UITheme
{
- public abstract Font DefaultFont { get; }
- public abstract Font LabelFont { get; }
+ public abstract IFontLibrary DefaultFontLibrary { get; }
+ public abstract IFontLibrary LabelFontLibrary { get; }
+ public Font DefaultFont { get => DefaultFontLibrary.StartFont().Current; }
+ public Font LabelFont { get => LabelFontLibrary.StartFont().Current; }
public abstract StyleBox PanelPanel { get; }
public abstract StyleBox ButtonStyle { get; }
public abstract StyleBox LineEditBox { get; }
}
+
public sealed class UIThemeDummy : UITheme
{
- public override Font DefaultFont { get; } = new DummyFont();
- public override Font LabelFont { get; } = new DummyFont();
+ private static readonly FontClass _defaultFontClass = new FontClass ( Id: "dummy", Size: default, Style: default );
+ public override IFontLibrary DefaultFontLibrary { get; } = new FontLibrary(_defaultFontClass);
+ public override IFontLibrary LabelFontLibrary { get; } = new FontLibrary(_defaultFontClass);
public override StyleBox PanelPanel { get; } = new StyleBoxFlat();
public override StyleBox ButtonStyle { get; } = new StyleBoxFlat();
public override StyleBox LineEditBox { get; } = new StyleBoxFlat();
+
+ public UIThemeDummy() : base()
+ {
+ DefaultFontLibrary.AddFont("dummy",
+ new []
+ {
+ new DummyVariant (default)
+ }
+ );
+ LabelFontLibrary.AddFont("dummy",
+ new []
+ {
+ new DummyVariant (default)
+ }
+ );
+ }
}
}
From 384cd4f4d0799b14fe074815ea83d7f14e1b567c Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Fri, 26 Nov 2021 07:38:09 -0500
Subject: [PATCH 30/44] UserInterface/TextLayout: Move to an un-nested
`ImmutableArray`
---
Robust.Client/UserInterface/TextLayout.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Robust.Client/UserInterface/TextLayout.cs b/Robust.Client/UserInterface/TextLayout.cs
index a3d1e81171d..26442fe593a 100644
--- a/Robust.Client/UserInterface/TextLayout.cs
+++ b/Robust.Client/UserInterface/TextLayout.cs
@@ -89,7 +89,7 @@ public Word(Word o) : base(o)
public WordType wt;
}
- public static ImmutableArray> Layout(
+ public static ImmutableArray Layout(
ISectionable text,
int w,
IFontLibrary fonts,
@@ -119,7 +119,7 @@ public static ImmutableArray> Layout(
// 4. Add up each gap's priority value (Σpri)
// 5. Assign each gap a final priority (fp) of ((priMax - pri) / Σpri)
// 6. That space has (fp*fs) pixels.
- public static ImmutableArray> Layout(
+ public static ImmutableArray Layout(
ISectionable src,
ImmutableArray text,
int w,
@@ -204,7 +204,7 @@ int tPri
py += lineSpacing + lh;
}
- return lw.Done.Select(l => l.wds.ToImmutableArray()).ToImmutableArray();
+ return lw.Done.SelectMany(e => e.wds).ToImmutableArray();
}
private static (int gapPri, int adv) TransitionWeights (TextAlign l, TextAlign r)
From a6f0f45c9274085c8da1fe3df99b7815e6eb8bd4 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Fri, 26 Nov 2021 07:46:10 -0500
Subject: [PATCH 31/44] UserInterface/RichTextEntry: Go ahead. Draw.
---
.../UserInterface/Controls/OutputPanel.cs | 25 +-
.../UserInterface/Controls/RichTextLabel.cs | 16 +-
Robust.Client/UserInterface/RichTextEntry.cs | 251 +++---------------
3 files changed, 48 insertions(+), 244 deletions(-)
diff --git a/Robust.Client/UserInterface/Controls/OutputPanel.cs b/Robust.Client/UserInterface/Controls/OutputPanel.cs
index adce1c3ef01..1d0f72a927d 100644
--- a/Robust.Client/UserInterface/Controls/OutputPanel.cs
+++ b/Robust.Client/UserInterface/Controls/OutputPanel.cs
@@ -62,7 +62,7 @@ public void RemoveEntry(Index index)
var entry = _entries[index];
_entries.RemoveAt(index.GetOffset(_entries.Count));
- var font = _getFont();
+ var font = _getFont().StartFont().Current;
_totalContentHeight -= entry.Height + font.GetLineSeparation(UIScale);
if (_entries.Count == 0)
{
@@ -86,7 +86,7 @@ public void AddMessage(FormattedMessage message)
entry.Update(_getFont(), _getContentBox().Width, UIScale);
_entries.Add(entry);
- var font = _getFont();
+ var font = _getFont().StartFont().Current;
_totalContentHeight += entry.Height;
if (_firstLine)
{
@@ -115,18 +115,12 @@ protected internal override void Draw(DrawingHandleScreen handle)
base.Draw(handle);
var style = _getStyleBox();
- var font = _getFont();
+ var font = _getFont().StartFont().Current;
style?.Draw(handle, PixelSizeBox);
var contentBox = _getContentBox();
var entryOffset = -_scrollBar.Value;
-#if false
- // A stack for format tags.
- // This stack contains the format tag to RETURN TO when popped off.
- // So when a new color tag gets hit this stack gets the previous color pushed on.
- var formatStack = new Stack(2);
-
foreach (var entry in _entries)
{
if (entryOffset + entry.Height < 0)
@@ -140,11 +134,10 @@ protected internal override void Draw(DrawingHandleScreen handle)
break;
}
- entry.Draw(handle, font, contentBox, entryOffset, formatStack, UIScale);
+ entry.Draw(handle, _getFont(), contentBox, entryOffset, UIScale);
entryOffset += entry.Height + font.GetLineSeparation(UIScale);
}
-#endif
}
protected internal override void MouseWheel(GUIMouseWheelEventArgs args)
@@ -184,7 +177,7 @@ private void _invalidateEntries()
var entry = _entries[i];
entry.Update(font, sizeX, UIScale);
_entries[i] = entry;
- _totalContentHeight += entry.Height + font.GetLineSeparation(UIScale);
+ _totalContentHeight += entry.Height;
}
_scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
@@ -195,14 +188,14 @@ private void _invalidateEntries()
}
[System.Diagnostics.Contracts.Pure]
- private Font _getFont()
+ private IFontLibrary _getFont()
{
- if (TryGetStyleProperty("font", out var font))
+ if (TryGetStyleProperty("font", out var font))
{
return font;
}
- return UserInterfaceManager.ThemeDefaults.DefaultFont;
+ return UserInterfaceManager.ThemeDefaults.DefaultFontLibrary;
}
[System.Diagnostics.Contracts.Pure]
@@ -220,7 +213,7 @@ private Font _getFont()
[System.Diagnostics.Contracts.Pure]
private int _getScrollSpeed()
{
- var font = _getFont();
+ var font = _getFont().StartFont().Current;
return font.GetLineHeight(UIScale) * 2;
}
diff --git a/Robust.Client/UserInterface/Controls/RichTextLabel.cs b/Robust.Client/UserInterface/Controls/RichTextLabel.cs
index 13adc9f031c..13bdb5ee42a 100644
--- a/Robust.Client/UserInterface/Controls/RichTextLabel.cs
+++ b/Robust.Client/UserInterface/Controls/RichTextLabel.cs
@@ -1,5 +1,4 @@
-using System.Collections.Generic;
-using JetBrains.Annotations;
+using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
@@ -32,8 +31,7 @@ protected override Vector2 MeasureOverride(Vector2 availableSize)
return Vector2.Zero;
}
- var font = _getFont();
- _entry.Update(font, availableSize.X * UIScale, UIScale);
+ _entry.Update(_getFont(), availableSize.X * UIScale, UIScale);
return (_entry.Width / UIScale, _entry.Height / UIScale);
}
@@ -47,20 +45,18 @@ protected internal override void Draw(DrawingHandleScreen handle)
return;
}
-#if false
- _entry.Draw(handle, _getFont(), SizeBox, 0, new Stack(), UIScale);
-#endif
+ _entry.Draw(handle, _getFont(), SizeBox, 0, UIScale);
}
[Pure]
- private Font _getFont()
+ private IFontLibrary _getFont()
{
- if (TryGetStyleProperty("font", out var font))
+ if (TryGetStyleProperty("font", out var font))
{
return font;
}
- return UserInterfaceManager.ThemeDefaults.DefaultFont;
+ return UserInterfaceManager.ThemeDefaults.DefaultFontLibrary;
}
}
}
diff --git a/Robust.Client/UserInterface/RichTextEntry.cs b/Robust.Client/UserInterface/RichTextEntry.cs
index a825d77cdef..26e88939e62 100644
--- a/Robust.Client/UserInterface/RichTextEntry.cs
+++ b/Robust.Client/UserInterface/RichTextEntry.cs
@@ -1,10 +1,6 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using JetBrains.Annotations;
+using System.Collections.Immutable;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
@@ -27,250 +23,69 @@ internal struct RichTextEntry
///
public int Width;
- ///
- /// The combined text indices in the message's text tags to put line breaks.
- ///
- public readonly List LineBreaks;
-
public RichTextEntry(FormattedMessage message)
{
Message = message;
Height = 0;
Width = 0;
- LineBreaks = new List();
}
+ // Last maxSizeX, used to detect resizing.
+ private int _lmsx = 0;
+ // Layout data, which needs to be refreshed when resized.
+ private ImmutableArray _ld = default;
+
///
/// Recalculate line dimensions and where it has line breaks for word wrapping.
///
/// The font being used for display.
/// The maximum horizontal size of the container of this entry.
///
- public void Update(Font font, float maxSizeX, float uiScale)
+ public void Update(IFontLibrary font, float maxSizeX, float uiScale)
{
- #if false
- // This method is gonna suck due to complexity.
- // Bear with me here.
- // I am so deeply sorry for the person adding stuff to this in the future.
- Height = font.GetHeight(uiScale);
- LineBreaks.Clear();
-
- var maxUsedWidth = 0f;
- // Index we put into the LineBreaks list when a line break should occur.
- var breakIndexCounter = 0;
- // If the CURRENT processing word ends up too long, this is the index to put a line break.
- (int index, float lineSize)? wordStartBreakIndex = null;
- // Word size in pixels.
- var wordSizePixels = 0;
- // The horizontal position of the text cursor.
- var posX = 0;
- var lastRune = new Rune('A');
- // If a word is larger than maxSizeX, we split it.
- // We need to keep track of some data to split it into two words.
- (int breakIndex, int wordSizePixels)? forceSplitData = null;
- // Go over every text tag.
- // We treat multiple text tags as one continuous one.
- // So changing color inside a single word doesn't create a word break boundary.
- foreach (var tag in Message.Tags)
- {
- // For now we can ignore every entry that isn't a text tag because those are only color related.
- // For now.
- if (!(tag is FormattedMessage.TagText tagText))
- {
- continue;
- }
-
- var text = tagText.Text;
- // And go over every character.
- foreach (var rune in text.EnumerateRunes())
- {
- breakIndexCounter += 1;
-
- if (IsWordBoundary(lastRune, rune) || rune == new Rune('\n'))
- {
- // Word boundary means we know where the word ends.
- if (posX > maxSizeX && lastRune != new Rune(' '))
- {
- DebugTools.Assert(wordStartBreakIndex.HasValue,
- "wordStartBreakIndex can only be null if the word begins at a new line, in which case this branch shouldn't be reached as the word would be split due to being longer than a single line.");
- // We ran into a word boundary and the word is too big to fit the previous line.
- // So we insert the line break BEFORE the last word.
- LineBreaks.Add(wordStartBreakIndex!.Value.index);
- Height += font.GetLineHeight(uiScale);
- maxUsedWidth = Math.Max(maxUsedWidth, wordStartBreakIndex.Value.lineSize);
- posX = wordSizePixels;
- }
-
- // Start a new word since we hit a word boundary.
- //wordSize = 0;
- wordSizePixels = 0;
- wordStartBreakIndex = (breakIndexCounter, posX);
- forceSplitData = null;
-
- // Just manually handle newlines.
- if (rune == new Rune('\n'))
- {
- LineBreaks.Add(breakIndexCounter);
- Height += font.GetLineHeight(uiScale);
- maxUsedWidth = Math.Max(maxUsedWidth, posX);
- posX = 0;
- lastRune = rune;
- wordStartBreakIndex = null;
- continue;
- }
- }
-
- // Uh just skip unknown characters I guess.
- if (!font.TryGetCharMetrics(rune, uiScale, out var metrics))
- {
- lastRune = rune;
- continue;
- }
-
- // Increase word size and such with the current character.
- var oldWordSizePixels = wordSizePixels;
- wordSizePixels += metrics.Advance;
- // TODO: Theoretically, does it make sense to break after the glyph's width instead of its advance?
- // It might result in some more tight packing but I doubt it'd be noticeable.
- // Also definitely even more complex to implement.
- posX += metrics.Advance;
-
- if (posX > maxSizeX)
- {
- if (!forceSplitData.HasValue)
- {
- forceSplitData = (breakIndexCounter, oldWordSizePixels);
- }
-
- // Oh hey we get to break a word that doesn't fit on a single line.
- if (wordSizePixels > maxSizeX)
- {
- var (breakIndex, splitWordSize) = forceSplitData.Value;
- if (splitWordSize == 0)
- {
- // Happens if there's literally not enough space for a single character so uh...
- // Yeah just don't.
- return;
- }
-
- // Reset forceSplitData so that we can split again if necessary.
- forceSplitData = null;
- LineBreaks.Add(breakIndex);
- Height += font.GetLineHeight(uiScale);
- wordSizePixels -= splitWordSize;
- wordStartBreakIndex = null;
- maxUsedWidth = Math.Max(maxUsedWidth, maxSizeX);
- posX = wordSizePixels;
- }
- }
-
- lastRune = rune;
- }
- }
-
- // This needs to happen because word wrapping doesn't get checked for the last word.
- if (posX > maxSizeX)
+ if ((int) maxSizeX != _lmsx)
{
- if (!wordStartBreakIndex.HasValue)
+ _ld = TextLayout.Layout(Message, (int) maxSizeX, font, scale: uiScale);
+ Height = 0;
+ Width = 0;
+ foreach (var w in _ld)
{
- Logger.Error(
- "Assert fail inside RichTextEntry.Update, " +
- "wordStartBreakIndex is null on method end w/ word wrap required. " +
- "Dumping relevant stuff. Send this to PJB.");
- Logger.Error($"Message: {Message}");
- Logger.Error($"maxSizeX: {maxSizeX}");
- Logger.Error($"maxUsedWidth: {maxUsedWidth}");
- Logger.Error($"breakIndexCounter: {breakIndexCounter}");
- Logger.Error("wordStartBreakIndex: null (duh)");
- Logger.Error($"wordSizePixels: {wordSizePixels}");
- Logger.Error($"posX: {posX}");
- Logger.Error($"lastChar: {lastRune}");
- Logger.Error($"forceSplitData: {forceSplitData}");
- Logger.Error($"LineBreaks: {string.Join(", ", LineBreaks)}");
-
- throw new Exception(
- "wordStartBreakIndex can only be null if the word begins at a new line," +
- "in which case this branch shouldn't be reached as" +
- "the word would be split due to being longer than a single line.");
+ if (w.x + w.w > Width) Width = w.x + w.w;
+ if (w.y + w.h > Height) Height = w.y + w.h;
}
-
- LineBreaks.Add(wordStartBreakIndex!.Value.index);
- Height += font.GetLineHeight(uiScale);
- maxUsedWidth = Math.Max(maxUsedWidth, wordStartBreakIndex.Value.lineSize);
- }
- else
- {
- maxUsedWidth = Math.Max(maxUsedWidth, posX);
}
-
- Width = (int) maxUsedWidth;
- #endif
}
public void Draw(
DrawingHandleScreen handle,
- Font font,
+ IFontLibrary font,
UIBox2 drawBox,
float verticalOffset,
float uiScale)
{
- #if false
- // The tag currently doing color.
- var currentColorTag = TagBaseColor;
-
- var globalBreakCounter = 0;
- var lineBreakIndex = 0;
- var baseLine = drawBox.TopLeft + new Vector2(0, font.GetAscent(uiScale) + verticalOffset);
- formatStack.Clear();
- foreach (var tag in Message.Tags)
+ var flib = font.StartFont();
+ foreach (var wd in _ld)
{
- switch (tag)
+ var s = Message.Sections[wd.section];
+ var baseLine = drawBox.TopLeft + new Vector2((float) wd.x, (float) wd.y);
+ foreach (var rune in s
+ .Content[wd.charOffs..(wd.charOffs+wd.length)]
+ .EnumerateRunes())
{
- case FormattedMessage.TagColor tagColor:
- formatStack.Push(currentColorTag);
- currentColorTag = tagColor;
- break;
- case FormattedMessage.TagPop _:
- var popped = formatStack.Pop();
- switch (popped)
- {
- case FormattedMessage.TagColor tagColor:
- currentColorTag = tagColor;
- break;
- default:
- throw new InvalidOperationException();
- }
-
- break;
- case FormattedMessage.TagText tagText:
- {
- var text = tagText.Text;
- foreach (var rune in text.EnumerateRunes())
- {
- globalBreakCounter += 1;
-
- if (lineBreakIndex < LineBreaks.Count &&
- LineBreaks[lineBreakIndex] == globalBreakCounter)
- {
- baseLine = new Vector2(drawBox.Left, baseLine.Y + font.GetLineHeight(uiScale));
- lineBreakIndex += 1;
+ baseLine.X += flib.Current.DrawChar(
+ handle,
+ rune,
+ baseLine,
+ uiScale,
+ new Color { // Why Color.FromArgb isn't a thing is beyond me.
+ A=(float) ((s.Color & 0xFF_00_00_00) >> 24),
+ R=(float) ((s.Color & 0x00_FF_00_00) >> 16),
+ G=(float) ((s.Color & 0x00_00_FF_00) >> 8),
+ B=(float) (s.Color & 0x00_00_00_FF)
}
-
- var advance = font.DrawChar(handle, rune, baseLine, uiScale, currentColorTag.Color);
- baseLine += new Vector2(advance, 0);
- }
-
- break;
- }
+ );
}
}
- #endif
- }
-
- [Pure]
- private static bool IsWordBoundary(Rune a, Rune b)
- {
- return a == new Rune(' ') || b == new Rune(' ') || a == new Rune('-') || b == new Rune('-');
}
}
}
From f47cf6effa6b78e699ff0f23c2186c21bc53876b Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Fri, 26 Nov 2021 08:39:22 -0500
Subject: [PATCH 32/44] Markup/Basic: Add extension & helpers for
FormattedMessage.Builder
---
Robust.Shared/Utility/Markup/Basic.cs | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/Robust.Shared/Utility/Markup/Basic.cs b/Robust.Shared/Utility/Markup/Basic.cs
index 8377e822f46..70fc45420cd 100644
--- a/Robust.Shared/Utility/Markup/Basic.cs
+++ b/Robust.Shared/Utility/Markup/Basic.cs
@@ -1,8 +1,6 @@
-using System;
using System.Collections.Generic;
using Pidgin;
using Robust.Shared.Maths;
-using static Robust.Shared.Utility.FormattedMessage;
using static Pidgin.Parser;
using static Pidgin.Parser;
@@ -112,7 +110,9 @@ private static bool ValidColorNameContents(char c)
}
- public FormattedMessage Render()
+ public FormattedMessage Render() => Build().Build();
+
+ public FormattedMessage.Builder Build()
{
var b = new FormattedMessage.Builder();
@@ -126,7 +126,21 @@ public FormattedMessage Render()
}
}
- return b.Build();
+ return b;
+ }
+
+ public static FormattedMessage.Builder BuildMarkup(string text)
+ {
+ var nb = new Basic();
+ nb.AddMarkup(text);
+ return nb.Build();
}
+
+ public static FormattedMessage RenderMarkup(string text) => BuildMarkup(text).Build();
+ }
+
+ public static class FormattedMessageExtensions
+ {
+ public static void AddMarkup(this FormattedMessage.Builder bld, string text) => bld.AddMessage(Basic.BuildMarkup(text));
}
}
From d8238a61541d61a1aea4f8827eaaf4f329e2e911 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Sat, 27 Nov 2021 14:37:16 -0500
Subject: [PATCH 33/44] Markup/Basic: Add `EscapeText` back in
A forgotten casualty of the great Markup separation of 2021
---
Robust.Shared/Utility/Markup/Basic.cs | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/Robust.Shared/Utility/Markup/Basic.cs b/Robust.Shared/Utility/Markup/Basic.cs
index 70fc45420cd..cfa70e5c9ed 100644
--- a/Robust.Shared/Utility/Markup/Basic.cs
+++ b/Robust.Shared/Utility/Markup/Basic.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using Pidgin;
using Robust.Shared.Maths;
@@ -137,10 +138,24 @@ public static FormattedMessage.Builder BuildMarkup(string text)
}
public static FormattedMessage RenderMarkup(string text) => BuildMarkup(text).Build();
+
+ ///
+ /// Escape a string of text to be able to be formatted into markup.
+ ///
+ public static string EscapeText(string text)
+ {
+ return text.Replace("\\", "\\\\").Replace("[", "\\[");
+ }
}
public static class FormattedMessageExtensions
{
public static void AddMarkup(this FormattedMessage.Builder bld, string text) => bld.AddMessage(Basic.BuildMarkup(text));
+
+ [Obsolete("Use Basic.EscapeText instead.")]
+ public static void EscapeText(this FormattedMessage _, string text) => Basic.EscapeText(text);
+
+ [Obsolete("Use Basic.EscapeText instead.")]
+ public static void EscapeText(this FormattedMessage.Builder _, string text) => Basic.EscapeText(text);
}
}
From b99acfc7829a6ed88b2cf2ec72f4ff945028ef6b Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Sat, 27 Nov 2021 14:38:30 -0500
Subject: [PATCH 34/44] Graphics/FontLibrary: Clean up bit magic, ensure that
at least one font is picked
---
Robust.Client/Graphics/FontLibrary.cs | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/Robust.Client/Graphics/FontLibrary.cs b/Robust.Client/Graphics/FontLibrary.cs
index 76d03e9a6fa..ece4635cae8 100644
--- a/Robust.Client/Graphics/FontLibrary.cs
+++ b/Robust.Client/Graphics/FontLibrary.cs
@@ -111,7 +111,13 @@ private FontVariant lookup(string id, FontStyle fst)
// Since the "style" flags are a bitfield, we can just see which one has more bits.
// More bits == closer to the desired font style. Free fallback!
- if (BitOperations.PopCount((ulong) (vr.Style & fst)) >= BitOperations.PopCount((ulong) (winfst & fst)))
+
+ // Variant's bit count
+ var vc = BitOperations.PopCount((ulong) (vr.Style & fst));
+ // Winner's bit count
+ var wc = BitOperations.PopCount((ulong) (winfst & fst));
+
+ if (winner is null || vc > wc)
winner = vr;
}
From 5b685b69c9c7cd9da3be8a976390c4d07f7ca1f7 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Sat, 27 Nov 2021 14:39:23 -0500
Subject: [PATCH 35/44] Graphics/FontLibrary: Add diagnostics to the "no fonts"
exception
---
Robust.Client/Graphics/FontLibrary.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Robust.Client/Graphics/FontLibrary.cs b/Robust.Client/Graphics/FontLibrary.cs
index ece4635cae8..ec8209cee61 100644
--- a/Robust.Client/Graphics/FontLibrary.cs
+++ b/Robust.Client/Graphics/FontLibrary.cs
@@ -122,7 +122,7 @@ private FontVariant lookup(string id, FontStyle fst)
}
if (winner is null)
- throw new Exception("no matching font style");
+ throw new Exception($"no matching font style ({id}, {fst})");
return winner;
}
From f8ab2b26252125dc730826557f794429ebd56d41 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Sat, 27 Nov 2021 14:41:46 -0500
Subject: [PATCH 36/44] UserInterface/TextLayout: Scrap `Word`, return to
`Offset`
---
Robust.Client/UserInterface/RichTextEntry.cs | 2 +-
Robust.Client/UserInterface/TextLayout.cs | 56 ++++++--------------
2 files changed, 17 insertions(+), 41 deletions(-)
diff --git a/Robust.Client/UserInterface/RichTextEntry.cs b/Robust.Client/UserInterface/RichTextEntry.cs
index 26e88939e62..3c01265e69a 100644
--- a/Robust.Client/UserInterface/RichTextEntry.cs
+++ b/Robust.Client/UserInterface/RichTextEntry.cs
@@ -33,7 +33,7 @@ public RichTextEntry(FormattedMessage message)
// Last maxSizeX, used to detect resizing.
private int _lmsx = 0;
// Layout data, which needs to be refreshed when resized.
- private ImmutableArray _ld = default;
+ private ImmutableArray _ld = default;
///
/// Recalculate line dimensions and where it has line breaks for word wrapping.
diff --git a/Robust.Client/UserInterface/TextLayout.cs b/Robust.Client/UserInterface/TextLayout.cs
index 26442fe593a..9b8e4a501b6 100644
--- a/Robust.Client/UserInterface/TextLayout.cs
+++ b/Robust.Client/UserInterface/TextLayout.cs
@@ -41,26 +41,21 @@ public static class TextLayout
/// The number of bytes after to render.
/// The offset from the base position's x coordinate to render this chunk of text.
/// The offset from the base position's y coordinate to render this chunk of text.
- public class Offset
+ /// The width the word (i.e. the sum of all its Advance's).
+ /// The height of the tallest character's BearingY.
+ /// The width allocated to this word.
+ /// The detected word type.
+ public record struct Offset
{
- public Offset()
- {
- }
-
- public Offset(Offset o)
- {
- section = o.section;
- charOffs = o.charOffs;
- length = o.length;
- x = o.x;
- y = o.y;
- }
-
public int section;
public int charOffs;
public int length;
public int x;
public int y;
+ public int h;
+ public int w;
+ public int spw;
+ public WordType wt;
}
public enum WordType : byte
@@ -70,26 +65,7 @@ public enum WordType : byte
LineBreak,
}
- public sealed class Word : Offset
- {
- public Word()
- {
- }
-
- public Word(Word o) : base(o)
- {
- h = o.h;
- w = o.w;
- spw = o.spw;
- wt = o.wt;
- }
- public int h;
- public int w;
- public int spw;
- public WordType wt;
- }
-
- public static ImmutableArray Layout(
+ public static ImmutableArray Layout(
ISectionable text,
int w,
IFontLibrary fonts,
@@ -119,9 +95,9 @@ public static ImmutableArray Layout(
// 4. Add up each gap's priority value (Σpri)
// 5. Assign each gap a final priority (fp) of ((priMax - pri) / Σpri)
// 6. That space has (fp*fs) pixels.
- public static ImmutableArray Layout(
+ public static ImmutableArray Layout(
ISectionable src,
- ImmutableArray text,
+ ImmutableArray text,
int w,
IFontLibrary fonts,
float scale = 1.0f,
@@ -132,7 +108,7 @@ public static ImmutableArray Layout(
)
{
var lw = new WorkQueue<(
- List wds,
+ List wds,
List gaps,
int lnrem,
int sptot,
@@ -140,7 +116,7 @@ public static ImmutableArray Layout(
int tPri
)>(postcreate: i => i with
{
- wds = new List(),
+ wds = new List(),
gaps = new List()
});
@@ -262,7 +238,7 @@ private static (int gapPri, int adv) TransitionWeights (TextAlign l, TextAlign r
// Split creates a list of words broken based on their boundaries.
// Users are encouraged to reuse this for as long as it accurately reflects
// the content they're trying to display.
- public static ImmutableArray Split(
+ public static ImmutableArray Split(
ISectionable text,
IFontLibrary fonts,
float scale,
@@ -277,7 +253,7 @@ public static ImmutableArray Split(
var s=0;
var lsbo=0;
var sbo=0;
- var wq = new WorkQueue(
+ var wq = new WorkQueue(
w =>
{
var len = lsbo-sbo;
From bbbf03190053e1d7f7463d54a8e98fe80e4c1940 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Sat, 27 Nov 2021 14:43:39 -0500
Subject: [PATCH 37/44] UserInterface/TextLayout: A whole bunch of hard-fought
bugfixes
---
Robust.Client/UserInterface/TextLayout.cs | 77 +++++++++++++++--------
1 file changed, 50 insertions(+), 27 deletions(-)
diff --git a/Robust.Client/UserInterface/TextLayout.cs b/Robust.Client/UserInterface/TextLayout.cs
index 9b8e4a501b6..e17eee4419f 100644
--- a/Robust.Client/UserInterface/TextLayout.cs
+++ b/Robust.Client/UserInterface/TextLayout.cs
@@ -113,7 +113,8 @@ public static ImmutableArray Layout(
int lnrem,
int sptot,
int maxPri,
- int tPri
+ int tPri,
+ int lnh
)>(postcreate: i => i with
{
wds = new List(),
@@ -132,8 +133,9 @@ int tPri
lw.Work.gaps.Add(gW+lw.Work.maxPri);
lw.Work.tPri += gW+lw.Work.maxPri;
lw.Work.maxPri += adv;
+ lw.Work.lnh = Math.Max(lw.Work.lnh, wd.h);
- if (lw.Work.lnrem < wd.w)
+ if (lw.Work.lnrem < wd.w || wd.wt == WordType.LineBreak)
{
lw.Flush();
lw.Work.lnrem = w;
@@ -147,37 +149,50 @@ int tPri
lw.Flush(true);
var flib = fonts.StartFont(fclass);
- int py=0;
- foreach ((var ln, var gaps, var lnrem, var sptot, var maxPri, var tPri) in lw.Done)
+ int py = flib.Current.GetAscent(scale);
+ foreach ((var ln, var gaps, var lnrem, var sptot, var maxPri, var tPri, var lnh) in lw.Done)
{
- int px=0;
- int lh=0;
+ int px=0, maxlh=0;
+
var spDist = new int[gaps.Count];
for (int i = 0; i < gaps.Count; i++)
spDist[i] = (int) (((float) gaps[i] / (float) tPri) * (float) sptot);
- int prevasc=0, prevdesc=0;
+ int prevAsc=0, prevDesc=0;
for (int i = 0; i < ln.Count; i++)
{
var ss = src[ln[i].section];
var sf = flib.Update(ss.Style, ss.Size);
var asc = sf.GetAscent(scale);
var desc = sf.GetDescent(scale);
- px += spDist[i] + ln[i].w;
- ln[i].x = px;
- lh = Math.Max(lh, ln[i].h);
- ln[i].y = src[ln[i].section].Alignment.Vertical() switch {
- TextAlign.Baseline => 0,
- TextAlign.Bottom => -(desc - prevdesc), // Scoot it up by the descent
- TextAlign.Top => (asc - prevasc),
- TextAlign.Subscript => -ln[i].h / 8, // Technically these should be derived from the font data,
- TextAlign.Superscript => ln[i].h / 4, // but I'm not gonna bother figuring out how to pull it from them.
- _ => 0,
+ maxlh = Math.Max(maxlh, sf.GetAscent(scale));
+
+ if (i - 1 > 0 && i - 1 < spDist.Length)
+ {
+ px += spDist[i - 1] / 2;
+ }
+
+ ln[i] = ln[i] with {
+ x = px,
+ y = py + ss.Alignment.Vertical() switch {
+ TextAlign.Baseline => 0,
+ TextAlign.Bottom => -(desc - prevDesc), // Scoot it up by the descent
+ TextAlign.Top => (asc - prevAsc),
+ TextAlign.Subscript => -ln[i].h / 8, // Technically these should be derived from the font data,
+ TextAlign.Superscript => ln[i].h / 4, // but I'm not gonna bother figuring out how to pull it from them.
+ _ => 0,
+ }
};
- prevasc = asc;
- prevdesc = desc;
+
+ if (i < spDist.Length)
+ {
+ px += spDist[i] / 2 + ln[i].w;
+ }
+
+ prevAsc = asc;
+ prevDesc = desc;
}
- py += lineSpacing + lh;
+ py += options.HasFlag(LayoutOptions.UseRenderTop) ? lnh : (lineSpacing + maxlh);
}
return lw.Done.SelectMany(e => e.wds).ToImmutableArray();
@@ -256,13 +271,13 @@ public static ImmutableArray Split(
var wq = new WorkQueue(
w =>
{
- var len = lsbo-sbo;
+ var len = sbo-lsbo;
lsbo = sbo;
- sbo = 0;
- return new(w) { length=len };
+ return w with { length=len };
},
- () => new Word() { section=s, charOffs=sbo },
- w => w.length > 0
+ default,
+ default,
+ w => w with { section=s, charOffs=sbo }
);
var flib = fonts.StartFont(fclass);
@@ -274,8 +289,10 @@ public static ImmutableArray Split(
if (sec.Meta != default)
throw new Exception("Text section with unknown or unimplemented Meta flag");
+ lsbo = 0;
sbo = 0;
var fnt = flib.Update(sec.Style, sec.Size);
+ wq.Reset();
foreach (var r in sec.Content.EnumerateRunes())
{
@@ -315,9 +332,9 @@ public static ImmutableArray Split(
if (wq.Work.wt == WordType.Normal)
wq.Work.spw = runeSpacing;
}
+ wq.Flush(true);
}
- wq.Flush(true);
return wq.Done.ToImmutableArray();
}
@@ -348,7 +365,6 @@ private class WorkQueue
private Func _conv;
-
public List Done = new();
public TIn Work;
@@ -376,6 +392,13 @@ public WorkQueue(
Work = _postcr.Invoke(Work);
}
+ public void Reset()
+ {
+ Work = _blank.Invoke();
+ if (_postcr is not null)
+ Work = _postcr.Invoke(Work);
+ }
+
public void Flush(bool force = false)
{
if (_check.Invoke(Work) || force)
From a1ffdf5f0c8313af7f24a7d1d6c3dd2aa886c06d Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Sat, 27 Nov 2021 14:45:11 -0500
Subject: [PATCH 38/44] Utility/FormattedMessage: Add a static, empty
FormattedMessage
---
Robust.Shared/Utility/FormattedMessage.cs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Robust.Shared/Utility/FormattedMessage.cs b/Robust.Shared/Utility/FormattedMessage.cs
index 9007ee81b48..b78868353b5 100644
--- a/Robust.Shared/Utility/FormattedMessage.cs
+++ b/Robust.Shared/Utility/FormattedMessage.cs
@@ -152,7 +152,8 @@ public string ToMarkup()
return sb.ToString();
}
- // Implements Robust.Client.UserInterface.TextLayout.ISectionable
+ public static readonly FormattedMessage Empty = new FormattedMessage(Array.Empty());
+
public Section this[int i] { get => Sections[i]; }
public int Length { get => Sections.Length; }
From 7f7876cbe88e7c99fcf9c03c49a75a2a4012a755 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Sat, 27 Nov 2021 14:48:42 -0500
Subject: [PATCH 39/44] Utility/FormattedMessage: Fix. Bugs.
---
Robust.Shared/Utility/FormattedMessage.cs | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/Robust.Shared/Utility/FormattedMessage.cs b/Robust.Shared/Utility/FormattedMessage.cs
index b78868353b5..37051228713 100644
--- a/Robust.Shared/Utility/FormattedMessage.cs
+++ b/Robust.Shared/Utility/FormattedMessage.cs
@@ -216,6 +216,7 @@ public void PushColor(Color color)
flushWork();
var last = _work[_idx];
+ last.Content = string.Empty;
last.Color = color.ToArgb();
_work.Add(last);
_idx = _work.Count - 1;
@@ -227,12 +228,20 @@ public void PushColor(Color color)
// actually set, and what parts are just default values.
// TODO: move _idx?
- public void AddMessage(FormattedMessage other) =>
+ public void AddMessage(FormattedMessage other)
+ {
+ flushWork();
_work.AddRange(other.Sections);
+ _idx = _work.Count-1;
+ }
// TODO: See above
- public void AddMessage(FormattedMessage.Builder other) =>
- _work.AddRange(other._work);
+ public void AddMessage(FormattedMessage.Builder other)
+ {
+ flushWork();
+ AddMessage(other.Build());
+ other.Clear();
+ }
// I wish I understood why this was needed...
// Did people not know you could AddText("\n")?
From 48951a3fd8cde2da302a5a295217944b9dadcb0a Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Sat, 27 Nov 2021 14:50:21 -0500
Subject: [PATCH 40/44] UserInterface/RichTextEntry: Bug fixin'
---
Robust.Client/UserInterface/RichTextEntry.cs | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/Robust.Client/UserInterface/RichTextEntry.cs b/Robust.Client/UserInterface/RichTextEntry.cs
index 3c01265e69a..b9694514ee2 100644
--- a/Robust.Client/UserInterface/RichTextEntry.cs
+++ b/Robust.Client/UserInterface/RichTextEntry.cs
@@ -51,8 +51,9 @@ public void Update(IFontLibrary font, float maxSizeX, float uiScale)
foreach (var w in _ld)
{
if (w.x + w.w > Width) Width = w.x + w.w;
- if (w.y + w.h > Height) Height = w.y + w.h;
+ if (w.y + w.h > Height) Height = w.y;
}
+ _lmsx = (int) maxSizeX;
}
}
@@ -67,24 +68,27 @@ public void Draw(
foreach (var wd in _ld)
{
var s = Message.Sections[wd.section];
- var baseLine = drawBox.TopLeft + new Vector2((float) wd.x, (float) wd.y);
+ var baseLine = drawBox.TopLeft + new Vector2((float) wd.x, verticalOffset + (float) wd.y);
+
foreach (var rune in s
.Content[wd.charOffs..(wd.charOffs+wd.length)]
.EnumerateRunes())
{
+ // TODO: Skip drawing when out of the drawBox
baseLine.X += flib.Current.DrawChar(
handle,
rune,
baseLine,
uiScale,
new Color { // Why Color.FromArgb isn't a thing is beyond me.
- A=(float) ((s.Color & 0xFF_00_00_00) >> 24),
- R=(float) ((s.Color & 0x00_FF_00_00) >> 16),
- G=(float) ((s.Color & 0x00_00_FF_00) >> 8),
- B=(float) (s.Color & 0x00_00_00_FF)
+ A=(float) ((s.Color & 0xFF_00_00_00) >> 24) / 255f,
+ R=(float) ((s.Color & 0x00_FF_00_00) >> 16) / 255f,
+ G=(float) ((s.Color & 0x00_00_FF_00) >> 8) / 255f,
+ B=(float) (s.Color & 0x00_00_00_FF) / 255f
}
);
}
+
}
}
}
From 569ce41e8bded964e2d3f24f34a9813812d3e244 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Sat, 27 Nov 2021 14:52:10 -0500
Subject: [PATCH 41/44] UserInterface: CSS teim
---
.../UserInterface/Controls/ItemList.cs | 11 +++--
Robust.Client/UserInterface/Controls/Label.cs | 5 +-
.../UserInterface/Controls/LineEdit.cs | 5 +-
.../UserInterface/Controls/OutputPanel.cs | 46 +++++++++++++------
.../UserInterface/Controls/RichTextLabel.cs | 22 +++++++--
.../UserInterface/Controls/TabContainer.cs | 5 +-
Robust.Client/UserInterface/Controls/Tree.cs | 5 +-
Robust.Client/UserInterface/RichTextEntry.cs | 4 +-
8 files changed, 73 insertions(+), 30 deletions(-)
diff --git a/Robust.Client/UserInterface/Controls/ItemList.cs b/Robust.Client/UserInterface/Controls/ItemList.cs
index 41f074ba604..12ad858cbce 100644
--- a/Robust.Client/UserInterface/Controls/ItemList.cs
+++ b/Robust.Client/UserInterface/Controls/ItemList.cs
@@ -235,12 +235,17 @@ public Font ActualFont
{
get
{
- if (TryGetStyleProperty("font", out var font))
+ TryGetStyleProperty("font", out var font);
+ if (TryGetStyleProperty("font-library", out var flib))
{
- return font;
+ return flib.StartFont(font).Current;
}
- return UserInterfaceManager.ThemeDefaults.DefaultFont;
+ return UserInterfaceManager
+ .ThemeDefaults
+ .DefaultFontLibrary
+ .StartFont(font)
+ .Current;
}
}
diff --git a/Robust.Client/UserInterface/Controls/Label.cs b/Robust.Client/UserInterface/Controls/Label.cs
index 1c38520a0df..4c448288c1e 100644
--- a/Robust.Client/UserInterface/Controls/Label.cs
+++ b/Robust.Client/UserInterface/Controls/Label.cs
@@ -83,9 +83,10 @@ private Font ActualFont
return FontOverride;
}
- if (TryGetStyleProperty(StylePropertyFont, out var font))
+ TryGetStyleProperty(StylePropertyFont, out var font);
+ if (TryGetStyleProperty("font-library", out var flib))
{
- return font;
+ return flib.StartFont(font).Current;
}
return UserInterfaceManager.ThemeDefaults.LabelFont;
diff --git a/Robust.Client/UserInterface/Controls/LineEdit.cs b/Robust.Client/UserInterface/Controls/LineEdit.cs
index b6dc5485643..3a970cfc066 100644
--- a/Robust.Client/UserInterface/Controls/LineEdit.cs
+++ b/Robust.Client/UserInterface/Controls/LineEdit.cs
@@ -667,9 +667,10 @@ protected internal override void KeyboardFocusExited()
[Pure]
private Font _getFont()
{
- if (TryGetStyleProperty("font", out var font))
+ TryGetStyleProperty("font", out var font);
+ if (TryGetStyleProperty("font-library", out var flib))
{
- return font;
+ return flib.StartFont(font).Current;
}
return UserInterfaceManager.ThemeDefaults.DefaultFont;
diff --git a/Robust.Client/UserInterface/Controls/OutputPanel.cs b/Robust.Client/UserInterface/Controls/OutputPanel.cs
index 1d0f72a927d..5013d3e5110 100644
--- a/Robust.Client/UserInterface/Controls/OutputPanel.cs
+++ b/Robust.Client/UserInterface/Controls/OutputPanel.cs
@@ -62,7 +62,7 @@ public void RemoveEntry(Index index)
var entry = _entries[index];
_entries.RemoveAt(index.GetOffset(_entries.Count));
- var font = _getFont().StartFont().Current;
+ var font = _getFont();
_totalContentHeight -= entry.Height + font.GetLineSeparation(UIScale);
if (_entries.Count == 0)
{
@@ -83,10 +83,10 @@ public void AddMessage(FormattedMessage message)
{
var entry = new RichTextEntry(message);
- entry.Update(_getFont(), _getContentBox().Width, UIScale);
+ entry.Update(_getFontLib(), _getContentBox().Width, UIScale);
_entries.Add(entry);
- var font = _getFont().StartFont().Current;
+ var font = _getFont();
_totalContentHeight += entry.Height;
if (_firstLine)
{
@@ -115,7 +115,8 @@ protected internal override void Draw(DrawingHandleScreen handle)
base.Draw(handle);
var style = _getStyleBox();
- var font = _getFont().StartFont().Current;
+ var flib = _getFontLib();
+ var font = _getFont();
style?.Draw(handle, PixelSizeBox);
var contentBox = _getContentBox();
@@ -134,7 +135,7 @@ protected internal override void Draw(DrawingHandleScreen handle)
break;
}
- entry.Draw(handle, _getFont(), contentBox, entryOffset, UIScale);
+ entry.Draw(handle, flib, contentBox, entryOffset, UIScale, _getFontColor());
entryOffset += entry.Height + font.GetLineSeparation(UIScale);
}
@@ -170,7 +171,7 @@ protected override Vector2 MeasureOverride(Vector2 availableSize)
private void _invalidateEntries()
{
_totalContentHeight = 0;
- var font = _getFont();
+ var font = _getFontLib();
var sizeX = _getContentBox().Width;
for (var i = 0; i < _entries.Count; i++)
{
@@ -188,14 +189,32 @@ private void _invalidateEntries()
}
[System.Diagnostics.Contracts.Pure]
- private IFontLibrary _getFont()
+ private IFontLibrary _getFontLib()
{
- if (TryGetStyleProperty("font", out var font))
- {
- return font;
- }
+ if (TryGetStyleProperty("font-library", out var flib))
+ return flib;
+
+ return UserInterfaceManager
+ .ThemeDefaults
+ .DefaultFontLibrary;
+ }
+
+ [System.Diagnostics.Contracts.Pure]
+ private Font _getFont()
+ {
+ TryGetStyleProperty("font", out var fclass);
+ return _getFontLib().StartFont(fclass).Current;
+ }
+
+ [System.Diagnostics.Contracts.Pure]
+ private Color _getFontColor()
+ {
+ if (TryGetStyleProperty("font-color", out var fc))
+ return fc;
- return UserInterfaceManager.ThemeDefaults.DefaultFontLibrary;
+ // From Robust.Client/UserInterface/RichTextEntry.cs#L19
+ // at 33008a2bce0cc4755b18b12edfaf5b6f1f87fdd9
+ return new Color(200, 200, 200);
}
[System.Diagnostics.Contracts.Pure]
@@ -213,8 +232,7 @@ private IFontLibrary _getFont()
[System.Diagnostics.Contracts.Pure]
private int _getScrollSpeed()
{
- var font = _getFont().StartFont().Current;
- return font.GetLineHeight(UIScale) * 2;
+ return _getFont().GetLineHeight(UIScale) * 2;
}
[System.Diagnostics.Contracts.Pure]
diff --git a/Robust.Client/UserInterface/Controls/RichTextLabel.cs b/Robust.Client/UserInterface/Controls/RichTextLabel.cs
index 13bdb5ee42a..453a517e727 100644
--- a/Robust.Client/UserInterface/Controls/RichTextLabel.cs
+++ b/Robust.Client/UserInterface/Controls/RichTextLabel.cs
@@ -45,18 +45,32 @@ protected internal override void Draw(DrawingHandleScreen handle)
return;
}
- _entry.Draw(handle, _getFont(), SizeBox, 0, UIScale);
+ _entry.Draw(handle, _getFont(), SizeBox, 0, UIScale, _getFontColor());
}
[Pure]
private IFontLibrary _getFont()
{
- if (TryGetStyleProperty("font", out var font))
+ TryGetStyleProperty("font", out var font);
+ if (TryGetStyleProperty("font-library", out var flib))
{
- return font;
+ return flib;
}
- return UserInterfaceManager.ThemeDefaults.DefaultFontLibrary;
+ return UserInterfaceManager
+ .ThemeDefaults
+ .DefaultFontLibrary;
+ }
+
+ [Pure]
+ private Color _getFontColor()
+ {
+ if (TryGetStyleProperty("font-color", out var fc))
+ return fc;
+
+ // From Robust.Client/UserInterface/RichTextEntry.cs#L19
+ // at 33008a2bce0cc4755b18b12edfaf5b6f1f87fdd9
+ return new Color(200, 200, 200);
}
}
}
diff --git a/Robust.Client/UserInterface/Controls/TabContainer.cs b/Robust.Client/UserInterface/Controls/TabContainer.cs
index 3f5c5e08dc2..f3ee65c1a61 100644
--- a/Robust.Client/UserInterface/Controls/TabContainer.cs
+++ b/Robust.Client/UserInterface/Controls/TabContainer.cs
@@ -385,9 +385,10 @@ private Color _getTabFontColorInactive()
[System.Diagnostics.Contracts.Pure]
private Font _getFont()
{
- if (TryGetStyleProperty("font", out var font))
+ TryGetStyleProperty("font", out var font);
+ if (TryGetStyleProperty("font-library", out var flib))
{
- return font;
+ return flib.StartFont(font).Current;
}
return UserInterfaceManager.ThemeDefaults.DefaultFont;
diff --git a/Robust.Client/UserInterface/Controls/Tree.cs b/Robust.Client/UserInterface/Controls/Tree.cs
index c10166aca4c..52bc4769fae 100644
--- a/Robust.Client/UserInterface/Controls/Tree.cs
+++ b/Robust.Client/UserInterface/Controls/Tree.cs
@@ -284,9 +284,10 @@ protected override void Resized()
private Font? _getFont()
{
- if (TryGetStyleProperty("font", out var font))
+ TryGetStyleProperty("font", out var font);
+ if (TryGetStyleProperty("font-library", out var flib))
{
- return font;
+ return flib.StartFont(font).Current;
}
return null;
diff --git a/Robust.Client/UserInterface/RichTextEntry.cs b/Robust.Client/UserInterface/RichTextEntry.cs
index b9694514ee2..bac87bfc0c2 100644
--- a/Robust.Client/UserInterface/RichTextEntry.cs
+++ b/Robust.Client/UserInterface/RichTextEntry.cs
@@ -62,7 +62,8 @@ public void Draw(
IFontLibrary font,
UIBox2 drawBox,
float verticalOffset,
- float uiScale)
+ float uiScale,
+ Color defColor)
{
var flib = font.StartFont();
foreach (var wd in _ld)
@@ -80,6 +81,7 @@ public void Draw(
rune,
baseLine,
uiScale,
+ s.Color == default ? defColor :
new Color { // Why Color.FromArgb isn't a thing is beyond me.
A=(float) ((s.Color & 0xFF_00_00_00) >> 24) / 255f,
R=(float) ((s.Color & 0x00_FF_00_00) >> 16) / 255f,
From b2f02cef36e279fa00c8ab3a1c8dae15383b3189 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Tue, 30 Nov 2021 14:36:58 -0500
Subject: [PATCH 42/44] Markup/Basic: Add an optional "default" style to use
---
Robust.Shared/Utility/Markup/Basic.cs | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/Robust.Shared/Utility/Markup/Basic.cs b/Robust.Shared/Utility/Markup/Basic.cs
index cfa70e5c9ed..2141a6a7abb 100644
--- a/Robust.Shared/Utility/Markup/Basic.cs
+++ b/Robust.Shared/Utility/Markup/Basic.cs
@@ -111,11 +111,17 @@ private static bool ValidColorNameContents(char c)
}
- public FormattedMessage Render() => Build().Build();
+ public FormattedMessage Render(Section? defStyle = default) => Build(defStyle).Build();
- public FormattedMessage.Builder Build()
+ public FormattedMessage.Builder Build(Section? defStyle = default)
{
- var b = new FormattedMessage.Builder();
+ FormattedMessage.Builder b;
+ if (defStyle != null)
+ b = FormattedMessage.Builder.FromFormattedMessage(
+ new FormattedMessage(new[] {defStyle.Value})
+ );
+ else
+ b = new FormattedMessage.Builder();
foreach (var t in _tags)
{
@@ -130,14 +136,14 @@ public FormattedMessage.Builder Build()
return b;
}
- public static FormattedMessage.Builder BuildMarkup(string text)
+ public static FormattedMessage.Builder BuildMarkup(string text, Section? defStyle = default)
{
var nb = new Basic();
nb.AddMarkup(text);
- return nb.Build();
+ return nb.Build(defStyle);
}
- public static FormattedMessage RenderMarkup(string text) => BuildMarkup(text).Build();
+ public static FormattedMessage RenderMarkup(string text, Section? defStyle = default) => BuildMarkup(text, defStyle).Build();
///
/// Escape a string of text to be able to be formatted into markup.
From 0d07f089bc574f3f8065a5d05ffb89dbf9932c68 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Tue, 30 Nov 2021 14:37:33 -0500
Subject: [PATCH 43/44] Utility/FormattedMessage: I'm surprised I only made
this mistake once.
---
Robust.Shared/Utility/FormattedMessage.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Robust.Shared/Utility/FormattedMessage.cs b/Robust.Shared/Utility/FormattedMessage.cs
index 37051228713..f9613e78762 100644
--- a/Robust.Shared/Utility/FormattedMessage.cs
+++ b/Robust.Shared/Utility/FormattedMessage.cs
@@ -177,7 +177,7 @@ public class Builder
new Section()
};
- public static Builder FromFormattedText(FormattedMessage orig) => new ()
+ public static Builder FromFormattedMessage(FormattedMessage orig) => new ()
{
// Again, we always need at least one _work item, so if the FormattedMessage
// is empty, we'll forge one.
From c2ec30ff3afa47a60dc0599275674099fab8c293 Mon Sep 17 00:00:00 2001
From: Efruit <602406+Efruit@users.noreply.github.com>
Date: Tue, 30 Nov 2021 14:37:55 -0500
Subject: [PATCH 44/44] Log/DebugConsoleLogHandler: work around lack of a
default style
---
Robust.Client/Log/DebugConsoleLogHandler.cs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Robust.Client/Log/DebugConsoleLogHandler.cs b/Robust.Client/Log/DebugConsoleLogHandler.cs
index 0ed80a1b893..9935af0ee18 100644
--- a/Robust.Client/Log/DebugConsoleLogHandler.cs
+++ b/Robust.Client/Log/DebugConsoleLogHandler.cs
@@ -32,7 +32,9 @@ public void Log(string sawmillName, LogEvent message)
formatted.Pop();
formatted.AddText($"] {sawmillName}: ");
formatted.Pop();
+ formatted.PushColor(Color.LightGray);
formatted.AddText(message.RenderMessage());
+ formatted.Pop();
if (message.Exception != null)
{
formatted.AddText("\n");