6 changed files with 353 additions and 147 deletions
@ -0,0 +1,199 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Controls.Documents; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Media.TextFormatting; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A control that displays a block of text.
|
||||
|
/// </summary>
|
||||
|
public class RichTextBlock : TextBlock, IInlineHost |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="Inlines"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<InlineCollection> InlinesProperty = |
||||
|
AvaloniaProperty.Register<RichTextBlock, InlineCollection>( |
||||
|
nameof(Inlines)); |
||||
|
|
||||
|
public RichTextBlock() |
||||
|
{ |
||||
|
Inlines = new InlineCollection |
||||
|
{ |
||||
|
Parent = this, |
||||
|
InlineHost = this |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the inlines.
|
||||
|
/// </summary>
|
||||
|
public InlineCollection Inlines |
||||
|
{ |
||||
|
get => GetValue(InlinesProperty); |
||||
|
set => SetValue(InlinesProperty, value); |
||||
|
} |
||||
|
|
||||
|
public void Add(Inline inline) |
||||
|
{ |
||||
|
if (Inlines is not null) |
||||
|
{ |
||||
|
Inlines.Add(inline); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public new void Add(string text) |
||||
|
{ |
||||
|
if (Inlines is not null) |
||||
|
{ |
||||
|
Inlines.Add(text); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates the <see cref="TextLayout"/> used to render the text.
|
||||
|
/// </summary>
|
||||
|
/// <param name="constraint">The constraint of the text.</param>
|
||||
|
/// <param name="text">The text to format.</param>
|
||||
|
/// <returns>A <see cref="TextLayout"/> object.</returns>
|
||||
|
protected override TextLayout CreateTextLayout(Size constraint, 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); |
||||
|
|
||||
|
ITextSource textSource; |
||||
|
|
||||
|
var inlines = Inlines; |
||||
|
|
||||
|
if (inlines is not null && inlines.HasComplexContent) |
||||
|
{ |
||||
|
var textRuns = new List<TextRun>(); |
||||
|
|
||||
|
foreach (var inline in inlines) |
||||
|
{ |
||||
|
inline.BuildTextRun(textRuns); |
||||
|
} |
||||
|
|
||||
|
textSource = new InlinesTextSource(textRuns); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
textSource = new SimpleTextSource((text ?? "").AsMemory(), defaultProperties); |
||||
|
} |
||||
|
|
||||
|
return new TextLayout( |
||||
|
textSource, |
||||
|
paragraphProperties, |
||||
|
TextTrimming, |
||||
|
constraint.Width, |
||||
|
constraint.Height, |
||||
|
maxLines: MaxLines, |
||||
|
lineHeight: LineHeight); |
||||
|
} |
||||
|
|
||||
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) |
||||
|
{ |
||||
|
base.OnPropertyChanged(change); |
||||
|
|
||||
|
switch (change.Property.Name) |
||||
|
{ |
||||
|
case nameof(InlinesProperty): |
||||
|
{ |
||||
|
OnInlinesChanged(change.OldValue as InlineCollection, change.NewValue as InlineCollection); |
||||
|
InvalidateTextLayout(); |
||||
|
break; |
||||
|
} |
||||
|
case nameof(TextProperty): |
||||
|
{ |
||||
|
OnTextChanged(change.OldValue as string, change.NewValue as string); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void OnTextChanged(string? oldValue, string? newValue) |
||||
|
{ |
||||
|
if (oldValue == newValue) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (Inlines is null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Inlines.Text = newValue; |
||||
|
} |
||||
|
|
||||
|
private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue) |
||||
|
{ |
||||
|
if (oldValue is not null) |
||||
|
{ |
||||
|
oldValue.Parent = null; |
||||
|
oldValue.InlineHost = null; |
||||
|
oldValue.Invalidated -= (s, e) => InvalidateTextLayout(); |
||||
|
} |
||||
|
|
||||
|
if (newValue is not null) |
||||
|
{ |
||||
|
newValue.Parent = this; |
||||
|
newValue.InlineHost = this; |
||||
|
newValue.Invalidated += (s, e) => InvalidateTextLayout(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void IInlineHost.AddVisualChild(IControl child) |
||||
|
{ |
||||
|
if (child.VisualParent == null) |
||||
|
{ |
||||
|
VisualChildren.Add(child); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void IInlineHost.Invalidate() |
||||
|
{ |
||||
|
InvalidateTextLayout(); |
||||
|
} |
||||
|
|
||||
|
private readonly struct InlinesTextSource : ITextSource |
||||
|
{ |
||||
|
private readonly IReadOnlyList<TextRun> _textRuns; |
||||
|
|
||||
|
public InlinesTextSource(IReadOnlyList<TextRun> textRuns) |
||||
|
{ |
||||
|
_textRuns = textRuns; |
||||
|
} |
||||
|
|
||||
|
public TextRun? GetTextRun(int textSourceIndex) |
||||
|
{ |
||||
|
var currentPosition = 0; |
||||
|
|
||||
|
foreach (var textRun in _textRuns) |
||||
|
{ |
||||
|
if (textRun.TextSourceLength == 0) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (currentPosition >= textSourceIndex) |
||||
|
{ |
||||
|
return textRun; |
||||
|
} |
||||
|
|
||||
|
currentPosition += textRun.TextSourceLength; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue