Pārlūkot izejas kodu

Fix RichTextBlock Inlines update handling

Benedikt Stebner 3 gadi atpakaļ
vecāks
revīzija
81b0dce302

+ 15 - 0
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>

+ 45 - 1
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; 

+ 6 - 1
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));
         }
     }

+ 1 - 1
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.

+ 6 - 1
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)

+ 11 - 11
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 - 0
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);
+            }
+        }
     }
 }

+ 7 - 5
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];
                     }
                 }
             }