From 37414f184b052a94975471ebd02d0e81216534cc Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 8 Apr 2023 19:10:15 +0600 Subject: [PATCH 1/3] Headless timer should report that it runs on the "UI" thread Should fix #10686 --- src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs | 2 ++ 1 file changed, 2 insertions(+) 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(); } From fc96e2949fb6e65e7e74f7142fa26810b637f3d6 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 8 Apr 2023 19:25:49 +0600 Subject: [PATCH 2/3] Fixed #10539 --- src/Avalonia.Base/Media/DrawingContext.cs | 4 +- .../Media/BoxShadowTests.cs | 51 ++++++++++++++++++ ...nderedEvenWithNullBrushAndPen.expected.png | Bin 0 -> 533 bytes 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 tests/Avalonia.RenderTests/Media/BoxShadowTests.cs create mode 100644 tests/TestFiles/Skia/Media/BoxShadow/BoxShadowShouldBeRenderedEvenWithNullBrushAndPen.expected.png 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/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 0000000000000000000000000000000000000000..6cb4974d51e26f089a55916417a31d8fa41508ed GIT binary patch literal 533 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yu@pObhHwBu4M$1`0|Vn3PZ!6K ziaBp@ALMmX6mfMl_;%l@tl-L#`O6EJHSK=T!n=kQs0$DHFwfTZ!OYFqX5Kqc`fd9B zgSWph{77SJR1o06Lln%HJFwqczb^fT^7%)16$DtA97i^h=+FOSPVqAFf~B@aVMGK7 cmI(Q?j-7KAJHLwI)*B#MPgg&ebxsLQ0B@vSrT_o{ literal 0 HcmV?d00001 From 9aec3e348b336cfda48122c83ce75cae008432a6 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 8 Apr 2023 18:09:38 +0600 Subject: [PATCH 3/3] Properly notify the TopLevel before destroying render targets --- src/Avalonia.Controls/TopLevel.cs | 14 ++++++++------ src/Avalonia.X11/X11Window.cs | 19 +++++++++++++++---- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 15 +++++++++++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 11 ----------- 4 files changed, 38 insertions(+), 21 deletions(-) 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.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)