Skip to content

Fix InlineUIContainer focus #14590

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/Avalonia.Controls/Documents/IInlineHost.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using Avalonia.LogicalTree;
using Avalonia.Collections;
using Avalonia.LogicalTree;

namespace Avalonia.Controls.Documents
{
internal interface IInlineHost : ILogical
{
void Invalidate();

IAvaloniaList<Visual> VisualChildren { get; }
}
}
16 changes: 15 additions & 1 deletion src/Avalonia.Controls/Documents/InlineCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,27 @@ public InlineCollection()
x =>
{
x.InlineHost = InlineHost;

LogicalChildren?.Add(x);

if (x is InlineUIContainer container)
{
InlineHost?.VisualChildren.Add(container.Child);
}

Invalidate();
},
x =>
{
LogicalChildren?.Remove(x);
x.InlineHost = InlineHost;

if(x is InlineUIContainer container)
{
InlineHost?.VisualChildren.Remove(container.Child);
}

x.InlineHost = null;

Invalidate();
},
() => throw new NotSupportedException());
Expand Down
40 changes: 14 additions & 26 deletions src/Avalonia.Controls/TextBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Automation.Peers;
using Avalonia.Collections;
using Avalonia.Controls.Documents;
using Avalonia.Layout;
using Avalonia.Media;
Expand Down Expand Up @@ -685,16 +686,18 @@ protected virtual TextLayout CreateTextLayout(string? text)
/// Invalidates <see cref="TextLayout"/>.
/// </summary>
protected void InvalidateTextLayout()
{
InvalidateMeasure();
}

protected override void OnMeasureInvalidated()
{
_textLayout?.Dispose();
_textLayout = null;

VisualChildren.Clear();

_textRuns = null;

InvalidateVisual();
InvalidateMeasure();
base.OnMeasureInvalidated();
}

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

_constraint = availableSize.Deflate(padding);
_textLayout?.Dispose();
_textLayout = null;

var inlines = Inlines;

if (HasComplexContent)
{
VisualChildren.Clear();

var textRuns = new List<TextRun>();

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

_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;
Expand Down Expand Up @@ -847,26 +831,30 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang

private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue)
{
VisualChildren.Clear();

if (oldValue is not null)
{
oldValue.LogicalChildren = null;
oldValue.InlineHost = null;
oldValue.Invalidated -= (s, e) => InvalidateTextLayout();
oldValue.Invalidated -= (s, e) => InvalidateMeasure();
}

if (newValue is not null)
{
newValue.LogicalChildren = LogicalChildren;
newValue.InlineHost = this;
newValue.Invalidated += (s, e) => InvalidateTextLayout();
newValue.Invalidated += (s, e) => InvalidateMeasure();
}
}

void IInlineHost.Invalidate()
{
InvalidateTextLayout();
InvalidateMeasure();
}

IAvaloniaList<Visual> IInlineHost.VisualChildren => VisualChildren;

protected readonly record struct SimpleTextSource : ITextSource
{
private readonly string _text;
Expand Down
69 changes: 69 additions & 0 deletions tests/Avalonia.Controls.UnitTests/TextBlockTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Avalonia.Controls.Documents;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Rendering;
Expand Down Expand Up @@ -50,6 +51,55 @@ public void Changing_InlinesCollection_Should_Invalidate_Measure()
}
}

[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]
public void Changing_Inlines_Properties_Should_Invalidate_Measure()
{
Expand Down Expand Up @@ -115,6 +165,25 @@ public void Changing_Inlines_Should_Reset_Inlines_Parent()
}
}

[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]
public void Changing_Inlines_Should_Reset_InlineUIContainer_VisualParent_On_Measure()
{
Expand Down
2 changes: 1 addition & 1 deletion tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1484,7 +1484,7 @@ public void Should_Throw_ArgumentOutOfRange()
textShaperImpl: new HeadlessTextShaperStub(),
fontManagerImpl: new HeadlessFontManagerStub());

private IControlTemplate CreateTemplate()
internal static IControlTemplate CreateTemplate()
{
return new FuncControlTemplate<TextBox>((control, scope) =>
new ScrollViewer
Expand Down