Browse Source

Fix RichTextBlock Inlines update handling

pull/8626/head
Benedikt Stebner 4 years ago
parent
commit
81b0dce302
  1. 15
      samples/Sandbox/MainWindow.axaml
  2. 46
      samples/Sandbox/MainWindow.axaml.cs
  3. 7
      src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
  4. 2
      src/Avalonia.Base/Media/TextFormatting/TextLine.cs
  5. 7
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  6. 22
      src/Avalonia.Controls/RichTextBlock.cs
  7. 44
      tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs
  8. 12
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

15
samples/Sandbox/MainWindow.axaml

@ -9,6 +9,21 @@
<NumericUpDown Value="{Binding Distance, Mode=TwoWay}"/> <NumericUpDown Value="{Binding Distance, Mode=TwoWay}"/>
<TextBlock Text="{Binding #txtBlock.SelectionStart}"/> <TextBlock Text="{Binding #txtBlock.SelectionStart}"/>
<TextBlock Text="{Binding #txtBlock.SelectionEnd}"/> <TextBlock Text="{Binding #txtBlock.SelectionEnd}"/>
<DockPanel DockPanel.Dock="Top" LastChildFill="False">
<RichTextBlock Name="RichTextBlock"
TextAlignment="Left"
Margin="4,4,4,8"
ClipToBounds="True"
FontSize="30"
MaxLines="3"
TextWrapping="Wrap"
FontWeight="DemiBold"
Text="{Binding Text}"
Inlines="{Binding InlineCollection, Mode=OneWay}">
</RichTextBlock>
<Button Click="Button_OnClick">Update inlines</Button>
<Button Click="TextButton_OnClick">Update text</Button>
</DockPanel>
</StackPanel> </StackPanel>
</Window> </Window>

46
samples/Sandbox/MainWindow.axaml.cs

@ -3,7 +3,9 @@ using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -11,6 +13,8 @@ namespace Sandbox
{ {
public class MainWindow : Window public class MainWindow : Window
{ {
private TestViewModel _dc;
public MainWindow() public MainWindow()
{ {
this.InitializeComponent(); this.InitializeComponent();
@ -27,13 +31,30 @@ namespace Sandbox
var textPresenter = e.NameScope.Find("PART_TextPresenter") as TextPresenter; var textPresenter = e.NameScope.Find("PART_TextPresenter") as TextPresenter;
DataContext = new TestViewModel(textPresenter); _dc = new TestViewModel(textPresenter);
DataContext = _dc;
} }
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
private void Button_OnClick(object? sender, RoutedEventArgs e)
{
_dc.InlineCollection = new InlineCollection
{
new Run(""),
new Run("test3") {FontWeight = Avalonia.Media.FontWeight.Bold},
};
// _dc.Text = "nununu";
}
private void TextButton_OnClick(object? sender, RoutedEventArgs e)
{
_dc.Text = "nununu";
}
} }
public class TestViewModel : ViewModelBase public class TestViewModel : ViewModelBase
@ -46,6 +67,29 @@ namespace Sandbox
_textPresenter = textPresenter; _textPresenter = textPresenter;
} }
private InlineCollection _inlineCollection;
private string _text;
public string Text
{
get => _text;
set
{
_text = value;
RaisePropertyChanged();
}
}
public InlineCollection InlineCollection
{
get => _inlineCollection;
set
{
_inlineCollection = value;
RaisePropertyChanged();
}
}
public double Distance public double Distance
{ {
get => _distance; get => _distance;

7
src/Avalonia.Base/Media/TextFormatting/TextLayout.cs

@ -537,8 +537,13 @@ namespace Avalonia.Media.TextFormatting
/// </summary> /// </summary>
/// <param name="width">The collapsing width.</param> /// <param name="width">The collapsing width.</param>
/// <returns>The <see cref="TextCollapsingProperties"/>.</returns> /// <returns>The <see cref="TextCollapsingProperties"/>.</returns>
private TextCollapsingProperties GetCollapsingProperties(double width) private TextCollapsingProperties? GetCollapsingProperties(double width)
{ {
if(_textTrimming == TextTrimming.None)
{
return null;
}
return _textTrimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, _paragraphProperties.DefaultTextRunProperties)); return _textTrimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, _paragraphProperties.DefaultTextRunProperties));
} }
} }

2
src/Avalonia.Base/Media/TextFormatting/TextLine.cs

@ -153,7 +153,7 @@ namespace Avalonia.Media.TextFormatting
/// <returns> /// <returns>
/// A <see cref="TextLine"/> value that represents a collapsed line that can be displayed. /// A <see cref="TextLine"/> value that represents a collapsed line that can be displayed.
/// </returns> /// </returns>
public abstract TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList); public abstract TextLine Collapse(params TextCollapsingProperties?[] collapsingPropertiesList);
/// <summary> /// <summary>
/// Create a justified line based on justification text properties. /// Create a justified line based on justification text properties.

7
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -119,7 +119,7 @@ namespace Avalonia.Media.TextFormatting
} }
/// <inheritdoc/> /// <inheritdoc/>
public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList) public override TextLine Collapse(params TextCollapsingProperties?[] collapsingPropertiesList)
{ {
if (collapsingPropertiesList.Length == 0) if (collapsingPropertiesList.Length == 0)
{ {
@ -128,6 +128,11 @@ namespace Avalonia.Media.TextFormatting
var collapsingProperties = collapsingPropertiesList[0]; var collapsingProperties = collapsingPropertiesList[0];
if(collapsingProperties is null)
{
return this;
}
var collapsedRuns = collapsingProperties.Collapse(this); var collapsedRuns = collapsingProperties.Collapse(this);
if (collapsedRuns is null) if (collapsedRuns is null)

22
src/Avalonia.Controls/RichTextBlock.cs

@ -44,8 +44,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Inlines"/> property. /// Defines the <see cref="Inlines"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<InlineCollection> InlinesProperty = public static readonly StyledProperty<InlineCollection?> InlinesProperty =
AvaloniaProperty.Register<RichTextBlock, InlineCollection>( AvaloniaProperty.Register<RichTextBlock, InlineCollection?>(
nameof(Inlines)); nameof(Inlines));
public static readonly DirectProperty<TextBox, bool> CanCopyProperty = public static readonly DirectProperty<TextBox, bool> CanCopyProperty =
@ -138,7 +138,7 @@ namespace Avalonia.Controls
/// Gets or sets the inlines. /// Gets or sets the inlines.
/// </summary> /// </summary>
[Content] [Content]
public InlineCollection Inlines public InlineCollection? Inlines
{ {
get => GetValue(InlinesProperty); get => GetValue(InlinesProperty);
set => SetValue(InlinesProperty, value); set => SetValue(InlinesProperty, value);
@ -159,7 +159,7 @@ namespace Avalonia.Controls
remove => RemoveHandler(CopyingToClipboardEvent, value); remove => RemoveHandler(CopyingToClipboardEvent, value);
} }
internal bool HasComplexContent => Inlines.Count > 0; internal bool HasComplexContent => Inlines != null && Inlines.Count > 0;
/// <summary> /// <summary>
/// Copies the current selection to the Clipboard. /// Copies the current selection to the Clipboard.
@ -260,18 +260,18 @@ namespace Avalonia.Controls
{ {
if (!string.IsNullOrEmpty(_text)) if (!string.IsNullOrEmpty(_text))
{ {
Inlines.Add(_text); Inlines?.Add(_text);
_text = null; _text = null;
} }
Inlines.Add(text); Inlines?.Add(text);
} }
} }
protected override string? GetText() protected override string? GetText()
{ {
return _text ?? Inlines.Text; return _text ?? Inlines?.Text;
} }
protected override void SetText(string? text) protected override void SetText(string? text)
@ -301,10 +301,10 @@ namespace Avalonia.Controls
ITextSource textSource; ITextSource textSource;
var inlines = Inlines;
if (HasComplexContent) if (HasComplexContent)
{ {
var inlines = Inlines!;
var textRuns = new List<TextRun>(); var textRuns = new List<TextRun>();
foreach (var inline in inlines) foreach (var inline in inlines)
@ -537,7 +537,7 @@ namespace Avalonia.Controls
switch (change.Property.Name) switch (change.Property.Name)
{ {
case nameof(InlinesProperty): case nameof(Inlines):
{ {
OnInlinesChanged(change.OldValue as InlineCollection, change.NewValue as InlineCollection); OnInlinesChanged(change.OldValue as InlineCollection, change.NewValue as InlineCollection);
InvalidateTextLayout(); InvalidateTextLayout();
@ -553,7 +553,7 @@ namespace Avalonia.Controls
return ""; return "";
} }
var text = Inlines.Text ?? Text; var text = GetText();
if (string.IsNullOrEmpty(text)) if (string.IsNullOrEmpty(text))
{ {

44
tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs

@ -48,5 +48,49 @@ namespace Avalonia.Controls.UnitTests
Assert.False(target.IsMeasureValid); Assert.False(target.IsMeasureValid);
} }
} }
[Fact]
public void Changing_Inlines_Should_Invalidate_Measure()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new RichTextBlock();
var inlines = new InlineCollection { new Run("Hello") };
target.Measure(Size.Infinity);
Assert.True(target.IsMeasureValid);
target.Inlines = inlines;
Assert.False(target.IsMeasureValid);
}
}
[Fact]
public void Changing_Inlines_Should_Reset_Inlines_Parent()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new RichTextBlock();
var run = new Run("Hello");
target.Inlines.Add(run);
target.Measure(Size.Infinity);
Assert.True(target.IsMeasureValid);
target.Inlines = null;
Assert.Null(run.Parent);
target.Inlines = new InlineCollection { run };
Assert.Equal(target, run.Parent);
}
}
} }
} }

12
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

@ -993,9 +993,11 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var currentX = 0.0; var currentX = 0.0;
for (int j = 0; j < clusters.Count; j++) var cluster = text.Length;
{
var cluster = clusters[j]; for (int j = 0; j < clusters.Count - 1; j++)
{
var glyphAdvance = glyphAdvances[j];
var characterHit = textLine.GetCharacterHitFromDistance(currentX); var characterHit = textLine.GetCharacterHitFromDistance(currentX);
@ -1005,9 +1007,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(currentX, distance); Assert.Equal(currentX, distance);
var glyphAdvance = glyphAdvances[j];
currentX += glyphAdvance; currentX += glyphAdvance;
cluster = clusters[j];
} }
} }
} }

Loading…
Cancel
Save