Skip to content

Commit

Permalink
Rich text redux (#2213)
Browse files Browse the repository at this point in the history
* Shared/Utility: Define new FormattedMessage core types

* Shared/Utility: Move MarkupParser to a new namespace, update to new FormattedText

* Shared/Serialization: Temporary fix for FormattedMessageSerializer

* Scripting/ScriptInstanceShared: Move to new FormattedMessage.Builder

* Shared/Utility: Add a FormattedMessage loader to the .Builder

* Server/Scripting: Port SciptHost to FormattedMessage.Builder

* UserInterface/RichTextEntry: NOP out almost everything

not gonna bother fixing it until more groundwork is laid

* Shared/Utility: Expand Utility.Extensions a bit

strictly for pesonal reasons

* Client/UserInterface: Add the base TextLayout engine

* Client/Graphics: Add a Font Library manager

* Graphics/TextLayout: Finish up implementing the TextLayout engine

* Utility/FormattedMessage: Add yet another hack to keep the serializer in service

* Commands/Debug: Use FormattedMessage.Builder

* Console/Completions: Use FormattedMessage.Builder

* Utility/FormattedMessage: Add `AddMessage` methods

* Console/ScriptConsole: Use FormattedMessage.Builder

* Client/Log: Use FormattedMessage.Builder

* CustomControls/DebugConsole: Use FormattedMessage.Builder

* Controls/OutputPanel: Use FormattedMessage.Builder, NOP `Draw` pending rewrite

* Controls/RichTextLabel: Use FormattedMessage.Builder, NOP `Draw` pending rewrite

* UnitTesting: Update FormattedMessage/Markup Tests

They will NOT pass yet, but I don't care; it compiles.

* Utility/FormattedMessage: Fix some off-by-one Builder bugs

* Utility/FormattedMessage: Continue cleanup, test compliance

* Utility/FormattedMessage: Work around dotnet/roslyn#57870

* Utility/FormattedMessage: Move ISectionable from TextLayout, implement it for FormattedMessage

* 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<T>` 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).

* UserInterface/TextLayout: Throw if `Meta` isn't recognized

TODO warning go brrr

* Graphics/FontLibrary: Add a `DummyVariant`

* UserInterface/UITheme: Move to FontLibraries

* UserInterface/TextLayout: Move to an un-nested `ImmutableArray`

* UserInterface/RichTextEntry: Go ahead. Draw.

* Markup/Basic: Add extension & helpers for FormattedMessage.Builder

* Markup/Basic: Add `EscapeText` back in

A forgotten casualty of the great Markup separation of 2021

* Graphics/FontLibrary: Clean up bit magic, ensure that at least one font is picked

* Graphics/FontLibrary: Add diagnostics to the "no fonts" exception

* UserInterface/TextLayout: Scrap `Word`, return to `Offset`

* UserInterface/TextLayout: A whole bunch of hard-fought bugfixes

* Utility/FormattedMessage: Add a static, empty FormattedMessage

* Utility/FormattedMessage: Fix. Bugs.

* UserInterface/RichTextEntry: Bug fixin'

* UserInterface: CSS teim

* Markup/Basic: Add an optional "default" style to use

* Utility/FormattedMessage: I'm surprised I only made this mistake once.

* Log/DebugConsoleLogHandler: work around lack of a default style
  • Loading branch information
Efruit authored Dec 12, 2021
1 parent f7f6b74 commit 2a8887d
Show file tree
Hide file tree
Showing 26 changed files with 1,204 additions and 562 deletions.
4 changes: 2 additions & 2 deletions Robust.Client/Console/Commands/Debug.cs
Original file line number Diff line number Diff line change
Expand Up @@ -559,12 +559,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

0 comments on commit 2a8887d

Please sign in to comment.