Skip to content

Commit 0c2c3f1

Browse files
authored
Fix InlineUIContainer focus (#14590)
* Fix TextBlock MeasureOverride visual child handling * Make sure InlineUIContainer's child retains focus on measure * Resolve merge error
1 parent ab71f38 commit 0c2c3f1

File tree

5 files changed

+103
-29
lines changed

5 files changed

+103
-29
lines changed
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
using Avalonia.LogicalTree;
1+
using Avalonia.Collections;
2+
using Avalonia.LogicalTree;
23

34
namespace Avalonia.Controls.Documents
45
{
56
internal interface IInlineHost : ILogical
67
{
78
void Invalidate();
9+
10+
IAvaloniaList<Visual> VisualChildren { get; }
811
}
912
}

src/Avalonia.Controls/Documents/InlineCollection.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,27 @@ public InlineCollection()
2626
x =>
2727
{
2828
x.InlineHost = InlineHost;
29+
2930
LogicalChildren?.Add(x);
31+
32+
if (x is InlineUIContainer container)
33+
{
34+
InlineHost?.VisualChildren.Add(container.Child);
35+
}
36+
3037
Invalidate();
3138
},
3239
x =>
3340
{
3441
LogicalChildren?.Remove(x);
35-
x.InlineHost = InlineHost;
42+
43+
if(x is InlineUIContainer container)
44+
{
45+
InlineHost?.VisualChildren.Remove(container.Child);
46+
}
47+
48+
x.InlineHost = null;
49+
3650
Invalidate();
3751
},
3852
() => throw new NotSupportedException());

src/Avalonia.Controls/TextBlock.cs

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Diagnostics;
44
using Avalonia.Automation.Peers;
5+
using Avalonia.Collections;
56
using Avalonia.Controls.Documents;
67
using Avalonia.Layout;
78
using Avalonia.Media;
@@ -685,16 +686,18 @@ protected virtual TextLayout CreateTextLayout(string? text)
685686
/// Invalidates <see cref="TextLayout"/>.
686687
/// </summary>
687688
protected void InvalidateTextLayout()
689+
{
690+
InvalidateMeasure();
691+
}
692+
693+
protected override void OnMeasureInvalidated()
688694
{
689695
_textLayout?.Dispose();
690696
_textLayout = null;
691-
692-
VisualChildren.Clear();
693697

694698
_textRuns = null;
695699

696-
InvalidateVisual();
697-
InvalidateMeasure();
700+
base.OnMeasureInvalidated();
698701
}
699702

700703
protected override Size MeasureOverride(Size availableSize)
@@ -703,15 +706,11 @@ protected override Size MeasureOverride(Size availableSize)
703706
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
704707

705708
_constraint = availableSize.Deflate(padding);
706-
_textLayout?.Dispose();
707-
_textLayout = null;
708709

709710
var inlines = Inlines;
710711

711712
if (HasComplexContent)
712713
{
713-
VisualChildren.Clear();
714-
715714
var textRuns = new List<TextRun>();
716715

717716
foreach (var inline in inlines!)
@@ -720,21 +719,6 @@ protected override Size MeasureOverride(Size availableSize)
720719
}
721720

722721
_textRuns = textRuns;
723-
724-
foreach (var textLine in TextLayout.TextLines)
725-
{
726-
foreach (var run in textLine.TextRuns)
727-
{
728-
if (run is DrawableTextRun drawable)
729-
{
730-
if (drawable is EmbeddedControlRun controlRun
731-
&& controlRun.Control is Control control)
732-
{
733-
VisualChildren.Add(control);
734-
}
735-
}
736-
}
737-
}
738722
}
739723

740724
var width = TextLayout.OverhangLeading + TextLayout.WidthIncludingTrailingWhitespace + TextLayout.OverhangTrailing;
@@ -847,26 +831,30 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
847831

848832
private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue)
849833
{
834+
VisualChildren.Clear();
835+
850836
if (oldValue is not null)
851837
{
852838
oldValue.LogicalChildren = null;
853839
oldValue.InlineHost = null;
854-
oldValue.Invalidated -= (s, e) => InvalidateTextLayout();
840+
oldValue.Invalidated -= (s, e) => InvalidateMeasure();
855841
}
856842

857843
if (newValue is not null)
858844
{
859845
newValue.LogicalChildren = LogicalChildren;
860846
newValue.InlineHost = this;
861-
newValue.Invalidated += (s, e) => InvalidateTextLayout();
847+
newValue.Invalidated += (s, e) => InvalidateMeasure();
862848
}
863849
}
864850

865851
void IInlineHost.Invalidate()
866852
{
867-
InvalidateTextLayout();
853+
InvalidateMeasure();
868854
}
869855

856+
IAvaloniaList<Visual> IInlineHost.VisualChildren => VisualChildren;
857+
870858
protected readonly record struct SimpleTextSource : ITextSource
871859
{
872860
private readonly string _text;

tests/Avalonia.Controls.UnitTests/TextBlockTests.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Avalonia.Controls.Documents;
33
using Avalonia.Controls.Templates;
44
using Avalonia.Data;
5+
using Avalonia.Input;
56
using Avalonia.Media;
67
using Avalonia.Metadata;
78
using Avalonia.Rendering;
@@ -50,6 +51,55 @@ public void Changing_InlinesCollection_Should_Invalidate_Measure()
5051
}
5152
}
5253

54+
[Fact]
55+
public void Can_Call_Measure_Without_InvalidateTextLayout()
56+
{
57+
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
58+
{
59+
var target = new TextBlock();
60+
61+
target.Inlines.Add(new TextBox { Text = "Hello"});
62+
63+
target.Measure(Size.Infinity);
64+
65+
target.InvalidateMeasure();
66+
67+
target.Measure(Size.Infinity);
68+
}
69+
}
70+
71+
[Fact]
72+
public void Embedded_Control_Should_Keep_Focus()
73+
{
74+
using (UnitTestApplication.Start(TestServices.RealFocus))
75+
{
76+
var target = new TextBlock();
77+
78+
var root = new TestRoot
79+
{
80+
Child = target
81+
};
82+
83+
var textBox = new TextBox { Text = "Hello", Template = TextBoxTests.CreateTemplate() };
84+
85+
target.Inlines.Add(textBox);
86+
87+
target.Measure(Size.Infinity);
88+
89+
textBox.Focus();
90+
91+
Assert.Same(textBox, root.FocusManager.GetFocusedElement());
92+
93+
target.InvalidateMeasure();
94+
95+
Assert.Same(textBox, root.FocusManager.GetFocusedElement());
96+
97+
target.Measure(Size.Infinity);
98+
99+
Assert.Same(textBox, root.FocusManager.GetFocusedElement());
100+
}
101+
}
102+
53103
[Fact]
54104
public void Changing_Inlines_Properties_Should_Invalidate_Measure()
55105
{
@@ -115,6 +165,25 @@ public void Changing_Inlines_Should_Reset_Inlines_Parent()
115165
}
116166
}
117167

168+
[Fact]
169+
public void Changing_Inlines_Should_Reset_VisualChildren()
170+
{
171+
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
172+
{
173+
var target = new TextBlock();
174+
175+
target.Inlines.Add(new Border());
176+
177+
target.Measure(Size.Infinity);
178+
179+
Assert.NotEmpty(target.VisualChildren);
180+
181+
target.Inlines = null;
182+
183+
Assert.Empty(target.VisualChildren);
184+
}
185+
}
186+
118187
[Fact]
119188
public void Changing_Inlines_Should_Reset_InlineUIContainer_VisualParent_On_Measure()
120189
{

tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1484,7 +1484,7 @@ public void Should_Throw_ArgumentOutOfRange()
14841484
textShaperImpl: new HeadlessTextShaperStub(),
14851485
fontManagerImpl: new HeadlessFontManagerStub());
14861486

1487-
private IControlTemplate CreateTemplate()
1487+
internal static IControlTemplate CreateTemplate()
14881488
{
14891489
return new FuncControlTemplate<TextBox>((control, scope) =>
14901490
new ScrollViewer

0 commit comments

Comments
 (0)