using System;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Documents;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
///
/// A control that displays a block of text.
///
public class TextBlock : Control
{
///
/// Defines the property.
///
public static readonly StyledProperty BackgroundProperty =
Border.BackgroundProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty PaddingProperty =
Decorator.PaddingProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty FontFamilyProperty =
TextElement.FontFamilyProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty FontSizeProperty =
TextElement.FontSizeProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty FontStyleProperty =
TextElement.FontStyleProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty FontWeightProperty =
TextElement.FontWeightProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty FontStretchProperty =
TextElement.FontStretchProperty.AddOwner();
///
/// Defines the property.
///
public static readonly StyledProperty ForegroundProperty =
TextElement.ForegroundProperty.AddOwner();
///
/// DependencyProperty for property.
///
public static readonly AttachedProperty BaselineOffsetProperty =
AvaloniaProperty.RegisterAttached(
nameof(BaselineOffset),
0,
true);
///
/// Defines the property.
///
public static readonly AttachedProperty LineHeightProperty =
AvaloniaProperty.RegisterAttached(
nameof(LineHeight),
double.NaN,
validate: IsValidLineHeight,
inherits: true);
///
/// Defines the property.
///
public static readonly AttachedProperty MaxLinesProperty =
AvaloniaProperty.RegisterAttached(
nameof(MaxLines),
validate: IsValidMaxLines,
inherits: true);
///
/// Defines the property.
///
public static readonly DirectProperty TextProperty =
AvaloniaProperty.RegisterDirect(
nameof(Text),
o => o.Text,
(o, v) => o.Text = v);
///
/// Defines the property.
///
public static readonly AttachedProperty TextAlignmentProperty =
AvaloniaProperty.RegisterAttached(
nameof(TextAlignment),
defaultValue: TextAlignment.Start,
inherits: true);
///
/// Defines the property.
///
public static readonly AttachedProperty TextWrappingProperty =
AvaloniaProperty.RegisterAttached(nameof(TextWrapping),
inherits: true);
///
/// Defines the property.
///
public static readonly AttachedProperty TextTrimmingProperty =
AvaloniaProperty.RegisterAttached(nameof(TextTrimming),
defaultValue: TextTrimming.None,
inherits: true);
///
/// Defines the property.
///
public static readonly StyledProperty TextDecorationsProperty =
AvaloniaProperty.Register(nameof(TextDecorations));
protected string? _text;
protected TextLayout? _textLayout;
protected Size _constraint;
///
/// Initializes static members of the class.
///
static TextBlock()
{
ClipToBoundsProperty.OverrideDefaultValue(true);
AffectsRender(BackgroundProperty, ForegroundProperty);
}
///
/// Gets the used to render the text.
///
public TextLayout TextLayout
{
get
{
return _textLayout ??= CreateTextLayout(_text);
}
}
///
/// Gets or sets the padding to place around the .
///
public Thickness Padding
{
get { return GetValue(PaddingProperty); }
set { SetValue(PaddingProperty, value); }
}
///
/// Gets or sets a brush used to paint the control's background.
///
public IBrush? Background
{
get { return GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
}
///
/// Gets or sets the text.
///
public string? Text
{
get => GetText();
set => SetText(value);
}
///
/// Gets or sets the font family used to draw the control's text.
///
public FontFamily FontFamily
{
get { return GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
///
/// Gets or sets the size of the control's text in points.
///
public double FontSize
{
get { return GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
///
/// Gets or sets the font style used to draw the control's text.
///
public FontStyle FontStyle
{
get { return GetValue(FontStyleProperty); }
set { SetValue(FontStyleProperty, value); }
}
///
/// Gets or sets the font weight used to draw the control's text.
///
public FontWeight FontWeight
{
get { return GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); }
}
///
/// Gets or sets the font stretch used to draw the control's text.
///
public FontStretch FontStretch
{
get { return GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
///
/// Gets or sets the brush used to draw the control's text and other foreground elements.
///
public IBrush? Foreground
{
get { return GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); }
}
///
/// Gets or sets the height of each line of content.
///
public double LineHeight
{
get => GetValue(LineHeightProperty);
set => SetValue(LineHeightProperty, value);
}
///
/// Gets or sets the maximum number of text lines.
///
public int MaxLines
{
get => GetValue(MaxLinesProperty);
set => SetValue(MaxLinesProperty, value);
}
///
/// Gets or sets the control's text wrapping mode.
///
public TextWrapping TextWrapping
{
get { return GetValue(TextWrappingProperty); }
set { SetValue(TextWrappingProperty, value); }
}
///
/// Gets or sets the control's text trimming mode.
///
public TextTrimming TextTrimming
{
get { return GetValue(TextTrimmingProperty); }
set { SetValue(TextTrimmingProperty, value); }
}
///
/// Gets or sets the text alignment.
///
public TextAlignment TextAlignment
{
get { return GetValue(TextAlignmentProperty); }
set { SetValue(TextAlignmentProperty, value); }
}
///
/// Gets or sets the text decorations.
///
public TextDecorationCollection? TextDecorations
{
get => GetValue(TextDecorationsProperty);
set => SetValue(TextDecorationsProperty, value);
}
protected override bool BypassFlowDirectionPolicies => true;
///
/// The BaselineOffset property provides an adjustment to baseline offset
///
public double BaselineOffset
{
get { return (double)GetValue(BaselineOffsetProperty); }
set { SetValue(BaselineOffsetProperty, value); }
}
///
/// Reads the attached property from the given element
///
/// The element to which to read the attached property.
public static double GetBaselineOffset(Control control)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(BaselineOffsetProperty);
}
///
/// Writes the attached property BaselineOffset to the given element.
///
/// The element to which to write the attached property.
/// The property value to set
public static void SetBaselineOffset(Control control, double value)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(BaselineOffsetProperty, value);
}
///
/// Reads the attached property from the given element
///
/// The element to which to read the attached property.
public static TextAlignment GetTextAlignment(Control control)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(TextAlignmentProperty);
}
///
/// Writes the attached property BaselineOffset to the given element.
///
/// The element to which to write the attached property.
/// The property value to set
public static void SetTextAlignment(Control control, TextAlignment alignment)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(TextAlignmentProperty, alignment);
}
///
/// Reads the attached property from the given element
///
/// The element to which to read the attached property.
public static TextWrapping GetTextWrapping(Control control)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(TextWrappingProperty);
}
///
/// Writes the attached property BaselineOffset to the given element.
///
/// The element to which to write the attached property.
/// The property value to set
public static void SetTextWrapping(Control control, TextWrapping wrapping)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(TextWrappingProperty, wrapping);
}
///
/// Reads the attached property from the given element
///
/// The element to which to read the attached property.
public static TextTrimming GetTextTrimming(Control control)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(TextTrimmingProperty);
}
///
/// Writes the attached property BaselineOffset to the given element.
///
/// The element to which to write the attached property.
/// The property value to set
public static void SetTextTrimming(Control control, TextTrimming trimming)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(TextTrimmingProperty, trimming);
}
///
/// Reads the attached property from the given element
///
/// The element to which to read the attached property.
public static double GetLineHeight(Control control)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(LineHeightProperty);
}
///
/// Writes the attached property BaselineOffset to the given element.
///
/// The element to which to write the attached property.
/// The property value to set
public static void SetLineHeight(Control control, double height)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(LineHeightProperty, height);
}
///
/// Reads the attached property from the given element
///
/// The element to which to read the attached property.
public static int GetMaxLines(Control control)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(MaxLinesProperty);
}
///
/// Writes the attached property BaselineOffset to the given element.
///
/// The element to which to write the attached property.
/// The property value to set
public static void SetMaxLines(Control control, int maxLines)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(MaxLinesProperty, maxLines);
}
public void Add(string text)
{
_text = text;
}
///
/// Renders the to a drawing context.
///
/// The drawing context.
public override void Render(DrawingContext context)
{
var background = Background;
if (background != null)
{
context.FillRectangle(background, new Rect(Bounds.Size));
}
var padding = Padding;
var top = padding.Top;
var textHeight = TextLayout.Bounds.Height;
if (Bounds.Height < textHeight)
{
switch (VerticalAlignment)
{
case VerticalAlignment.Center:
top += (Bounds.Height - textHeight) / 2;
break;
case VerticalAlignment.Bottom:
top += (Bounds.Height - textHeight);
break;
}
}
RenderTextLayout(context, new Point(padding.Left, top));
}
protected virtual void RenderTextLayout(DrawingContext context, Point origin)
{
TextLayout.Draw(context, origin);
}
protected virtual string? GetText()
{
return _text;
}
protected virtual void SetText(string? text)
{
SetAndRaise(TextProperty, ref _text, text);
}
///
/// Creates the used to render the text.
///
/// A object.
protected virtual TextLayout CreateTextLayout(string? text)
{
var defaultProperties = new GenericTextRunProperties(
new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
FontSize,
TextDecorations,
Foreground);
var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false,
defaultProperties, TextWrapping, LineHeight, 0);
return new TextLayout(
new SimpleTextSource((text ?? "").AsMemory(), defaultProperties),
paragraphProperties,
TextTrimming,
_constraint.Width,
_constraint.Height,
maxLines: MaxLines,
lineHeight: LineHeight);
}
///
/// Invalidates .
///
protected void InvalidateTextLayout()
{
_textLayout = null;
InvalidateMeasure();
}
protected override Size MeasureOverride(Size availableSize)
{
var scale = LayoutHelper.GetLayoutScale(this);
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
_constraint = availableSize.Deflate(padding);
_textLayout = null;
InvalidateArrange();
var measuredSize = TextLayout.Bounds.Size.Inflate(padding);
return measuredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
var textWidth = Math.Ceiling(TextLayout.Bounds.Width);
if(finalSize.Width < textWidth)
{
finalSize = finalSize.WithWidth(textWidth);
}
if (MathUtilities.AreClose(_constraint.Width, finalSize.Width))
{
return finalSize;
}
var scale = LayoutHelper.GetLayoutScale(this);
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
_constraint = new Size(Math.Ceiling(finalSize.Deflate(padding).Width), double.PositiveInfinity);
_textLayout = null;
return finalSize;
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new TextBlockAutomationPeer(this);
}
private static bool IsValidMaxLines(int maxLines) => maxLines >= 0;
private static bool IsValidLineHeight(double lineHeight) => double.IsNaN(lineHeight) || lineHeight > 0;
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
switch (change.Property.Name)
{
case nameof (FontSize):
case nameof (FontWeight):
case nameof (FontStyle):
case nameof (FontFamily):
case nameof (FontStretch):
case nameof (TextWrapping):
case nameof (TextTrimming):
case nameof (TextAlignment):
case nameof (FlowDirection):
case nameof (Padding):
case nameof (LineHeight):
case nameof (MaxLines):
case nameof (Text):
case nameof (TextDecorations):
case nameof (Foreground):
{
InvalidateTextLayout();
break;
}
}
}
protected readonly struct SimpleTextSource : ITextSource
{
private readonly ReadOnlySlice _text;
private readonly TextRunProperties _defaultProperties;
public SimpleTextSource(ReadOnlySlice text, TextRunProperties defaultProperties)
{
_text = text;
_defaultProperties = defaultProperties;
}
public TextRun? GetTextRun(int textSourceIndex)
{
if (textSourceIndex > _text.Length)
{
return new TextEndOfParagraph();
}
var runText = _text.Skip(textSourceIndex);
if (runText.IsEmpty)
{
return new TextEndOfParagraph();
}
return new TextCharacters(runText, _defaultProperties);
}
}
}
}