diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml
index fe9455bd29..cb49ba96c6 100644
--- a/samples/ControlCatalog/Pages/TextBlockPage.xaml
+++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml
@@ -118,7 +118,7 @@
-
+
This is a
TextBlock
with several
@@ -126,7 +126,7 @@
using a variety of styles
.
-
+
diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs
index a76222385e..0cbf272297 100644
--- a/src/Avalonia.Controls/Documents/InlineCollection.cs
+++ b/src/Avalonia.Controls/Documents/InlineCollection.cs
@@ -12,39 +12,55 @@ namespace Avalonia.Controls.Documents
[WhitespaceSignificantCollection]
public class InlineCollection : AvaloniaList
{
- private readonly IInlineHost? _host;
+ private ILogical? _parent;
+ private IInlineHost? _inlineHost;
private string? _text = string.Empty;
///
/// Initializes a new instance of the class.
///
- public InlineCollection(ILogical parent) : this(parent, null) { }
-
- ///
- /// Initializes a new instance of the class.
- ///
- internal InlineCollection(ILogical parent, IInlineHost? host = null) : base(0)
+ public InlineCollection()
{
- _host = host;
-
ResetBehavior = ResetBehavior.Remove;
this.ForEachItem(
x =>
{
- ((ISetLogicalParent)x).SetParent(parent);
- x.InlineHost = host;
- host?.Invalidate();
+ ((ISetLogicalParent)x).SetParent(Parent);
+ x.InlineHost = InlineHost;
+ Invalidate();
},
x =>
{
((ISetLogicalParent)x).SetParent(null);
- x.InlineHost = host;
- host?.Invalidate();
+ x.InlineHost = InlineHost;
+ Invalidate();
},
() => throw new NotSupportedException());
}
+ internal ILogical? Parent
+ {
+ get => _parent;
+ set
+ {
+ _parent = value;
+
+ OnParentChanged(value);
+ }
+ }
+
+ internal IInlineHost? InlineHost
+ {
+ get => _inlineHost;
+ set
+ {
+ _inlineHost = value;
+
+ OnInlineHostChanged(value);
+ }
+ }
+
public bool HasComplexContent => Count > 0;
///
@@ -57,14 +73,16 @@ namespace Avalonia.Controls.Documents
{
get
{
+ return _text;
+
if (!HasComplexContent)
{
return _text;
}
-
+
var builder = new StringBuilder();
- foreach(var inline in this)
+ foreach (var inline in this)
{
inline.AppendText(builder);
}
@@ -100,7 +118,7 @@ namespace Avalonia.Controls.Documents
}
else
{
- _text += text;
+ _text = text;
}
}
@@ -136,14 +154,30 @@ namespace Avalonia.Controls.Documents
///
protected void Invalidate()
{
- if(_host != null)
+ if(InlineHost != null)
{
- _host.Invalidate();
+ InlineHost.Invalidate();
}
Invalidated?.Invoke(this, EventArgs.Empty);
}
private void Invalidate(object? sender, EventArgs e) => Invalidate();
+
+ private void OnParentChanged(ILogical? parent)
+ {
+ foreach(var child in this)
+ {
+ ((ISetLogicalParent)child).SetParent(parent);
+ }
+ }
+
+ private void OnInlineHostChanged(IInlineHost? inlineHost)
+ {
+ foreach (var child in this)
+ {
+ child.InlineHost = inlineHost;
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls/Documents/Span.cs b/src/Avalonia.Controls/Documents/Span.cs
index bd1b4fc5e1..c2576ec231 100644
--- a/src/Avalonia.Controls/Documents/Span.cs
+++ b/src/Avalonia.Controls/Documents/Span.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Media.TextFormatting;
-using Avalonia.Metadata;
namespace Avalonia.Controls.Documents
{
@@ -14,25 +13,42 @@ namespace Avalonia.Controls.Documents
///
/// Defines the property.
///
- public static readonly DirectProperty InlinesProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(Inlines),
- o => o.Inlines);
+ public static readonly StyledProperty InlinesProperty =
+ AvaloniaProperty.Register(
+ nameof(Inlines));
- ///
- /// Initializes a new instance of a Span element.
- ///
public Span()
{
- Inlines = new InlineCollection(this);
- Inlines.Invalidated += (s, e) => InlineHost?.Invalidate();
+ Inlines = new InlineCollection
+ {
+ Parent = this
+ };
}
///
/// Gets or sets the inlines.
- ///
- [Content]
- public InlineCollection Inlines { get; }
+ ///
+ public InlineCollection Inlines
+ {
+ get => GetValue(InlinesProperty);
+ set => SetValue(InlinesProperty, value);
+ }
+
+ public void Add(Inline inline)
+ {
+ if (Inlines is not null)
+ {
+ Inlines.Add(inline);
+ }
+ }
+
+ public void Add(string text)
+ {
+ if (Inlines is not null)
+ {
+ Inlines.Add(text);
+ }
+ }
internal override void BuildTextRun(IList textRuns)
{
@@ -52,7 +68,7 @@ namespace Avalonia.Controls.Documents
var textCharacters = new TextCharacters(text.AsMemory(), textRunProperties);
textRuns.Add(textCharacters);
- }
+ }
}
}
@@ -71,5 +87,45 @@ namespace Avalonia.Controls.Documents
stringBuilder.Append(text);
}
}
+
+ 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);
+ InlineHost?.Invalidate();
+ break;
+ }
+ }
+
+ internal override void OnInlinesHostChanged(IInlineHost? oldValue, IInlineHost? newValue)
+ {
+ base.OnInlinesHostChanged(oldValue, newValue);
+
+ if(Inlines is not null)
+ {
+ Inlines.InlineHost = newValue;
+ }
+ }
+
+ private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue)
+ {
+ if (oldValue is not null)
+ {
+ oldValue.Parent = null;
+ oldValue.InlineHost = null;
+ oldValue.Invalidated -= (s, e) => InlineHost?.Invalidate();
+ }
+
+ if (newValue is not null)
+ {
+ newValue.Parent = this;
+ newValue.InlineHost = InlineHost;
+ newValue.Invalidated += (s, e) => InlineHost?.Invalidate();
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls/Documents/TextElement.cs b/src/Avalonia.Controls/Documents/TextElement.cs
index f228519e60..e75fd87615 100644
--- a/src/Avalonia.Controls/Documents/TextElement.cs
+++ b/src/Avalonia.Controls/Documents/TextElement.cs
@@ -67,6 +67,8 @@ namespace Avalonia.Controls.Documents
Brushes.Black,
inherits: true);
+ private IInlineHost? _inlineHost;
+
///
/// Gets or sets a brush used to paint the control's background.
///
@@ -250,7 +252,21 @@ namespace Avalonia.Controls.Documents
control.SetValue(ForegroundProperty, value);
}
- internal IInlineHost? InlineHost { get; set; }
+ internal IInlineHost? InlineHost
+ {
+ get => _inlineHost;
+ set
+ {
+ var oldValue = _inlineHost;
+ _inlineHost = value;
+ OnInlinesHostChanged(oldValue, value);
+ }
+ }
+
+ internal virtual void OnInlinesHostChanged(IInlineHost? oldValue, IInlineHost? newValue)
+ {
+
+ }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs
new file mode 100644
index 0000000000..16d0254f4a
--- /dev/null
+++ b/src/Avalonia.Controls/RichTextBlock.cs
@@ -0,0 +1,199 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls.Documents;
+using Avalonia.Media;
+using Avalonia.Media.TextFormatting;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// A control that displays a block of text.
+ ///
+ public class RichTextBlock : TextBlock, IInlineHost
+ {
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty InlinesProperty =
+ AvaloniaProperty.Register(
+ nameof(Inlines));
+
+ public RichTextBlock()
+ {
+ Inlines = new InlineCollection
+ {
+ Parent = this,
+ InlineHost = this
+ };
+ }
+
+ ///
+ /// Gets or sets the inlines.
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Creates the used to render the text.
+ ///
+ /// The constraint of the text.
+ /// The text to format.
+ /// A object.
+ 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();
+
+ 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 _textRuns;
+
+ public InlinesTextSource(IReadOnlyList 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;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs
index 1a69d1218c..87966e9a6f 100644
--- a/src/Avalonia.Controls/TextBlock.cs
+++ b/src/Avalonia.Controls/TextBlock.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Text;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Documents;
using Avalonia.Layout;
@@ -14,7 +13,7 @@ namespace Avalonia.Controls
///
/// A control that displays a block of text.
///
- public class TextBlock : Control, IInlineHost
+ public class TextBlock : Control
{
///
/// Defines the property.
@@ -101,14 +100,6 @@ namespace Avalonia.Controls
o => o.Text,
(o, v) => o.Text = v);
- ///
- /// Defines the property.
- ///
- public static readonly DirectProperty InlinesProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(Inlines),
- o => o.Inlines);
-
///
/// Defines the property.
///
@@ -137,6 +128,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty TextDecorationsProperty =
AvaloniaProperty.Register(nameof(TextDecorations));
+ private string? _text;
private TextLayout? _textLayout;
private Size _constraint;
@@ -150,14 +142,6 @@ namespace Avalonia.Controls
AffectsRender(BackgroundProperty, ForegroundProperty);
}
- ///
- /// Initializes a new instance of the class.
- ///
- public TextBlock()
- {
- Inlines = new InlineCollection(this, this);
- }
-
///
/// Gets the used to render the text.
///
@@ -165,7 +149,7 @@ namespace Avalonia.Controls
{
get
{
- return _textLayout ?? (_textLayout = CreateTextLayout(_constraint, Text));
+ return _textLayout ??= CreateTextLayout(_constraint, Text);
}
}
@@ -192,28 +176,13 @@ namespace Avalonia.Controls
///
public string? Text
{
- get => Inlines.Text;
+ get => _text;
set
{
- var old = Text;
-
- if (value == old)
- {
- return;
- }
-
- Inlines.Text = value;
-
- RaisePropertyChanged(TextProperty, old, value);
+ SetAndRaise(TextProperty, ref _text, value);
}
}
- ///
- /// Gets the inlines.
- ///
- [Content]
- public InlineCollection Inlines { get; }
-
///
/// Gets or sets the font family used to draw the control's text.
///
@@ -333,6 +302,11 @@ namespace Avalonia.Controls
set { SetValue(BaselineOffsetProperty, value); }
}
+ public void Add(string text)
+ {
+ Text = text;
+ }
+
///
/// Reads the attached property from the given element
///
@@ -559,26 +533,8 @@ namespace Avalonia.Controls
var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false,
defaultProperties, TextWrapping, LineHeight, 0);
- ITextSource textSource;
-
- if (Inlines.HasComplexContent)
- {
- var textRuns = new List();
-
- foreach (var inline in Inlines)
- {
- inline.BuildTextRun(textRuns);
- }
-
- textSource = new InlinesTextSource(textRuns);
- }
- else
- {
- textSource = new SimpleTextSource((text ?? "").AsMemory(), defaultProperties);
- }
-
return new TextLayout(
- textSource,
+ new SimpleTextSource((text ?? "").AsMemory(), defaultProperties),
paragraphProperties,
TextTrimming,
constraint.Width,
@@ -599,11 +555,6 @@ namespace Avalonia.Controls
protected override Size MeasureOverride(Size availableSize)
{
- if (!Inlines.HasComplexContent && string.IsNullOrEmpty(Text))
- {
- return new Size();
- }
-
var scale = LayoutHelper.GetLayoutScale(this);
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
@@ -683,57 +634,7 @@ namespace Avalonia.Controls
}
}
- private void InlinesChanged(object? sender, EventArgs 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 _textRuns;
-
- public InlinesTextSource(IReadOnlyList 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;
- }
- }
-
- private readonly struct SimpleTextSource : ITextSource
+ protected readonly struct SimpleTextSource : ITextSource
{
private readonly ReadOnlySlice _text;
private readonly TextRunProperties _defaultProperties;