Files
RobustToolbox/Robust.Client/UserInterface/Controls/Label.cs
T
E F RandGitHub 2a8887d0b4 Rich text redux (#2213)
* 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 https://github.com/dotnet/roslyn/issues/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
2021-12-12 14:35:26 -08:00

288 lines
7.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
using Robust.Client.Graphics;
using Robust.Shared.Animations;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface.Controls
{
/// <summary>
/// A label is a GUI control that displays simple text.
/// </summary>
public class Label : Control
{
public const string StylePropertyFontColor = "font-color";
public const string StylePropertyFont = "font";
public const string StylePropertyAlignMode = "alignMode";
private int _cachedTextHeight;
private readonly List<int> _cachedTextWidths = new();
private bool _textDimensionCacheValid;
private string? _text;
private bool _clipText;
private AlignMode _align;
public Label()
{
VerticalAlignment = VAlignment.Center;
}
/// <summary>
/// The text to display.
/// </summary>
[ViewVariables]
public string? Text
{
get => _text;
set
{
_text = value;
_textDimensionCacheValid = false;
InvalidateMeasure();
}
}
[ViewVariables]
public bool ClipText
{
get => _clipText;
set
{
_clipText = value;
RectClipContent = value;
InvalidateMeasure();
}
}
[ViewVariables] public AlignMode Align {
get
{
if (TryGetStyleProperty<AlignMode>(StylePropertyAlignMode, out var alignMode))
{
return alignMode;
}
return _align;
}
set => _align = value;
}
[ViewVariables] public VAlignMode VAlign { get; set; }
public Font? FontOverride { get; set; }
private Font ActualFont
{
get
{
if (FontOverride != null)
{
return FontOverride;
}
TryGetStyleProperty<FontClass>(StylePropertyFont, out var font);
if (TryGetStyleProperty<IFontLibrary>("font-library", out var flib))
{
return flib.StartFont(font).Current;
}
return UserInterfaceManager.ThemeDefaults.LabelFont;
}
}
public Color? FontColorShadowOverride { get; set; }
private Color ActualFontColor
{
get
{
if (FontColorOverride.HasValue)
{
return FontColorOverride.Value;
}
if (TryGetStyleProperty<Color>(StylePropertyFontColor, out var color))
{
return color;
}
return Color.White;
}
}
[ViewVariables(VVAccess.ReadWrite)]
[Animatable]
public Color? FontColorOverride { get; set; }
public int? ShadowOffsetXOverride { get; set; }
public int? ShadowOffsetYOverride { get; set; }
protected internal override void Draw(DrawingHandleScreen handle)
{
if (_text == null)
{
return;
}
if (!_textDimensionCacheValid)
{
_calculateTextDimension();
DebugTools.Assert(_textDimensionCacheValid);
}
int vOffset;
switch (VAlign)
{
case VAlignMode.Top:
vOffset = 0;
break;
case VAlignMode.Fill:
case VAlignMode.Center:
vOffset = (PixelSize.Y - _cachedTextHeight) / 2;
break;
case VAlignMode.Bottom:
vOffset = PixelSize.Y - _cachedTextHeight;
break;
default:
throw new ArgumentOutOfRangeException();
}
var newlines = 0;
var font = ActualFont;
var actualFontColor = ActualFontColor;
Vector2 CalcBaseline()
{
DebugTools.Assert(_textDimensionCacheValid);
int hOffset;
switch (Align)
{
case AlignMode.Left:
hOffset = 0;
break;
case AlignMode.Center:
case AlignMode.Fill:
hOffset = (PixelSize.X - _cachedTextWidths[newlines]) / 2;
break;
case AlignMode.Right:
hOffset = PixelSize.X - _cachedTextWidths[newlines];
break;
default:
throw new ArgumentOutOfRangeException();
}
return (hOffset, font.GetAscent(UIScale) + font.GetLineHeight(UIScale) * newlines + vOffset);
}
var baseLine = CalcBaseline();
foreach (var rune in _text.EnumerateRunes())
{
if (rune == new Rune('\n'))
{
newlines += 1;
baseLine = CalcBaseline();
}
var advance = font.DrawChar(handle, rune, baseLine, UIScale, actualFontColor);
baseLine += (advance, 0);
}
}
public enum AlignMode : byte
{
Left = 0,
Center = 1,
Right = 2,
Fill = 3
}
public enum VAlignMode : byte
{
Top = 0,
Center = 1,
Bottom = 2,
Fill = 3
}
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
if (!_textDimensionCacheValid)
{
_calculateTextDimension();
DebugTools.Assert(_textDimensionCacheValid);
}
if (ClipText)
{
return (0, _cachedTextHeight / UIScale);
}
var totalWidth = 0;
foreach (var width in _cachedTextWidths)
{
totalWidth = Math.Max(totalWidth, width);
}
return (totalWidth / UIScale, _cachedTextHeight / UIScale);
}
protected internal override void UIScaleChanged()
{
_textDimensionCacheValid = false;
base.UIScaleChanged();
}
private void _calculateTextDimension()
{
_cachedTextWidths.Clear();
_cachedTextWidths.Add(0);
if (_text == null)
{
_cachedTextHeight = 0;
_textDimensionCacheValid = true;
return;
}
var font = ActualFont;
var height = font.GetHeight(UIScale);
foreach (var rune in _text.EnumerateRunes())
{
if (rune == new Rune('\n'))
{
_cachedTextWidths.Add(0);
height += font.GetLineHeight(UIScale);
}
else
{
var metrics = font.GetCharMetrics(rune, UIScale);
if (metrics == null)
{
continue;
}
_cachedTextWidths[^1] += metrics.Value.Advance;
}
}
_cachedTextHeight = height;
_textDimensionCacheValid = true;
}
protected override void StylePropertiesChanged()
{
_textDimensionCacheValid = false;
base.StylePropertiesChanged();
}
}
}