From 158ebe7710f9cdc8ec69a113200a6b8a66566695 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 26 Aug 2021 22:52:49 +0200 Subject: [PATCH 01/53] Lazy subscribe to collection changed. Subscribing to the inner collection's `CollectionChanged` event when an `ItemsSourceView` was created means the only way to unsubscribe is to dispose the `ItemsSourceView` meaning that the instance can't be easily shared. --- src/Avalonia.Controls/ItemsSourceView.cs | 48 ++++++++++++++++-------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Controls/ItemsSourceView.cs b/src/Avalonia.Controls/ItemsSourceView.cs index b2663f3213..2884295386 100644 --- a/src/Avalonia.Controls/ItemsSourceView.cs +++ b/src/Avalonia.Controls/ItemsSourceView.cs @@ -33,7 +33,7 @@ namespace Avalonia.Controls public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty()); private protected readonly IList _inner; - private INotifyCollectionChanged? _notifyCollectionChanged; + private NotifyCollectionChangedEventHandler? _collectionChanged; /// /// Initializes a new instance of the ItemsSourceView class for the specified data source. @@ -55,8 +55,6 @@ namespace Avalonia.Controls { _inner = new List(source.Cast()); } - - ListenToCollectionChanges(); } /// @@ -82,14 +80,41 @@ namespace Avalonia.Controls /// /// Occurs when the collection has changed to indicate the reason for the change and which items changed. /// - public event NotifyCollectionChangedEventHandler? CollectionChanged; + public event NotifyCollectionChangedEventHandler? CollectionChanged + { + add + { + if (_collectionChanged is null) + { + if (_inner is INotifyCollectionChanged incc) + { + incc.CollectionChanged += OnCollectionChanged; + } + } + + _collectionChanged += value; + } + + remove + { + _collectionChanged -= value; + + if (_collectionChanged is null) + { + if (_inner is INotifyCollectionChanged incc) + { + incc.CollectionChanged -= OnCollectionChanged; + } + } + } + } /// public void Dispose() { - if (_notifyCollectionChanged != null) + if (_inner is INotifyCollectionChanged incc) { - _notifyCollectionChanged.CollectionChanged -= OnCollectionChanged; + incc.CollectionChanged -= OnCollectionChanged; } } @@ -162,16 +187,7 @@ namespace Avalonia.Controls protected void OnItemsSourceChanged(NotifyCollectionChangedEventArgs args) { - CollectionChanged?.Invoke(this, args); - } - - private void ListenToCollectionChanges() - { - if (_inner is INotifyCollectionChanged incc) - { - incc.CollectionChanged += OnCollectionChanged; - _notifyCollectionChanged = incc; - } + _collectionChanged?.Invoke(this, args); } private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) From 5ae9a2d60c902a1b5fb0067b65df9ef5ea0c398c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 26 Aug 2021 23:15:07 +0200 Subject: [PATCH 02/53] Validate source collection, and add tests. --- src/Avalonia.Controls/ItemsSourceView.cs | 21 +++---- .../ItemsSourceViewTests.cs | 63 +++++++++++++++++++ 2 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs diff --git a/src/Avalonia.Controls/ItemsSourceView.cs b/src/Avalonia.Controls/ItemsSourceView.cs index 2884295386..d306939e4b 100644 --- a/src/Avalonia.Controls/ItemsSourceView.cs +++ b/src/Avalonia.Controls/ItemsSourceView.cs @@ -42,19 +42,16 @@ namespace Avalonia.Controls public ItemsSourceView(IEnumerable source) { source = source ?? throw new ArgumentNullException(nameof(source)); - - if (source is IList list) - { - _inner = list; - } - else if (source is IEnumerable objectEnumerable) + _inner = source switch { - _inner = new List(objectEnumerable); - } - else - { - _inner = new List(source.Cast()); - } + ItemsSourceView => throw new ArgumentException("Cannot wrap an existing ItemsSourceView.", nameof(source)), + IList list => list, + INotifyCollectionChanged => throw new ArgumentException( + "Collection implements INotifyCollectionChanged by not IList.", + nameof(source)), + IEnumerable iObj => new List(iObj), + _ => new List(source.Cast()) + }; } /// diff --git a/tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs new file mode 100644 index 0000000000..529b3b1aa8 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/ItemsSourceViewTests.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Text; +using Avalonia.Collections; +using Avalonia.Diagnostics; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + public class ItemsSourceViewTests + { + [Fact] + public void Only_Subscribes_To_Source_CollectionChanged_When_CollectionChanged_Subscribed() + { + var source = new AvaloniaList(); + var target = new ItemsSourceView(source); + var debug = (INotifyCollectionChangedDebug)source; + + Assert.Null(debug.GetCollectionChangedSubscribers()); + + void Handler(object sender, NotifyCollectionChangedEventArgs e) { } + target.CollectionChanged += Handler; + + Assert.NotNull(debug.GetCollectionChangedSubscribers()); + Assert.Equal(1, debug.GetCollectionChangedSubscribers().Length); + + target.CollectionChanged -= Handler; + + Assert.Null(debug.GetCollectionChangedSubscribers()); + } + + [Fact] + public void Cannot_Wrap_An_ItemsSourceView_In_Another() + { + var source = new ItemsSourceView(new string[0]); + Assert.Throws(() => new ItemsSourceView(source)); + } + + [Fact] + public void Cannot_Create_ItemsSourceView_With_Collection_That_Implements_INCC_But_Not_List() + { + var source = new InvalidCollection(); + Assert.Throws(() => new ItemsSourceView(source)); + } + + private class InvalidCollection : INotifyCollectionChanged, IEnumerable + { + public event NotifyCollectionChangedEventHandler CollectionChanged; + + public IEnumerator GetEnumerator() + { + yield break; + } + + IEnumerator IEnumerable.GetEnumerator() + { + yield break; + } + } + } +} From 2517a70994f6630d80c96e90b324e39262416db9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 26 Aug 2021 23:25:54 +0200 Subject: [PATCH 03/53] Expose inner list, and throw if disposed. --- src/Avalonia.Controls/ItemsSourceView.cs | 41 ++++++++++++++++++------ 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/ItemsSourceView.cs b/src/Avalonia.Controls/ItemsSourceView.cs index d306939e4b..e8869d6d0c 100644 --- a/src/Avalonia.Controls/ItemsSourceView.cs +++ b/src/Avalonia.Controls/ItemsSourceView.cs @@ -32,7 +32,7 @@ namespace Avalonia.Controls /// public static ItemsSourceView Empty { get; } = new ItemsSourceView(Array.Empty()); - private protected readonly IList _inner; + private IList? _inner; private NotifyCollectionChangedEventHandler? _collectionChanged; /// @@ -57,7 +57,7 @@ namespace Avalonia.Controls /// /// Gets the number of items in the collection. /// - public int Count => _inner.Count; + public int Count => Inner.Count; /// /// Gets a value that indicates whether the items source can provide a unique key for each item. @@ -67,6 +67,19 @@ namespace Avalonia.Controls /// public bool HasKeyIndexMapping => false; + /// + /// Gets the inner collection. + /// + public IList Inner + { + get + { + if (_inner is null) + ThrowDisposed(); + return _inner!; + } + } + /// /// Retrieves the item at the specified index. /// @@ -81,6 +94,9 @@ namespace Avalonia.Controls { add { + if (_inner is null) + ThrowDisposed(); + if (_collectionChanged is null) { if (_inner is INotifyCollectionChanged incc) @@ -94,6 +110,9 @@ namespace Avalonia.Controls remove { + if (_inner is null) + ThrowDisposed(); + _collectionChanged -= value; if (_collectionChanged is null) @@ -113,6 +132,8 @@ namespace Avalonia.Controls { incc.CollectionChanged -= OnCollectionChanged; } + + _inner = null; } /// @@ -120,9 +141,9 @@ namespace Avalonia.Controls /// /// The index. /// The item. - public object? GetAt(int index) => _inner[index]; + public object? GetAt(int index) => Inner[index]; - public int IndexOf(object? item) => _inner.IndexOf(item); + public int IndexOf(object? item) => Inner.IndexOf(item); public static ItemsSourceView GetOrCreate(IEnumerable? items) { @@ -168,7 +189,7 @@ namespace Avalonia.Controls internal void AddListener(ICollectionChangedListener listener) { - if (_inner is INotifyCollectionChanged incc) + if (Inner is INotifyCollectionChanged incc) { CollectionChangedEventManager.Instance.AddListener(incc, listener); } @@ -176,7 +197,7 @@ namespace Avalonia.Controls internal void RemoveListener(ICollectionChangedListener listener) { - if (_inner is INotifyCollectionChanged incc) + if (Inner is INotifyCollectionChanged incc) { CollectionChangedEventManager.Instance.RemoveListener(incc, listener); } @@ -191,6 +212,8 @@ namespace Avalonia.Controls { OnItemsSourceChanged(e); } + + private void ThrowDisposed() => throw new ObjectDisposedException(nameof(ItemsSourceView)); } public class ItemsSourceView : ItemsSourceView, IReadOnlyList @@ -229,10 +252,10 @@ namespace Avalonia.Controls /// The index. /// The item. [return: MaybeNull] - public new T GetAt(int index) => (T)_inner[index]; + public new T GetAt(int index) => (T)Inner[index]; - public IEnumerator GetEnumerator() => _inner.Cast().GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); + public IEnumerator GetEnumerator() => Inner.Cast().GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => Inner.GetEnumerator(); public static new ItemsSourceView GetOrCreate(IEnumerable? items) { From 59ebe6e0c8edbaecf24d0edc5325b43502ad2e8c Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 31 Aug 2021 10:33:34 +0200 Subject: [PATCH 04/53] fixes(Dialogs): Warning CS0642 Possible mistaken empty statement --- src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs b/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs index 55e30396e1..5d7619d184 100644 --- a/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs +++ b/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs @@ -30,13 +30,13 @@ namespace Avalonia.Dialogs } else { - using (Process process = Process.Start(new ProcessStartInfo + using Process process = Process.Start(new ProcessStartInfo { FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? url : "open", Arguments = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"{url}" : "", CreateNoWindow = true, UseShellExecute = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - })); + }); } } From 98a0f43f9ec5ccff309813ba36ad21987fc03bf6 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 31 Aug 2021 10:36:20 +0200 Subject: [PATCH 05/53] fixes(Dialogs): Suppress warning CS0618 'PointerPressedEventArgs.ClickCount' is obsolete: 'Use DoubleTapped event or Gestures.DoubleRightTapped attached event' --- src/Avalonia.Dialogs/ManagedFileChooser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Dialogs/ManagedFileChooser.cs b/src/Avalonia.Dialogs/ManagedFileChooser.cs index f9f38ac474..9058c405a3 100644 --- a/src/Avalonia.Dialogs/ManagedFileChooser.cs +++ b/src/Avalonia.Dialogs/ManagedFileChooser.cs @@ -1,13 +1,11 @@ using System; using System.Linq; using System.Threading.Tasks; -using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.LogicalTree; -using Avalonia.Markup.Xaml; namespace Avalonia.Dialogs { @@ -35,7 +33,9 @@ namespace Avalonia.Dialogs if (_quickLinksRoot != null) { var isQuickLink = _quickLinksRoot.IsLogicalAncestorOf(e.Source as Control); +#pragma warning disable CS0618 // Type or member is obsolete if (e.ClickCount == 2 || isQuickLink) +#pragma warning restore CS0618 // Type or member is obsolete { if (model.ItemType == ManagedFileChooserItemType.File) { From 17229f80a733964f68fc360faecbd3a150ab7b08 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 31 Aug 2021 10:41:20 +0200 Subject: [PATCH 06/53] fixes(Dialogs): Warning CS0168 The variable '_' is declared but never used --- src/Avalonia.Dialogs/ManagedFileChooserSources.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Dialogs/ManagedFileChooserSources.cs b/src/Avalonia.Dialogs/ManagedFileChooserSources.cs index 050d618ce1..a217a67bc6 100644 --- a/src/Avalonia.Dialogs/ManagedFileChooserSources.cs +++ b/src/Avalonia.Dialogs/ManagedFileChooserSources.cs @@ -67,7 +67,7 @@ namespace Avalonia.Dialogs { Directory.GetFiles(x.VolumePath); } - catch (Exception _) + catch (Exception) { return null; } From dc71df166ce493b00c8e521adbb0b00bd5e1bd93 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 31 Aug 2021 12:05:28 +0200 Subject: [PATCH 07/53] fixes(Visuals): Suppress warning CS0618 'IVisualWithRoundRectClip' is obsolete: 'Internal API, will be removed in future versions, you've been warned' --- src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs | 7 +++++-- src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index 85feb06c44..52427c4ae6 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -289,11 +289,14 @@ namespace Avalonia.Rendering using (context.PushPostTransform(m)) using (context.PushOpacity(opacity)) - using (clipToBounds - ? visual is IVisualWithRoundRectClip roundClipVisual + using (clipToBounds +#pragma warning disable CS0618 // Type or member is obsolete + ? visual is IVisualWithRoundRectClip roundClipVisual ? context.PushClip(new RoundedRect(bounds, roundClipVisual.ClipToBoundsRadius)) : context.PushClip(bounds) : default(DrawingContext.PushedState)) +#pragma warning restore CS0618 // Type or member is obsolete + using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState)) using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default(DrawingContext.PushedState)) using (context.PushTransformContainer()) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index c6cdf474bb..b9131c26f4 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -164,10 +164,12 @@ namespace Avalonia.Rendering.SceneGraph var visual = node.Visual; var opacity = visual.Opacity; var clipToBounds = visual.ClipToBounds; +#pragma warning disable CS0618 // Type or member is obsolete var clipToBoundsRadius = visual is IVisualWithRoundRectClip roundRectClip ? roundRectClip.ClipToBoundsRadius : default; - +#pragma warning restore CS0618 // Type or member is obsolete + var bounds = new Rect(visual.Bounds.Size); var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl; From 5d3796092178f90fa625407ca875c99e0a14381c Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 31 Aug 2021 12:11:55 +0200 Subject: [PATCH 08/53] fixes(Input): Suppress warning CS0618 'MouseDevice.Position' is obsolete --- src/Avalonia.Input/MouseDevice.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index cfa3690daf..401c6cb2ac 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -75,7 +75,9 @@ namespace Avalonia.Input throw new InvalidOperationException("Control is not attached to visual tree."); } +#pragma warning disable CS0618 // Type or member is obsolete var rootPoint = relativeTo.VisualRoot.PointToClient(Position); +#pragma warning restore CS0618 // Type or member is obsolete var transform = relativeTo.VisualRoot.TransformToVisual(relativeTo); return rootPoint * transform!.Value; } From 91c9281511ad7697d322173edb7ce19ea4dbd0d7 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 31 Aug 2021 12:15:03 +0200 Subject: [PATCH 09/53] fixes(Input): Suppress warning CS0618 'PointerPressedEventArgs.ClickCount' is obsolete --- src/Avalonia.Input/Gestures.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Input/Gestures.cs b/src/Avalonia.Input/Gestures.cs index f2cc9e9072..8d74001309 100644 --- a/src/Avalonia.Input/Gestures.cs +++ b/src/Avalonia.Input/Gestures.cs @@ -81,17 +81,21 @@ namespace Avalonia.Input var e = (PointerPressedEventArgs)ev; var visual = (IVisual)ev.Source; - if (e.ClickCount <= 1) +#pragma warning disable CS0618 // Type or member is obsolete + var clickCount = e.ClickCount; +#pragma warning restore CS0618 // Type or member is obsolete + if (clickCount <= 1) { s_lastPress = new WeakReference(ev.Source); } - else if (s_lastPress != null && e.ClickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) + else if (s_lastPress != null && clickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) { if (s_lastPress.TryGetTarget(out var target) && target == e.Source) { e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e)); } } + } } From 3b65579b1abc74b289f355037a0e36f2ebb853ce Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 1 Sep 2021 12:50:02 +0200 Subject: [PATCH 10/53] fixes(DevTools): Warning CS8604 Possible null reference argument for parameter 'topLevel' --- .../Diagnostics/Views/MainWindow.xaml.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index ea06c33e4d..73d867bf10 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -160,13 +160,19 @@ namespace Avalonia.Diagnostics.Views return; } + var root = Root; + if (root is null) + { + return; + } + switch (e.Modifiers) { case RawInputModifiers.Control | RawInputModifiers.Shift: { IControl? control = null; - foreach (var popupRoot in GetPopupRoots(Root)) + foreach (var popupRoot in GetPopupRoots(root)) { control = GetHoveredControl(popupRoot); @@ -176,7 +182,7 @@ namespace Avalonia.Diagnostics.Views } } - control ??= GetHoveredControl(Root); + control ??= GetHoveredControl(root); if (control != null) { @@ -190,7 +196,7 @@ namespace Avalonia.Diagnostics.Views { vm.FreezePopups = !vm.FreezePopups; - foreach (var popupRoot in GetPopupRoots(Root)) + foreach (var popupRoot in GetPopupRoots(root)) { if (popupRoot.Parent is Popup popup) { From be4614e4236977a93833c6091a1a52ea47c9e469 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 9 Sep 2021 15:19:48 +0200 Subject: [PATCH 11/53] Fix errors with old SDK. --- src/Avalonia.Controls/ItemsSourceView.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/ItemsSourceView.cs b/src/Avalonia.Controls/ItemsSourceView.cs index e8869d6d0c..ca95b7acc8 100644 --- a/src/Avalonia.Controls/ItemsSourceView.cs +++ b/src/Avalonia.Controls/ItemsSourceView.cs @@ -44,9 +44,9 @@ namespace Avalonia.Controls source = source ?? throw new ArgumentNullException(nameof(source)); _inner = source switch { - ItemsSourceView => throw new ArgumentException("Cannot wrap an existing ItemsSourceView.", nameof(source)), + ItemsSourceView _ => throw new ArgumentException("Cannot wrap an existing ItemsSourceView.", nameof(source)), IList list => list, - INotifyCollectionChanged => throw new ArgumentException( + INotifyCollectionChanged _ => throw new ArgumentException( "Collection implements INotifyCollectionChanged by not IList.", nameof(source)), IEnumerable iObj => new List(iObj), From 8f833dbb2fba5d9a4df0946eef1122bc4f097d87 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Thu, 9 Sep 2021 19:16:32 +0300 Subject: [PATCH 12/53] it works --- packages/Avalonia/AvaloniaBuildTasks.targets | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 45a7f1aa44..a18930527f 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -42,12 +42,20 @@ - $(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences + $(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences;ReGenerateAvaloniaResourcesOnResourceDeletion + + + + + + + + From 890eabbd1fb2e211aaadb3a8bb8b3cb1cf6b4e68 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Fri, 10 Sep 2021 11:27:17 +0300 Subject: [PATCH 13/53] upd --- packages/Avalonia/AvaloniaBuildTasks.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index a18930527f..7fbe939390 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -50,12 +50,12 @@ - + From 89cb07677824ed70584b5379b314e63ec0e18fb6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 10 Sep 2021 15:16:47 +0200 Subject: [PATCH 14/53] Implement GeometryGroup. --- .../HeadlessPlatformRenderInterface.cs | 1 + .../Media/GeometryCollection.cs | 37 +++++ src/Avalonia.Visuals/Media/GeometryGroup.cs | 80 +++++++++ .../Platform/IPlatformRenderInterface.cs | 8 + src/Skia/Avalonia.Skia/GeometryGroupImpl.cs | 36 ++++ .../Avalonia.Skia/PlatformRenderInterface.cs | 5 + .../Avalonia.Direct2D1/Direct2D1Platform.cs | 1 + .../Media/GeometryGroupImpl.cs | 33 ++++ .../NullRenderingPlatform.cs | 5 + .../Media/GeometryGroupTests.cs | 154 ++++++++++++++++++ .../MockPlatformRenderInterface.cs | 5 + .../Media/GeometryGroupTests.cs | 26 +++ .../VisualTree/MockRenderInterface.cs | 5 + .../FillRule_EvenOdd.expected.png | Bin 0 -> 2349 bytes .../FillRule_EvenOdd_Stroke.expected.png | Bin 0 -> 4047 bytes .../FillRule_NonZero.expected.png | Bin 0 -> 1966 bytes .../FillRule_NonZero_Stroke.expected.png | Bin 0 -> 3880 bytes .../FillRule_EvenOdd.expected.png | Bin 0 -> 2607 bytes .../FillRule_EvenOdd_Stroke.expected.png | Bin 0 -> 3814 bytes .../FillRule_NonZero.expected.png | Bin 0 -> 2137 bytes .../FillRule_NonZero_Stroke.expected.png | Bin 0 -> 3693 bytes 21 files changed, 396 insertions(+) create mode 100644 src/Avalonia.Visuals/Media/GeometryCollection.cs create mode 100644 src/Avalonia.Visuals/Media/GeometryGroup.cs create mode 100644 src/Skia/Avalonia.Skia/GeometryGroupImpl.cs create mode 100644 src/Windows/Avalonia.Direct2D1/Media/GeometryGroupImpl.cs create mode 100644 tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs create mode 100644 tests/Avalonia.Visuals.UnitTests/Media/GeometryGroupTests.cs create mode 100644 tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_EvenOdd.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_EvenOdd_Stroke.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_NonZero.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_NonZero_Stroke.expected.png create mode 100644 tests/TestFiles/Skia/Media/GeometryGroup/FillRule_EvenOdd.expected.png create mode 100644 tests/TestFiles/Skia/Media/GeometryGroup/FillRule_EvenOdd_Stroke.expected.png create mode 100644 tests/TestFiles/Skia/Media/GeometryGroup/FillRule_NonZero.expected.png create mode 100644 tests/TestFiles/Skia/Media/GeometryGroup/FillRule_NonZero_Stroke.expected.png diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 268171d467..abc07d0e71 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -47,6 +47,7 @@ namespace Avalonia.Headless } public IStreamGeometryImpl CreateStreamGeometry() => new HeadlessStreamingGeometryStub(); + public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) => throw new NotImplementedException(); public IRenderTarget CreateRenderTarget(IEnumerable surfaces) => new HeadlessRenderTarget(); diff --git a/src/Avalonia.Visuals/Media/GeometryCollection.cs b/src/Avalonia.Visuals/Media/GeometryCollection.cs new file mode 100644 index 0000000000..0bd02d5438 --- /dev/null +++ b/src/Avalonia.Visuals/Media/GeometryCollection.cs @@ -0,0 +1,37 @@ +using System.Collections; +using System.Collections.Generic; +using Avalonia.Animation; + +#nullable enable + +namespace Avalonia.Media +{ + public class GeometryCollection : Animatable, IList, IReadOnlyList + { + private List _inner; + + public GeometryCollection() => _inner = new List(); + public GeometryCollection(IEnumerable collection) => _inner = new List(collection); + public GeometryCollection(int capacity) => _inner = new List(capacity); + + public Geometry this[int index] + { + get => _inner[index]; + set => _inner[index] = value; + } + + public int Count => _inner.Count; + public bool IsReadOnly => false; + + public void Add(Geometry item) => _inner.Add(item); + public void Clear() => _inner.Clear(); + public bool Contains(Geometry item) => _inner.Contains(item); + public void CopyTo(Geometry[] array, int arrayIndex) => _inner.CopyTo(array, arrayIndex); + public IEnumerator GetEnumerator() => _inner.GetEnumerator(); + public int IndexOf(Geometry item) => _inner.IndexOf(item); + public void Insert(int index, Geometry item) => _inner.Insert(index, item); + public bool Remove(Geometry item) => _inner.Remove(item); + public void RemoveAt(int index) => _inner.RemoveAt(index); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/Avalonia.Visuals/Media/GeometryGroup.cs b/src/Avalonia.Visuals/Media/GeometryGroup.cs new file mode 100644 index 0000000000..a3ce6a9dcd --- /dev/null +++ b/src/Avalonia.Visuals/Media/GeometryGroup.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Linq; +using Avalonia.Metadata; +using Avalonia.Platform; + +#nullable enable + +namespace Avalonia.Media +{ + /// + /// Represents a composite geometry, composed of other objects. + /// + public class GeometryGroup : Geometry + { + public static readonly DirectProperty ChildrenProperty = + AvaloniaProperty.RegisterDirect ( + nameof(Children), + o => o.Children, + (o, v) => o.Children = v); + + public static readonly StyledProperty FillRuleProperty = + AvaloniaProperty.Register(nameof(FillRule)); + + private GeometryCollection? _children; + private bool _childrenSet; + + /// + /// Gets or sets the collection that contains the child geometries. + /// + [Content] + public GeometryCollection? Children + { + get => _children ??= (!_childrenSet ? new GeometryCollection() : null); + set + { + SetAndRaise(ChildrenProperty, ref _children, value); + _childrenSet = true; + } + } + + /// + /// Gets or sets how the intersecting areas of the objects contained in this + /// are combined. The default is . + /// + public FillRule FillRule + { + get => GetValue(FillRuleProperty); + set => SetValue(FillRuleProperty, value); + } + + public override Geometry Clone() + { + var result = new GeometryGroup { FillRule = FillRule }; + if (_children?.Count > 0) + result.Children = new GeometryCollection(_children); + return result; + } + + protected override IGeometryImpl? CreateDefiningGeometry() + { + if (_children?.Count > 0) + { + var factory = AvaloniaLocator.Current.GetService(); + return factory.CreateGeometryGroup(FillRule, _children); + } + + return null; + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ChildrenProperty || change.Property == FillRuleProperty) + { + InvalidateGeometry(); + } + } + } +} diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index de67aca5a8..2cc44681d5 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -59,6 +59,14 @@ namespace Avalonia.Platform /// An . IStreamGeometryImpl CreateStreamGeometry(); + /// + /// Creates a geometry group implementation. + /// + /// The fill rule. + /// The geometries to group. + /// A combined geometry. + IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children); + /// /// Creates a renderer. /// diff --git a/src/Skia/Avalonia.Skia/GeometryGroupImpl.cs b/src/Skia/Avalonia.Skia/GeometryGroupImpl.cs new file mode 100644 index 0000000000..d6f19612c1 --- /dev/null +++ b/src/Skia/Avalonia.Skia/GeometryGroupImpl.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using Avalonia.Media; +using SkiaSharp; + +#nullable enable + +namespace Avalonia.Skia +{ + /// + /// A Skia implementation of a . + /// + internal class GeometryGroupImpl : GeometryImpl + { + public GeometryGroupImpl(FillRule fillRule, IReadOnlyList children) + { + var path = new SKPath + { + FillType = fillRule == FillRule.NonZero ? SKPathFillType.Winding : SKPathFillType.EvenOdd, + }; + + var count = children.Count; + + for (var i = 0; i < count; ++i) + { + if (children[i]?.PlatformImpl is GeometryImpl child) + path.AddPath(child.EffectivePath); + } + + EffectivePath = path; + Bounds = path.Bounds.ToAvaloniaRect(); + } + + public override Rect Bounds { get; } + public override SKPath EffectivePath { get; } + } +} diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 7bc83ec85b..447d683a2c 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -62,6 +62,11 @@ namespace Avalonia.Skia return new StreamGeometryImpl(); } + public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) + { + return new GeometryGroupImpl(fillRule, children); + } + /// public IBitmapImpl LoadBitmap(string fileName) { diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index f50167b39a..91f81223a0 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -175,6 +175,7 @@ namespace Avalonia.Direct2D1 public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2); public IGeometryImpl CreateRectangleGeometry(Rect rect) => new RectangleGeometryImpl(rect); public IStreamGeometryImpl CreateStreamGeometry() => new StreamGeometryImpl(); + public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) => new GeometryGroupImpl(fillRule, children); /// public IBitmapImpl LoadBitmap(string fileName) diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryGroupImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryGroupImpl.cs new file mode 100644 index 0000000000..352708bf03 --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryGroupImpl.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using SharpDX.Direct2D1; +using AM = Avalonia.Media; + +namespace Avalonia.Direct2D1.Media +{ + /// + /// A Direct2D implementation of a . + /// + internal class GeometryGroupImpl : GeometryImpl + { + /// + /// Initializes a new instance of the class. + /// + public GeometryGroupImpl(AM.FillRule fillRule, IReadOnlyList geometry) + : base(CreateGeometry(fillRule, geometry)) + { + } + + private static Geometry CreateGeometry(AM.FillRule fillRule, IReadOnlyList children) + { + var count = children.Count; + var c = new Geometry[count]; + + for (var i = 0; i < count; ++i) + { + c[i] = ((GeometryImpl)children[i].PlatformImpl).Geometry; + } + + return new GeometryGroup(Direct2D1Platform.Direct2D1Factory, (FillMode)fillRule, c); + } + } +} diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index 876a0de643..fd8dd3ff94 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -36,6 +36,11 @@ namespace Avalonia.Benchmarks return new MockStreamGeometryImpl(); } + public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) + { + throw new NotImplementedException(); + } + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { throw new NotImplementedException(); diff --git a/tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs b/tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs new file mode 100644 index 0000000000..6201c2c55e --- /dev/null +++ b/tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs @@ -0,0 +1,154 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Xunit; + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests +#else +namespace Avalonia.Direct2D1.RenderTests.Media +#endif +{ + public class GeometryGroupTests : TestBase + { + public GeometryGroupTests() + : base(@"Media\GeometryGroup") + { + } + + [Fact] + public async Task FillRule_EvenOdd() + { + var target = new Border + { + Width = 200, + Height = 200, + Background = Brushes.White, + Child = new Path + { + Data = new GeometryGroup + { + FillRule = FillRule.EvenOdd, + Children = + { + new RectangleGeometry(new Rect(25, 25, 100, 100)), + new EllipseGeometry + { + Center = new Point(125, 125), + RadiusX = 50, + RadiusY = 50, + }, + } + }, + Fill = Brushes.Blue, + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task FillRule_NonZero() + { + var target = new Border + { + Width = 200, + Height = 200, + Background = Brushes.White, + Child = new Path + { + Data = new GeometryGroup + { + FillRule = FillRule.NonZero, + Children = + { + new RectangleGeometry(new Rect(25, 25, 100, 100)), + new EllipseGeometry + { + Center = new Point(125, 125), + RadiusX = 50, + RadiusY = 50, + }, + } + }, + Fill = Brushes.Blue, + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task FillRule_EvenOdd_Stroke() + { + var target = new Border + { + Width = 200, + Height = 200, + Background = Brushes.White, + Child = new Path + { + Data = new GeometryGroup + { + FillRule = FillRule.EvenOdd, + Children = + { + new RectangleGeometry(new Rect(25, 25, 100, 100)), + new EllipseGeometry + { + Center = new Point(125, 125), + RadiusX = 50, + RadiusY = 50, + }, + } + }, + Fill = Brushes.Blue, + Stroke = Brushes.Red, + StrokeThickness = 1, + } + }; + + await RenderToFile(target); + CompareImages(); + } + + [Fact] + public async Task FillRule_NonZero_Stroke() + { + var target = new Border + { + Width = 200, + Height = 200, + Background = Brushes.White, + Child = new Path + { + Data = new GeometryGroup + { + FillRule = FillRule.NonZero, + Children = + { + new RectangleGeometry(new Rect(25, 25, 100, 100)), + new EllipseGeometry + { + Center = new Point(125, 125), + RadiusX = 50, + RadiusY = 50, + }, + } + }, + Fill = Brushes.Blue, + Stroke = Brushes.Red, + StrokeThickness = 1, + } + }; + + await RenderToFile(target); + CompareImages(); + } + } +} diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index 74366f9e26..34697b8616 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -52,6 +52,11 @@ namespace Avalonia.UnitTests return new MockStreamGeometryImpl(); } + public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) + { + return Mock.Of(); + } + public IWriteableBitmapImpl CreateWriteableBitmap( PixelSize size, Vector dpi, diff --git a/tests/Avalonia.Visuals.UnitTests/Media/GeometryGroupTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/GeometryGroupTests.cs new file mode 100644 index 0000000000..8f80238903 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/GeometryGroupTests.cs @@ -0,0 +1,26 @@ +using Avalonia.Media; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Media +{ + public class GeometryGroupTests + { + [Fact] + public void Children_Should_Have_Initial_Collection() + { + var target = new GeometryGroup(); + + Assert.NotNull(target.Children); + } + + [Fact] + public void Children_Can_Be_Set_To_Null() + { + var target = new GeometryGroup(); + + target.Children = null; + + Assert.Null(target.Children); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 51ea1e893f..dded3caa88 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -37,6 +37,11 @@ namespace Avalonia.Visuals.UnitTests.VisualTree return new MockStreamGeometry(); } + public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) + { + throw new NotImplementedException(); + } + public IBitmapImpl LoadBitmap(Stream stream) { throw new NotImplementedException(); diff --git a/tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_EvenOdd.expected.png b/tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_EvenOdd.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..ad8eb361584591b82d6d2206bd8d4caa881672a5 GIT binary patch literal 2349 zcmd5;dpHwp8{cNM&!G{GmUk#Qgv=aDquSZWD5De11h2wPeLSz z>r6nE$IcrbB^HTxA&iIX--U0)vb~;mJq|Y-Zsdheb(fhhm-N?pU8=fNa;a*oeiI(w zgtkRVKZFVgbNMv^fo&Q2U-^ogpAC#X5%CsHmEJxtY+sz?%F|y@dv1QVVY7qE&#ike zdmuysq)Fw?h5pDRv~fy4}PC3&nMVE zd*a^egVmB%*6J`4tci(`4Di}L(Zef}r>9S2VkfThmr2ORzFWp`F&o4ge4Sdvm7~U> zthB}R?7Tcq26kodF>>779;pc*6COc37tj_bRFJFT=MD0&O!G7IE^%N>`|w#Fa9Bb4uXw<77Dr z-w4k9ha>E{fo7UDx8*9X*eo>TgyR*tG^ReZVA=IW`4s~QXh)hEs|?97Rs2|`+PT+l zt6ZN!P*62HD_rnGjJ3CZf5x9K<8f2TPW?i)4XT_j;kXb5bpi zvK?72<;N^@*V79uIWc^xU`IZt9BWHWcvdrUirsNGN zbl9|J5#v-tXM_2|$43iB9g@J1T$(`)&3wve^UhmLn^T*??YDRcE-6a2W(c=1ac{h2 zJhxf$#xfetWH+Z1R!$5=IvuCoY=Ph~ecw{a9q|JeWPHqrlf`K~K*6`TEz3{YUR&<= zJ_mq8lVsk1dNvsoCC(Nj>MBSJn0wjfQEVSDbU@g$snY>iQ*d+Zf@*mnkYxw0#d;X{(2^k#oKyU)UT^&3o?o#jHKtU7#g_0ug;Isg#J#B0 zMb+O!>2P~Q!z`n0&5Q29BSsKCz z{cCI<@4w_|?+HwFP%8=*I{Hy?;rsVpFpp#6Sc-LFTB=ON)P;cAu-(7CGeB@3%+zaK zdNq4hrSOdSs9y|+P50MyCQ|!QYMN?)2QPI_S^D3U1hOs*)YnIEyp}z$W%18S9brAy z4F+aPRZenjEB;VdM-r`Y|B$I)lBj2Kt0+Nf-Lk8c(Sofq2(QRyFqPS|;>^3Q-SqA{ zIO^ZZAQg8njQB~~8LkAsaWy3MzwfEZAlpa)XD;8>Pd@CgmcSrbaMR%%t0_CNtr8^Z zB9K{>W`#QZ7s24|4S70ZF^9E=SB)04M4L@;&Q&D&Wzo11H@PS81B+Fi*gr!kc``zU zGoj))pRkI*R_j#PR&pX_5?mxTlO&z)!K9eIVQF=VQFf@pij6;K*(4a>j}YIj_RbH> zBm#O|-^*Tk`#q@;sz`kslySfyMK1V?e%QdFo|DJxv2wkDougfj1att&@gxmXiiVSx zn0$7SpCsi-Oy~UKcy*RMFobS4UtGL+*Q>{^(PMSwX)Prq=5P(13F6g%d>r8?5&FI+ zVa+gu2myu+ii^m6H~}3|ADQ=yrRu4JD3k|r^BKP`%ug~uR$Me%u!Ih<_)``f2@p=Z z5H8QPq?M5w!{9M6(GN%vbyf5LvV6SPw66ey%b_z`;l#X-Fent*3wnP}q8pgEq zrrIr3tx4B>bIfPOq4DC^ zTa}OSoV@qDmN@s*ZqoG~Ts+T2ELHZpH4L@wXx0F08p&;0QamWlu`~R=L&{&XMZ5Pw z&nwa$gGmm*zhfzSE(r!>lCp*9joxI1qayWPy_9_>=NKc;y}EO(rD=g`4Hp_bwQc@` zdt(o=cTcLhA#Moo?^wq98k+m6XcxFLPUF z*ShkI%gf#9hXutVquGH}k<6@BaEqhb1!T6Xo-EWInS9aTBJ@a*SOO3c|5gV4ua4Mk apZC!jx5}Ri(~A>J9)O+AacrfvZ{lA-+8WIO literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_EvenOdd_Stroke.expected.png b/tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_EvenOdd_Stroke.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..4dd547c4bf5b01343a211277795b2201e639c16b GIT binary patch literal 4047 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 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/GeometryGroup/FillRule_EvenOdd_Stroke.expected.png b/tests/TestFiles/Skia/Media/GeometryGroup/FillRule_EvenOdd_Stroke.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..80b91d12094545a721c032a4dd3dd569e745c212 GIT binary patch literal 3814 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 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/GeometryGroup/FillRule_NonZero_Stroke.expected.png b/tests/TestFiles/Skia/Media/GeometryGroup/FillRule_NonZero_Stroke.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..a101525cb30d8c7dcc344174bf9ad8bae6490e0b GIT binary patch literal 3693 zcmb_fhdUeE`zJ;TS|g|$iBV#-iq@)#E%siws<)|8BUWlu)GjG4p%kUkYg5#W-GSJo zluP8=d(YbCoBR6nVhjSG*`t8bTqAlvo|L(zG;h*UAx;oJs%t+*p=hxQB}$o z=5o-ssEE2XnnM9b*<#0c69Gn=WXtS`coQ5q69Ee7^>Wv|WzQ!g*6^BzprK38f_?CZ z^JvpQ{(#<_cK*>d;p2}B3a&MNIC^fjch|qN653{x#s>${gcKzw7mo4A&>CLjT3L{QtH1 z-$-KXU>YKyM2mJU;_>WZTCC1jHkjH=ZDx+bfAuX#n||;z3%JQJ%qV8A9f9;-(5{&M z_7W1BYH1>0aD4GT4sR_rUd5M)q9EQ$#3Zq2vK5HMekw8iHH2H0P^6f}W%*;*@hp)v zVq)VLoaGh}i_$A`%;v^85%0x^@yt6sqGaMN-Y6(hUy%25anMass0}p2?RYRfUqZCA zjsGeWbNQ3q{ekrLF+a9?m~vt@e{71XI^rBgyE4??Bl}a8fm8RiDhr}Z4=v;>k`(_2 z*lTz@aO>P zJhMa$A{&0_*!o5pSF0GxkIF3g8SYYYegzqJBQH+WEC3nG$N+?GKdniS zU2O{rjzdNfmX%!v^Z2a4BxjhqTYHgF7}GpS_A4*&i!I&Os^$aB2eq&+Km2e)jPI>D zJ>%TaAs_{PLpM^{nZt_jn<-;u-yvwd;uCoweBc!3a?Pv;wpb0q(h0fIcCB`b%GAS% z_qIMeeh)8O?cU8~L7B-}LO2N6k`Ajb$DC>yF<`HB&!I6tYEM7R3)2hF*AQ7blaa{u zSRzDkTi+Y&FAY_^JO!m0*-qs68=PuBjyZ7SP1-?EqVKk~rb(YzIl;e>k5p3_k7{7c zB0w=fO0g?T{ZOO_#VS{Cy9QIt&w1@$8avnrGsqC~iktRqRU3Rr$>9hHi9158HYaWJ z@mfI2yX9>NGkjvqDt7!q*Zz2fa{2N(81d6K5vd*KJO z8(dq#=sbY?O>MH4nYt(Mc6og5YFf?7(_i>M&jKk{AcUu+vv|UB@+Kdu^Pv4w?_`0G z;0DG0eJ4I3^+&787njL*f~-i=Bh{ak-qE9izU z%i%kQb&jio04o?!`BvwpN=|w!+v19f2Dtylu#nsZpeRg~tuFJ-ypShNoB;C~3afBq z*THGm?6Eur$Ir``)O(j&o-T$m3qWLV$a&kN`C@l!wu4V(QYv zIzF&!8oYYqCWR9p9&N9fzDo~1OGC@nfDl}(7wr`yQP$=DTQ2Va?osp}4QWAwC*ul@ zwe@_a^>FXlSVsLpD~QR*bi1ue6*(H>U`xLx#Kcz=yvHMoB0W8tQpPCRP>xjn{}uOC z)=*g`xYc5~^ZgFnlLS?aBJV~fSsLz=bB4BF?x44xb|?RP>oGIOFvX~hWme*lg<(#$ zDXKx62`HSlw+*cxot+?HpTDk8eNHhWv5C5%b;LMm6|hQSed z%m1G|4M*L`1|x&Avvl^ai^}9 z7KYTGNjfjGSQW~zIuC2YWS>kmJ31X@upf(tn-y!^=-ck%wS{=kvPiQB?Vfb2*?C}I zp4+%a1N_oWKOlg1TxR8)H#eO`++MMaX@)FJJCtj@|0qw4yY^7mYo9AuY9JGDAc+$% zChg4Y9|;pqm@THMTA3OX0Dg3E;TAHmBY^X438(dv`o&ICi(-=*^W6@>d}d4UfyFQM z&G<}-JL2wig5Gb=uQAiA&HAQ(4Qex}s~eFh;s|$|?x|kQ?lLldAsF1s0XMS46(SHb z9rv^wqfH3uwGZ0Kh?Uc3fmd`O#M^>IHqH+66=>fji>1z*@^X$>NxHpBzyec&-EXdo zQ&H-9BMIbhXQ8R}ZnGgxD%AQAoxFSltsA^s_si)_E_$+BAc2ZhBlc#fPljKwDkuMn z{{NU`>;bc;5W*5-ih(p{9PAFN^E*V|)%U(l_Ul?5l%p6;ypdMc8HIdlP5>#;q6BMH&sN`s3qNmPYT1Kg{@cTT02CwU-^|+9v_{13Mf~ad0V4 z%XOUU&fN0Rr9~{|+2zs*mJbF8$yUut^C4?U9cRDPq!dP+#fy>NlcuOz^TnS{@j8~m z3d&hN%WJgMo%kp2D*rfDs%ndC38sbbFH!Ay>AXKn zf1;YfRtbu;{jrLtTR1$9qHXcP7-q?31T}8)bLYeEw5?rb7&dIW@K>)H=B4BexvEY2 zF<+$Zqe$kEaw3Le?j2UlOA5|AoBiAS&e;8gFs{$-3cUwZS~_a*G}d1dSAT+u_k^V%aCw8* z^CW}2F}NPpVdemU6#;|k3N?d<#_SSn5v)hJ;y{eq3B$@$shfIwh2W;_Q?Qdz^?Q6^cRfsE&Iyr89H#s|{Hu>Hcz*I1IybRQ;pXs%kdl*)W*l+8@868#&c*^IHoE;F z#L6UA-|=Rw4?4jP!XHJrIT}pzaUS-$8)~i!W zkPaJp8)kgputd>rD0y0Zj)q~*u;gvmTU$Ql`G$|h(q{kLDXnV1>8@Lj--TVDFZ)Xo zq@OG={HU6PO2V&uQj4o$qcW)Z#e!O!(W5zg6GqQ<0Eoj`b}&?LdU~zPo9eZgx%gd) zbdLRT=i#N=$u1#D+|z0wa2yXW*KH}0S!-l;L+ zLPmjbTrC?=%y1MeH9Ou%3|0gszxY$(>n8imSXz*k=?&p?-mp>JNb$66a?PW&uUh#Q zgVB=w4XvdaqGMCK%IspEmJtoox$FE_ZWU-uM8z^fZx?!xIY|YD_c!JVw@7H^r^XfU zckI6$Gb`yLRaN4(10MZm_$sfiLoLK*1w{58vB7J_#=p=NpTIr@{K&-^`xu-TK-;B$ z=jR&`;(?tl<6l3BUuQlg$(@~kK6uuA|DhqZd0L^g{C68EY76rHlJ@oczemUYn|G=G PBaMNssZNE~pHcq-(MI2G literal 0 HcmV?d00001 From 4dbf646a734bc31df2270f6a638acf2d31b0d783 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 10 Sep 2021 22:40:43 +0200 Subject: [PATCH 15/53] Implement CombinedGeometry. --- .../HeadlessPlatformRenderInterface.cs | 1 + .../Media/CombinedGeometry.cs | 170 ++++++++++++++++++ src/Avalonia.Visuals/Media/GeometryGroup.cs | 2 +- .../Platform/IPlatformRenderInterface.cs | 9 + .../Avalonia.Skia/CombinedGeometryImpl.cs | 35 ++++ .../Avalonia.Skia/PlatformRenderInterface.cs | 5 + .../Avalonia.Direct2D1/Direct2D1Platform.cs | 1 + .../Media/CombinedGeometryImpl.cs | 36 ++++ .../NullRenderingPlatform.cs | 5 + .../Media/CombinedGeometryTests.cs | 56 ++++++ .../MockPlatformRenderInterface.cs | 5 + .../VisualTree/MockRenderInterface.cs | 5 + .../GeometryCombineMode_Exclude.expected.png | Bin 0 -> 1691 bytes ...GeometryCombineMode_Intersect.expected.png | Bin 0 -> 1618 bytes .../GeometryCombineMode_Union.expected.png | Bin 0 -> 3268 bytes .../GeometryCombineMode_Xor.expected.png | Bin 0 -> 4047 bytes .../GeometryCombineMode_Exclude.expected.png | Bin 0 -> 1691 bytes ...GeometryCombineMode_Intersect.expected.png | Bin 0 -> 1618 bytes .../GeometryCombineMode_Union.expected.png | Bin 0 -> 3268 bytes .../GeometryCombineMode_Xor.expected.png | Bin 0 -> 4047 bytes 20 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.Visuals/Media/CombinedGeometry.cs create mode 100644 src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs create mode 100644 src/Windows/Avalonia.Direct2D1/Media/CombinedGeometryImpl.cs create mode 100644 tests/Avalonia.RenderTests/Media/CombinedGeometryTests.cs create mode 100644 tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Exclude.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Intersect.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Union.expected.png create mode 100644 tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Xor.expected.png create mode 100644 tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Exclude.expected.png create mode 100644 tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Intersect.expected.png create mode 100644 tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Union.expected.png create mode 100644 tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Xor.expected.png diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index abc07d0e71..63cbfb2dbe 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -48,6 +48,7 @@ namespace Avalonia.Headless public IStreamGeometryImpl CreateStreamGeometry() => new HeadlessStreamingGeometryStub(); public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) => throw new NotImplementedException(); + public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => throw new NotImplementedException(); public IRenderTarget CreateRenderTarget(IEnumerable surfaces) => new HeadlessRenderTarget(); diff --git a/src/Avalonia.Visuals/Media/CombinedGeometry.cs b/src/Avalonia.Visuals/Media/CombinedGeometry.cs new file mode 100644 index 0000000000..2202030b7a --- /dev/null +++ b/src/Avalonia.Visuals/Media/CombinedGeometry.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Platform; + +#nullable enable + +namespace Avalonia.Media +{ + public enum GeometryCombineMode + { + /// + /// The two regions are combined by taking the union of both. The resulting geometry is + /// geometry A + geometry B. + /// + Union, + + /// + /// The two regions are combined by taking their intersection. The new area consists of the + /// overlapping region between the two geometries. + /// + Intersect, + + /// + /// The two regions are combined by taking the area that exists in the first region but not + /// the second and the area that exists in the second region but not the first. The new + /// region consists of (A-B) + (B-A), where A and B are geometries. + /// + Xor, + + /// + /// The second region is excluded from the first. Given two geometries, A and B, the area of + /// geometry B is removed from the area of geometry A, producing a region that is A-B. + /// + Exclude, + } + + /// + /// Represents a 2-D geometric shape defined by the combination of two Geometry objects. + /// + public class CombinedGeometry : Geometry + { + /// + /// Defines the property. + /// + public static readonly StyledProperty Geometry1Property = + AvaloniaProperty.Register(nameof(Geometry1)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty Geometry2Property = + AvaloniaProperty.Register(nameof(Geometry2)); + /// + /// Defines the property. + /// + public static readonly StyledProperty GeometryCombineModeProperty = + AvaloniaProperty.Register(nameof(GeometryCombineMode)); + + /// + /// Initializes a new instance of the class. + /// + public CombinedGeometry() + { + } + + /// + /// Initializes a new instance of the class with the + /// specified objects. + /// + /// The first geometry to combine. + /// The second geometry to combine. + public CombinedGeometry(Geometry geometry1, Geometry geometry2) + { + Geometry1 = geometry1; + Geometry2 = geometry2; + } + + /// + /// Initializes a new instance of the class with the + /// specified objects and . + /// + /// The method by which geometry1 and geometry2 are combined. + /// The first geometry to combine. + /// The second geometry to combine. + public CombinedGeometry(GeometryCombineMode combineMode, Geometry? geometry1, Geometry? geometry2) + { + Geometry1 = geometry1; + Geometry2 = geometry2; + GeometryCombineMode = combineMode; + } + + /// + /// Initializes a new instance of the class with the + /// specified objects, and + /// . + /// + /// The method by which geometry1 and geometry2 are combined. + /// The first geometry to combine. + /// The second geometry to combine. + /// The transform applied to the geometry. + public CombinedGeometry( + GeometryCombineMode combineMode, + Geometry? geometry1, + Geometry? geometry2, + Transform? transform) + { + Geometry1 = geometry1; + Geometry2 = geometry2; + GeometryCombineMode = combineMode; + Transform = transform; + } + + /// + /// Gets or sets the first object of this + /// object. + /// + public Geometry? Geometry1 + { + get => GetValue(Geometry1Property); + set => SetValue(Geometry1Property, value); + } + + /// + /// Gets or sets the second object of this + /// object. + /// + public Geometry? Geometry2 + { + get => GetValue(Geometry2Property); + set => SetValue(Geometry2Property, value); + } + + /// + /// Gets or sets the method by which the two geometries (specified by the + /// and properties) are combined. The + /// default value is . + /// + public GeometryCombineMode GeometryCombineMode + { + get => GetValue(GeometryCombineModeProperty); + set => SetValue(GeometryCombineModeProperty, value); + } + + public override Geometry Clone() + { + return new CombinedGeometry(GeometryCombineMode, Geometry1, Geometry2, Transform); + } + + protected override IGeometryImpl? CreateDefiningGeometry() + { + var g1 = Geometry1; + var g2 = Geometry2; + + if (g1 is object && g2 is object) + { + var factory = AvaloniaLocator.Current.GetService(); + return factory.CreateCombinedGeometry(GeometryCombineMode, g1, g2); + } + else if (GeometryCombineMode == GeometryCombineMode.Intersect) + return null; + else if (g1 is object) + return g1.PlatformImpl; + else if (g2 is object) + return g2.PlatformImpl; + else + return null; + } + } +} diff --git a/src/Avalonia.Visuals/Media/GeometryGroup.cs b/src/Avalonia.Visuals/Media/GeometryGroup.cs index a3ce6a9dcd..edbe63d4bb 100644 --- a/src/Avalonia.Visuals/Media/GeometryGroup.cs +++ b/src/Avalonia.Visuals/Media/GeometryGroup.cs @@ -50,7 +50,7 @@ namespace Avalonia.Media public override Geometry Clone() { - var result = new GeometryGroup { FillRule = FillRule }; + var result = new GeometryGroup { FillRule = FillRule, Transform = Transform }; if (_children?.Count > 0) result.Children = new GeometryCollection(_children); return result; diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index 2cc44681d5..772f1ac9f3 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -67,6 +67,15 @@ namespace Avalonia.Platform /// A combined geometry. IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children); + /// + /// Creates a geometry group implementation. + /// + /// The combine mode + /// The first geometry. + /// The second geometry. + /// A combined geometry. + IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2); + /// /// Creates a renderer. /// diff --git a/src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs b/src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs new file mode 100644 index 0000000000..40d7e10ae3 --- /dev/null +++ b/src/Skia/Avalonia.Skia/CombinedGeometryImpl.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using Avalonia.Media; +using SkiaSharp; + +#nullable enable + +namespace Avalonia.Skia +{ + /// + /// A Skia implementation of a . + /// + internal class CombinedGeometryImpl : GeometryImpl + { + public CombinedGeometryImpl(GeometryCombineMode combineMode, Geometry g1, Geometry g2) + { + var path1 = ((GeometryImpl)g1.PlatformImpl).EffectivePath; + var path2 = ((GeometryImpl)g2.PlatformImpl).EffectivePath; + var op = combineMode switch + { + GeometryCombineMode.Intersect => SKPathOp.Intersect, + GeometryCombineMode.Xor => SKPathOp.Xor, + GeometryCombineMode.Exclude => SKPathOp.Difference, + _ => SKPathOp.Union, + }; + + var path = path1.Op(path2, op); + + EffectivePath = path; + Bounds = path.Bounds.ToAvaloniaRect(); + } + + public override Rect Bounds { get; } + public override SKPath EffectivePath { get; } + } +} diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 447d683a2c..e2175f1145 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -67,6 +67,11 @@ namespace Avalonia.Skia return new GeometryGroupImpl(fillRule, children); } + public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) + { + return new CombinedGeometryImpl(combineMode, g1, g2); + } + /// public IBitmapImpl LoadBitmap(string fileName) { diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 91f81223a0..eef4416101 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -176,6 +176,7 @@ namespace Avalonia.Direct2D1 public IGeometryImpl CreateRectangleGeometry(Rect rect) => new RectangleGeometryImpl(rect); public IStreamGeometryImpl CreateStreamGeometry() => new StreamGeometryImpl(); public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) => new GeometryGroupImpl(fillRule, children); + public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => new CombinedGeometryImpl(combineMode, g1, g2); /// public IBitmapImpl LoadBitmap(string fileName) diff --git a/src/Windows/Avalonia.Direct2D1/Media/CombinedGeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/CombinedGeometryImpl.cs new file mode 100644 index 0000000000..5a13c10bbc --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/Media/CombinedGeometryImpl.cs @@ -0,0 +1,36 @@ +using SharpDX.Direct2D1; +using AM = Avalonia.Media; + +namespace Avalonia.Direct2D1.Media +{ + /// + /// A Direct2D implementation of a . + /// + internal class CombinedGeometryImpl : GeometryImpl + { + /// + /// Initializes a new instance of the class. + /// + public CombinedGeometryImpl( + AM.GeometryCombineMode combineMode, + AM.Geometry geometry1, + AM.Geometry geometry2) + : base(CreateGeometry(combineMode, geometry1, geometry2)) + { + } + + private static Geometry CreateGeometry( + AM.GeometryCombineMode combineMode, + AM.Geometry geometry1, + AM.Geometry geometry2) + { + var g1 = ((GeometryImpl)geometry1.PlatformImpl).Geometry; + var g2 = ((GeometryImpl)geometry2.PlatformImpl).Geometry; + var dest = new PathGeometry(Direct2D1Platform.Direct2D1Factory); + using var sink = dest.Open(); + g1.Combine(g2, (CombineMode)combineMode, sink); + sink.Close(); + return dest; + } + } +} diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index fd8dd3ff94..3e11c74e1c 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -41,6 +41,11 @@ namespace Avalonia.Benchmarks throw new NotImplementedException(); } + public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) + { + throw new NotImplementedException(); + } + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { throw new NotImplementedException(); diff --git a/tests/Avalonia.RenderTests/Media/CombinedGeometryTests.cs b/tests/Avalonia.RenderTests/Media/CombinedGeometryTests.cs new file mode 100644 index 0000000000..0469dec651 --- /dev/null +++ b/tests/Avalonia.RenderTests/Media/CombinedGeometryTests.cs @@ -0,0 +1,56 @@ +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Xunit; + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests +#else +namespace Avalonia.Direct2D1.RenderTests.Media +#endif +{ + public class CombinedGeometryTests : TestBase + { + public CombinedGeometryTests() + : base(@"Media\CombinedGeometry") + { + } + + [Theory] + [InlineData(Avalonia.Media.GeometryCombineMode.Union)] + [InlineData(Avalonia.Media.GeometryCombineMode.Intersect)] + [InlineData(Avalonia.Media.GeometryCombineMode.Xor)] + [InlineData(Avalonia.Media.GeometryCombineMode.Exclude)] + public async Task GeometryCombineMode(GeometryCombineMode mode) + { + var target = new Border + { + Width = 200, + Height = 200, + Background = Brushes.White, + Child = new Path + { + Data = new CombinedGeometry + { + GeometryCombineMode = mode, + Geometry1 = new RectangleGeometry(new Rect(25, 25, 100, 100)), + Geometry2 = new EllipseGeometry + { + Center = new Point(125, 125), + RadiusX = 50, + RadiusY = 50, + } + }, + Fill = Brushes.Blue, + Stroke = Brushes.Red, + StrokeThickness = 1, + } + }; + + var testName = $"GeometryCombineMode_{mode}"; + await RenderToFile(target, testName); + CompareImages(testName); + } + } +} diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index 34697b8616..1f632034be 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -57,6 +57,11 @@ namespace Avalonia.UnitTests return Mock.Of(); } + public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) + { + return Mock.Of(); + } + public IWriteableBitmapImpl CreateWriteableBitmap( PixelSize size, Vector dpi, diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index dded3caa88..229bb8aef3 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -42,6 +42,11 @@ namespace Avalonia.Visuals.UnitTests.VisualTree throw new NotImplementedException(); } + public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) + { + throw new NotImplementedException(); + } + public IBitmapImpl LoadBitmap(Stream stream) { throw new NotImplementedException(); diff --git a/tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Exclude.expected.png b/tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Exclude.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..2c4aa99eeb4dab763fc8ebb91dc2b9d47c4e6cfc GIT binary patch literal 1691 zcmc&#>pK$)7#~&DHOr7C?f7hXVjm!&F=k+5rIUbar)e@V*`}JL-}07eTj8 z8W=M;)9v)@_%GLkO$6i? zde<-hE$Vqg(=BCVnw$ufY)(PdTsineU>rD%&><%g60;MLjt~||{xDEhf&O!-6>o{} zMGn@7gG9WVmAP-1q5H%H-wf0H#}+5YvUAf`SEo7O3vFVAO`kzmBJ9ACk(&k_Zr$Ez z0q5_`kHiW(C`X-s!^%?YmSi$&#T@xIb z8IMg{BiC&%;CZR)s<7Arnsb#xu_)6*?-JEGVH~;_{A~G}<*JA);MwTma?VwM1oj%1 zPH?65spkCoE>zUW-<&i-nvd4w^0?f$!DkeTRj3x-g7Nc_8I&st`VVMSGeIjpsU`%y zVUp_#$t|NE_km+KLI+2s!)OWfi%8@&GwO$MLx9?k7>#2$N*7yClHPHPY8H}HiqK-h zS3}t$;I7G7spDjx^xkA%c6_K$&t%gN#ji|NR#k1dYkSCC~G1HxW!pTn~`|fi~YL_Wj<%@-Y7fEq?6%U0MG3D>NY(AZ-yAF5)QnUQSx zlUZ2J>|=x#Ny&n~fE*G@F>7<-n-dDr3>ib#wX;hs#JiQNb>R56MjsRsA}-VMTYNHb zIjZY*$WpYD0==S$bn6`*1q&8W4aF-aVj<#Uo!SI(LsB+0#0%~YtljhGHCCk&>oWO* zIQFsaRpCL_H49i2v|*f_h#E*{Avs>Q$?N%*DlYRQ{a;`7sJ8n+`%mhgDX^b&Z__tm zu{~Xwe!~3p8VX_BROtX6pVTC{AuZ1#E?lK26&kRtjVYZ7Z3H7VM}2YM?Yqc`10}udHcZtT%EB_9LEc{{sp+}&;kGe literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Intersect.expected.png b/tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Intersect.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..abb3cea27080e77aa6063148ee9a5814ff9ea99d GIT binary patch literal 1618 zcmdT_`!~}I0H3wVBaO<<~5kL~U%8Om;CVYqRx zlvij*m|kWSp*AAaRz{;)!}XZC?w@gg_`H7k{Pa2J!#EWdq^D!70|J5cLW2Dxwp8#R zv^2N44qIQgB^s27AYV}ZfXVDufF$^w^Z|hy^K@0`pdb)b72@ynbMi0q6ERu1B-3XK z{GCkRlfj#RLc0TdGg!$Qhd9B#1-20Z7ohivH5`3nA~@aPVW4xsI)XDXn6uXk#=Fq% z>r&T!T6j+z}o*`%g~c*K8m+K*$fB7{{Cn-&B4_3Cv;6I?J62v z1gXRLR~mFqsV$y%(GIxqhI4DeT_o?P?EzKarN+nlK-xPI7MGh$kYnF3f6uBOsd3FW z5V#S!mOIUFFH?eI6>V*!sxx-f^Z&uQU4^#kvf@pdjwUqIH1 z!xiB3HB%WQ7Fm``bi2Az`tFXSuxjI18qdo$6>-#(EjFi-n^R zomm`fCOR?`SZ7rz%l3(f$~zBCHZ73n9L^W=4RKU3+;D@s2bN~}RNiU9TUW}&{hcgA zIGW4FCV=5k3T5cpc)Mh^M{Oei#xt7oipit(91#YJ@=>Bl^uhE}kJZ^!$!(gKiMiY? zi_pj!6YSR)q$F}Gl=n--H!>RdEtBQ3E|=?EkjEiSX4BG2?nZ{<3!Vs^*n9?s%{UrE z$Zh=tf$RJj@%It4+YM!_m_0xMhmN*lzOEjP%%f4MNgqem@m_}9C=xs#+-5R1KQp!b zVMQVdDy05RUQA8XimTHT&rrV*fKP*`fsI+ep=?r(UhXz}ZKJx>Q}Su5rAO*<%xTid z4;=0H^`4~REmy2tDvu%=xq_qZnwx=?^R%=di{bO`{CgoPBGPwHW%I){0&k>F@(cLuCm_d$zid^%YBOT>{9V7k%#)zOI} zA~fG0!K&s;?iv8lb@Um>1oYY5#p|F& zVH)&WlsokKKOXp6HQ|nS9Cm(rSY z;%-%?G%^t3*SZ$>&MdA^E3U1$-;HxEr^G1~PCq!p*bS(Tr{*PEGQZkpW3knRMaJOh zdNQo3_ZGbB)>c6))*gApg}tx3u=l-eD0v~+gkdVoRy-jrXcJSnm!Dg>*6Kz-7*z?V z9kq!ZCCuSCf{E0mUq>M<^n CHK(Eg literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Union.expected.png b/tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Union.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..f431ef4403d76b9798ea4d048d80caf909856bbd GIT binary patch literal 3268 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 GCHg-yqwvE3 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Xor.expected.png b/tests/TestFiles/Direct2D1/Media/CombinedGeometry/GeometryCombineMode_Xor.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..4dd547c4bf5b01343a211277795b2201e639c16b GIT binary patch literal 4047 zcmb`KX*3jW7r>2WEDZ+PcarQzgg;p_wiGRrt!RwwTPV9BnVDouimaiCv5oA;lEFx} zY*{k)nW9FR?Bku@FYl-K+yBG!+;f-bo_o)E&i$Q>zGiLC$sxkQz`($1X#u@KmxRBE z4M@K$?W^+X0uX$|{1QXekl1&+!t7yu)tG^yHucoeJr)KA?r=+}v2EDhpWnEzy&&bW zZdTWH(wHENit}A8d|$fsiavN3zY6pf$sIe*9}zKn;n8cfHSCD*YDm>k599myq53}W zs6f8pQQOMMeF>2&a=M&81r0d`VoPUax>VAM04u0VK{@^f)s@y}KR9?S z_#nlo@Z?g#z!746ys)`}Mde%uC%jbSIFHSCo$`d%K0>NHBUN92kV!NB!$&a*t=~(e z^cmG~|EtCPi+%l{RK=QkNAqG@T4j|J)jMifJL#EpBv%voeAN@vF#b5i-u?6-(eQA@ zz(njF`>#Z^6C}Wrv6&2I7|dGJMXE*429gFJKKto!P9%%%tO&};GBu^UAjWMww@2kP zwV5Qt4+2QUCiq+daV!OLF|X-D6`M9iIpGmZT7TCjG5y_~^S)uSKFES`OBVG&bJ2$; z5T!z8^QWvsH)srPZx19tlrfr`9#6$|90_)q|Bh+mG3A+g_DQzN3RfBwixG})Bz?Rk zfuOw6C@fl^?f9sN@oGi&)Q5J1eUl79z3*J|7YZ68Cl}p_VPg3; z62TpjYHgAIS{DTkf*^vOuQECE7wqg=%{^`GuWkNw^pi%ooX(#$Z$-ALV45_@+iwqYHXlP1`V zpq#95CA2#~C1c7nX{4at*pUlsw0`Y+ROKh~+N8EPNRQr|0X_IWR4wpFlf*X8+ljThY2X zD<=xBbj+%mNC4ELzv@wgiA-6MME<)4GpHF;hj z9w?`iw4MKcZ(tSBBuXt$$Ft7IZ@T%18PfKbh%z@c#C5`N%x4G*fs<}6sXHFm1s*Cn zrM2(t9WNJ}Ap&*{k~&%)k(-a};=Z-IS8PM11ZK$PUG1}WABi(;%Jx!D<|#m1*9T4f z9e3-6al;r^7$iJ3m>vdpE9GhygstKQ+|)Ap%{%hSIZe;~GKkx~Ymh_+;a=;sZ4V)` zt|TmrQ$U}~wq)1870X7zHz=s(v+h&twcEDrGCeoN#pxZuBj>DwrsiOKF>S&49c`ct zP|)tlg~gtVZSean%GW^^ARbyY{ClE*?5)Z}a0ffz9z7wMD`vm9aFsKAcs9ZJ+ zJ=nF1dZFjBtHz}949{-O_s&E1c;9>a((j0>T_?{r=V#=P=I%*2dTNd(kIEk*|8qHisRH;sTa|7W* zwypVnK4+t%HUh{OO+v~zaz1@>83Z+T&lG3U#69JT@2x{TIdd((ITs1kzfkYit z$x)Xw`7pYzKR2vw^YlC*Z;j)@wQq+usH#~hCrlp#6jvq@d_yE{YlmKPw_oH2`%Zm6 z^H6@K$|TGvv9PyJ*sFs2BOSq~e1-$UgAcxVf+9ym9>{ z^3Hkc`VW9o=$?z~(@)3y1my8;x6sxbf1=+6=ti`+-fFN%0)&|%B`cx|tP75`CSU0~ zv?AcW1+m$^>Mm0?--ud^NGz_SnZlc+|VI;REzYF?^SC zt?OBvxAmzyTG|514h+`WI?2t;5&XA~cpkpgc3k$cIvPYSWFdwNV7M&V>58-ohu&8XK!vGxw?%j|R; z3>9gX*k;_Yj#_opFjb&iisI)8Bs|Dky4T&I_8l7&FLK;=pL1F@>LxdeMcyX)OUPDa z@c-zHet#vFr5B9B;$y@;y^Q^Wg}ryGp=a}B>$4agP7XCP_5nO5jLIScu8POpR&CFq z+$~bTBjLw1Ui&TPGY%W`$y52hUGW6fX`_b`6Rn_=no!{}- zS?(Dv1up-RakEvMt`H)PMwV)kR@J3~Hm%_-L5#npGa+a|kl64C%X8HHmyHBfciLDW zQO|N-gO07=>y=2o zuRjC*lT~dYAmgbU(lNV^*`Z7-!f87D5{lid`h8rF0XER<%vX-K>3 zmZ^Z9`~j){*!p=U6W4^+5~>v{a_e2pUto9RJ+D!a<#0kLq=rGe@Ao-4M(Xk61LMOr z*|5(&im=R{DxA=*TV7r|%ywyA-T~&GOCc+!sx@`~8l$==U$rH&`$Sb-a-Q(RYdB7& zYm_qj+)*O{H3m4QxJoju>)aP*?9$$N`k3s#VBLH&Vwx`tp(EGi@H!JRU(cP@+sMr!)09rds@B$&9e2rJ^A(n0wbeq?BsZ^>d>2JM@V35oWbwF^{uSHvS3!Ef!ftn6bP{B?Kys}*;>8BEFUn<7qa@%P+~@E6d#5WMQQAc2 zwZz>40g-2thcB1bUA`OjIwjKwE=H=Gfb)>QztXtaypxQ5vP8A*<+45Q4}u@<6#CW* zDiH$L$wpycLiCTk#6_-2i+V}n`>&a6kYtxhl_?HavjC9p{tPxl9np;L%HG%WJNx+e zA4u5C>C}unM$N$wLjw5*Kx6N^4%M7SUO2dK49IKMx-Hw=@gGeL>nMsbDxuI31rDiy z;fP(QqPmW<349a7SOJ(>rleQvqfY`PR~E?igEh*6l?!7#g+8mv+qU^`tJ71$WbE9UnZuFytz)Gkz+s}R9$2u_!#?eW$ zGZ`_tW;?of2OPa^xhNf!N4`4D{xAn)F|UB~K3vk%k(aum&|%79EACd&@M1KnthoCg zm_I@JgU>60tu@c{qh+`Kuj9;zTS|VuLd78sw^Yt2rZ$Ok>#niw z8`W~dUbX3m&HMD2#ERq9qHYEmrVQvkM+#wS6`1$EohG}irM#+Qz~YoN+@Fn6nd#;k z;}e()Yr#>+U-EB(TI)HmdFY6R&gT@uuMrf6az|y(E7h;JJNs4oZDPY$EHus;+oUv%-l|+(+)_Ew`PsIz)Hu`V?DR}MiBz8V zk6c02*1}!oQl9|xo&8E7>I|T*z<|ZDse!H9E#GfD5Pj7UGSHG`5pj8^2_o)!&{FD~ zJwC_*vM56Rh<@IUrFyamJ{rCH;k@5x->}_&g~7WmZs3P2EtE=|RswD@*dI3Am{` z{xt$NecsKz^eKnc6sbm(={aa-2C;UZPA(*Q{~_F2C-uWM!I)(ge5ryi8MP{s17FPX zcLt?u(fY$E=9}+OljpXa^Mooxr47du4O|*(Ejs=SKwYrK=Vig}1c0SoSK(+Zk5w*Y2JTp5B=Z1HY&+-qy{I> WpK$)7#~&DHOr7C?f7hXVjm!&F=k+5rIUbar)e@V*`}JL-}07eTj8 z8W=M;)9v)@_%GLkO$6i? zde<-hE$Vqg(=BCVnw$ufY)(PdTsineU>rD%&><%g60;MLjt~||{xDEhf&O!-6>o{} zMGn@7gG9WVmAP-1q5H%H-wf0H#}+5YvUAf`SEo7O3vFVAO`kzmBJ9ACk(&k_Zr$Ez z0q5_`kHiW(C`X-s!^%?YmSi$&#T@xIb z8IMg{BiC&%;CZR)s<7Arnsb#xu_)6*?-JEGVH~;_{A~G}<*JA);MwTma?VwM1oj%1 zPH?65spkCoE>zUW-<&i-nvd4w^0?f$!DkeTRj3x-g7Nc_8I&st`VVMSGeIjpsU`%y zVUp_#$t|NE_km+KLI+2s!)OWfi%8@&GwO$MLx9?k7>#2$N*7yClHPHPY8H}HiqK-h zS3}t$;I7G7spDjx^xkA%c6_K$&t%gN#ji|NR#k1dYkSCC~G1HxW!pTn~`|fi~YL_Wj<%@-Y7fEq?6%U0MG3D>NY(AZ-yAF5)QnUQSx zlUZ2J>|=x#Ny&n~fE*G@F>7<-n-dDr3>ib#wX;hs#JiQNb>R56MjsRsA}-VMTYNHb zIjZY*$WpYD0==S$bn6`*1q&8W4aF-aVj<#Uo!SI(LsB+0#0%~YtljhGHCCk&>oWO* zIQFsaRpCL_H49i2v|*f_h#E*{Avs>Q$?N%*DlYRQ{a;`7sJ8n+`%mhgDX^b&Z__tm zu{~Xwe!~3p8VX_BROtX6pVTC{AuZ1#E?lK26&kRtjVYZ7Z3H7VM}2YM?Yqc`10}udHcZtT%EB_9LEc{{sp+}&;kGe literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Intersect.expected.png b/tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Intersect.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..abb3cea27080e77aa6063148ee9a5814ff9ea99d GIT binary patch literal 1618 zcmdT_`!~}I0H3wVBaO<<~5kL~U%8Om;CVYqRx zlvij*m|kWSp*AAaRz{;)!}XZC?w@gg_`H7k{Pa2J!#EWdq^D!70|J5cLW2Dxwp8#R zv^2N44qIQgB^s27AYV}ZfXVDufF$^w^Z|hy^K@0`pdb)b72@ynbMi0q6ERu1B-3XK z{GCkRlfj#RLc0TdGg!$Qhd9B#1-20Z7ohivH5`3nA~@aPVW4xsI)XDXn6uXk#=Fq% z>r&T!T6j+z}o*`%g~c*K8m+K*$fB7{{Cn-&B4_3Cv;6I?J62v z1gXRLR~mFqsV$y%(GIxqhI4DeT_o?P?EzKarN+nlK-xPI7MGh$kYnF3f6uBOsd3FW z5V#S!mOIUFFH?eI6>V*!sxx-f^Z&uQU4^#kvf@pdjwUqIH1 z!xiB3HB%WQ7Fm``bi2Az`tFXSuxjI18qdo$6>-#(EjFi-n^R zomm`fCOR?`SZ7rz%l3(f$~zBCHZ73n9L^W=4RKU3+;D@s2bN~}RNiU9TUW}&{hcgA zIGW4FCV=5k3T5cpc)Mh^M{Oei#xt7oipit(91#YJ@=>Bl^uhE}kJZ^!$!(gKiMiY? zi_pj!6YSR)q$F}Gl=n--H!>RdEtBQ3E|=?EkjEiSX4BG2?nZ{<3!Vs^*n9?s%{UrE z$Zh=tf$RJj@%It4+YM!_m_0xMhmN*lzOEjP%%f4MNgqem@m_}9C=xs#+-5R1KQp!b zVMQVdDy05RUQA8XimTHT&rrV*fKP*`fsI+ep=?r(UhXz}ZKJx>Q}Su5rAO*<%xTid z4;=0H^`4~REmy2tDvu%=xq_qZnwx=?^R%=di{bO`{CgoPBGPwHW%I){0&k>F@(cLuCm_d$zid^%YBOT>{9V7k%#)zOI} zA~fG0!K&s;?iv8lb@Um>1oYY5#p|F& zVH)&WlsokKKOXp6HQ|nS9Cm(rSY z;%-%?G%^t3*SZ$>&MdA^E3U1$-;HxEr^G1~PCq!p*bS(Tr{*PEGQZkpW3knRMaJOh zdNQo3_ZGbB)>c6))*gApg}tx3u=l-eD0v~+gkdVoRy-jrXcJSnm!Dg>*6Kz-7*z?V z9kq!ZCCuSCf{E0mUq>M<^n CHK(Eg literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Union.expected.png b/tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Union.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..f431ef4403d76b9798ea4d048d80caf909856bbd GIT binary patch literal 3268 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 GCHg-yqwvE3 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Xor.expected.png b/tests/TestFiles/Skia/Media/CombinedGeometry/GeometryCombineMode_Xor.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..4dd547c4bf5b01343a211277795b2201e639c16b GIT binary patch literal 4047 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 W Date: Sun, 12 Sep 2021 00:08:13 +0200 Subject: [PATCH 16/53] Updated ApiCompat baseline. --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 39a4c3004c..e3f9f9a070 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -67,6 +67,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avaloni InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAndTangentAtDistance(System.Double, Avalonia.Point, Avalonia.Point)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAtDistance(System.Double, Avalonia.Point)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetSegment(System.Double, System.Double, System.Boolean, Avalonia.Platform.IGeometryImpl)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGeometryImpl Avalonia.Platform.IPlatformRenderInterface.CreateCombinedGeometry(Avalonia.Media.GeometryCombineMode, Avalonia.Media.Geometry, Avalonia.Media.Geometry)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGeometryImpl Avalonia.Platform.IPlatformRenderInterface.CreateGeometryGroup(Avalonia.Media.FillRule, System.Collections.Generic.IReadOnlyList)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' is present in the contract but not in the implementation. MembersMustExist : Member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' does not exist in the implementation but it does exist in the contract. @@ -74,4 +76,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWr InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. -Total Issues: 75 +Total Issues: 77 From f66bdefaf629fcdef48090a34483c885b6205256 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 12 Sep 2021 19:05:25 +0200 Subject: [PATCH 17/53] Simplified GeometryGroup tests, --- .../Media/CombinedGeometryTests.cs | 2 +- .../Media/GeometryGroupTests.cs | 111 ++---------------- .../FillRule_EvenOdd.expected.png | Bin 2349 -> 0 bytes .../FillRule_NonZero.expected.png | Bin 1966 -> 0 bytes ...g => FillRule_Stroke_EvenOdd.expected.png} | Bin ...g => FillRule_Stroke_NonZero.expected.png} | Bin .../FillRule_EvenOdd.expected.png | Bin 2607 -> 0 bytes .../FillRule_NonZero.expected.png | Bin 2137 -> 0 bytes ...g => FillRule_Stroke_EvenOdd.expected.png} | Bin ...g => FillRule_Stroke_NonZero.expected.png} | Bin 10 files changed, 9 insertions(+), 104 deletions(-) delete mode 100644 tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_EvenOdd.expected.png delete mode 100644 tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_NonZero.expected.png rename tests/TestFiles/Direct2D1/Media/GeometryGroup/{FillRule_EvenOdd_Stroke.expected.png => FillRule_Stroke_EvenOdd.expected.png} (100%) rename tests/TestFiles/Direct2D1/Media/GeometryGroup/{FillRule_NonZero_Stroke.expected.png => FillRule_Stroke_NonZero.expected.png} (100%) delete mode 100644 tests/TestFiles/Skia/Media/GeometryGroup/FillRule_EvenOdd.expected.png delete mode 100644 tests/TestFiles/Skia/Media/GeometryGroup/FillRule_NonZero.expected.png rename tests/TestFiles/Skia/Media/GeometryGroup/{FillRule_EvenOdd_Stroke.expected.png => FillRule_Stroke_EvenOdd.expected.png} (100%) rename tests/TestFiles/Skia/Media/GeometryGroup/{FillRule_NonZero_Stroke.expected.png => FillRule_Stroke_NonZero.expected.png} (100%) diff --git a/tests/Avalonia.RenderTests/Media/CombinedGeometryTests.cs b/tests/Avalonia.RenderTests/Media/CombinedGeometryTests.cs index 0469dec651..d927b96dd0 100644 --- a/tests/Avalonia.RenderTests/Media/CombinedGeometryTests.cs +++ b/tests/Avalonia.RenderTests/Media/CombinedGeometryTests.cs @@ -48,7 +48,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - var testName = $"GeometryCombineMode_{mode}"; + var testName = $"{nameof(GeometryCombineMode)}_{mode}"; await RenderToFile(target, testName); CompareImages(testName); } diff --git a/tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs b/tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs index 6201c2c55e..f90a441d41 100644 --- a/tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs +++ b/tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs @@ -19,8 +19,10 @@ namespace Avalonia.Direct2D1.RenderTests.Media { } - [Fact] - public async Task FillRule_EvenOdd() + [Theory] + [InlineData(FillRule.EvenOdd)] + [InlineData(FillRule.NonZero)] + public async Task FillRule_Stroke(FillRule fillRule) { var target = new Border { @@ -31,105 +33,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media { Data = new GeometryGroup { - FillRule = FillRule.EvenOdd, - Children = - { - new RectangleGeometry(new Rect(25, 25, 100, 100)), - new EllipseGeometry - { - Center = new Point(125, 125), - RadiusX = 50, - RadiusY = 50, - }, - } - }, - Fill = Brushes.Blue, - } - }; - - await RenderToFile(target); - CompareImages(); - } - - [Fact] - public async Task FillRule_NonZero() - { - var target = new Border - { - Width = 200, - Height = 200, - Background = Brushes.White, - Child = new Path - { - Data = new GeometryGroup - { - FillRule = FillRule.NonZero, - Children = - { - new RectangleGeometry(new Rect(25, 25, 100, 100)), - new EllipseGeometry - { - Center = new Point(125, 125), - RadiusX = 50, - RadiusY = 50, - }, - } - }, - Fill = Brushes.Blue, - } - }; - - await RenderToFile(target); - CompareImages(); - } - - [Fact] - public async Task FillRule_EvenOdd_Stroke() - { - var target = new Border - { - Width = 200, - Height = 200, - Background = Brushes.White, - Child = new Path - { - Data = new GeometryGroup - { - FillRule = FillRule.EvenOdd, - Children = - { - new RectangleGeometry(new Rect(25, 25, 100, 100)), - new EllipseGeometry - { - Center = new Point(125, 125), - RadiusX = 50, - RadiusY = 50, - }, - } - }, - Fill = Brushes.Blue, - Stroke = Brushes.Red, - StrokeThickness = 1, - } - }; - - await RenderToFile(target); - CompareImages(); - } - - [Fact] - public async Task FillRule_NonZero_Stroke() - { - var target = new Border - { - Width = 200, - Height = 200, - Background = Brushes.White, - Child = new Path - { - Data = new GeometryGroup - { - FillRule = FillRule.NonZero, + FillRule = fillRule, Children = { new RectangleGeometry(new Rect(25, 25, 100, 100)), @@ -147,8 +51,9 @@ namespace Avalonia.Direct2D1.RenderTests.Media } }; - await RenderToFile(target); - CompareImages(); + var testName = $"{nameof(FillRule_Stroke)}_{fillRule}"; + await RenderToFile(target, testName); + CompareImages(testName); } } } diff --git a/tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_EvenOdd.expected.png b/tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_EvenOdd.expected.png deleted file mode 100644 index ad8eb361584591b82d6d2206bd8d4caa881672a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2349 zcmd5;dpHwp8{cNM&!G{GmUk#Qgv=aDquSZWD5De11h2wPeLSz z>r6nE$IcrbB^HTxA&iIX--U0)vb~;mJq|Y-Zsdheb(fhhm-N?pU8=fNa;a*oeiI(w zgtkRVKZFVgbNMv^fo&Q2U-^ogpAC#X5%CsHmEJxtY+sz?%F|y@dv1QVVY7qE&#ike zdmuysq)Fw?h5pDRv~fy4}PC3&nMVE zd*a^egVmB%*6J`4tci(`4Di}L(Zef}r>9S2VkfThmr2ORzFWp`F&o4ge4Sdvm7~U> zthB}R?7Tcq26kodF>>779;pc*6COc37tj_bRFJFT=MD0&O!G7IE^%N>`|w#Fa9Bb4uXw<77Dr z-w4k9ha>E{fo7UDx8*9X*eo>TgyR*tG^ReZVA=IW`4s~QXh)hEs|?97Rs2|`+PT+l zt6ZN!P*62HD_rnGjJ3CZf5x9K<8f2TPW?i)4XT_j;kXb5bpi zvK?72<;N^@*V79uIWc^xU`IZt9BWHWcvdrUirsNGN zbl9|J5#v-tXM_2|$43iB9g@J1T$(`)&3wve^UhmLn^T*??YDRcE-6a2W(c=1ac{h2 zJhxf$#xfetWH+Z1R!$5=IvuCoY=Ph~ecw{a9q|JeWPHqrlf`K~K*6`TEz3{YUR&<= zJ_mq8lVsk1dNvsoCC(Nj>MBSJn0wjfQEVSDbU@g$snY>iQ*d+Zf@*mnkYxw0#d;X{(2^k#oKyU)UT^&3o?o#jHKtU7#g_0ug;Isg#J#B0 zMb+O!>2P~Q!z`n0&5Q29BSsKCz z{cCI<@4w_|?+HwFP%8=*I{Hy?;rsVpFpp#6Sc-LFTB=ON)P;cAu-(7CGeB@3%+zaK zdNq4hrSOdSs9y|+P50MyCQ|!QYMN?)2QPI_S^D3U1hOs*)YnIEyp}z$W%18S9brAy z4F+aPRZenjEB;VdM-r`Y|B$I)lBj2Kt0+Nf-Lk8c(Sofq2(QRyFqPS|;>^3Q-SqA{ zIO^ZZAQg8njQB~~8LkAsaWy3MzwfEZAlpa)XD;8>Pd@CgmcSrbaMR%%t0_CNtr8^Z zB9K{>W`#QZ7s24|4S70ZF^9E=SB)04M4L@;&Q&D&Wzo11H@PS81B+Fi*gr!kc``zU zGoj))pRkI*R_j#PR&pX_5?mxTlO&z)!K9eIVQF=VQFf@pij6;K*(4a>j}YIj_RbH> zBm#O|-^*Tk`#q@;sz`kslySfyMK1V?e%QdFo|DJxv2wkDougfj1att&@gxmXiiVSx zn0$7SpCsi-Oy~UKcy*RMFobS4UtGL+*Q>{^(PMSwX)Prq=5P(13F6g%d>r8?5&FI+ zVa+gu2myu+ii^m6H~}3|ADQ=yrRu4JD3k|r^BKP`%ug~uR$Me%u!Ih<_)``f2@p=Z z5H8QPq?M5w!{9M6(GN%vbyf5LvV6SPw66ey%b_z`;l#X-Fent*3wnP}q8pgEq zrrIr3tx4B>bIfPOq4DC^ zTa}OSoV@qDmN@s*ZqoG~Ts+T2ELHZpH4L@wXx0F08p&;0QamWlu`~R=L&{&XMZ5Pw z&nwa$gGmm*zhfzSE(r!>lCp*9joxI1qayWPy_9_>=NKc;y}EO(rD=g`4Hp_bwQc@` zdt(o=cTcLhA#Moo?^wq98k+m6XcxFLPUF z*ShkI%gf#9hXutVquGH}k<6@BaEqhb1!T6Xo-EWInS9aTBJ@a*SOO3c|5gV4ua4Mk apZC!jx5}Ri(~A>J9)O+AacrfvZ{lA-+8WIO diff --git a/tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_NonZero.expected.png b/tests/TestFiles/Direct2D1/Media/GeometryGroup/FillRule_NonZero.expected.png deleted file mode 100644 index bf1f49c36c071dd756d0cf7424f8db34c9a26d90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1966 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 diff --git a/tests/TestFiles/Skia/Media/GeometryGroup/FillRule_NonZero.expected.png b/tests/TestFiles/Skia/Media/GeometryGroup/FillRule_NonZero.expected.png deleted file mode 100644 index 8e156e7d06c04cb1d0e87a60fa4dc2f272c27d1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2137 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 diff --git a/tests/TestFiles/Skia/Media/GeometryGroup/FillRule_EvenOdd_Stroke.expected.png b/tests/TestFiles/Skia/Media/GeometryGroup/FillRule_Stroke_EvenOdd.expected.png similarity index 100% rename from tests/TestFiles/Skia/Media/GeometryGroup/FillRule_EvenOdd_Stroke.expected.png rename to tests/TestFiles/Skia/Media/GeometryGroup/FillRule_Stroke_EvenOdd.expected.png diff --git a/tests/TestFiles/Skia/Media/GeometryGroup/FillRule_NonZero_Stroke.expected.png b/tests/TestFiles/Skia/Media/GeometryGroup/FillRule_Stroke_NonZero.expected.png similarity index 100% rename from tests/TestFiles/Skia/Media/GeometryGroup/FillRule_NonZero_Stroke.expected.png rename to tests/TestFiles/Skia/Media/GeometryGroup/FillRule_Stroke_NonZero.expected.png From 591a791889c13326cdbdc212a34c6a052217ff5b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 12 Sep 2021 19:21:45 +0200 Subject: [PATCH 18/53] Added center properties to RotateTransform. --- src/Avalonia.Visuals/Media/RotateTransform.cs | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Media/RotateTransform.cs b/src/Avalonia.Visuals/Media/RotateTransform.cs index 653d38eb45..126bb7c274 100644 --- a/src/Avalonia.Visuals/Media/RotateTransform.cs +++ b/src/Avalonia.Visuals/Media/RotateTransform.cs @@ -14,6 +14,18 @@ namespace Avalonia.Media public static readonly StyledProperty AngleProperty = AvaloniaProperty.Register(nameof(Angle)); + /// + /// Defines the property. + /// + public static readonly StyledProperty CenterXProperty = + AvaloniaProperty.Register(nameof(CenterX)); + + /// + /// Defines the property. + /// + public static readonly StyledProperty CenterYProperty = + AvaloniaProperty.Register(nameof(CenterY)); + /// /// Initializes a new instance of the class. /// @@ -32,18 +44,52 @@ namespace Avalonia.Media Angle = angle; } + /// + /// Initializes a new instance of the class. + /// + /// The angle, in degrees. + /// The x-coordinate of the center point for the rotation. + /// The y-coordinate of the center point for the rotation. + public RotateTransform(double angle, double centerX, double centerY) + : this() + { + Angle = angle; + CenterX = centerX; + CenterY = centerY; + } + /// /// Gets or sets the angle of rotation, in degrees. /// public double Angle { - get { return GetValue(AngleProperty); } - set { SetValue(AngleProperty, value); } + get => GetValue(AngleProperty); + set => SetValue(AngleProperty, value); + } + + /// + /// Gets or sets the x-coordinate of the rotation center point. The default is 0. + /// + public double CenterX + { + get => GetValue(CenterXProperty); + set => SetValue(CenterXProperty, value); + } + + /// + /// Gets or sets the y-coordinate of the rotation center point. The default is 0. + /// + public double CenterY + { + get => GetValue(CenterYProperty); + set => SetValue(CenterYProperty, value); } /// /// Gets the transform's . /// - public override Matrix Value => Matrix.CreateRotation(Matrix.ToRadians(Angle)); + public override Matrix Value => Matrix.CreateTranslation(-CenterX, -CenterY) * + Matrix.CreateRotation(Matrix.ToRadians(Angle)) * + Matrix.CreateTranslation(CenterX, CenterY); } } From 6e0a911a3ae3d8b6bd3da7769683b84ef87d8f9c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 12 Sep 2021 19:26:36 +0200 Subject: [PATCH 19/53] Add test for transforms on GeometryGroup children. --- .../Media/GeometryGroupTests.cs | 36 ++++++++++++++++++ .../Child_Transform.expected.png | Bin 0 -> 4497 bytes .../Child_Transform.expected.png | Bin 0 -> 4784 bytes 3 files changed, 36 insertions(+) create mode 100644 tests/TestFiles/Direct2D1/Media/GeometryGroup/Child_Transform.expected.png create mode 100644 tests/TestFiles/Skia/Media/GeometryGroup/Child_Transform.expected.png diff --git a/tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs b/tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs index f90a441d41..9ebbd30e05 100644 --- a/tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs +++ b/tests/Avalonia.RenderTests/Media/GeometryGroupTests.cs @@ -55,5 +55,41 @@ namespace Avalonia.Direct2D1.RenderTests.Media await RenderToFile(target, testName); CompareImages(testName); } + + [Fact] + public async Task Child_Transform() + { + var target = new Border + { + Width = 200, + Height = 200, + Background = Brushes.White, + Child = new Path + { + Data = new GeometryGroup + { + Children = + { + new RectangleGeometry(new Rect(25, 25, 100, 100)) + { + Transform = new RotateTransform(45, 75, 75) + }, + new EllipseGeometry + { + Center = new Point(125, 125), + RadiusX = 50, + RadiusY = 50, + }, + } + }, + Fill = Brushes.Blue, + Stroke = Brushes.Red, + StrokeThickness = 1, + } + }; + + await RenderToFile(target); + CompareImages(); + } } } diff --git a/tests/TestFiles/Direct2D1/Media/GeometryGroup/Child_Transform.expected.png b/tests/TestFiles/Direct2D1/Media/GeometryGroup/Child_Transform.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..d8e4c549245203d6574cc17973199735f5bc6893 GIT binary patch literal 4497 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 literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Media/GeometryGroup/Child_Transform.expected.png b/tests/TestFiles/Skia/Media/GeometryGroup/Child_Transform.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..7182602fce9bb12359fc6fe974cd4f2071a900e4 GIT binary patch literal 4784 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 Mwmzyt^M2_603BuBL;wH) literal 0 HcmV?d00001 From 79f601886e6ba7a124140d3c1e24a17f896fc1e4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 12 Sep 2021 19:31:07 +0200 Subject: [PATCH 20/53] Added tests for transforms on CombinedGeometry children. --- .../Media/CombinedGeometryTests.cs | 33 ++++++++++++++++++ .../Geometry1_Transform.expected.png | Bin 0 -> 3572 bytes .../Geometry1_Transform.expected.png | Bin 0 -> 3627 bytes 3 files changed, 33 insertions(+) create mode 100644 tests/TestFiles/Direct2D1/Media/CombinedGeometry/Geometry1_Transform.expected.png create mode 100644 tests/TestFiles/Skia/Media/CombinedGeometry/Geometry1_Transform.expected.png diff --git a/tests/Avalonia.RenderTests/Media/CombinedGeometryTests.cs b/tests/Avalonia.RenderTests/Media/CombinedGeometryTests.cs index d927b96dd0..9c5c0248cf 100644 --- a/tests/Avalonia.RenderTests/Media/CombinedGeometryTests.cs +++ b/tests/Avalonia.RenderTests/Media/CombinedGeometryTests.cs @@ -52,5 +52,38 @@ namespace Avalonia.Direct2D1.RenderTests.Media await RenderToFile(target, testName); CompareImages(testName); } + + [Fact] + public async Task Geometry1_Transform() + { + var target = new Border + { + Width = 200, + Height = 200, + Background = Brushes.White, + Child = new Path + { + Data = new CombinedGeometry + { + Geometry1 = new RectangleGeometry(new Rect(25, 25, 100, 100)) + { + Transform = new RotateTransform(45, 75, 75) + }, + Geometry2 = new EllipseGeometry + { + Center = new Point(125, 125), + RadiusX = 50, + RadiusY = 50, + } + }, + Fill = Brushes.Blue, + Stroke = Brushes.Red, + StrokeThickness = 1, + } + }; + + await RenderToFile(target); + CompareImages(); + } } } diff --git a/tests/TestFiles/Direct2D1/Media/CombinedGeometry/Geometry1_Transform.expected.png b/tests/TestFiles/Direct2D1/Media/CombinedGeometry/Geometry1_Transform.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..34976f3de6e4712b77781024d3e1fa3c90989192 GIT binary patch literal 3572 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 L+E`SXyWjgC*u0wT literal 0 HcmV?d00001 From 68418d7f2abb997da308a736280ef2a27e67c686 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 13 Sep 2021 18:56:03 +0200 Subject: [PATCH 21/53] Add failing test --- .../Media/PathMarkupParserTests.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs index c5ad705654..ba8c490829 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs @@ -297,5 +297,28 @@ namespace Avalonia.Visuals.UnitTests.Media Assert.Equal(new Point(20, 20), figure.StartPoint); } } + + [Fact] + public void Should_Parse_Flags_Without_Separator() + { + var pathGeometry = new PathGeometry(); + using (var context = new PathGeometryContext(pathGeometry)) + using (var parser = new PathMarkupParser(context)) + { + parser.Parse("a.898.898 0 01.27.188"); + + var figure = pathGeometry.Figures[0]; + + var segments = figure.Segments; + + Assert.NotNull(segments); + + Assert.Equal(1, segments.Count); + + var arcSegment = segments[0]; + + Assert.IsType(arcSegment); + } + } } } From 5b09e7a60ee7cf283a9a7040a6980a370ed8b32e Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 13 Sep 2021 18:56:26 +0200 Subject: [PATCH 22/53] Fix flag parsing --- src/Avalonia.Visuals/Media/PathMarkupParser.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Media/PathMarkupParser.cs b/src/Avalonia.Visuals/Media/PathMarkupParser.cs index 9fefcb6645..8b9d0833db 100644 --- a/src/Avalonia.Visuals/Media/PathMarkupParser.cs +++ b/src/Avalonia.Visuals/Media/PathMarkupParser.cs @@ -496,12 +496,18 @@ namespace Avalonia.Media private bool ReadBool(ref ReadOnlySpan span) { - if (!ReadArgument(ref span, out var boolValue) || boolValue.Length != 1) + span = SkipWhitespace(span); + + if (span.IsEmpty) { throw new InvalidDataException("Invalid bool rule."); } - switch (boolValue[0]) + var c = span[0]; + + span = span.Slice(1); + + switch (c) { case '0': return false; From 9d54cd3b889687d3143081e00754265eb8c04697 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 15 Sep 2021 09:41:34 +0200 Subject: [PATCH 23/53] Disable win32 handling of Alt/F10 keys. https://stackoverflow.com/questions/9627379/how-to-disable-normal-behaviour-of-alt-key/9627980#9627980 Fixes #6592. --- .../Interop/UnmanagedMethods.cs | 25 +++++++++++++++++++ .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 6 +++++ 2 files changed, 31 insertions(+) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index ad409810b8..6a6bcc5715 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -808,6 +808,31 @@ namespace Avalonia.Win32.Interop MNC_SELECT = 3 } + public enum SysCommands + { + SC_SIZE = 0xF000, + SC_MOVE = 0xF010, + SC_MINIMIZE = 0xF020, + SC_MAXIMIZE = 0xF030, + SC_NEXTWINDOW = 0xF040, + SC_PREVWINDOW = 0xF050, + SC_CLOSE = 0xF060, + SC_VSCROLL = 0xF070, + SC_HSCROLL = 0xF080, + SC_MOUSEMENU = 0xF090, + SC_KEYMENU = 0xF100, + SC_ARRANGE = 0xF110, + SC_RESTORE = 0xF120, + SC_TASKLIST = 0xF130, + SC_SCREENSAVE = 0xF140, + SC_HOTKEY = 0xF150, + SC_DEFAULT = 0xF160, + SC_MONITORPOWER = 0xF170, + SC_CONTEXTHELP = 0xF180, + SC_SEPARATOR = 0xF00F, + SCF_ISSECURE = 0x00000001, + } + [StructLayout(LayoutKind.Sequential)] public struct RGBQUAD { diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index d163b3d068..f7ed2f215f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -123,6 +123,12 @@ namespace Avalonia.Win32 break; } + case WindowsMessage.WM_SYSCOMMAND: + // Disable system handling of Alt/F10 menu keys. + if ((SysCommands)wParam == SysCommands.SC_KEYMENU && HighWord(ToInt32(lParam)) <= 0) + return IntPtr.Zero; + break; + case WindowsMessage.WM_MENUCHAR: { // mute the system beep From acc6f6d3198189068295684af0026b7dd1f2808a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 16 Sep 2021 12:31:02 +0300 Subject: [PATCH 24/53] [X11/RNDR] Wait for any in-progress rendering before destroying the window --- .../Rendering/DeferredRenderer.cs | 33 +++++++++++++++---- src/Avalonia.X11/X11Window.cs | 3 +- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 3bd8e05ee2..6c84cfd55c 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -35,6 +35,8 @@ namespace Avalonia.Rendering private IRef _currentDraw; private readonly IDeferredRendererLock _lock; private readonly object _sceneLock = new object(); + private readonly object _startStopLock = new object(); + private readonly object _renderLoopIsRenderingLock = new object(); private readonly Action _updateSceneIfNeededDelegate; /// @@ -139,6 +141,8 @@ namespace Avalonia.Rendering } Stop(); + // Wait for any in-progress rendering to complete + lock(_renderLoopIsRenderingLock){} DisposeRenderTarget(); } @@ -233,20 +237,26 @@ namespace Avalonia.Rendering /// public void Start() { - if (!_running && _renderLoop != null) + lock (_startStopLock) { - _renderLoop.Add(this); - _running = true; + if (!_running && _renderLoop != null) + { + _renderLoop.Add(this); + _running = true; + } } } /// public void Stop() { - if (_running && _renderLoop != null) + lock (_startStopLock) { - _renderLoop.Remove(this); - _running = false; + if (_running && _renderLoop != null) + { + _renderLoop.Remove(this); + _running = false; + } } } @@ -255,7 +265,16 @@ namespace Avalonia.Rendering void IRenderLoopTask.Update(TimeSpan time) => UpdateScene(); - void IRenderLoopTask.Render() => Render(false); + void IRenderLoopTask.Render() + { + lock (_renderLoopIsRenderingLock) + { + lock(_startStopLock) + if(!_running) + return; + Render(false); + } + } /// Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 149d7fb9b2..f0d2d5ca8a 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -805,13 +805,14 @@ namespace Avalonia.X11 if (_handle != IntPtr.Zero) { - XDestroyWindow(_x11.Display, _handle); _platform.Windows.Remove(_handle); _platform.XI2?.OnWindowDestroyed(_handle); + var handle = _handle; _handle = IntPtr.Zero; Closed?.Invoke(); _mouse.Dispose(); _touch.Dispose(); + XDestroyWindow(_x11.Display, handle); } if (_useRenderWindow && _renderHandle != IntPtr.Zero) From 20e970f3359160a67644d382f54305730f52da82 Mon Sep 17 00:00:00 2001 From: 0x90d <46010672+0x90d@users.noreply.github.com> Date: Mon, 20 Sep 2021 09:48:15 +0200 Subject: [PATCH 25/53] Update DefaultMenuInteractionHandler.cs --- .../Platform/DefaultMenuInteractionHandler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 209feb351c..726d2c596c 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -303,7 +303,8 @@ namespace Avalonia.Controls.Platform { item.Parent.SelectedItem.Close(); SelectItemAndAncestors(item); - Open(item, false); + if (item.HasSubMenu) + Open(item, false); } else { From 1e918b1ed4dccf08e3504f4f515c3095b0bde9d6 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Mon, 20 Sep 2021 12:30:14 +0300 Subject: [PATCH 26/53] upd --- packages/Avalonia/AvaloniaBuildTasks.targets | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 7fbe939390..f32ffffa12 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -42,20 +42,24 @@ - $(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences;ReGenerateAvaloniaResourcesOnResourceDeletion + $(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences;_GenerateAvaloniaResourcesDependencyCache - + + + + + - + - + From 55de8084a14d0f6e9886ab0d90580fcb1e930ff3 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Mon, 20 Sep 2021 12:41:38 +0300 Subject: [PATCH 27/53] upd --- packages/Avalonia/AvaloniaBuildTasks.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index f32ffffa12..de3830ffea 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -47,14 +47,14 @@ - + - + Date: Mon, 20 Sep 2021 13:45:17 +0100 Subject: [PATCH 28/53] Add DesignStyle property which allows style to be assigned only at design time, similar to WPF. --- src/Avalonia.Controls/Design.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Avalonia.Controls/Design.cs b/src/Avalonia.Controls/Design.cs index 0d05e19e53..36435a46cc 100644 --- a/src/Avalonia.Controls/Design.cs +++ b/src/Avalonia.Controls/Design.cs @@ -60,6 +60,19 @@ namespace Avalonia.Controls return target.GetValue(PreviewWithProperty); } + public static readonly AttachedProperty