diff --git a/src/Avalonia.Base/Media/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs index f2106f2f86..18d6968168 100644 --- a/src/Avalonia.Base/Media/DrawingContext.cs +++ b/src/Avalonia.Base/Media/DrawingContext.cs @@ -132,7 +132,7 @@ namespace Avalonia.Media double radiusX = 0, double radiusY = 0, BoxShadows boxShadows = default) { - if (brush == null && !PenIsVisible(pen)) + if (brush == null && !PenIsVisible(pen) && boxShadows.Count == 0) return; if (!MathUtilities.IsZero(radiusX)) { @@ -160,7 +160,7 @@ namespace Avalonia.Media /// public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rrect, BoxShadows boxShadows = default) { - if (brush == null && !PenIsVisible(pen)) + if (brush == null && !PenIsVisible(pen) && boxShadows.Count == 0) return; DrawRectangleCore(brush, pen, rrect, boxShadows); } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 2cd7a6f5ef..b78437b559 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -527,6 +527,13 @@ namespace Avalonia.Controls /// protected virtual void HandleClosed() { + Renderer.SceneInvalidated -= SceneInvalidated; + // We need to wait for the renderer to complete any in-flight operations + Renderer.Dispose(); + + // The PlatformImpl is completely invalid at this point + PlatformImpl = null; + if (_globalStyles is object) { _globalStyles.GlobalStylesAdded -= ((IStyleHost)this).StylesAdded; @@ -536,10 +543,7 @@ namespace Avalonia.Controls { _applicationThemeHost.ActualThemeVariantChanged -= GlobalActualThemeVariantChanged; } - - Renderer.SceneInvalidated -= SceneInvalidated; - Renderer.Dispose(); - + _layoutDiagnosticBridge?.Dispose(); _layoutDiagnosticBridge = null; @@ -547,8 +551,6 @@ namespace Avalonia.Controls _pointerOverPreProcessorSubscription?.Dispose(); _backGestureSubscription?.Dispose(); - PlatformImpl = null; - var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null); ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs); diff --git a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs index 319b0da7bf..90749caf6f 100644 --- a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs +++ b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs @@ -43,6 +43,8 @@ namespace Avalonia.Headless _framesPerSecond = framesPerSecond; } + public override bool RunsInBackground => false; + public void ForceTick() => _forceTick?.Invoke(); } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 9665782323..ecd2726e09 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -48,6 +48,7 @@ namespace Avalonia.X11 private readonly X11NativeControlHost _nativeControlHost; private PixelPoint? _position; private PixelSize _realSize; + private bool _cleaningUp; private IntPtr _handle; private IntPtr _xic; private IntPtr _renderHandle; @@ -522,7 +523,7 @@ namespace Avalonia.X11 else if (ev.type == XEventName.DestroyNotify && ev.DestroyWindowEvent.window == _handle) { - Cleanup(); + Cleanup(true); } else if (ev.type == XEventName.ClientMessage) { @@ -816,7 +817,7 @@ namespace Avalonia.X11 public void Dispose() { - Cleanup(); + Cleanup(false); } public virtual object? TryGetFeature(Type featureType) @@ -849,8 +850,17 @@ namespace Avalonia.X11 return null; } - private void Cleanup() + private void Cleanup(bool fromDestroyNotification) { + // Prevent reentrancy + if(_cleaningUp) + return; + _cleaningUp = true; + + // Before doing anything else notify the TopLevel that ITopLevelImpl is no longer valid + if (_handle != IntPtr.Zero) + Closed?.Invoke(); + if (_rawEventGrouper != null) { _rawEventGrouper.Dispose(); @@ -891,7 +901,8 @@ namespace Avalonia.X11 Closed?.Invoke(); _mouse.Dispose(); _touch.Dispose(); - XDestroyWindow(_x11.Display, handle); + if (!fromDestroyNotification) + XDestroyWindow(_x11.Display, handle); } if (_useRenderWindow && _renderHandle != IntPtr.Zero) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 84f27f080e..2e09a38bad 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -85,6 +85,9 @@ namespace Avalonia.Win32 case WindowsMessage.WM_DESTROY: { + // The first and foremost thing to do - notify the TopLevel + Closed?.Invoke(); + if (UiaCoreTypesApi.IsNetComInteropAvailable) { UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, IntPtr.Zero, IntPtr.Zero, null); @@ -95,6 +98,18 @@ namespace Avalonia.Win32 { Imm32InputMethod.Current.ClearLanguageAndWindow(); } + + // Cleanup render targets + (_gl as IDisposable)?.Dispose(); + + if (_dropTarget != null) + { + OleContext.Current?.UnregisterDragDrop(Handle); + _dropTarget.Dispose(); + _dropTarget = null; + } + + _framebuffer.Dispose(); //Window doesn't exist anymore _hwnd = IntPtr.Zero; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9dbb7b77d8..545513c732 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -624,15 +624,6 @@ namespace Avalonia.Win32 public void Dispose() { - (_gl as IDisposable)?.Dispose(); - - if (_dropTarget != null) - { - OleContext.Current?.UnregisterDragDrop(Handle); - _dropTarget.Dispose(); - _dropTarget = null; - } - if (_hwnd != IntPtr.Zero) { // Detect if we are being closed programmatically - this would mean that WM_CLOSE was not called @@ -645,8 +636,6 @@ namespace Avalonia.Win32 DestroyWindow(_hwnd); _hwnd = IntPtr.Zero; } - - _framebuffer.Dispose(); } public void Invalidate(Rect rect) diff --git a/tests/Avalonia.RenderTests/Media/BoxShadowTests.cs b/tests/Avalonia.RenderTests/Media/BoxShadowTests.cs new file mode 100644 index 0000000000..a28fd49777 --- /dev/null +++ b/tests/Avalonia.RenderTests/Media/BoxShadowTests.cs @@ -0,0 +1,51 @@ +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Media; +using Xunit; +#pragma warning disable CS0649 + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests; + +public class BoxShadowTests : TestBase +{ + + public BoxShadowTests() : base(@"Media\BoxShadow") + { + } + + [Fact] + public async Task BoxShadowShouldBeRenderedEvenWithNullBrushAndPen() + { + var target = new Border + { + Width = 200, + Height = 200, + Background = null, + Child = new Border() + { + Background = null, + Margin = new Thickness(40), + BoxShadow = new BoxShadows(new BoxShadow + { + Blur = 0, + Color = Colors.Blue, + OffsetX = 10, + OffsetY = 15, + Spread = 0 + }), + Child = new Border + { + Background = Brushes.Red + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + + +} + +#endif \ No newline at end of file diff --git a/tests/TestFiles/Skia/Media/BoxShadow/BoxShadowShouldBeRenderedEvenWithNullBrushAndPen.expected.png b/tests/TestFiles/Skia/Media/BoxShadow/BoxShadowShouldBeRenderedEvenWithNullBrushAndPen.expected.png new file mode 100644 index 0000000000..6cb4974d51 Binary files /dev/null and b/tests/TestFiles/Skia/Media/BoxShadow/BoxShadowShouldBeRenderedEvenWithNullBrushAndPen.expected.png differ