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}"/>
<TextBlock Text="{Binding #txtBlock.SelectionStart}"/>
<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>
</Window>

46
samples/Sandbox/MainWindow.axaml.cs

@ -3,7 +3,9 @@ using System.Diagnostics;
using System.Runtime.CompilerServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Presenters;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.VisualTree;
@ -11,6 +13,8 @@ namespace Sandbox
{
public class MainWindow : Window
{
private TestViewModel _dc;
public MainWindow()
{
this.InitializeComponent();
@ -27,13 +31,30 @@ namespace Sandbox
var textPresenter = e.NameScope.Find("PART_TextPresenter") as TextPresenter;
DataContext = new TestViewModel(textPresenter);
_dc = new TestViewModel(textPresenter);
DataContext = _dc;
}
private void InitializeComponent()
{
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
@ -46,6 +67,29 @@ namespace Sandbox
_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
{
get => _distance;

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

@ -537,8 +537,13 @@ namespace Avalonia.Media.TextFormatting
/// </summary>
/// <param name="width">The collapsing width.</param>
/// <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));
}
}

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

@ -153,7 +153,7 @@ namespace Avalonia.Media.TextFormatting
/// <returns>
/// A <see cref="TextLine"/> value that represents a collapsed line that can be displayed.
/// </returns>
public abstract TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList);
public abstract TextLine Collapse(params TextCollapsingProperties?[] collapsingPropertiesList);
/// <summary>
/// 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/>
public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList)
public override TextLine Collapse(params TextCollapsingProperties?[] collapsingPropertiesList)
{
if (collapsingPropertiesList.Length == 0)
{
@ -128,6 +128,11 @@ namespace Avalonia.Media.TextFormatting
var collapsingProperties = collapsingPropertiesList[0];
if(collapsingProperties is null)
{
return this;
}
var collapsedRuns = collapsingProperties.Collapse(this);
if (collapsedRuns is null)

22
src/Avalonia.Controls/RichTextBlock.cs

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

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

@ -48,5 +48,49 @@ namespace Avalonia.Controls.UnitTests
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;
for (int j = 0; j < clusters.Count; j++)
{
var cluster = clusters[j];
var cluster = text.Length;
for (int j = 0; j < clusters.Count - 1; j++)
{
var glyphAdvance = glyphAdvances[j];
var characterHit = textLine.GetCharacterHitFromDistance(currentX);
@ -1005,9 +1007,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(currentX, distance);
var glyphAdvance = glyphAdvances[j];
currentX += glyphAdvance;
cluster = clusters[j];
}
}
}

Loading…
Cancel
Save