diff --git a/src/Avalonia.Base/Layout/ILayoutManager.cs b/src/Avalonia.Base/Layout/ILayoutManager.cs
index 588ce97ba5..560d0aab00 100644
--- a/src/Avalonia.Base/Layout/ILayoutManager.cs
+++ b/src/Avalonia.Base/Layout/ILayoutManager.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Layout
///
/// Manages measuring and arranging of controls.
///
- internal interface ILayoutManager : IDisposable
+ public interface ILayoutManager : IDisposable
{
///
/// Raised when the layout manager completes a layout pass.
diff --git a/src/Avalonia.Base/Layout/ILayoutRoot.cs b/src/Avalonia.Base/Layout/ILayoutRoot.cs
index 0bfb10ea36..8624194c42 100644
--- a/src/Avalonia.Base/Layout/ILayoutRoot.cs
+++ b/src/Avalonia.Base/Layout/ILayoutRoot.cs
@@ -10,11 +10,13 @@ namespace Avalonia.Layout
///
/// The scaling factor to use in layout.
///
- double LayoutScaling { get; }
+ public double LayoutScaling { get; }
///
/// Associated instance of layout manager
///
- internal ILayoutManager LayoutManager { get; }
+ public ILayoutManager LayoutManager { get; }
+
+ public Layoutable RootVisual { get; }
}
}
diff --git a/src/Avalonia.Base/Layout/LayoutHelper.cs b/src/Avalonia.Base/Layout/LayoutHelper.cs
index a342c654f9..84f1fc4bcc 100644
--- a/src/Avalonia.Base/Layout/LayoutHelper.cs
+++ b/src/Avalonia.Base/Layout/LayoutHelper.cs
@@ -140,8 +140,7 @@ namespace Avalonia.Layout
///
/// The control.
/// Thrown when control has no root or returned layout scaling is invalid.
- public static double GetLayoutScale(Layoutable control)
- => control.VisualRoot is ILayoutRoot layoutRoot ? layoutRoot.LayoutScaling : 1.0;
+ public static double GetLayoutScale(Layoutable control) => control.GetLayoutRoot()?.LayoutScaling ?? 1.0;
///
/// Rounds a size to integer values for layout purposes, compensating for high DPI screen
diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs
index 6199b38b5c..fa82ff1d31 100644
--- a/src/Avalonia.Base/Layout/LayoutManager.cs
+++ b/src/Avalonia.Base/Layout/LayoutManager.cs
@@ -9,6 +9,7 @@ using Avalonia.Metadata;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.Utilities;
+using Avalonia.VisualTree;
#nullable enable
@@ -20,7 +21,7 @@ namespace Avalonia.Layout
internal class LayoutManager : ILayoutManager, IDisposable
{
private const int MaxPasses = 10;
- private readonly Layoutable _owner;
+ private readonly ILayoutRoot _owner;
private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid);
private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid);
private readonly List _toArrangeAfterMeasure = new();
@@ -33,7 +34,7 @@ namespace Avalonia.Layout
public LayoutManager(ILayoutRoot owner)
{
- _owner = owner as Layoutable ?? throw new ArgumentNullException(nameof(owner));
+ _owner = owner;
_invokeOnRender = ExecuteQueuedLayoutPass;
}
@@ -62,7 +63,7 @@ namespace Avalonia.Layout
#endif
}
- if (control.VisualRoot != _owner)
+ if (control.GetLayoutRoot() != _owner)
{
throw new ArgumentException("Attempt to call InvalidateMeasure on wrong LayoutManager.");
}
@@ -92,7 +93,7 @@ namespace Avalonia.Layout
#endif
}
- if (control.VisualRoot != _owner)
+ if (control.GetLayoutRoot() != _owner)
{
throw new ArgumentException("Attempt to call InvalidateArrange on wrong LayoutManager.");
}
@@ -187,9 +188,12 @@ namespace Avalonia.Layout
try
{
+ if (_owner?.RootVisual == null)
+ return;
+ var root = _owner.RootVisual;
_running = true;
- Measure(_owner);
- Arrange(_owner);
+ Measure(root);
+ Arrange(root);
}
finally
{
@@ -299,7 +303,7 @@ namespace Avalonia.Layout
// control to be removed.
if (!control.IsMeasureValid)
{
- if (control is ILayoutRoot root)
+ if (control.GetLayoutRoot()?.RootVisual == control)
{
control.Measure(Size.Infinity);
}
@@ -328,7 +332,7 @@ namespace Avalonia.Layout
if (!control.IsArrangeValid)
{
- if (control is ILayoutRoot root)
+ if (control.GetLayoutRoot()?.RootVisual == control)
control.Arrange(new Rect(control.DesiredSize));
else if (control.PreviousArrange != null)
{
diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs
index 39a8096a49..fedea332b6 100644
--- a/src/Avalonia.Base/Layout/Layoutable.cs
+++ b/src/Avalonia.Base/Layout/Layoutable.cs
@@ -168,7 +168,7 @@ namespace Avalonia.Layout
{
add
{
- if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r && !_isAttachingToVisualTree)
+ if (_effectiveViewportChanged is null && this.GetLayoutRoot() is {} r && !_isAttachingToVisualTree)
{
r.LayoutManager.RegisterEffectiveViewportListener(this);
}
@@ -180,7 +180,7 @@ namespace Avalonia.Layout
{
_effectiveViewportChanged -= value;
- if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r)
+ if (_effectiveViewportChanged is null && this.GetLayoutRoot() is {} r)
{
r.LayoutManager.UnregisterEffectiveViewportListener(this);
}
@@ -194,7 +194,7 @@ namespace Avalonia.Layout
{
add
{
- if (_layoutUpdated is null && VisualRoot is ILayoutRoot r && !_isAttachingToVisualTree)
+ if (_layoutUpdated is null && this.GetLayoutRoot() is {} r && !_isAttachingToVisualTree)
{
r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated;
}
@@ -206,7 +206,7 @@ namespace Avalonia.Layout
{
_layoutUpdated -= value;
- if (_layoutUpdated is null && VisualRoot is ILayoutRoot r)
+ if (_layoutUpdated is null && this.GetLayoutRoot() is {} r)
{
r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated;
}
@@ -221,7 +221,7 @@ namespace Avalonia.Layout
/// schedule layout passes itself.
///
- public void UpdateLayout() => (this.GetPresentationSource()?.RootVisual as ILayoutRoot)?.LayoutManager?.ExecuteLayoutPass();
+ public void UpdateLayout() => this.GetLayoutManager()?.ExecuteLayoutPass();
///
/// Gets or sets the width of the element.
@@ -449,7 +449,7 @@ namespace Avalonia.Layout
if (IsAttachedToVisualTree)
{
- (VisualRoot as ILayoutRoot)?.LayoutManager.InvalidateMeasure(this);
+ this.GetLayoutManager()?.InvalidateMeasure(this);
InvalidateVisual();
}
OnMeasureInvalidated();
@@ -466,7 +466,7 @@ namespace Avalonia.Layout
Logger.TryGet(LogEventLevel.Verbose, LogArea.Layout)?.Log(this, "Invalidated arrange");
IsArrangeValid = false;
- (VisualRoot as ILayoutRoot)?.LayoutManager?.InvalidateArrange(this);
+ this.GetLayoutManager()?.InvalidateArrange(this);
InvalidateVisual();
}
}
@@ -794,7 +794,7 @@ namespace Avalonia.Layout
_isAttachingToVisualTree = false;
}
- if (e.Root is ILayoutRoot r)
+ if (this.GetLayoutRoot() is {} r)
{
if (_layoutUpdated is object)
{
@@ -810,7 +810,7 @@ namespace Avalonia.Layout
protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
- if (e.Root is ILayoutRoot r)
+ if (this.GetLayoutRoot() is {} r)
{
if (_layoutUpdated is object)
{
@@ -853,7 +853,7 @@ namespace Avalonia.Layout
// they will need to be registered with the layout manager now that they
// are again effectively visible. If IsEffectivelyVisible becomes an observable
// property then we can piggy-pack on that; for the moment we do this manually.
- if (VisualRoot is ILayoutRoot layoutRoot)
+ if (this.GetLayoutRoot() is {} layoutRoot)
{
var count = VisualChildren.Count;
diff --git a/src/Avalonia.Base/Rendering/IPresentationSource.cs b/src/Avalonia.Base/Rendering/IPresentationSource.cs
index 8e36ba0c4d..27e0ead8f3 100644
--- a/src/Avalonia.Base/Rendering/IPresentationSource.cs
+++ b/src/Avalonia.Base/Rendering/IPresentationSource.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia.Input;
+using Avalonia.Layout;
using Avalonia.Platform;
namespace Avalonia.Rendering;
@@ -13,6 +14,8 @@ public interface IPresentationSource
internal IHitTester HitTester { get; }
internal IInputRoot InputRoot { get; }
+
+ internal ILayoutRoot LayoutRoot { get; }
public Visual? RootVisual { get; }
diff --git a/src/Avalonia.Base/VisualTree/VisualExtensions.cs b/src/Avalonia.Base/VisualTree/VisualExtensions.cs
index 19c5c2b65a..b97c15c4df 100644
--- a/src/Avalonia.Base/VisualTree/VisualExtensions.cs
+++ b/src/Avalonia.Base/VisualTree/VisualExtensions.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
+using Avalonia.Layout;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Utilities;
@@ -464,6 +465,14 @@ namespace Avalonia.VisualTree
// TODO: Verify all usages, this is no longer necessary a TopLevel
internal static Visual? GetVisualRoot(this Visual visual) => visual.PresentationSource?.RootVisual;
+ internal static ILayoutRoot? GetLayoutRoot(this Visual visual) => visual.PresentationSource?.LayoutRoot;
+
+ ///
+ /// Gets the layout manager for the visual's presentation source, or null if the visual is not attached to a visual root.
+ ///
+ public static ILayoutManager? GetLayoutManager(this Visual visual) =>
+ visual.PresentationSource?.LayoutRoot.LayoutManager;
+
///
/// Attempts to obtain platform settings from the visual's root.
/// This will return null if the visual is not attached to a visual root.
diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
index 2f88e662a2..0704d1325d 100644
--- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
+++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
@@ -8,6 +8,7 @@ using Avalonia.Layout;
using System;
using System.Collections.Generic;
using System.Globalization;
+using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -406,7 +407,7 @@ namespace Avalonia.Controls
// Overlay popup hosts won't get measured until the next layout pass, but we need the
// template to be applied to `_presenter` now. Detect this case and force a layout pass.
if (!_presenter.IsMeasureValid)
- (VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass();
+ this.GetLayoutManager()?.ExecuteInitialLayoutPass();
var deltaY = _presenter.GetOffsetForPopup();
diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
index ca5c43463d..95d9e82782 100644
--- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
+++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
@@ -7,6 +7,7 @@ using System;
using System.Globalization;
using Avalonia.Controls.Utils;
using Avalonia.Automation.Peers;
+using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -380,7 +381,7 @@ namespace Avalonia.Controls
// Overlay popup hosts won't get measured until the next layout pass, but we need the
// template to be applied to `_presenter` now. Detect this case and force a layout pass.
if (!_presenter.IsMeasureValid)
- (VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass();
+ this.GetLayoutManager()?.ExecuteInitialLayoutPass();
var deltaY = _presenter.GetOffsetForPopup();
diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs
index df70cdf6d0..d181328134 100644
--- a/src/Avalonia.Controls/Grid.cs
+++ b/src/Avalonia.Controls/Grid.cs
@@ -2117,7 +2117,7 @@ namespace Avalonia.Controls
{
// DpiScale dpiScale = GetDpi();
// double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY;
- var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
+ var dpi = this.GetLayoutRoot()?.LayoutScaling ?? 1.0;
double[] roundingErrors = RoundingErrors;
double roundedTakenSize = 0;
diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs
index ba857687f6..df502207c6 100644
--- a/src/Avalonia.Controls/GridSplitter.cs
+++ b/src/Avalonia.Controls/GridSplitter.cs
@@ -13,6 +13,7 @@ using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Utilities;
+using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -226,7 +227,7 @@ namespace Avalonia.Controls
ResizeDirection = resizeDirection,
SplitterLength = Math.Min(Bounds.Width, Bounds.Height),
ResizeBehavior = GetEffectiveResizeBehavior(resizeDirection),
- Scaling = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1,
+ Scaling = this.GetLayoutRoot()?.LayoutScaling ?? 1,
};
// Store the rows and columns to resize on drag events.
diff --git a/src/Avalonia.Controls/PresentationSource/PresentationSource.Layout.cs b/src/Avalonia.Controls/PresentationSource/PresentationSource.Layout.cs
new file mode 100644
index 0000000000..156c76cc6f
--- /dev/null
+++ b/src/Avalonia.Controls/PresentationSource/PresentationSource.Layout.cs
@@ -0,0 +1,69 @@
+using System;
+using System.ComponentModel;
+using Avalonia.Layout;
+using Avalonia.Rendering;
+
+namespace Avalonia.Controls;
+
+internal partial class PresentationSource : ILayoutRoot
+{
+ private LayoutDiagnosticBridge? _layoutDiagnosticBridge;
+ public double LayoutScaling => RenderScaling;
+ public ILayoutManager LayoutManager { get; }
+ ILayoutRoot IPresentationSource.LayoutRoot => this;
+ Layoutable ILayoutRoot.RootVisual => RootVisual;
+
+ private ILayoutManager CreateLayoutManager()
+ {
+ var manager = new LayoutManager(this);
+ _layoutDiagnosticBridge = new LayoutDiagnosticBridge(Renderer.Diagnostics, manager);
+ _layoutDiagnosticBridge.SetupBridge();
+ return manager;
+ }
+
+
+ ///
+ /// Provides layout pass timing from the layout manager to the renderer, for diagnostics purposes.
+ ///
+ private sealed class LayoutDiagnosticBridge : IDisposable
+ {
+ private readonly RendererDiagnostics _diagnostics;
+ private readonly LayoutManager _layoutManager;
+ private bool _isHandling;
+
+ public LayoutDiagnosticBridge(RendererDiagnostics diagnostics, LayoutManager layoutManager)
+ {
+ _diagnostics = diagnostics;
+ _layoutManager = layoutManager;
+
+ diagnostics.PropertyChanged += OnDiagnosticsPropertyChanged;
+ }
+
+ public void SetupBridge()
+ {
+ var needsHandling = (_diagnostics.DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0;
+ if (needsHandling != _isHandling)
+ {
+ _isHandling = needsHandling;
+ _layoutManager.LayoutPassTimed = needsHandling
+ ? timing => _diagnostics.LastLayoutPassTiming = timing
+ : null;
+ }
+ }
+
+ private void OnDiagnosticsPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(RendererDiagnostics.DebugOverlays))
+ {
+ SetupBridge();
+ }
+ }
+
+ public void Dispose()
+ {
+ _diagnostics.PropertyChanged -= OnDiagnosticsPropertyChanged;
+ _layoutManager.LayoutPassTimed = null;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/PresentationSource/PresentationSource.cs b/src/Avalonia.Controls/PresentationSource/PresentationSource.cs
index b2c2789347..9afc70b113 100644
--- a/src/Avalonia.Controls/PresentationSource/PresentationSource.cs
+++ b/src/Avalonia.Controls/PresentationSource/PresentationSource.cs
@@ -1,6 +1,7 @@
using System;
using Avalonia.Input;
using Avalonia.Input.TextInput;
+using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.Platform;
using Avalonia.Rendering;
@@ -37,8 +38,9 @@ internal partial class PresentationSource : IPresentationSource, IInputRoot, IDi
Renderer = new CompositingRenderer(this, PlatformImpl.Compositor, () => PlatformImpl.Surfaces ?? []);
Renderer.SceneInvalidated += SceneInvalidated;
- RootVisual = rootVisual;
+ LayoutManager = CreateLayoutManager();
+ RootVisual = rootVisual;
}
// In WPF it's a Visual and it's nullable. For now we have it as non-nullable InputElement since
@@ -81,6 +83,9 @@ internal partial class PresentationSource : IPresentationSource, IInputRoot, IDi
public void Dispose()
{
+ _layoutDiagnosticBridge?.Dispose();
+ _layoutDiagnosticBridge = null;
+ LayoutManager.Dispose();
Renderer.SceneInvalidated -= SceneInvalidated;
// We need to wait for the renderer to complete any in-flight operations
Renderer.Dispose();
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index e2625a489f..829de2a2ee 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -39,7 +39,6 @@ namespace Avalonia.Controls
///
[TemplatePart("PART_TransparencyFallback", typeof(Border))]
public abstract class TopLevel : ContentControl,
- ILayoutRoot,
ICloseable,
IStyleHost,
ILogicalRoot
@@ -126,12 +125,10 @@ namespace Avalonia.Controls
private Size _clientSize;
private Size? _frameSize;
private WindowTransparencyLevel _actualTransparencyLevel;
- private ILayoutManager? _layoutManager;
private Border? _transparencyFallbackBorder;
private TargetWeakEventSubscriber? _resourcesChangesSubscriber;
private IStorageProvider? _storageProvider;
private Screens? _screens;
- private LayoutDiagnosticBridge? _layoutDiagnosticBridge;
private PresentationSource _source;
internal PresentationSource PresentationSource => _source;
internal IInputRoot InputRoot => _source;
@@ -384,26 +381,7 @@ namespace Avalonia.Controls
remove => RemoveHandler(BackRequestedEvent, value);
}
- internal ILayoutManager LayoutManager
- {
- get
- {
- if (_layoutManager is null)
- {
- _layoutManager = CreateLayoutManager();
-
- if (_layoutManager is LayoutManager typedLayoutManager)
- {
- _layoutDiagnosticBridge = new LayoutDiagnosticBridge(Renderer.Diagnostics, typedLayoutManager);
- _layoutDiagnosticBridge.SetupBridge();
- }
- }
-
- return _layoutManager;
- }
- }
-
- ILayoutManager ILayoutRoot.LayoutManager => LayoutManager;
+ internal ILayoutManager LayoutManager => _source.LayoutManager;
///
/// Gets the platform-specific window implementation.
@@ -504,8 +482,6 @@ namespace Avalonia.Controls
return control.GetValue(AutoSafeAreaPaddingProperty);
}
- double ILayoutRoot.LayoutScaling => _scaling;
-
///
public double RenderScaling => _scaling;
@@ -641,11 +617,6 @@ namespace Avalonia.Controls
}
}
- ///
- /// Creates the layout manager for this .
- ///
- private protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this);
-
///
/// Handles a paint notification from .
///
@@ -682,9 +653,6 @@ namespace Avalonia.Controls
_applicationThemeHost.ActualThemeVariantChanged -= GlobalActualThemeVariantChanged;
}
-
- _layoutDiagnosticBridge?.Dispose();
- _layoutDiagnosticBridge = null;
_backGestureSubscription?.Dispose();
var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null);
@@ -863,49 +831,5 @@ namespace Avalonia.Controls
return scaling;
}
-
- ///
- /// Provides layout pass timing from the layout manager to the renderer, for diagnostics purposes.
- ///
- private sealed class LayoutDiagnosticBridge : IDisposable
- {
- private readonly RendererDiagnostics _diagnostics;
- private readonly LayoutManager _layoutManager;
- private bool _isHandling;
-
- public LayoutDiagnosticBridge(RendererDiagnostics diagnostics, LayoutManager layoutManager)
- {
- _diagnostics = diagnostics;
- _layoutManager = layoutManager;
-
- diagnostics.PropertyChanged += OnDiagnosticsPropertyChanged;
- }
-
- public void SetupBridge()
- {
- var needsHandling = (_diagnostics.DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0;
- if (needsHandling != _isHandling)
- {
- _isHandling = needsHandling;
- _layoutManager.LayoutPassTimed = needsHandling
- ? timing => _diagnostics.LastLayoutPassTiming = timing
- : null;
- }
- }
-
- private void OnDiagnosticsPropertyChanged(object? sender, PropertyChangedEventArgs e)
- {
- if (e.PropertyName == nameof(RendererDiagnostics.DebugOverlays))
- {
- SetupBridge();
- }
- }
-
- public void Dispose()
- {
- _diagnostics.PropertyChanged -= OnDiagnosticsPropertyChanged;
- _layoutManager.LayoutPassTimed = null;
- }
- }
}
}
diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index 068ffbcb66..033be87c7f 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -168,7 +168,7 @@ namespace Avalonia.Controls
item.IsExpanded = true;
if (item.Presenter?.Panel is null)
- (this.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass();
+ this.GetLayoutManager()?.ExecuteLayoutPass();
if (item.Presenter?.Panel is { } panel)
{
diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs
index e883bb533b..7e79026c60 100644
--- a/src/Avalonia.Controls/VirtualizingStackPanel.cs
+++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs
@@ -459,7 +459,7 @@ namespace Avalonia.Controls
element.BringIntoView();
return element;
}
- else if (this.GetVisualRoot() is ILayoutRoot root)
+ else if (this.GetLayoutRoot() is {} root)
{
// Create and measure the element to be brought into view. Store it in a field so that
// it can be re-used in the layout pass.
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index 65ac2897cc..accda02fc6 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -86,7 +86,7 @@ namespace Avalonia.Controls
///
/// A top-level window.
///
- public class Window : WindowBase, IFocusScope, ILayoutRoot
+ public class Window : WindowBase, IFocusScope
{
private static readonly Lazy s_defaultIcon = new(LoadDefaultIcon);
private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>();
diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
index 661791a6e4..20065bac60 100644
--- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
@@ -759,18 +759,9 @@ namespace Avalonia.Controls.UnitTests
}
}
- private class TestTopLevel : TopLevel
+ private class TestTopLevel(ITopLevelImpl impl) : TopLevel(impl)
{
- private readonly ILayoutManager _layoutManager;
- public bool IsClosed { get; private set; }
- public TestTopLevel(ITopLevelImpl impl, ILayoutManager? layoutManager = null)
- : base(impl)
- {
- _layoutManager = layoutManager ?? new LayoutManager(this);
- }
-
- private protected override ILayoutManager CreateLayoutManager() => _layoutManager;
}
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs
index 85ba254b5d..ab93686966 100644
--- a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs
@@ -336,7 +336,7 @@ namespace Avalonia.Controls.UnitTests
private static void Layout(Carousel target)
{
- ((ILayoutRoot)target.GetVisualRoot()!).LayoutManager.ExecuteLayoutPass();
+ target.GetLayoutManager()?.ExecuteLayoutPass();
}
private static IControlTemplate CarouselTemplate()
diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
index 85d366953f..b4752c5186 100644
--- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
@@ -1218,7 +1218,7 @@ namespace Avalonia.Controls.UnitTests
private static void Layout(Control c)
{
- (c.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass();
+ c.GetLayoutManager()?.ExecuteLayoutPass();
}
private static ContentPresenter GetContainer(ItemsControl target, int index = 0)
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
index c76369ef0b..2fa6a47d27 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
@@ -642,7 +642,7 @@ namespace Avalonia.Controls.UnitTests
private static void Layout(Control c)
{
- ((ILayoutRoot)c.GetVisualRoot()!).LayoutManager.ExecuteLayoutPass();
+ c.GetLayoutManager()?.ExecuteLayoutPass();
}
private class Item
diff --git a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs
index 4ff22cf402..cddac813f8 100644
--- a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs
@@ -1002,17 +1002,8 @@ namespace Avalonia.Controls.UnitTests
}
}
- private class TestTopLevel : TopLevel
+ private class TestTopLevel(ITopLevelImpl impl) : TopLevel(impl)
{
- private readonly ILayoutManager _layoutManager;
-
- public TestTopLevel(ITopLevelImpl impl, ILayoutManager? layoutManager = null)
- : base(impl)
- {
- _layoutManager = layoutManager ?? new LayoutManager(this);
- }
-
- private protected override ILayoutManager CreateLayoutManager() => _layoutManager;
}
private static Mock CreateMockTopLevelImpl()
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
index 814bf00344..3c36b7ff74 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
@@ -2555,7 +2555,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
private static void Layout(Control c)
{
- ((ILayoutRoot)c.GetVisualRoot()!).LayoutManager.ExecuteLayoutPass();
+ c.GetLayoutManager()?.ExecuteLayoutPass();
}
private static FuncControlTemplate Template()
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
index f0f4fe05ae..2b9d7a940f 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
@@ -1341,7 +1341,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
private static void Layout(Control c)
{
- (c.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass();
+ c.GetLayoutManager()?.ExecuteLayoutPass();
}
public static IDisposable Start()
diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
index a4cdf592cc..0e171474fe 100644
--- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
@@ -883,18 +883,8 @@ namespace Avalonia.Controls.UnitTests
};
}
- private class TestTopLevel : TopLevel
+ private class TestTopLevel(ITopLevelImpl impl) : TopLevel(impl)
{
- private readonly ILayoutManager _layoutManager;
- public bool IsClosed { get; private set; }
-
- public TestTopLevel(ITopLevelImpl impl, ILayoutManager? layoutManager = null)
- : base(impl)
- {
- _layoutManager = layoutManager ?? new LayoutManager(this);
- }
-
- private protected override ILayoutManager CreateLayoutManager() => _layoutManager;
}
private static void Prepare(TabControl target)
diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
index 31b8fe45e8..fa578a7afe 100644
--- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
@@ -2284,17 +2284,8 @@ namespace Avalonia.Controls.UnitTests
}
}
- private class TestTopLevel : TopLevel
+ private class TestTopLevel(ITopLevelImpl impl) : TopLevel(impl)
{
- private readonly ILayoutManager _layoutManager;
-
- public TestTopLevel(ITopLevelImpl impl, ILayoutManager? layoutManager = null)
- : base(impl)
- {
- _layoutManager = layoutManager ?? new LayoutManager(this);
- }
-
- private protected override ILayoutManager CreateLayoutManager() => _layoutManager;
}
private static Mock CreateMockTopLevelImpl()
diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
index c48e45bc4e..9614ab8df9 100644
--- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
@@ -82,11 +82,11 @@ namespace Avalonia.Controls.UnitTests
{
var impl = CreateMockTopLevelImpl();
- var target = new TestTopLevel(impl.Object, Mock.Of());
-
+ var target = new TestTopLevel(impl.Object);
+
// The layout pass should be scheduled by the derived class.
- var layoutManagerMock = Mock.Get(target.LayoutManager);
- layoutManagerMock.Verify(x => x.ExecuteLayoutPass(), Times.Never);
+ Assert.Equal(0, target.Measured);
+ Assert.Equal(0, target.Arranged);
}
}
@@ -254,22 +254,6 @@ namespace Avalonia.Controls.UnitTests
}
}
- [Fact]
- public void Close_Should_Dispose_LayoutManager()
- {
- using (UnitTestApplication.Start(TestServices.StyledWindow))
- {
- var impl = CreateMockTopLevelImpl(true);
-
- var layoutManager = new Mock();
- var target = new TestTopLevel(impl.Object, layoutManager.Object);
-
- impl.Object.Closed!();
-
- layoutManager.Verify(x => x.Dispose());
- }
- }
-
[Fact]
public void Reacts_To_Changes_In_Global_Styles()
{
@@ -329,18 +313,20 @@ namespace Avalonia.Controls.UnitTests
return topLevel;
}
- private class TestTopLevel : TopLevel
+ private class TestTopLevel(ITopLevelImpl impl) : TopLevel(impl)
{
- private readonly ILayoutManager _layoutManager;
- public bool IsClosed { get; private set; }
-
- public TestTopLevel(ITopLevelImpl impl, ILayoutManager? layoutManager = null)
- : base(impl)
+ public int Measured, Arranged;
+ protected override Size MeasureCore(Size availableSize)
{
- _layoutManager = layoutManager ?? new LayoutManager(this);
+ Measured++;
+ return base.MeasureCore(availableSize);
}
- private protected override ILayoutManager CreateLayoutManager() => _layoutManager;
+ protected override void ArrangeCore(Rect finalRect)
+ {
+ Arranged++;
+ base.ArrangeCore(finalRect);
+ }
}
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs
index 8db2702e35..b84fef1fb2 100644
--- a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs
@@ -377,7 +377,7 @@ namespace Avalonia.Controls.UnitTests
private void Layout(Control c)
{
- (c.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass();
+ c.GetLayoutManager()?.ExecuteLayoutPass();
}
private class TestTransition : IPageTransition
diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
index bf35bc117d..1d678fa4f4 100644
--- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
@@ -1781,7 +1781,7 @@ namespace Avalonia.Controls.UnitTests
private void Layout(Control c)
{
- (c.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass();
+ c.GetLayoutManager()?.ExecuteLayoutPass();
}
private void ClickContainer(Control container, KeyModifiers modifiers)
diff --git a/tests/Avalonia.Controls.UnitTests/VirtualizingCarouselPanelTests.cs b/tests/Avalonia.Controls.UnitTests/VirtualizingCarouselPanelTests.cs
index 4c0ec8436a..cc506dd7a9 100644
--- a/tests/Avalonia.Controls.UnitTests/VirtualizingCarouselPanelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/VirtualizingCarouselPanelTests.cs
@@ -344,6 +344,6 @@ namespace Avalonia.Controls.UnitTests
});
}
- private static void Layout(Control c) => ((ILayoutRoot)c.GetVisualRoot()!).LayoutManager.ExecuteLayoutPass();
+ private static void Layout(Control c) => c.GetLayoutManager()?.ExecuteLayoutPass();
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
index 18c53efae7..0643e0758f 100644
--- a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
@@ -2352,8 +2352,7 @@ namespace Avalonia.Controls.UnitTests
private static void Layout(Control target)
{
- var root = (ILayoutRoot?)target.GetVisualRoot();
- root?.LayoutManager.ExecuteLayoutPass();
+ target.GetLayoutManager()?.ExecuteLayoutPass();
}
private static IControlTemplate ListBoxItemTemplate()
diff --git a/tests/Avalonia.RenderTests/TestRenderRoot.cs b/tests/Avalonia.RenderTests/TestRenderRoot.cs
index 51eea3bd21..d6e88d174f 100644
--- a/tests/Avalonia.RenderTests/TestRenderRoot.cs
+++ b/tests/Avalonia.RenderTests/TestRenderRoot.cs
@@ -5,13 +5,14 @@ using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.TextInput;
+using Avalonia.Layout;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
namespace Avalonia.Skia.RenderTests
{
- public class TestRenderRoot : Decorator, IPresentationSource, IInputRoot
+ public class TestRenderRoot : Decorator, IPresentationSource, IInputRoot, ILayoutRoot
{
private readonly IRenderTarget _renderTarget;
public Size ClientSize { get; private set; }
@@ -19,6 +20,15 @@ namespace Avalonia.Skia.RenderTests
IRenderer IPresentationSource.Renderer => Renderer;
IHitTester IPresentationSource.HitTester => new NullHitTester();
public IInputRoot InputRoot => this;
+
+ ILayoutRoot IPresentationSource.LayoutRoot => this;
+
+ public double LayoutScaling => 1l;
+
+ public ILayoutManager LayoutManager { get; }
+
+ Layoutable ILayoutRoot.RootVisual => this;
+
public Visual? RootVisual => this;
public double RenderScaling { get; }
@@ -26,7 +36,8 @@ namespace Avalonia.Skia.RenderTests
{
_renderTarget = renderTarget;
RenderScaling = scaling;
-
+ LayoutManager = new LayoutManager(this);
+
}
class NullHitTester : IHitTester
diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs
index ede7e12c09..0237cdbdce 100644
--- a/tests/Avalonia.UnitTests/TestRoot.cs
+++ b/tests/Avalonia.UnitTests/TestRoot.cs
@@ -58,6 +58,11 @@ namespace Avalonia.UnitTests
public double LayoutScaling { get; set; } = 1;
internal ILayoutManager LayoutManager { get; set; }
+
+ ILayoutRoot IPresentationSource.LayoutRoot => this;
+
+ Layoutable ILayoutRoot.RootVisual => RootElement;
+
ILayoutManager ILayoutRoot.LayoutManager => LayoutManager;
public Visual? RootVisual => this;