Browse Source

Properly arrange Embedded controls during ArrangeOverride

pull/9147/head
Benedikt Stebner 3 years ago
parent
commit
75b3abbdd8
  1. 2
      src/Avalonia.Controls/Documents/IInlineHost.cs
  2. 42
      src/Avalonia.Controls/Documents/InlineRun.cs
  3. 48
      src/Avalonia.Controls/Documents/InlineUIContainer.cs
  4. 87
      src/Avalonia.Controls/RichTextBlock.cs
  5. 36
      tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs

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

@ -4,8 +4,6 @@ namespace Avalonia.Controls.Documents
{
internal interface IInlineHost : ILogical
{
void AddVisualChild(IControl child);
void Invalidate();
}
}

42
src/Avalonia.Controls/Documents/InlineRun.cs

@ -0,0 +1,42 @@
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
namespace Avalonia.Controls.Documents
{
internal class EmbeddedControlRun : DrawableTextRun
{
public EmbeddedControlRun(IControl control, TextRunProperties properties)
{
Control = control;
Properties = properties;
}
public IControl Control { get; }
public override TextRunProperties? Properties { get; }
public override Size Size => Control.DesiredSize;
public override double Baseline
{
get
{
double baseline = Size.Height;
double baselineOffsetValue = Control.GetValue<double>(TextBlock.BaselineOffsetProperty);
if (!MathUtilities.IsZero(baselineOffsetValue))
{
baseline = baselineOffsetValue;
}
return -baseline;
}
}
public override void Draw(DrawingContext drawingContext, Point origin)
{
//noop
}
}
}

48
src/Avalonia.Controls/Documents/InlineUIContainer.cs

@ -3,7 +3,6 @@ using System.Text;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Metadata;
using Avalonia.Utilities;
namespace Avalonia.Controls.Documents
{
@ -59,56 +58,11 @@ namespace Avalonia.Controls.Documents
internal override void BuildTextRun(IList<TextRun> textRuns)
{
if(InlineHost == null)
{
return;
}
((ISetLogicalParent)Child).SetParent(InlineHost);
InlineHost.AddVisualChild(Child);
textRuns.Add(new InlineRun(Child, CreateTextRunProperties()));
textRuns.Add(new EmbeddedControlRun(Child, CreateTextRunProperties()));
}
internal override void AppendText(StringBuilder stringBuilder)
{
}
private class InlineRun : DrawableTextRun
{
public InlineRun(IControl control, TextRunProperties properties)
{
Control = control;
Properties = properties;
}
public IControl Control { get; }
public override TextRunProperties? Properties { get; }
public override Size Size => Control.DesiredSize;
public override double Baseline
{
get
{
double baseline = Size.Height;
double baselineOffsetValue = Control.GetValue<double>(TextBlock.BaselineOffsetProperty);
if (!MathUtilities.IsZero(baselineOffsetValue))
{
baseline = baselineOffsetValue;
}
return -baseline;
}
}
public override void Draw(DrawingContext drawingContext, Point origin)
{
//noop
}
}
}
}

87
src/Avalonia.Controls/RichTextBlock.cs

@ -61,6 +61,7 @@ namespace Avalonia.Controls
private int _selectionStart;
private int _selectionEnd;
private int _wordSelectionStart = -1;
private IReadOnlyList<TextRun>? _textRuns;
static RichTextBlock()
{
@ -277,8 +278,8 @@ namespace Avalonia.Controls
protected override void SetText(string? text)
{
var oldValue = GetText();
AddText(text);
AddText(text);
RaisePropertyChanged(TextProperty, oldValue, text);
}
@ -301,18 +302,9 @@ namespace Avalonia.Controls
ITextSource textSource;
if (HasComplexContent)
if (_textRuns != null)
{
var inlines = Inlines!;
var textRuns = new List<TextRun>();
foreach (var inline in inlines)
{
inline.BuildTextRun(textRuns);
}
textSource = new InlinesTextSource(textRuns);
textSource = new InlinesTextSource(_textRuns);
}
else
{
@ -546,27 +538,72 @@ namespace Avalonia.Controls
protected override Size MeasureOverride(Size availableSize)
{
foreach (var child in VisualChildren)
LogicalChildren.Clear();
VisualChildren.Clear();
if (Inlines != null && Inlines.Count > 0)
{
if (child is Control control)
var inlines = Inlines;
var textRuns = new List<TextRun>();
foreach (var inline in inlines)
{
control.Measure(Size.Infinity);
inline.BuildTextRun(textRuns);
}
foreach (var textRun in textRuns)
{
if (textRun is EmbeddedControlRun controlRun &&
controlRun.Control is Control control)
{
((ISetLogicalParent)control).SetParent(this);
VisualChildren.Add(control);
control.Measure(Size.Infinity);
}
}
_textRuns = textRuns;
}
else
{
_textRuns = null;
}
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (var child in VisualChildren)
if (HasComplexContent)
{
if (child is Control control)
var currentY = 0.0;
foreach (var textLine in TextLayout.TextLines)
{
control.Arrange(new Rect(control.DesiredSize));
var currentX = textLine.Start;
foreach (var run in textLine.TextRuns)
{
if (run is DrawableTextRun drawable)
{
if (drawable is EmbeddedControlRun controlRun
&& controlRun.Control is Control control)
{
control.Arrange(new Rect(new Point(currentX, currentY), control.DesiredSize));
}
currentX += drawable.Size.Width;
}
}
currentY += textLine.Height;
}
}
return base.ArrangeOverride(finalSize);
}
@ -618,14 +655,6 @@ namespace Avalonia.Controls
}
}
void IInlineHost.AddVisualChild(IControl child)
{
if (child.VisualParent == null)
{
VisualChildren.Add(child);
}
}
void IInlineHost.Invalidate()
{
InvalidateTextLayout();

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

@ -1,4 +1,6 @@
using Avalonia.Controls.Documents;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Media;
using Avalonia.UnitTests;
using Xunit;
@ -92,5 +94,39 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(target, run.Parent);
}
}
[Fact]
public void InlineUIContainer_Child_Schould_Be_Arranged()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = new RichTextBlock();
var button = new Button { Content = "12345678" };
button.Template = new FuncControlTemplate<Button>((parent, scope) =>
new TextBlock
{
Name = "PART_ContentPresenter",
[!TextBlock.TextProperty] = parent[!ContentControl.ContentProperty],
}.RegisterInNameScope(scope)
);
target.Inlines!.Add("123456");
target.Inlines.Add(new InlineUIContainer(button));
target.Inlines.Add("123456");
target.Measure(Size.Infinity);
Assert.True(button.IsMeasureValid);
Assert.Equal(80, button.DesiredSize.Width);
target.Arrange(new Rect(new Size(200, 50)));
Assert.True(button.IsArrangeValid);
Assert.Equal(60, button.Bounds.Left);
}
}
}
}

Loading…
Cancel
Save