mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-02 18:17:09 +02:00
* 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
288 lines
7.8 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|