From 158ebe7710f9cdc8ec69a113200a6b8a66566695 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 26 Aug 2021 22:52:49 +0200 Subject: [PATCH 01/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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 9777b9a5ee12f45cf99d4ae4f3f37717393b9c9b Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Thu, 23 Sep 2021 15:01:31 +0300 Subject: [PATCH 12/20] fix --- .../Platform/DefaultMenuInteractionHandler.cs | 158 +++++++++--------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 726d2c596c..dd3388b30e 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -99,7 +99,7 @@ namespace Avalonia.Controls.Platform { root.Deactivated -= WindowDeactivated; } - + if (_root is TopLevel tl) tl.PlatformImpl.LostFocus -= TopLevelLostPlatformFocus; @@ -148,116 +148,116 @@ namespace Avalonia.Controls.Platform { case Key.Up: case Key.Down: - { - if (item?.IsTopLevel == true) { - if (item.HasSubMenu && !item.IsSubMenuOpen) + if (item?.IsTopLevel == true) { - Open(item, true); - e.Handled = true; + if (item.HasSubMenu && !item.IsSubMenuOpen) + { + Open(item, true); + e.Handled = true; + } } + else + { + goto default; + } + break; } - else - { - goto default; - } - break; - } case Key.Left: - { - if (item?.Parent is IMenuItem parent && !parent.IsTopLevel && parent.IsSubMenuOpen) { - parent.Close(); - parent.Focus(); - e.Handled = true; - } - else - { - goto default; + if (item?.Parent is IMenuItem parent && !parent.IsTopLevel && parent.IsSubMenuOpen) + { + parent.Close(); + parent.Focus(); + e.Handled = true; + } + else + { + goto default; + } + break; } - break; - } case Key.Right: - { - if (item != null && !item.IsTopLevel && item.HasSubMenu) { - Open(item, true); - e.Handled = true; + if (item != null && !item.IsTopLevel && item.HasSubMenu) + { + Open(item, true); + e.Handled = true; + } + else + { + goto default; + } + break; } - else + + case Key.Enter: { - goto default; + if (item != null) + { + if (!item.HasSubMenu) + { + Click(item); + } + else + { + Open(item, true); + } + + e.Handled = true; + } + break; } - break; - } - case Key.Enter: - { - if (item != null) + case Key.Escape: { - if (!item.HasSubMenu) + if (item?.Parent is IMenuElement parent) { - Click(item); + parent.Close(); + parent.Focus(); } else { - Open(item, true); + Menu!.Close(); } e.Handled = true; + break; } - break; - } - - case Key.Escape: - { - if (item?.Parent is IMenuElement parent) - { - parent.Close(); - parent.Focus(); - } - else - { - Menu!.Close(); - } - - e.Handled = true; - break; - } default: - { - var direction = e.Key.ToNavigationDirection(); - - if (direction?.IsDirectional() == true) { - if (item == null && _isContextMenu) + var direction = e.Key.ToNavigationDirection(); + + if (direction?.IsDirectional() == true) { - if (Menu!.MoveSelection(direction.Value, true) == true) + if (item == null && _isContextMenu) { - e.Handled = true; + if (Menu!.MoveSelection(direction.Value, true) == true) + { + e.Handled = true; + } } - } - else if (item?.Parent?.MoveSelection(direction.Value, true) == true) - { - // If the the parent is an IMenu which successfully moved its selection, - // and the current menu is open then close the current menu and open the - // new menu. - if (item.IsSubMenuOpen && - item.Parent is IMenu && - item.Parent.SelectedItem is object) + else if (item?.Parent?.MoveSelection(direction.Value, true) == true) { - item.Close(); - Open(item.Parent.SelectedItem, true); + // If the the parent is an IMenu which successfully moved its selection, + // and the current menu is open then close the current menu and open the + // new menu. + if (item.IsSubMenuOpen && + item.Parent is IMenu && + item.Parent.SelectedItem is object) + { + item.Close(); + Open(item.Parent.SelectedItem, true); + } + e.Handled = true; } - e.Handled = true; } - } - break; - } + break; + } } if (!e.Handled && item?.Parent is IMenuItem parentItem) @@ -275,7 +275,7 @@ namespace Avalonia.Controls.Platform return; } - if (item.HasSubMenu) + if (item.HasSubMenu && item.IsEnabled) { Open(item, true); } @@ -441,7 +441,7 @@ namespace Avalonia.Controls.Platform { Menu?.Close(); } - + private void TopLevelLostPlatformFocus() { Menu?.Close(); From 63716e19d7988fc732b1c82c6edcea22ab1abbe8 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Thu, 23 Sep 2021 15:14:06 +0300 Subject: [PATCH 13/20] Revert "fix" This reverts commit 9777b9a5ee12f45cf99d4ae4f3f37717393b9c9b. --- .../Platform/DefaultMenuInteractionHandler.cs | 158 +++++++++--------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index dd3388b30e..726d2c596c 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -99,7 +99,7 @@ namespace Avalonia.Controls.Platform { root.Deactivated -= WindowDeactivated; } - + if (_root is TopLevel tl) tl.PlatformImpl.LostFocus -= TopLevelLostPlatformFocus; @@ -148,116 +148,116 @@ namespace Avalonia.Controls.Platform { case Key.Up: case Key.Down: + { + if (item?.IsTopLevel == true) { - if (item?.IsTopLevel == true) + if (item.HasSubMenu && !item.IsSubMenuOpen) { - if (item.HasSubMenu && !item.IsSubMenuOpen) - { - Open(item, true); - e.Handled = true; - } - } - else - { - goto default; + Open(item, true); + e.Handled = true; } - break; } + else + { + goto default; + } + break; + } case Key.Left: + { + if (item?.Parent is IMenuItem parent && !parent.IsTopLevel && parent.IsSubMenuOpen) { - if (item?.Parent is IMenuItem parent && !parent.IsTopLevel && parent.IsSubMenuOpen) - { - parent.Close(); - parent.Focus(); - e.Handled = true; - } - else - { - goto default; - } - break; + parent.Close(); + parent.Focus(); + e.Handled = true; + } + else + { + goto default; } + break; + } case Key.Right: + { + if (item != null && !item.IsTopLevel && item.HasSubMenu) { - if (item != null && !item.IsTopLevel && item.HasSubMenu) - { - Open(item, true); - e.Handled = true; - } - else - { - goto default; - } - break; + Open(item, true); + e.Handled = true; } - - case Key.Enter: + else { - if (item != null) - { - if (!item.HasSubMenu) - { - Click(item); - } - else - { - Open(item, true); - } - - e.Handled = true; - } - break; + goto default; } + break; + } - case Key.Escape: + case Key.Enter: + { + if (item != null) { - if (item?.Parent is IMenuElement parent) + if (!item.HasSubMenu) { - parent.Close(); - parent.Focus(); + Click(item); } else { - Menu!.Close(); + Open(item, true); } e.Handled = true; - break; } + break; + } - default: + case Key.Escape: + { + if (item?.Parent is IMenuElement parent) { - var direction = e.Key.ToNavigationDirection(); + parent.Close(); + parent.Focus(); + } + else + { + Menu!.Close(); + } - if (direction?.IsDirectional() == true) + e.Handled = true; + break; + } + + default: + { + var direction = e.Key.ToNavigationDirection(); + + if (direction?.IsDirectional() == true) + { + if (item == null && _isContextMenu) { - if (item == null && _isContextMenu) + if (Menu!.MoveSelection(direction.Value, true) == true) { - if (Menu!.MoveSelection(direction.Value, true) == true) - { - e.Handled = true; - } + e.Handled = true; } - else if (item?.Parent?.MoveSelection(direction.Value, true) == true) + } + else if (item?.Parent?.MoveSelection(direction.Value, true) == true) + { + // If the the parent is an IMenu which successfully moved its selection, + // and the current menu is open then close the current menu and open the + // new menu. + if (item.IsSubMenuOpen && + item.Parent is IMenu && + item.Parent.SelectedItem is object) { - // If the the parent is an IMenu which successfully moved its selection, - // and the current menu is open then close the current menu and open the - // new menu. - if (item.IsSubMenuOpen && - item.Parent is IMenu && - item.Parent.SelectedItem is object) - { - item.Close(); - Open(item.Parent.SelectedItem, true); - } - e.Handled = true; + item.Close(); + Open(item.Parent.SelectedItem, true); } + e.Handled = true; } - - break; } + + break; + } } if (!e.Handled && item?.Parent is IMenuItem parentItem) @@ -275,7 +275,7 @@ namespace Avalonia.Controls.Platform return; } - if (item.HasSubMenu && item.IsEnabled) + if (item.HasSubMenu) { Open(item, true); } @@ -441,7 +441,7 @@ namespace Avalonia.Controls.Platform { Menu?.Close(); } - + private void TopLevelLostPlatformFocus() { Menu?.Close(); From a13e9c1c1c932a124513a478ae8493203fdf4782 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Thu, 23 Sep 2021 15:14:29 +0300 Subject: [PATCH 14/20] fix --- src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 726d2c596c..cde78ae839 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -275,7 +275,7 @@ namespace Avalonia.Controls.Platform return; } - if (item.HasSubMenu) + if (item.HasSubMenu && item.IsEnabled) { Open(item, true); } From cfc50e45f54b5ea7727ec07cb17413011771c76f Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Thu, 23 Sep 2021 16:37:18 +0300 Subject: [PATCH 15/20] upd --- src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index cde78ae839..e361e7b736 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -275,7 +275,7 @@ namespace Avalonia.Controls.Platform return; } - if (item.HasSubMenu && item.IsEnabled) + if (item.HasSubMenu && item.IsEffectivelyEnabled) { Open(item, true); } From d56715b38858978d3b4a0a0d1a854415b800fbd0 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Sun, 26 Sep 2021 23:52:30 +0300 Subject: [PATCH 16/20] Used EqualityComparer.Default to check values for equality --- src/Avalonia.Visuals/Media/FormattedText.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/FormattedText.cs b/src/Avalonia.Visuals/Media/FormattedText.cs index a843be4c4b..ddffbe7500 100644 --- a/src/Avalonia.Visuals/Media/FormattedText.cs +++ b/src/Avalonia.Visuals/Media/FormattedText.cs @@ -200,7 +200,7 @@ namespace Avalonia.Media private void Set(ref T field, T value) { - if (field != null && field.Equals(value)) + if (EqualityComparer.Default.Equals(field, value)) { return; } From 6fba30e3d56fde29a3fb02ab29d31a6602217145 Mon Sep 17 00:00:00 2001 From: AmRo Date: Mon, 27 Sep 2021 10:17:52 +0330 Subject: [PATCH 17/20] Improve build documentation --- Documentation/build.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/build.md b/Documentation/build.md index a7d68eb599..9f5436e68e 100644 --- a/Documentation/build.md +++ b/Documentation/build.md @@ -6,6 +6,7 @@ Avalonia requires at least Visual Studio 2019 and .NET Core SDK 3.1 to build on ``` git clone https://github.com/AvaloniaUI/Avalonia.git +cd Avalonia git submodule update --init ``` From a99786d9c4a660b9096a6cff5aaf02e7bc71820c Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 29 Sep 2021 17:22:03 +0200 Subject: [PATCH 18/20] Fix ICustomDrawOperation HitTest coordinate space. The transform should be applied inverse, not directly, so that the draw operation can operate on local space. --- .../Rendering/SceneGraph/CustomDrawOperation.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs index 15e5660671..b7311936d3 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs @@ -17,7 +17,12 @@ namespace Avalonia.Rendering.SceneGraph public override bool HitTest(Point p) { - return Custom.HitTest(p * Transform); + if (Transform.HasInverse) + { + return Custom.HitTest(p * Transform.Invert()); + } + + return false; } public override void Render(IDrawingContextImpl context) From d0401bd01c3b521d3747b836441a4341492934ab Mon Sep 17 00:00:00 2001 From: workgroupengineering Date: Thu, 30 Sep 2021 09:39:40 +0200 Subject: [PATCH 19/20] Fixes markup warnings (#6525) * fixes: suppress warning CS0659 Type overrides Object.Equals(object o) but does not override Object.GetHashCode() * fixes: Warning CS0108 'AvaloniaXamlIlCompiler._configuration' hides inherited member 'XamlCompiler._configuration'. Use the new keyword if hiding was intended. * fixes: Warning CS8765 Nullability of type of parameter 'obj' doesn't match overridden member. --- src/Avalonia.Visuals/Vector.cs | 2 +- .../CompilerExtensions/AvaloniaXamlIlCompiler.cs | 3 --- .../Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs | 6 +++++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Visuals/Vector.cs b/src/Avalonia.Visuals/Vector.cs index 79c4202be4..810530066f 100644 --- a/src/Avalonia.Visuals/Vector.cs +++ b/src/Avalonia.Visuals/Vector.cs @@ -175,7 +175,7 @@ namespace Avalonia MathUtilities.AreClose(_y, other._y); } - public override bool Equals(object obj) => obj is Vector other && Equals(other); + public override bool Equals(object? obj) => obj is Vector other && Equals(other); public override int GetHashCode() { diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index a191dc59fb..1ca7be67a7 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -14,7 +14,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { class AvaloniaXamlIlCompiler : XamlILCompiler { - private readonly TransformerConfiguration _configuration; private readonly IXamlType _contextType; private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer; private readonly AvaloniaBindingExtensionTransformer _bindingTransformer; @@ -22,8 +21,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions private AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings emitMappings) : base(configuration, emitMappings, true) { - _configuration = configuration; - void InsertAfter(params IXamlAstTransformer[] t) => Transformers.InsertRange(Transformers.FindIndex(x => x is T) + 1, t); diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs index 250eca1852..bf11a02fee 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs @@ -184,6 +184,9 @@ namespace Avalonia.Markup.Parsers } + // Don't need to override GetHashCode as the ISyntax objects will not be stored in a hash; the + // only reason they have overridden Equals methods is for unit testing. +#pragma warning disable CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() public class PropertySyntax : ISyntax { public string Name { get; set; } = string.Empty; @@ -205,7 +208,7 @@ namespace Avalonia.Markup.Parsers && other.TypeName == TypeName && other.TypeNamespace == TypeNamespace; } - + public class ChildTraversalSyntax : ISyntax { public static ChildTraversalSyntax Instance { get; } = new ChildTraversalSyntax(); @@ -231,5 +234,6 @@ namespace Avalonia.Markup.Parsers && other.TypeName == TypeName && other.TypeNamespace == TypeNamespace; } +#pragma warning restore CS0659 // Type overrides Object.Equals(object o) but does not override Object.GetHashCode() } } From 1e6155fea377fe88ca07acef1eb8a8995992575b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 30 Sep 2021 13:15:17 +0200 Subject: [PATCH 20/20] Simplify code a bit. --- src/Avalonia.Controls/ItemsSourceView.cs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Controls/ItemsSourceView.cs b/src/Avalonia.Controls/ItemsSourceView.cs index ca95b7acc8..c2d20495ef 100644 --- a/src/Avalonia.Controls/ItemsSourceView.cs +++ b/src/Avalonia.Controls/ItemsSourceView.cs @@ -94,15 +94,9 @@ namespace Avalonia.Controls { add { - if (_inner is null) - ThrowDisposed(); - - if (_collectionChanged is null) + if (_collectionChanged is null && Inner is INotifyCollectionChanged incc) { - if (_inner is INotifyCollectionChanged incc) - { - incc.CollectionChanged += OnCollectionChanged; - } + incc.CollectionChanged += OnCollectionChanged; } _collectionChanged += value; @@ -110,17 +104,11 @@ namespace Avalonia.Controls remove { - if (_inner is null) - ThrowDisposed(); - _collectionChanged -= value; - if (_collectionChanged is null) + if (_collectionChanged is null && Inner is INotifyCollectionChanged incc) { - if (_inner is INotifyCollectionChanged incc) - { - incc.CollectionChanged -= OnCollectionChanged; - } + incc.CollectionChanged -= OnCollectionChanged; } } }