Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rich text redux #2213

Merged
merged 44 commits into from
Dec 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
4371067
Shared/Utility: Define new FormattedMessage core types
Efruit Nov 9, 2021
29eff23
Shared/Utility: Move MarkupParser to a new namespace, update to new F…
Efruit Nov 9, 2021
c69294b
Shared/Serialization: Temporary fix for FormattedMessageSerializer
Efruit Nov 9, 2021
7a03e5b
Scripting/ScriptInstanceShared: Move to new FormattedMessage.Builder
Efruit Nov 9, 2021
5b23a74
Shared/Utility: Add a FormattedMessage loader to the .Builder
Efruit Nov 9, 2021
cf1739c
Server/Scripting: Port SciptHost to FormattedMessage.Builder
Efruit Nov 9, 2021
5ba079f
UserInterface/RichTextEntry: NOP out almost everything
Efruit Nov 18, 2021
ec66b71
Shared/Utility: Expand Utility.Extensions a bit
Efruit Nov 18, 2021
730628f
Client/UserInterface: Add the base TextLayout engine
Efruit Nov 18, 2021
3f18ec3
Client/Graphics: Add a Font Library manager
Efruit Nov 25, 2021
58ea238
Graphics/TextLayout: Finish up implementing the TextLayout engine
Efruit Nov 25, 2021
e230c85
Utility/FormattedMessage: Add yet another hack to keep the serializer…
Efruit Nov 25, 2021
5d9b845
Commands/Debug: Use FormattedMessage.Builder
Efruit Nov 26, 2021
8547efa
Console/Completions: Use FormattedMessage.Builder
Efruit Nov 26, 2021
8f0ae95
Utility/FormattedMessage: Add `AddMessage` methods
Efruit Nov 26, 2021
21b0058
Console/ScriptConsole: Use FormattedMessage.Builder
Efruit Nov 26, 2021
7049ade
Client/Log: Use FormattedMessage.Builder
Efruit Nov 26, 2021
e5401c5
CustomControls/DebugConsole: Use FormattedMessage.Builder
Efruit Nov 26, 2021
2b1a23b
Controls/OutputPanel: Use FormattedMessage.Builder, NOP `Draw` pendin…
Efruit Nov 26, 2021
6de5ad4
Controls/RichTextLabel: Use FormattedMessage.Builder, NOP `Draw` pend…
Efruit Nov 26, 2021
90f042a
UnitTesting: Update FormattedMessage/Markup Tests
Efruit Nov 26, 2021
3881489
Utility/FormattedMessage: Fix some off-by-one Builder bugs
Efruit Nov 26, 2021
9accc73
Utility/FormattedMessage: Continue cleanup, test compliance
Efruit Nov 26, 2021
1d31ebd
Utility/FormattedMessage: Work around https://github.com/dotnet/rosly…
Efruit Nov 26, 2021
b5dfd28
Utility/FormattedMessage: Move ISectionable from TextLayout, implemen…
Efruit Nov 26, 2021
5caa72f
UserInterface/TextLayout: Add a `postcreate` function to set up new `…
Efruit Nov 26, 2021
e3b154e
UserInterface/TextLayout: Throw if `Meta` isn't recognized
Efruit Nov 26, 2021
a3f9e07
Graphics/FontLibrary: Add a `DummyVariant`
Efruit Nov 26, 2021
e8ed541
UserInterface/UITheme: Move to FontLibraries
Efruit Nov 26, 2021
384cd4f
UserInterface/TextLayout: Move to an un-nested `ImmutableArray`
Efruit Nov 26, 2021
a6f0f45
UserInterface/RichTextEntry: Go ahead. Draw.
Efruit Nov 26, 2021
f47cf6e
Markup/Basic: Add extension & helpers for FormattedMessage.Builder
Efruit Nov 26, 2021
d8238a6
Markup/Basic: Add `EscapeText` back in
Efruit Nov 27, 2021
b99acfc
Graphics/FontLibrary: Clean up bit magic, ensure that at least one fo…
Efruit Nov 27, 2021
5b685b6
Graphics/FontLibrary: Add diagnostics to the "no fonts" exception
Efruit Nov 27, 2021
f8ab2b2
UserInterface/TextLayout: Scrap `Word`, return to `Offset`
Efruit Nov 27, 2021
bbbf031
UserInterface/TextLayout: A whole bunch of hard-fought bugfixes
Efruit Nov 27, 2021
a1ffdf5
Utility/FormattedMessage: Add a static, empty FormattedMessage
Efruit Nov 27, 2021
7f7876c
Utility/FormattedMessage: Fix. Bugs.
Efruit Nov 27, 2021
48951a3
UserInterface/RichTextEntry: Bug fixin'
Efruit Nov 27, 2021
569ce41
UserInterface: CSS teim
Efruit Nov 27, 2021
b2f02ce
Markup/Basic: Add an optional "default" style to use
Efruit Nov 30, 2021
0d07f08
Utility/FormattedMessage: I'm surprised I only made this mistake once.
Efruit Nov 30, 2021
c2ec30f
Log/DebugConsoleLogHandler: work around lack of a default style
Efruit Nov 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Robust.Client/Console/Commands/Debug.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions Robust.Client/Console/Completions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -120,7 +120,7 @@ public Entry(LiteResult result)
compl.PushColor(Color.LightSlateGray);
compl.AddText(Result.InlineDescription);
}
SetMessage(compl);
SetMessage(compl.Build());
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions Robust.Client/Console/ScriptClient.ScriptConsoleServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
16 changes: 8 additions & 8 deletions Robust.Client/Console/ScriptConsoleClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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);

Expand All @@ -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);
Expand All @@ -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(">");
Expand Down
185 changes: 185 additions & 0 deletions Robust.Client/Graphics/FontLibrary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Robust.Shared.Utility;
using Robust.Client.ResourceManagement;
namespace Robust.Client.Graphics;

/// <summary>
/// Stores a single style (Bold, Italic, Monospace), or any combination thereof.
/// </summary>
public record FontVariant (FontStyle Style, FontResource[] Resource)
{
public virtual 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);
}
};

internal record DummyVariant(FontStyle fs) : FontVariant(fs, new FontResource[0])
{
public override Font ToFont(byte size) => new DummyFont();
};

public record FontClass
(
string Id,
FontStyle Style,
FontSize Size
);

/// <summary>
/// Manages font-based bookkeeping across a single stylesheet.
/// </summary>
public interface IFontLibrary
{
FontClass Default { get; }

/// <summary>Associates a name to a set of font resources.</summary>
void AddFont(string name, params FontVariant[] variants);

/// <summary>Sets a standard size which can be reused across the Font Library.</summary>
void SetStandardSize(ushort number, byte size);

/// <summary>Sets a standard style which can be reused across the Font Library.</summary>
void SetStandardStyle(ushort number, string name, FontStyle style);

/// <summary>
/// Returns a fancy handle in to the library.
/// The handle keeps track of relative changes to <paramref name="fst"/> and <paramref name="fsz"/>.
/// </summary>
IFontLibrarian StartFont(string id, FontStyle fst, FontSize fsz);

IFontLibrarian StartFont(FontClass? fclass = default) =>
StartFont(
(fclass ?? Default).Id,
(fclass ?? Default).Style,
(fclass ?? Default).Size
);
}

/// <summary>
/// Acts as a handle in to an <seealso cref="IFontLibrary"/>.
/// </summary>
public interface IFontLibrarian
{
Font Current { get; }
Font Update(FontStyle fst, FontSize fsz);
}

public class FontLibrary : IFontLibrary
{
public FontClass Default { get; set; }

public FontLibrary(FontClass def)
{
Default = def;
}

private Dictionary<string, FontVariant[]> _styles = new();
private Dictionary<FontStyle, (string, FontStyle)> _standardSt = new();
private Dictionary<FontSize, byte> _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!

// 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;
}

if (winner is null)
throw new Exception($"no matching font style ({id}, {fst})");

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);
}
}
}
6 changes: 4 additions & 2 deletions Robust.Client/Log/DebugConsoleLogHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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("[");
Expand All @@ -32,13 +32,15 @@ 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");
formatted.AddText(message.Exception.ToString());
}
Console.AddFormattedLine(formatted);
Console.AddFormattedLine(formatted.Build());
}

private static Color LogLevelToColor(LogLevel level)
Expand Down
11 changes: 8 additions & 3 deletions Robust.Client/UserInterface/Controls/ItemList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,17 @@ public Font ActualFont
{
get
{
if (TryGetStyleProperty<Font>("font", out var font))
TryGetStyleProperty<FontClass>("font", out var font);
if (TryGetStyleProperty<IFontLibrary>("font-library", out var flib))
{
return font;
return flib.StartFont(font).Current;
}

return UserInterfaceManager.ThemeDefaults.DefaultFont;
return UserInterfaceManager
.ThemeDefaults
.DefaultFontLibrary
.StartFont(font)
.Current;
}
}

Expand Down
5 changes: 3 additions & 2 deletions Robust.Client/UserInterface/Controls/Label.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,10 @@ private Font ActualFont
return FontOverride;
}

if (TryGetStyleProperty<Font>(StylePropertyFont, out var font))
TryGetStyleProperty<FontClass>(StylePropertyFont, out var font);
if (TryGetStyleProperty<IFontLibrary>("font-library", out var flib))
{
return font;
return flib.StartFont(font).Current;
}

return UserInterfaceManager.ThemeDefaults.LabelFont;
Expand Down
5 changes: 3 additions & 2 deletions Robust.Client/UserInterface/Controls/LineEdit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -667,9 +667,10 @@ protected internal override void KeyboardFocusExited()
[Pure]
private Font _getFont()
{
if (TryGetStyleProperty<Font>("font", out var font))
TryGetStyleProperty<FontClass>("font", out var font);
if (TryGetStyleProperty<IFontLibrary>("font-library", out var flib))
{
return font;
return flib.StartFont(font).Current;
}

return UserInterfaceManager.ThemeDefaults.DefaultFont;
Expand Down
Loading