Add text block alignment options for rich text box (and valign tag)

This commit is contained in:
Wojciech Figat
2022-08-04 13:52:25 +02:00
parent 3089cc3b59
commit 142d81065a
4 changed files with 151 additions and 23 deletions

View File

@@ -90,6 +90,7 @@ namespace FlaxEngine.GUI
{ "i", ProcessItalic },
{ "size", ProcessSize },
{ "img", ProcessImage },
{ "valign", ProcessVAlign },
};
private HtmlParser _parser = new HtmlParser();
@@ -132,8 +133,13 @@ namespace FlaxEngine.GUI
OnParseTag(ref context, ref tag);
}
// Insert remaining text
// Insert remaining text (and line)
OnAddTextBlock(ref context, testStartPos, _text.Length);
if (context.LineStartTextBlockIndex != _textBlocks.Count)
{
context.Caret.X = 0;
OnLineAdded(ref context, _text.Length - 1);
}
}
/// <summary>
@@ -224,21 +230,54 @@ namespace FlaxEngine.GUI
{
ref TextBlock textBlock = ref textBlocks[i];
var textBlockSize = textBlock.Bounds.BottomRight - lineOrigin;
var textBlockFont = textBlock.Style.Font.GetFont();
if (textBlockFont)
lineAscender = Mathf.Max(lineAscender, textBlockFont.Ascender);
var ascender = textBlock.Ascender;
//if (ascender <= 0)
{
var textBlockFont = textBlock.Style.Font.GetFont();
if (textBlockFont)
ascender = textBlockFont.Ascender;
}
lineAscender = Mathf.Max(lineAscender, ascender);
lineSize = Float2.Max(lineSize, textBlockSize);
}
// Organize text blocks to match the baseline of the line (use ascender)
// Organize text blocks within line
for(int i = context.LineStartTextBlockIndex; i < _textBlocks.Count; i++)
{
ref TextBlock textBlock = ref textBlocks[i];
var offset = lineSize.Y - textBlock.Bounds.Height;
var textBlockFont = textBlock.Style.Font.GetFont();
if (textBlockFont)
offset = lineAscender - textBlockFont.Ascender;
textBlock.Bounds.Location.Y += offset;
var vOffset = lineSize.Y - textBlock.Bounds.Height;
switch (textBlock.Style.Alignment & TextBlockStyle.Alignments.VerticalMask)
{
case TextBlockStyle.Alignments.Baseline:
{
// Match the baseline of the line (use ascender)
var ascender = textBlock.Ascender;
if (ascender <= 0)
{
var textBlockFont = textBlock.Style.Font.GetFont();
if (textBlockFont)
ascender = textBlockFont.Ascender;
}
vOffset = lineAscender - ascender;
textBlock.Bounds.Location.Y += vOffset;
break;
}
case TextBlockStyle.Alignments.Top:
{
textBlock.Bounds.Location.Y = lineOrigin.Y;
break;
}
case TextBlockStyle.Alignments.Middle:
{
textBlock.Bounds.Location.Y = lineOrigin.Y + vOffset * 0.5f;
break;
}
case TextBlockStyle.Alignments.Bottom:
{
textBlock.Bounds.Location.Y = lineOrigin.Y + vOffset;
break;
}
}
}
// Move to the next line

View File

@@ -176,17 +176,60 @@ namespace FlaxEngine.GUI
var font = imageBlock.Style.Font.GetFont();
if (font)
imageBlock.Bounds.Size = new Float2(font.Height);
imageBlock.Bounds.Size.X *= image.Size.X / image.Size.Y; // Keep aspect ration
TryParseNumberTag(ref tag, "width", imageBlock.Bounds.Width, out var width);
imageBlock.Bounds.Size.X *= image.Size.X / image.Size.Y; // Keep original aspect ratio
bool hasWidth = TryParseNumberTag(ref tag, "width", imageBlock.Bounds.Width, out var width);
imageBlock.Bounds.Width = width;
TryParseNumberTag(ref tag, "height", imageBlock.Bounds.Height, out var height);
bool hasHeight = TryParseNumberTag(ref tag, "height", imageBlock.Bounds.Height, out var height);
imageBlock.Bounds.Height = height;
if ((hasHeight || hasWidth) && (hasWidth != hasHeight))
{
// Maintain aspect ratio after scaling by just width or height
if (hasHeight)
imageBlock.Bounds.Size.X = imageBlock.Bounds.Size.Y * image.Size.X / image.Size.Y;
else
imageBlock.Bounds.Size.Y = imageBlock.Bounds.Size.X * image.Size.Y / image.Size.X;
}
TryParseNumberTag(ref tag, "scale", 1.0f, out var scale);
imageBlock.Bounds.Size *= scale;
// Image height defines the ascender so it's placed on the baseline by default
imageBlock.Ascender = imageBlock.Bounds.Size.Y;
context.AddTextBlock(ref imageBlock);
context.Caret.X += imageBlock.Bounds.Size.X;
}
private static void ProcessVAlign(ref ParsingContext context, ref HtmlTag tag)
{
if (tag.IsSlash)
{
context.StyleStack.Pop();
}
else
{
var style = context.StyleStack.Peek();
if (tag.Attributes.TryGetValue(string.Empty, out var valign))
{
style.Alignment &= ~TextBlockStyle.Alignments.VerticalMask;
switch(valign)
{
case "top":
style.Alignment = TextBlockStyle.Alignments.Top;
break;
case "bottom":
style.Alignment = TextBlockStyle.Alignments.Bottom;
break;
case "middle":
style.Alignment = TextBlockStyle.Alignments.Middle;
break;
case "baseline":
style.Alignment = TextBlockStyle.Alignments.Baseline;
break;
}
}
context.StyleStack.Push(style);
}
}
private static Asset FindAsset(string name, System.Type type)
{
@@ -201,16 +244,19 @@ namespace FlaxEngine.GUI
return null;
}
private static void TryParseNumberTag(ref HtmlTag tag, string name, float input, out float output)
private static bool TryParseNumberTag(ref HtmlTag tag, string name, float input, out float output)
{
output = input;
bool used = false;
if (tag.Attributes.TryGetValue(name, out var text))
{
if (float.TryParse(text, out var width))
output = width;
if (text.Length > 1 && text[text.Length - 1] == '%')
output = input * float.Parse(text.Substring(0, text.Length - 1)) / 100.0f;
used = true;
}
return used;
}
}
}

View File

@@ -22,6 +22,11 @@ namespace FlaxEngine.GUI
/// </summary>
public Rectangle Bounds;
/// <summary>
/// Custom ascender value for the line layout (block size above the baseline). Set to 0 to use ascender from the font.
/// </summary>
public float Ascender;
/// <summary>
/// The custom tag.
/// </summary>

View File

@@ -7,52 +7,90 @@ namespace FlaxEngine.GUI
/// </summary>
public struct TextBlockStyle
{
/// <summary>
/// Text block alignments modes.
/// </summary>
public enum Alignments
{
/// <summary>
/// Block will be aligned to the baseline of the text (vertically).
/// </summary>
Baseline,
/// <summary>
/// Block will be aligned to the top edge of the line (vertically).
/// </summary>
Top = 1,
/// <summary>
/// Block will be aligned to center of the line (vertically).
/// </summary>
Middle = 2,
/// <summary>
/// Block will be aligned to the bottom edge of the line (vertically).
/// </summary>
Bottom = 4,
/// <summary>
/// Mask with vertical alignment flags.
/// </summary>
[HideInEditor]
VerticalMask = Top | Middle | Bottom,
}
/// <summary>
/// The text font.
/// </summary>
[EditorOrder(0), Tooltip("The text font.")]
[EditorOrder(0)]
public FontReference Font;
/// <summary>
/// The custom material for the text rendering (must be GUI domain).
/// </summary>
[EditorOrder(10), Tooltip("The custom material for the text rendering (must be GUI domain).")]
[EditorOrder(10)]
public MaterialBase CustomMaterial;
/// <summary>
/// The text color (tint and opacity).
/// </summary>
[EditorOrder(20), Tooltip("The text color (tint and opacity).")]
[EditorOrder(20)]
public Color Color;
/// <summary>
/// The text shadow color (tint and opacity). Set to transparent to disable shadow drawing.
/// </summary>
[EditorOrder(30), Tooltip("The text shadow color (tint and opacity). Set to transparent to disable shadow drawing.")]
[EditorOrder(30)]
public Color ShadowColor;
/// <summary>
/// The text shadow offset from the text location. Set to zero to disable shadow drawing.
/// </summary>
[EditorOrder(40), Tooltip("The text shadow offset from the text location. Set to zero to disable shadow drawing.")]
[EditorOrder(40)]
public Float2 ShadowOffset;
/// <summary>
/// The background brush for the text range.
/// </summary>
[EditorOrder(45), Tooltip("The background brush for the text range.")]
[EditorOrder(45)]
public IBrush BackgroundBrush;
/// <summary>
/// The background brush for the selected text range.
/// </summary>
[EditorOrder(50), Tooltip("The background brush for the selected text range.")]
[EditorOrder(50)]
public IBrush BackgroundSelectedBrush;
/// <summary>
/// The underline line brush.
/// </summary>
[EditorOrder(60), Tooltip("The underline line brush.")]
[EditorOrder(60)]
public IBrush UnderlineBrush;
/// <summary>
/// The text block alignment.
/// </summary>
[EditorOrder(100)]
public Alignments Alignment;
}
}