diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs index f47738f2e4..7873f83edb 100644 --- a/src/Avalonia.Base/Layout/LayoutManager.cs +++ b/src/Avalonia.Base/Layout/LayoutManager.cs @@ -21,6 +21,7 @@ namespace Avalonia.Layout private readonly Layoutable _owner; private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); + private readonly List _toArrangeAfterMeasure = new(); private readonly Action _executeLayoutPass; private List? _effectiveViewportChangedListeners; private bool _disposed; @@ -266,9 +267,14 @@ namespace Avalonia.Layout if (!control.IsArrangeValid) { - Arrange(control); + if (Arrange(control) == ArrangeResult.AncestorMeasureInvalid) + _toArrangeAfterMeasure.Add(control); } } + + foreach (var i in _toArrangeAfterMeasure) + InvalidateArrange(i); + _toArrangeAfterMeasure.Clear(); } private bool Measure(Layoutable control) @@ -304,19 +310,19 @@ namespace Avalonia.Layout return true; } - private bool Arrange(Layoutable control) + private ArrangeResult Arrange(Layoutable control) { if (!control.IsVisible || !control.IsAttachedToVisualTree) - return false; + return ArrangeResult.NotVisible; if (control.VisualParent is Layoutable parent) { - if (!Arrange(parent)) - return false; + if (Arrange(parent) is var parentResult && parentResult != ArrangeResult.Arranged) + return parentResult; } if (!control.IsMeasureValid) - return false; + return ArrangeResult.AncestorMeasureInvalid; if (!control.IsArrangeValid) { @@ -332,7 +338,7 @@ namespace Avalonia.Layout } } - return true; + return ArrangeResult.Arranged; } private void QueueLayoutPass() @@ -435,5 +441,12 @@ namespace Avalonia.Layout public Layoutable Listener { get; } public Rect Viewport { get; set; } } + + private enum ArrangeResult + { + Arranged, + NotVisible, + AncestorMeasureInvalid, + } } } diff --git a/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs b/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs index e77dd9d1ab..4921e9b756 100644 --- a/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs @@ -9,7 +9,7 @@ namespace Avalonia.Media.Imaging /// /// A bitmap that holds the rendering of a . /// - public class RenderTargetBitmap : Bitmap, IDisposable + public class RenderTargetBitmap : Bitmap { /// /// Initializes a new instance of the class. @@ -68,5 +68,11 @@ namespace Avalonia.Media.Imaging platform.Clear(Colors.Transparent); return new PlatformDrawingContext(platform); } + + public override void Dispose() + { + PlatformImpl.Dispose(); + base.Dispose(); + } } } diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index b486f8ad42..9e912db797 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -191,16 +191,7 @@ namespace Avalonia.Skia throw new ArgumentException("Height can't be less than 1", nameof(size)); } - var createInfo = new SurfaceRenderTarget.CreateInfo - { - Width = size.Width, - Height = size.Height, - Dpi = dpi, - DisableTextLcdRendering = false, - DisableManualFbo = true, - }; - - return new SurfaceRenderTarget(createInfo); + return new RenderTargetBitmapImpl(size, dpi); } /// diff --git a/src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs b/src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs new file mode 100644 index 0000000000..e20755b4e2 --- /dev/null +++ b/src/Skia/Avalonia.Skia/RenderTargetBitmapImpl.cs @@ -0,0 +1,31 @@ +using System.IO; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Media.Imaging; +using Avalonia.Platform; +using SkiaSharp; + +namespace Avalonia.Skia; + +internal class RenderTargetBitmapImpl : WriteableBitmapImpl, + IRenderTargetBitmapImpl, + IFramebufferPlatformSurface +{ + private readonly FramebufferRenderTarget _renderTarget; + + public RenderTargetBitmapImpl(PixelSize size, Vector dpi) : base(size, dpi, + SKImageInfo.PlatformColorType == SKColorType.Rgba8888 ? PixelFormats.Rgba8888 : PixelFormat.Bgra8888, + AlphaFormat.Premul) + { + _renderTarget = new FramebufferRenderTarget(this); + } + + public IDrawingContextImpl CreateDrawingContext() => _renderTarget.CreateDrawingContext(); + + public bool IsCorrupted => false; + + public override void Dispose() + { + _renderTarget.Dispose(); + base.Dispose(); + } +} \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 8ea7434c23..38df7b2933 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -131,7 +131,7 @@ namespace Avalonia.Skia } /// - public void Dispose() + public virtual void Dispose() { _bitmap.Dispose(); } diff --git a/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs b/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs index 45a6efdd4a..cadf23c754 100644 --- a/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs +++ b/tests/Avalonia.Base.UnitTests/Layout/LayoutManagerTests.cs @@ -547,5 +547,38 @@ namespace Avalonia.Base.UnitTests.Layout Assert.True(root.IsMeasureValid); Assert.True(root.IsArrangeValid); } + + [Fact] + public void GreatGrandparent_Can_Invalidate_Grandparent_Measure_During_Arrange() + { + // Issue #7706 (second part: scrollbar gets stuck) + var child = new LayoutTestControl(); + var parent = new LayoutTestControl { Child = child }; + var grandparent = new LayoutTestControl { Child = parent }; + var greatGrandparent = new LayoutTestControl { Child = grandparent }; + var root = new LayoutTestRoot { Child = greatGrandparent }; + + root.LayoutManager.ExecuteInitialLayoutPass(); + + greatGrandparent.DoArrangeOverride = (_, s) => + { + grandparent.InvalidateMeasure(); + return s; + }; + + child.InvalidateArrange(); + greatGrandparent.InvalidateArrange(); + + root.LayoutManager.ExecuteLayoutPass(); + + Assert.True(child.IsMeasureValid); + Assert.True(child.IsArrangeValid); + Assert.True(parent.IsMeasureValid); + Assert.True(parent.IsArrangeValid); + Assert.True(greatGrandparent.IsMeasureValid); + Assert.True(greatGrandparent.IsArrangeValid); + Assert.True(root.IsMeasureValid); + Assert.True(root.IsArrangeValid); + } } }