Browse Source

Fix InlineUIContainer focus (#14590)

* Fix TextBlock MeasureOverride visual child handling

* Make sure InlineUIContainer's child retains focus on measure

* Resolve merge error
pull/14564/head
Benedikt Stebner 3 years ago
committed by GitHub
parent
commit
0c2c3f1aeb
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      src/Avalonia.Controls/Documents/IInlineHost.cs
  2. 16
      src/Avalonia.Controls/Documents/InlineCollection.cs
  3. 40
      src/Avalonia.Controls/TextBlock.cs
  4. 69
      tests/Avalonia.Controls.UnitTests/TextBlockTests.cs
  5. 2
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

5
src/Avalonia.Controls/Documents/IInlineHost.cs

@ -1,9 +1,12 @@
using Avalonia.LogicalTree; using Avalonia.Collections;
using Avalonia.LogicalTree;
namespace Avalonia.Controls.Documents namespace Avalonia.Controls.Documents
{ {
internal interface IInlineHost : ILogical internal interface IInlineHost : ILogical
{ {
void Invalidate(); void Invalidate();
IAvaloniaList<Visual> VisualChildren { get; }
} }
} }

16
src/Avalonia.Controls/Documents/InlineCollection.cs

@ -26,13 +26,27 @@ namespace Avalonia.Controls.Documents
x => x =>
{ {
x.InlineHost = InlineHost; x.InlineHost = InlineHost;
LogicalChildren?.Add(x); LogicalChildren?.Add(x);
if (x is InlineUIContainer container)
{
InlineHost?.VisualChildren.Add(container.Child);
}
Invalidate(); Invalidate();
}, },
x => x =>
{ {
LogicalChildren?.Remove(x); LogicalChildren?.Remove(x);
x.InlineHost = InlineHost;
if(x is InlineUIContainer container)
{
InlineHost?.VisualChildren.Remove(container.Child);
}
x.InlineHost = null;
Invalidate(); Invalidate();
}, },
() => throw new NotSupportedException()); () => throw new NotSupportedException());

40
src/Avalonia.Controls/TextBlock.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Collections;
using Avalonia.Controls.Documents; using Avalonia.Controls.Documents;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
@ -685,16 +686,18 @@ namespace Avalonia.Controls
/// Invalidates <see cref="TextLayout"/>. /// Invalidates <see cref="TextLayout"/>.
/// </summary> /// </summary>
protected void InvalidateTextLayout() protected void InvalidateTextLayout()
{
InvalidateMeasure();
}
protected override void OnMeasureInvalidated()
{ {
_textLayout?.Dispose(); _textLayout?.Dispose();
_textLayout = null; _textLayout = null;
VisualChildren.Clear();
_textRuns = null; _textRuns = null;
InvalidateVisual(); base.OnMeasureInvalidated();
InvalidateMeasure();
} }
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
@ -703,15 +706,11 @@ namespace Avalonia.Controls
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale); var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
_constraint = availableSize.Deflate(padding); _constraint = availableSize.Deflate(padding);
_textLayout?.Dispose();
_textLayout = null;
var inlines = Inlines; var inlines = Inlines;
if (HasComplexContent) if (HasComplexContent)
{ {
VisualChildren.Clear();
var textRuns = new List<TextRun>(); var textRuns = new List<TextRun>();
foreach (var inline in inlines!) foreach (var inline in inlines!)
@ -720,21 +719,6 @@ namespace Avalonia.Controls
} }
_textRuns = textRuns; _textRuns = textRuns;
foreach (var textLine in TextLayout.TextLines)
{
foreach (var run in textLine.TextRuns)
{
if (run is DrawableTextRun drawable)
{
if (drawable is EmbeddedControlRun controlRun
&& controlRun.Control is Control control)
{
VisualChildren.Add(control);
}
}
}
}
} }
var width = TextLayout.OverhangLeading + TextLayout.WidthIncludingTrailingWhitespace + TextLayout.OverhangTrailing; var width = TextLayout.OverhangLeading + TextLayout.WidthIncludingTrailingWhitespace + TextLayout.OverhangTrailing;
@ -847,26 +831,30 @@ namespace Avalonia.Controls
private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue) private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue)
{ {
VisualChildren.Clear();
if (oldValue is not null) if (oldValue is not null)
{ {
oldValue.LogicalChildren = null; oldValue.LogicalChildren = null;
oldValue.InlineHost = null; oldValue.InlineHost = null;
oldValue.Invalidated -= (s, e) => InvalidateTextLayout(); oldValue.Invalidated -= (s, e) => InvalidateMeasure();
} }
if (newValue is not null) if (newValue is not null)
{ {
newValue.LogicalChildren = LogicalChildren; newValue.LogicalChildren = LogicalChildren;
newValue.InlineHost = this; newValue.InlineHost = this;
newValue.Invalidated += (s, e) => InvalidateTextLayout(); newValue.Invalidated += (s, e) => InvalidateMeasure();
} }
} }
void IInlineHost.Invalidate() void IInlineHost.Invalidate()
{ {
InvalidateTextLayout(); InvalidateMeasure();
} }
IAvaloniaList<Visual> IInlineHost.VisualChildren => VisualChildren;
protected readonly record struct SimpleTextSource : ITextSource protected readonly record struct SimpleTextSource : ITextSource
{ {
private readonly string _text; private readonly string _text;

69
tests/Avalonia.Controls.UnitTests/TextBlockTests.cs

@ -2,6 +2,7 @@ using System;
using Avalonia.Controls.Documents; using Avalonia.Controls.Documents;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Rendering; using Avalonia.Rendering;
@ -50,6 +51,55 @@ namespace Avalonia.Controls.UnitTests
} }
} }
[Fact]
public void Can_Call_Measure_Without_InvalidateTextLayout()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new TextBlock();
target.Inlines.Add(new TextBox { Text = "Hello"});
target.Measure(Size.Infinity);
target.InvalidateMeasure();
target.Measure(Size.Infinity);
}
}
[Fact]
public void Embedded_Control_Should_Keep_Focus()
{
using (UnitTestApplication.Start(TestServices.RealFocus))
{
var target = new TextBlock();
var root = new TestRoot
{
Child = target
};
var textBox = new TextBox { Text = "Hello", Template = TextBoxTests.CreateTemplate() };
target.Inlines.Add(textBox);
target.Measure(Size.Infinity);
textBox.Focus();
Assert.Same(textBox, root.FocusManager.GetFocusedElement());
target.InvalidateMeasure();
Assert.Same(textBox, root.FocusManager.GetFocusedElement());
target.Measure(Size.Infinity);
Assert.Same(textBox, root.FocusManager.GetFocusedElement());
}
}
[Fact] [Fact]
public void Changing_Inlines_Properties_Should_Invalidate_Measure() public void Changing_Inlines_Properties_Should_Invalidate_Measure()
{ {
@ -115,6 +165,25 @@ namespace Avalonia.Controls.UnitTests
} }
} }
[Fact]
public void Changing_Inlines_Should_Reset_VisualChildren()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var target = new TextBlock();
target.Inlines.Add(new Border());
target.Measure(Size.Infinity);
Assert.NotEmpty(target.VisualChildren);
target.Inlines = null;
Assert.Empty(target.VisualChildren);
}
}
[Fact] [Fact]
public void Changing_Inlines_Should_Reset_InlineUIContainer_VisualParent_On_Measure() public void Changing_Inlines_Should_Reset_InlineUIContainer_VisualParent_On_Measure()
{ {

2
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -1484,7 +1484,7 @@ namespace Avalonia.Controls.UnitTests
textShaperImpl: new HeadlessTextShaperStub(), textShaperImpl: new HeadlessTextShaperStub(),
fontManagerImpl: new HeadlessFontManagerStub()); fontManagerImpl: new HeadlessFontManagerStub());
private IControlTemplate CreateTemplate() internal static IControlTemplate CreateTemplate()
{ {
return new FuncControlTemplate<TextBox>((control, scope) => return new FuncControlTemplate<TextBox>((control, scope) =>
new ScrollViewer new ScrollViewer

Loading…
Cancel
Save