From 5437f8a76e1654d5f67d5d102497a43799d9cc69 Mon Sep 17 00:00:00 2001 From: nicola36631 Date: Sat, 1 Apr 2023 11:44:12 +0200 Subject: [PATCH 01/60] Added PageUp and PageDown KeyGestures --- .../Input/Platform/PlatformHotkeyConfiguration.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs index b87ca5eded..1bd54e37ea 100644 --- a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs +++ b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs @@ -85,6 +85,14 @@ namespace Avalonia.Input.Platform { new KeyGesture(Key.Left, KeyModifiers.Alt) }; + PageUp = new List + { + new KeyGesture(Key.PageUp) + }; + PageDown = new List + { + new KeyGesture(Key.PageDown) + }; } public KeyModifiers CommandModifiers { get; set; } @@ -106,5 +114,7 @@ namespace Avalonia.Input.Platform public List MoveCursorToTheEndOfDocumentWithSelection { get; set; } public List OpenContextMenu { get; set; } public List Back { get; set; } + public List PageUp { get; set; } + public List PageDown { get; set; } } } From e635d900ff80ee2bcc81eabd9dcbad83e88a87b8 Mon Sep 17 00:00:00 2001 From: nicola36631 Date: Sun, 2 Apr 2023 14:36:44 +0200 Subject: [PATCH 02/60] Added PageRight and PageLeft Keygestures in PlatformHotkeyConfiguration --- .../Input/Platform/PlatformHotkeyConfiguration.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs index 1bd54e37ea..23950c2a71 100644 --- a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs +++ b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs @@ -85,6 +85,14 @@ namespace Avalonia.Input.Platform { new KeyGesture(Key.Left, KeyModifiers.Alt) }; + PageLeft = new List + { + new KeyGesture(Key.PageUp, KeyModifiers.Shift) + }; + PageRight = new List + { + new KeyGesture(Key.PageDown, KeyModifiers.Shift) + }; PageUp = new List { new KeyGesture(Key.PageUp) @@ -116,5 +124,7 @@ namespace Avalonia.Input.Platform public List Back { get; set; } public List PageUp { get; set; } public List PageDown { get; set; } + public List PageRight { get; set; } + public List PageLeft { get; set; } } } From 92ba4bc19c8fb36f574f74ba5067d619df045188 Mon Sep 17 00:00:00 2001 From: nicola36631 Date: Sun, 2 Apr 2023 14:39:56 +0200 Subject: [PATCH 03/60] Added PageLeft, PageRight, PageUp, PageDown handling in TextBox Added cases in OnKeyDown method and added FetchChildScrollViewer method to fetch the child ScrollViewer in the VisualChildren. --- src/Avalonia.Controls/TextBox.cs | 92 ++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 9bb68ba419..9c4be3a907 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -19,6 +19,7 @@ using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Automation.Peers; using Avalonia.Threading; using Avalonia.Platform; +using Avalonia.Collections; namespace Avalonia.Controls { @@ -1240,6 +1241,34 @@ namespace Avalonia.Controls selection = true; handled = true; } + else if (Match(keymap.PageLeft)) + { + MovePageLeft(); + movement = true; + selection = false; + handled = true; + } + else if (Match(keymap.PageRight)) + { + MovePageRight(); + movement = true; + selection = false; + handled = true; + } + else if (Match(keymap.PageUp)) + { + MovePageUp(); + movement = true; + selection = false; + handled = true; + } + else if (Match(keymap.PageDown)) + { + MovePageDown(); + movement = true; + selection = false; + handled = true; + } else { bool hasWholeWordModifiers = modifiers.HasAllFlags(keymap.WholeWordTextActionModifiers); @@ -1755,6 +1784,69 @@ namespace Avalonia.Controls } } + private void MovePageRight() + { + ScrollViewer? childScrollviewer = FetchChildScrollViewer(10, 0, this.VisualChildren); + if (childScrollviewer == null) + { + return; + } + childScrollviewer.PageRight(); + } + + private void MovePageLeft() + { + ScrollViewer? childScrollviewer = FetchChildScrollViewer(10, 0, this.VisualChildren); + if (childScrollviewer == null) + { + return; + } + childScrollviewer.PageLeft(); + } + private void MovePageUp() + { + ScrollViewer? childScrollviewer = FetchChildScrollViewer(10, 0, this.VisualChildren); + if (childScrollviewer == null) + { + return; + } + childScrollviewer.PageUp(); + } + + private void MovePageDown() + { + ScrollViewer? childScrollviewer = FetchChildScrollViewer(10, 0, this.VisualChildren); + if (childScrollviewer == null) + { + return; + } + childScrollviewer.PageDown(); + } + + private ScrollViewer? FetchChildScrollViewer(in int maxSearchDepth, int currentSearchDepth, IAvaloniaList visualChildren) + { + foreach(Visual innerChild in visualChildren) + { + if(innerChild is ScrollViewer) + { + return (ScrollViewer?)innerChild; + } + else + { + if (currentSearchDepth < maxSearchDepth) + { + ScrollViewer? innerScrollViewer = FetchChildScrollViewer(maxSearchDepth, currentSearchDepth + 1, innerChild.VisualChildren); + if (innerScrollViewer != null) + { + return innerScrollViewer; + } + } + } + } + + return null; + } + /// /// Select all text in the TextBox /// From ca66c4d00db4d1b1ed2fc9c0c466626449978ed6 Mon Sep 17 00:00:00 2001 From: nicola36631 Date: Sat, 1 Apr 2023 11:44:12 +0200 Subject: [PATCH 04/60] Added PageUp and PageDown KeyGestures --- .../Input/Platform/PlatformHotkeyConfiguration.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs index b87ca5eded..1bd54e37ea 100644 --- a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs +++ b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs @@ -85,6 +85,14 @@ namespace Avalonia.Input.Platform { new KeyGesture(Key.Left, KeyModifiers.Alt) }; + PageUp = new List + { + new KeyGesture(Key.PageUp) + }; + PageDown = new List + { + new KeyGesture(Key.PageDown) + }; } public KeyModifiers CommandModifiers { get; set; } @@ -106,5 +114,7 @@ namespace Avalonia.Input.Platform public List MoveCursorToTheEndOfDocumentWithSelection { get; set; } public List OpenContextMenu { get; set; } public List Back { get; set; } + public List PageUp { get; set; } + public List PageDown { get; set; } } } From 01cbf9790355e5ec909006b31811131b0ae086c5 Mon Sep 17 00:00:00 2001 From: nicola36631 Date: Sun, 2 Apr 2023 14:36:44 +0200 Subject: [PATCH 05/60] Added PageRight and PageLeft Keygestures in PlatformHotkeyConfiguration --- .../Input/Platform/PlatformHotkeyConfiguration.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs index 1bd54e37ea..23950c2a71 100644 --- a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs +++ b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs @@ -85,6 +85,14 @@ namespace Avalonia.Input.Platform { new KeyGesture(Key.Left, KeyModifiers.Alt) }; + PageLeft = new List + { + new KeyGesture(Key.PageUp, KeyModifiers.Shift) + }; + PageRight = new List + { + new KeyGesture(Key.PageDown, KeyModifiers.Shift) + }; PageUp = new List { new KeyGesture(Key.PageUp) @@ -116,5 +124,7 @@ namespace Avalonia.Input.Platform public List Back { get; set; } public List PageUp { get; set; } public List PageDown { get; set; } + public List PageRight { get; set; } + public List PageLeft { get; set; } } } From d25b9a4f88ed28267e28e5dc4d0531563a320566 Mon Sep 17 00:00:00 2001 From: nicola36631 Date: Sun, 2 Apr 2023 14:39:56 +0200 Subject: [PATCH 06/60] Added PageLeft, PageRight, PageUp, PageDown handling in TextBox Added cases in OnKeyDown method and added FetchChildScrollViewer method to fetch the child ScrollViewer in the VisualChildren. --- src/Avalonia.Controls/TextBox.cs | 92 ++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 9bb68ba419..9c4be3a907 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -19,6 +19,7 @@ using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Automation.Peers; using Avalonia.Threading; using Avalonia.Platform; +using Avalonia.Collections; namespace Avalonia.Controls { @@ -1240,6 +1241,34 @@ namespace Avalonia.Controls selection = true; handled = true; } + else if (Match(keymap.PageLeft)) + { + MovePageLeft(); + movement = true; + selection = false; + handled = true; + } + else if (Match(keymap.PageRight)) + { + MovePageRight(); + movement = true; + selection = false; + handled = true; + } + else if (Match(keymap.PageUp)) + { + MovePageUp(); + movement = true; + selection = false; + handled = true; + } + else if (Match(keymap.PageDown)) + { + MovePageDown(); + movement = true; + selection = false; + handled = true; + } else { bool hasWholeWordModifiers = modifiers.HasAllFlags(keymap.WholeWordTextActionModifiers); @@ -1755,6 +1784,69 @@ namespace Avalonia.Controls } } + private void MovePageRight() + { + ScrollViewer? childScrollviewer = FetchChildScrollViewer(10, 0, this.VisualChildren); + if (childScrollviewer == null) + { + return; + } + childScrollviewer.PageRight(); + } + + private void MovePageLeft() + { + ScrollViewer? childScrollviewer = FetchChildScrollViewer(10, 0, this.VisualChildren); + if (childScrollviewer == null) + { + return; + } + childScrollviewer.PageLeft(); + } + private void MovePageUp() + { + ScrollViewer? childScrollviewer = FetchChildScrollViewer(10, 0, this.VisualChildren); + if (childScrollviewer == null) + { + return; + } + childScrollviewer.PageUp(); + } + + private void MovePageDown() + { + ScrollViewer? childScrollviewer = FetchChildScrollViewer(10, 0, this.VisualChildren); + if (childScrollviewer == null) + { + return; + } + childScrollviewer.PageDown(); + } + + private ScrollViewer? FetchChildScrollViewer(in int maxSearchDepth, int currentSearchDepth, IAvaloniaList visualChildren) + { + foreach(Visual innerChild in visualChildren) + { + if(innerChild is ScrollViewer) + { + return (ScrollViewer?)innerChild; + } + else + { + if (currentSearchDepth < maxSearchDepth) + { + ScrollViewer? innerScrollViewer = FetchChildScrollViewer(maxSearchDepth, currentSearchDepth + 1, innerChild.VisualChildren); + if (innerScrollViewer != null) + { + return innerScrollViewer; + } + } + } + } + + return null; + } + /// /// Select all text in the TextBox /// From 8f952396c912f9abdd0b2c6fa1dcb46c87177947 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 29 May 2023 18:03:11 -0400 Subject: [PATCH 07/60] Make GetVisualRoot trimmable + some other helper methods --- src/Avalonia.Base/Utilities/ThrowHelper.cs | 24 +++++++++++++++++++ .../VisualTree/VisualExtensions.cs | 19 ++++++++------- 2 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 src/Avalonia.Base/Utilities/ThrowHelper.cs diff --git a/src/Avalonia.Base/Utilities/ThrowHelper.cs b/src/Avalonia.Base/Utilities/ThrowHelper.cs new file mode 100644 index 0000000000..e97ab754e5 --- /dev/null +++ b/src/Avalonia.Base/Utilities/ThrowHelper.cs @@ -0,0 +1,24 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Avalonia.Utilities; + +/// +/// Helper method to help inlining methods that do a throw check. +/// Equivalent of .NET6+ ArgumentNullException.ThrowIfNull() for netstandard2.0+ +/// +internal class ThrowHelper +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ThrowIfNull([NotNull] object? argument, string paramName) + { + if (argument is null) + { + ThrowArgumentNullException(paramName); + } + } + + [DoesNotReturn] + private static void ThrowArgumentNullException(string paramName) => throw new ArgumentNullException(paramName); +} diff --git a/src/Avalonia.Base/VisualTree/VisualExtensions.cs b/src/Avalonia.Base/VisualTree/VisualExtensions.cs index b3cdd70fa0..e244323ed9 100644 --- a/src/Avalonia.Base/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Base/VisualTree/VisualExtensions.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.CompilerServices; using Avalonia.Rendering; +using Avalonia.Utilities; namespace Avalonia.VisualTree { @@ -125,9 +128,9 @@ namespace Avalonia.VisualTree /// The visual's ancestors. public static IEnumerable GetVisualAncestors(this Visual visual) { - Visual? v = visual ?? throw new ArgumentNullException(nameof(visual)); + ThrowHelper.ThrowIfNull(visual, nameof(visual)); - v = v.VisualParent; + var v = visual.VisualParent; while (v != null) { @@ -194,7 +197,7 @@ namespace Avalonia.VisualTree /// The visual and its ancestors. public static IEnumerable GetSelfAndVisualAncestors(this Visual visual) { - _ = visual ?? throw new ArgumentNullException(nameof(visual)); + ThrowHelper.ThrowIfNull(visual, nameof(visual)); yield return visual; @@ -275,7 +278,7 @@ namespace Avalonia.VisualTree /// The visual at the requested point. public static Visual? GetVisualAt(this Visual visual, Point p) { - _ = visual ?? throw new ArgumentNullException(nameof(visual)); + ThrowHelper.ThrowIfNull(visual, nameof(visual)); return visual.GetVisualAt(p, x => x.IsVisible); } @@ -292,7 +295,7 @@ namespace Avalonia.VisualTree /// The visual at the requested point. public static Visual? GetVisualAt(this Visual visual, Point p, Func filter) { - _ = visual ?? throw new ArgumentNullException(nameof(visual)); + ThrowHelper.ThrowIfNull(visual, nameof(visual)); var root = visual.GetVisualRoot(); @@ -321,7 +324,7 @@ namespace Avalonia.VisualTree this Visual visual, Point p) { - _ = visual ?? throw new ArgumentNullException(nameof(visual)); + ThrowHelper.ThrowIfNull(visual, nameof(visual)); return visual.GetVisualsAt(p, x => x.IsVisible); } @@ -341,7 +344,7 @@ namespace Avalonia.VisualTree Point p, Func filter) { - _ = visual ?? throw new ArgumentNullException(nameof(visual)); + ThrowHelper.ThrowIfNull(visual, nameof(visual)); var root = visual.GetVisualRoot(); @@ -435,7 +438,7 @@ namespace Avalonia.VisualTree /// public static IRenderRoot? GetVisualRoot(this Visual visual) { - _ = visual ?? throw new ArgumentNullException(nameof(visual)); + ThrowHelper.ThrowIfNull(visual, nameof(visual)); return visual as IRenderRoot ?? visual.VisualRoot; } From 0041401e252bd9aad146623def90f90d28e6899c Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 29 May 2023 18:04:38 -0400 Subject: [PATCH 08/60] Introduce IPlatformSettings to the public API --- src/Avalonia.Base/Input/IInputRoot.cs | 11 ++++++++++- src/Avalonia.Base/Platform/DefaultPlatformSettings.cs | 2 ++ src/Avalonia.Base/Platform/IPlatformSettings.cs | 6 +++++- src/Avalonia.Controls/TopLevel.cs | 3 +++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Input/IInputRoot.cs b/src/Avalonia.Base/Input/IInputRoot.cs index d0921eea2d..f358658005 100644 --- a/src/Avalonia.Base/Input/IInputRoot.cs +++ b/src/Avalonia.Base/Input/IInputRoot.cs @@ -1,4 +1,5 @@ using Avalonia.Metadata; +using Avalonia.Platform; namespace Avalonia.Input { @@ -17,10 +18,18 @@ namespace Avalonia.Input /// Gets focus manager of the root. /// /// - /// Focus manager can be null only if application wasn't initialized yet. + /// Focus manager can be null only if window wasn't initialized yet. /// IFocusManager? FocusManager { get; } + /// + /// Represents a contract for accessing platform-specific settings and information. + /// + /// + /// Focus manager can be null only if window wasn't initialized yet. + /// + IPlatformSettings? PlatformSettings { get; } + /// /// Gets or sets the input element that the pointer is currently over. /// diff --git a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs index 08fcdb50aa..54528efec1 100644 --- a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs +++ b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs @@ -1,12 +1,14 @@ using System; using Avalonia.Input; using Avalonia.Media; +using Avalonia.Metadata; namespace Avalonia.Platform { /// /// A default implementation of for platforms. /// + [PrivateApi] public class DefaultPlatformSettings : IPlatformSettings { public virtual Size GetTapSize(PointerType type) diff --git a/src/Avalonia.Base/Platform/IPlatformSettings.cs b/src/Avalonia.Base/Platform/IPlatformSettings.cs index b167cf0edc..c5d46cb7e6 100644 --- a/src/Avalonia.Base/Platform/IPlatformSettings.cs +++ b/src/Avalonia.Base/Platform/IPlatformSettings.cs @@ -4,7 +4,11 @@ using Avalonia.Metadata; namespace Avalonia.Platform { - [Unstable] + /// + /// The interface represents a contract for accessing platform-specific settings and information. + /// Some of these settings might be changed by used globally in the OS in runtime. + /// + [NotClientImplementable] public interface IPlatformSettings { /// diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index ec6b3f7421..be9df4188d 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -487,6 +487,9 @@ namespace Avalonia.Controls /// public IFocusManager? FocusManager => AvaloniaLocator.Current.GetService(); + /// + public IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService(); + /// Point IRenderRoot.PointToClient(PixelPoint p) { From 350dd60792779c9e59f4fe7f739474e8f259e307 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 29 May 2023 18:04:51 -0400 Subject: [PATCH 09/60] Introduce PlatformHotkeyConfiguration.Instance to the public PAI --- .../Platform/PlatformHotkeyConfiguration.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs index b87ca5eded..a4cf90e221 100644 --- a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs +++ b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs @@ -1,16 +1,29 @@ using System.Collections.Generic; - -#nullable enable +using Avalonia.Metadata; namespace Avalonia.Input.Platform { - public class PlatformHotkeyConfiguration + /// + /// The PlatformHotkeyConfiguration class represents a configuration for platform-specific hotkeys in an Avalonia application. + /// + public sealed class PlatformHotkeyConfiguration { + /// + /// Retrieves shared static instance of PlatformHotkeyConfiguration. + /// + /// + /// Return value might be null, when application wasn't yet initialized. + /// + public static PlatformHotkeyConfiguration? Instance => + AvaloniaLocator.Current.GetService(); + + [PrivateApi] public PlatformHotkeyConfiguration() : this(KeyModifiers.Control) { } + [PrivateApi] public PlatformHotkeyConfiguration(KeyModifiers commandModifiers, KeyModifiers selectionModifiers = KeyModifiers.Shift, KeyModifiers wholeWordTextActionModifiers = KeyModifiers.Control) From 3515143219e40f69a4ed3b82eacfe7675566fa2f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 29 May 2023 18:05:34 -0400 Subject: [PATCH 10/60] Use PlatformSettings from the top level internally --- src/Avalonia.Base/Input/Gestures.cs | 10 ++++----- src/Avalonia.Base/Input/MouseDevice.cs | 17 +++++++++------ src/Avalonia.Base/Input/PenDevice.cs | 27 +++++++++++++++--------- src/Avalonia.Base/Input/TouchDevice.cs | 29 ++++++++++++++++---------- src/Avalonia.Controls/ListBoxItem.cs | 2 +- tests/Avalonia.UnitTests/TestRoot.cs | 1 + 6 files changed, 53 insertions(+), 33 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 167f61eead..e9d23632b1 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -182,7 +182,7 @@ namespace Avalonia.Input s_lastPressPoint = e.GetPosition((Visual)ev.Source); s_holdCancellationToken = new CancellationTokenSource(); var token = s_holdCancellationToken.Token; - var settings = AvaloniaLocator.Current.GetService(); + var settings = ((IInputRoot?)visual.GetVisualRoot())?.PlatformSettings; if (settings != null) { @@ -221,7 +221,7 @@ namespace Avalonia.Input e.Source is Interactive i) { var point = e.GetCurrentPoint((Visual)target); - var settings = AvaloniaLocator.Current.GetService(); + var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings; var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4); var tapRect = new Rect(s_lastPressPoint, new Size()) .Inflate(new Thickness(tapSize.Width, tapSize.Height)); @@ -260,10 +260,10 @@ namespace Avalonia.Input var e = (PointerEventArgs)ev; if (s_lastPress.TryGetTarget(out var target)) { - if (e.Pointer == s_lastPointer) + if (e.Pointer == s_lastPointer && ev.Source is Interactive i) { var point = e.GetCurrentPoint((Visual)target); - var settings = AvaloniaLocator.Current.GetService(); + var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings; var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4); var tapRect = new Rect(s_lastPressPoint, new Size()) .Inflate(new Thickness(tapSize.Width, tapSize.Height)); @@ -273,7 +273,7 @@ namespace Avalonia.Input return; } - if (s_isHolding && ev.Source is Interactive i) + if (s_isHolding) { i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type)); } diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs index f3e77433a9..69c7a83900 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/src/Avalonia.Base/Input/MouseDevice.cs @@ -2,9 +2,12 @@ using System; using System.Collections.Generic; using Avalonia.Reactive; using Avalonia.Input.Raw; +using Avalonia.Interactivity; using Avalonia.Metadata; using Avalonia.Platform; using Avalonia.Utilities; +using Avalonia.VisualTree; + #pragma warning disable CS0618 namespace Avalonia.Input @@ -126,9 +129,10 @@ namespace Avalonia.Input if (source != null) { _pointer.Capture(source); - if (source != null) + + var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings; + if (settings is not null) { - var settings = AvaloniaLocator.Current.GetRequiredService(); var doubleClickTime = settings.GetDoubleTapTime(PointerType.Mouse).TotalMilliseconds; var doubleClickSize = settings.GetDoubleTapSize(PointerType.Mouse); @@ -141,11 +145,12 @@ namespace Avalonia.Input _lastClickTime = timestamp; _lastClickRect = new Rect(p, new Size()) .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2)); - _lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton(); - var e = new PointerPressedEventArgs(source, _pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount); - source.RaiseEvent(e); - return e.Handled; } + + _lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton(); + var e = new PointerPressedEventArgs(source, _pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount); + source.RaiseEvent(e); + return e.Handled; } return false; diff --git a/src/Avalonia.Base/Input/PenDevice.cs b/src/Avalonia.Base/Input/PenDevice.cs index 832f32fb03..d5ccfdb18d 100644 --- a/src/Avalonia.Base/Input/PenDevice.cs +++ b/src/Avalonia.Base/Input/PenDevice.cs @@ -3,8 +3,11 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using Avalonia.Input.Raw; +using Avalonia.Interactivity; using Avalonia.Metadata; using Avalonia.Platform; +using Avalonia.VisualTree; + #pragma warning disable CS0618 namespace Avalonia.Input @@ -80,19 +83,23 @@ namespace Avalonia.Input if (source != null) { pointer.Capture(source); - var settings = AvaloniaLocator.Current.GetService(); - var doubleClickTime = settings?.GetDoubleTapTime(PointerType.Pen).TotalMilliseconds ?? 500; - var doubleClickSize = settings?.GetDoubleTapSize(PointerType.Pen) ?? new Size(4, 4); - - if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime) + var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings; + if (settings is not null) { - _clickCount = 0; + var doubleClickTime = settings.GetDoubleTapTime(PointerType.Pen).TotalMilliseconds; + var doubleClickSize = settings.GetDoubleTapSize(PointerType.Pen); + + if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime) + { + _clickCount = 0; + } + + ++_clickCount; + _lastClickTime = timestamp; + _lastClickRect = new Rect(p, new Size()) + .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2)); } - ++_clickCount; - _lastClickTime = timestamp; - _lastClickRect = new Rect(p, new Size()) - .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2)); _lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton(); var e = new PointerPressedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount); source.RaiseEvent(e); diff --git a/src/Avalonia.Base/Input/TouchDevice.cs b/src/Avalonia.Base/Input/TouchDevice.cs index 78b570da14..74c5837b84 100644 --- a/src/Avalonia.Base/Input/TouchDevice.cs +++ b/src/Avalonia.Base/Input/TouchDevice.cs @@ -3,8 +3,11 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using Avalonia.Input.Raw; +using Avalonia.Interactivity; using Avalonia.Metadata; using Avalonia.Platform; +using Avalonia.VisualTree; + #pragma warning disable CS0618 namespace Avalonia.Input @@ -62,19 +65,23 @@ namespace Avalonia.Input } else { - var settings = AvaloniaLocator.Current.GetRequiredService(); - var doubleClickTime = settings.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds; - var doubleClickSize = settings.GetDoubleTapSize(PointerType.Touch); - - if (!_lastClickRect.Contains(args.Position) - || ev.Timestamp - _lastClickTime > doubleClickTime) + var settings = ((IInputRoot?)(target as Interactive)?.GetVisualRoot())?.PlatformSettings; + if (settings is not null) { - _clickCount = 0; + var doubleClickTime = settings.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds; + var doubleClickSize = settings.GetDoubleTapSize(PointerType.Touch); + + if (!_lastClickRect.Contains(args.Position) + || ev.Timestamp - _lastClickTime > doubleClickTime) + { + _clickCount = 0; + } + + ++_clickCount; + _lastClickTime = ev.Timestamp; + _lastClickRect = new Rect(args.Position, new Size()) + .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2)); } - ++_clickCount; - _lastClickTime = ev.Timestamp; - _lastClickRect = new Rect(args.Position, new Size()) - .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2)); } target.RaiseEvent(new PointerPressedEventArgs(target, pointer, diff --git a/src/Avalonia.Controls/ListBoxItem.cs b/src/Avalonia.Controls/ListBoxItem.cs index c2044661e2..15136e5992 100644 --- a/src/Avalonia.Controls/ListBoxItem.cs +++ b/src/Avalonia.Controls/ListBoxItem.cs @@ -90,7 +90,7 @@ namespace Avalonia.Controls e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right) { var point = e.GetCurrentPoint(this); - var settings = AvaloniaLocator.Current.GetService(); + var settings = TopLevel.GetTopLevel(e.Source as Visual)?.PlatformSettings; var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4); var tapRect = new Rect(_pointerDownPoint, new Size()) .Inflate(new Thickness(tapSize.Width, tapSize.Height)); diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index 569e5d114c..fcfd845602 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -67,6 +67,7 @@ namespace Avalonia.UnitTests public IKeyboardNavigationHandler KeyboardNavigationHandler => null; public IFocusManager FocusManager => AvaloniaLocator.Current.GetService(); + public IPlatformSettings PlatformSettings => AvaloniaLocator.Current.GetService(); public IInputElement PointerOverElement { get; set; } From 596f876868bb35849cf06882830444f7f81eeb44 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 29 May 2023 18:06:08 -0400 Subject: [PATCH 11/60] Use PlatformHotkeyConfiguration.Instance instead of locator --- .../Utils/KeyboardHelper.cs | 6 ++--- src/Avalonia.Controls/ContextMenu.cs | 2 +- src/Avalonia.Controls/Control.cs | 2 +- .../Flyouts/PopupFlyoutBase.cs | 2 +- src/Avalonia.Controls/ListBox.cs | 4 ++-- src/Avalonia.Controls/MaskedTextBox.cs | 2 +- src/Avalonia.Controls/SelectableTextBlock.cs | 2 +- src/Avalonia.Controls/TextBox.cs | 11 ++++----- src/Avalonia.Controls/TopLevel.cs | 2 +- src/Avalonia.Controls/TreeView.cs | 4 ++-- src/Avalonia.Native/AvaloniaNativePlatform.cs | 4 ++-- .../ListBoxTests_Multiple.cs | 24 +++++++++---------- .../ListBoxTests_Single.cs | 14 +++++------ .../Primitives/SelectingItemsControlTests.cs | 8 +++---- .../TreeViewTests.cs | 6 ++--- 15 files changed, 45 insertions(+), 48 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/Utils/KeyboardHelper.cs b/src/Avalonia.Controls.DataGrid/Utils/KeyboardHelper.cs index d2b1fd4b8e..78b349fd23 100644 --- a/src/Avalonia.Controls.DataGrid/Utils/KeyboardHelper.cs +++ b/src/Avalonia.Controls.DataGrid/Utils/KeyboardHelper.cs @@ -24,9 +24,9 @@ namespace Avalonia.Controls.Utils } public static KeyModifiers GetPlatformCtrlOrCmdKeyModifier() - { - var keymap = AvaloniaLocator.Current.GetService(); - return keymap?.CommandModifiers ?? KeyModifiers.Control; + { + var keymap = PlatformHotkeyConfiguration.Instance; + return keymap.CommandModifiers; } } } diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 3dc1514667..238ccd091c 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -405,7 +405,7 @@ namespace Avalonia.Controls { if (IsOpen) { - var keymap = AvaloniaLocator.Current.GetService(); + var keymap = PlatformHotkeyConfiguration.Instance; if (keymap?.OpenContextMenu.Any(k => k.Matches(e)) == true && !CancelClosing()) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 8d140bdd62..4a9737a267 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -480,7 +480,7 @@ namespace Avalonia.Controls if (e.Source == this && !e.Handled) { - var keymap = AvaloniaLocator.Current.GetService()?.OpenContextMenu; + var keymap = PlatformHotkeyConfiguration.Instance?.OpenContextMenu; if (keymap is null) { diff --git a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs index 7fd9fad605..c94200ac8b 100644 --- a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs @@ -392,7 +392,7 @@ namespace Avalonia.Controls.Primitives && IsOpen && Target?.ContextFlyout == this) { - var keymap = AvaloniaLocator.Current.GetService(); + var keymap = PlatformHotkeyConfiguration.Instance; if (keymap?.OpenContextMenu.Any(k => k.Matches(e)) == true) { diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index f3f0b135b8..cd3c72c3a4 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -127,7 +127,7 @@ namespace Avalonia.Controls protected override void OnKeyDown(KeyEventArgs e) { - var hotkeys = AvaloniaLocator.Current.GetService(); + var hotkeys = PlatformHotkeyConfiguration.Instance; var ctrl = hotkeys is not null && e.KeyModifiers.HasAllFlags(hotkeys.CommandModifiers); if (!ctrl && @@ -165,7 +165,7 @@ namespace Avalonia.Controls internal bool UpdateSelectionFromPointerEvent(Control source, PointerEventArgs e) { - var hotkeys = AvaloniaLocator.Current.GetService(); + var hotkeys = PlatformHotkeyConfiguration.Instance; var toggle = hotkeys is not null && e.KeyModifiers.HasAllFlags(hotkeys.CommandModifiers); return UpdateSelectionFromEventSource( diff --git a/src/Avalonia.Controls/MaskedTextBox.cs b/src/Avalonia.Controls/MaskedTextBox.cs index 4800b7b1e4..fa4b8b2fb4 100644 --- a/src/Avalonia.Controls/MaskedTextBox.cs +++ b/src/Avalonia.Controls/MaskedTextBox.cs @@ -204,7 +204,7 @@ namespace Avalonia.Controls return; } - var keymap = AvaloniaLocator.Current.GetService(); + var keymap = PlatformHotkeyConfiguration.Instance; bool Match(List gestures) => gestures.Any(g => g.Matches(e)); diff --git a/src/Avalonia.Controls/SelectableTextBlock.cs b/src/Avalonia.Controls/SelectableTextBlock.cs index 8b32638835..973f086e2a 100644 --- a/src/Avalonia.Controls/SelectableTextBlock.cs +++ b/src/Avalonia.Controls/SelectableTextBlock.cs @@ -198,7 +198,7 @@ namespace Avalonia.Controls var handled = false; var modifiers = e.KeyModifiers; - var keymap = AvaloniaLocator.Current.GetRequiredService(); + var keymap = PlatformHotkeyConfiguration.Instance!; bool Match(List gestures) => gestures.Any(g => g.Matches(e)); diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 7bc26bf3b0..2479622b31 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -32,20 +32,17 @@ namespace Avalonia.Controls /// /// Gets a platform-specific for the Cut action /// - public static KeyGesture? CutGesture { get; } = AvaloniaLocator.Current - .GetService()?.Cut.FirstOrDefault(); + public static KeyGesture? CutGesture { get; } = PlatformHotkeyConfiguration.Instance?.Cut.FirstOrDefault(); /// /// Gets a platform-specific for the Copy action /// - public static KeyGesture? CopyGesture { get; } = AvaloniaLocator.Current - .GetService()?.Copy.FirstOrDefault(); + public static KeyGesture? CopyGesture { get; } = PlatformHotkeyConfiguration.Instance?.Copy.FirstOrDefault(); /// /// Gets a platform-specific for the Paste action /// - public static KeyGesture? PasteGesture { get; } = AvaloniaLocator.Current - .GetService()?.Paste.FirstOrDefault(); + public static KeyGesture? PasteGesture { get; } = PlatformHotkeyConfiguration.Instance?.Paste.FirstOrDefault(); /// /// Defines the property @@ -1103,7 +1100,7 @@ namespace Avalonia.Controls var handled = false; var modifiers = e.KeyModifiers; - var keymap = AvaloniaLocator.Current.GetRequiredService(); + var keymap = PlatformHotkeyConfiguration.Instance!; bool Match(List gestures) => gestures.Any(g => g.Matches(e)); bool DetectSelection() => e.KeyModifiers.HasAllFlags(keymap.SelectionModifiers); diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index be9df4188d..5da82813e2 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -250,7 +250,7 @@ namespace Avalonia.Controls if (e is RawKeyEventArgs rawKeyEventArgs && rawKeyEventArgs.Type == RawKeyEventType.KeyDown) { - var keymap = AvaloniaLocator.Current.GetService()?.Back; + var keymap = PlatformHotkeyConfiguration.Instance?.Back; if (keymap != null) { diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index a499829d4a..d449590bd8 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -571,7 +571,7 @@ namespace Avalonia.Controls if (!e.Handled) { - var keymap = AvaloniaLocator.Current.GetService(); + var keymap = PlatformHotkeyConfiguration.Instance; bool Match(List? gestures) => gestures?.Any(g => g.Matches(e)) ?? false; if (this.SelectionMode == SelectionMode.Multiple && Match(keymap?.SelectAll)) @@ -656,7 +656,7 @@ namespace Avalonia.Controls e.Source, true, e.KeyModifiers.HasAllFlags(KeyModifiers.Shift), - e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService().CommandModifiers), + e.KeyModifiers.HasAllFlags(PlatformHotkeyConfiguration.Instance!.CommandModifiers), point.Properties.IsRightButtonPressed); } } diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index c471cf91d2..a1ab90b3f7 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -113,8 +113,8 @@ namespace Avalonia.Native .Bind().ToConstant(new AvaloniaNativeDragSource(_factory)) .Bind().ToConstant(applicationPlatform) .Bind().ToConstant(new MacOSNativeMenuCommands(_factory.CreateApplicationCommands())); - - var hotkeys = AvaloniaLocator.Current.GetService(); + + var hotkeys = PlatformHotkeyConfiguration.Instance; if (hotkeys is not null) { hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers)); diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs index ed19d1dc97..21e25a6f1c 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs @@ -34,7 +34,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); root.LayoutManager.ExecuteInitialLayoutPass(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); _helper.Click(target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift); Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); @@ -60,7 +60,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); root.LayoutManager.ExecuteInitialLayoutPass(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); SelectionChangedEventArgs receivedArgs = null; @@ -118,7 +118,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); root.LayoutManager.ExecuteInitialLayoutPass(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); _helper.Click(target.Presenter.Panel.Children[1]); _helper.Click(target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); _helper.Click(target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control); @@ -152,7 +152,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); root.LayoutManager.ExecuteInitialLayoutPass(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); _helper.Click(target.Presenter.Panel.Children[1]); _helper.Click(target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); @@ -183,7 +183,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); root.LayoutManager.ExecuteInitialLayoutPass(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); _helper.Click(target.Presenter.Panel.Children[3]); _helper.Click(target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); @@ -211,7 +211,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); root.LayoutManager.ExecuteInitialLayoutPass(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); _helper.Click(target.Presenter.Panel.Children[3]); _helper.Click(target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); @@ -239,7 +239,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); root.LayoutManager.ExecuteInitialLayoutPass(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); _helper.Click(target.Presenter.Panel.Children[0]); _helper.Click(target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); @@ -266,7 +266,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); root.LayoutManager.ExecuteInitialLayoutPass(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); SelectionChangedEventArgs receivedArgs = null; @@ -319,7 +319,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); root.LayoutManager.ExecuteInitialLayoutPass(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); _helper.Click(target.Presenter.Panel.Children[0]); Assert.Equal(new[] { "Foo" }, target.SelectedItems); @@ -414,7 +414,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); root.LayoutManager.ExecuteInitialLayoutPass(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); _helper.Click(target.Presenter.Panel.Children[0]); _helper.Click(target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Shift); @@ -444,7 +444,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); root.LayoutManager.ExecuteInitialLayoutPass(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); _helper.Click(target.Presenter.Panel.Children[0]); _helper.Click(target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Shift); @@ -471,7 +471,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); root.LayoutManager.ExecuteInitialLayoutPass(); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); _helper.Click(target.Presenter.Panel.Children[0]); _helper.Click(target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Control); diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index 92ea2df2a9..f442202e26 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -49,7 +49,7 @@ namespace Avalonia.Controls.UnitTests Template = new FuncControlTemplate(CreateListBoxTemplate), ItemsSource = new[] { "Foo", "Bar", "Baz " }, }; - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); ApplyTemplate(target); target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs @@ -79,7 +79,7 @@ namespace Avalonia.Controls.UnitTests Template = new FuncControlTemplate(CreateListBoxTemplate), ItemsSource = new[] { "Foo", "Bar", "Baz " }, }; - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); ApplyTemplate(target); _mouse.Click(target.Presenter.Panel.Children[0]); @@ -97,7 +97,7 @@ namespace Avalonia.Controls.UnitTests Template = new FuncControlTemplate(CreateListBoxTemplate), ItemsSource = new[] { "Foo", "Bar", "Baz " }, }; - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); ApplyTemplate(target); target.SelectedIndex = 0; @@ -118,7 +118,7 @@ namespace Avalonia.Controls.UnitTests ItemsSource = new[] { "Foo", "Bar", "Baz " }, SelectionMode = SelectionMode.Single | SelectionMode.Toggle, }; - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); ApplyTemplate(target); _mouse.Click(target.Presenter.Panel.Children[0]); @@ -139,7 +139,7 @@ namespace Avalonia.Controls.UnitTests SelectionMode = SelectionMode.Toggle, }; - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); ApplyTemplate(target); target.SelectedIndex = 0; @@ -160,7 +160,7 @@ namespace Avalonia.Controls.UnitTests ItemsSource = new[] { "Foo", "Bar", "Baz " }, SelectionMode = SelectionMode.Toggle | SelectionMode.AlwaysSelected, }; - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); ApplyTemplate(target); target.SelectedIndex = 0; @@ -181,7 +181,7 @@ namespace Avalonia.Controls.UnitTests ItemsSource = new[] { "Foo", "Bar", "Baz " }, SelectionMode = SelectionMode.Single | SelectionMode.Toggle, }; - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); ApplyTemplate(target); target.SelectedIndex = 1; diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index e456f25238..c41f3c18cb 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -1234,7 +1234,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Template = Template(), ItemsSource = new[] { "Foo", "Bar", "Baz " }, }; - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); Prepare(target); var container = target.ContainerFromIndex(1)!; @@ -1258,7 +1258,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Template = Template(), ItemsSource = items, }; - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); Prepare(target); _helper.Down(target.Presenter.Panel.Children[1]); @@ -1355,7 +1355,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Template = Template(), ItemsSource = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, }; - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); Prepare(target); _helper.Down((Interactive)target.Presenter.Panel.Children[3]); @@ -1373,7 +1373,7 @@ namespace Avalonia.Controls.UnitTests.Primitives Template = Template(), ItemsSource = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" }, }; - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new PlatformHotkeyConfiguration()); Prepare(target); _helper.Down((Interactive)target.Presenter.Panel.Children[3]); diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index f4306f63db..f5e7992203 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1045,7 +1045,7 @@ namespace Avalonia.Controls.UnitTests var data = CreateTestTreeData(); var target = CreateTarget(data: data, multiSelect: true); var rootNode = data[0]; - var keymap = AvaloniaLocator.Current.GetRequiredService(); + var keymap = PlatformHotkeyConfiguration.Instance!; var selectAllGesture = keymap.SelectAll.First(); var keyEvent = new KeyEventArgs @@ -1075,7 +1075,7 @@ namespace Avalonia.Controls.UnitTests ClickContainer(fromContainer, KeyModifiers.None); ClickContainer(toContainer, KeyModifiers.Shift); - var keymap = AvaloniaLocator.Current.GetRequiredService(); + var keymap = PlatformHotkeyConfiguration.Instance!; var selectAllGesture = keymap.SelectAll.First(); var keyEvent = new KeyEventArgs @@ -1105,7 +1105,7 @@ namespace Avalonia.Controls.UnitTests ClickContainer(fromContainer, KeyModifiers.None); ClickContainer(toContainer, KeyModifiers.Shift); - var keymap = AvaloniaLocator.Current.GetRequiredService(); + var keymap = PlatformHotkeyConfiguration.Instance!; var selectAllGesture = keymap.SelectAll.First(); var keyEvent = new KeyEventArgs From 6d57d3cf7d81c4c83bf38ab0f6c726cfca9d00e6 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 29 May 2023 20:30:26 -0400 Subject: [PATCH 12/60] Introduce Application.PlatformSettings --- src/Avalonia.Base/Input/IInputRoot.cs | 2 +- src/Avalonia.Controls/Application.cs | 23 ++++++++++--- .../Accents/SystemAccentColors.cs | 34 ++++++++++++------- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Base/Input/IInputRoot.cs b/src/Avalonia.Base/Input/IInputRoot.cs index f358658005..8e2a5d6932 100644 --- a/src/Avalonia.Base/Input/IInputRoot.cs +++ b/src/Avalonia.Base/Input/IInputRoot.cs @@ -26,7 +26,7 @@ namespace Avalonia.Input /// Represents a contract for accessing platform-specific settings and information. /// /// - /// Focus manager can be null only if window wasn't initialized yet. + /// PlatformSettings can be null only if window wasn't initialized yet. /// IPlatformSettings? PlatformSettings { get; } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index b98ad123f1..1187ada8a7 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -180,6 +180,17 @@ namespace Avalonia /// public IApplicationLifetime? ApplicationLifetime { get; set; } + /// + /// Represents a contract for accessing platform-specific settings and information. + /// + /// + /// PlatformSettings can be null only if application wasn't initialized yet. + /// 's is an equivalent API + /// which should always be preferred over a global one, + /// as specific top levels might have different settings set-up. + /// + public IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService(); + event Action>? IGlobalStyles.GlobalStylesAdded { add => _stylesAdded += value; @@ -229,10 +240,12 @@ namespace Avalonia var focusManager = new FocusManager(); InputManager = new InputManager(); - var settings = AvaloniaLocator.Current.GetRequiredService(); - settings.ColorValuesChanged += OnColorValuesChanged; - OnColorValuesChanged(settings, settings.GetColorValues()); - + if (PlatformSettings is { } settings) + { + settings.ColorValuesChanged += OnColorValuesChanged; + OnColorValuesChanged(settings, settings.GetColorValues()); + } + AvaloniaLocator.CurrentMutable .Bind().ToTransient() .Bind().ToConstant(this) @@ -242,7 +255,7 @@ namespace Avalonia .Bind().ToConstant(InputManager) .Bind().ToTransient() .Bind().ToConstant(DragDropDevice.Instance); - + // TODO: Fix this, for now we keep this behavior since someone might be relying on it in 0.9.x if (AvaloniaLocator.Current.GetService() == null) AvaloniaLocator.CurrentMutable diff --git a/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs b/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs index a4ef15f950..a796a5704d 100644 --- a/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs +++ b/src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs @@ -16,18 +16,12 @@ internal class SystemAccentColors : IResourceProvider public const string AccentLight2Key = "SystemAccentColorLight2"; public const string AccentLight3Key = "SystemAccentColorLight3"; - private static readonly Color s_defaultSystemAccentColor = Color.FromRgb(0, 120, 215); - private readonly IPlatformSettings? _platformSettings; + private static readonly Color s_defaultSystemAccentColor = Color.FromRgb(0, 120, 215); private bool _invalidateColors = true; private Color _systemAccentColor; private Color _systemAccentColorDark1, _systemAccentColorDark2, _systemAccentColorDark3; private Color _systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3; - public SystemAccentColors() - { - _platformSettings = AvaloniaLocator.Current.GetService(); - } - public bool HasResources => true; public bool TryGetResource(object key, ThemeVariant? theme, out object? value) { @@ -96,10 +90,12 @@ internal class SystemAccentColors : IResourceProvider Owner = owner; OwnerChanged?.Invoke(this, EventArgs.Empty); - if (_platformSettings is not null) + if (GetFromOwner(owner) is { } platformSettings) { - _platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged; + platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged; } + + _invalidateColors = true; } } @@ -110,10 +106,12 @@ internal class SystemAccentColors : IResourceProvider Owner = null; OwnerChanged?.Invoke(this, EventArgs.Empty); - if (_platformSettings is not null) + if (GetFromOwner(owner) is { } platformSettings) { - _platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged; + platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged; } + + _invalidateColors = true; } } @@ -122,13 +120,25 @@ internal class SystemAccentColors : IResourceProvider if (_invalidateColors) { _invalidateColors = false; + + var platformSettings = GetFromOwner(Owner); - _systemAccentColor = _platformSettings?.GetColorValues().AccentColor1 ?? s_defaultSystemAccentColor; + _systemAccentColor = platformSettings?.GetColorValues().AccentColor1 ?? s_defaultSystemAccentColor; (_systemAccentColorDark1,_systemAccentColorDark2, _systemAccentColorDark3, _systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3) = CalculateAccentShades(_systemAccentColor); } } + private static IPlatformSettings? GetFromOwner(IResourceHost? owner) + { + return owner switch + { + Application app => app.PlatformSettings, + Visual visual => TopLevel.GetTopLevel(visual)?.PlatformSettings, + _ => null + }; + } + public static (Color d1, Color d2, Color d3, Color l1, Color l2, Color l3) CalculateAccentShades(Color accentColor) { // dark1step = (hslAccent.L - SystemAccentColorDark1.L) * 255 From 0d6306725ad129a46c6e4f14720472f9b76a8d07 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 29 May 2023 22:14:28 -0400 Subject: [PATCH 13/60] Fix ButtonTests --- .../ButtonTests.cs | 76 ++++++++----------- 1 file changed, 30 insertions(+), 46 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 1d62588ec0..0ddc247be6 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -3,9 +3,11 @@ using System.Windows.Input; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Threading; using Avalonia.UnitTests; using Avalonia.VisualTree; using Moq; @@ -14,7 +16,7 @@ using MouseButton = Avalonia.Input.MouseButton; namespace Avalonia.Controls.UnitTests { - public class ButtonTests + public class ButtonTests : ScopedTestBase { private MouseTestHelper _helper = new MouseTestHelper(); @@ -139,11 +141,19 @@ namespace Avalonia.Controls.UnitTests renderer.Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns>((p, r, f) => r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]); - - var target = new TestButton(renderer.Object) + + using var _ = UnitTestApplication.Start(TestServices.StyledWindow); + + var root = new Window() { HitTesterOverride = renderer.Object }; + var target = new Button() { - Bounds = new Rect(0, 0, 100, 100) + Width = 100, + Height = 100, + VerticalAlignment = VerticalAlignment.Top, + HorizontalAlignment = HorizontalAlignment.Left }; + root.Content = target; + root.Show(); bool clicked = false; @@ -165,16 +175,13 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside() { - var renderer = new Mock(); - - renderer.Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) - .Returns>((p, r, f) => - r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]); - - var target = new TestButton(renderer.Object) + var root = new TestRoot(); + var target = new Button() { - Bounds = new Rect(0, 0, 100, 100) + Width = 100, + Height = 100 }; + root.Child = target; bool clicked = false; @@ -203,12 +210,20 @@ namespace Avalonia.Controls.UnitTests .Returns>((p, r, f) => r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ? new Visual[] { r } : new Visual[0]); - - var target = new TestButton(renderer.Object) + + using var _ = UnitTestApplication.Start(TestServices.StyledWindow); + + var root = new Window() { HitTesterOverride = renderer.Object }; + var target = new Button() { - Bounds = new Rect(0, 0, 100, 100), + Width = 100, + Height = 100, + VerticalAlignment = VerticalAlignment.Top, + HorizontalAlignment = HorizontalAlignment.Left, RenderTransform = new TranslateTransform { X = 100, Y = 0 } }; + root.Content = target; + root.Show(); //actual bounds of button should be 100,0,100,100 x -> translated 100 pixels //so mouse with x=150 coordinates should trigger click @@ -381,37 +396,6 @@ namespace Avalonia.Controls.UnitTests return new KeyEventArgs { RoutedEvent = InputElement.KeyDownEvent, Key = key, Source = source }; } - private class TestButton : Button, IRenderRoot - { - public TestButton(IHitTester hit) - { - IsVisible = true; - HitTester = hit; - Renderer = new NullRenderer(); - } - - public new Rect Bounds - { - get => base.Bounds; - set => base.Bounds = value; - } - - public Size ClientSize => throw new NotImplementedException(); - - public IRenderer Renderer { get; } - public IHitTester HitTester { get; } - - public double RenderScaling => throw new NotImplementedException(); - - public IRenderTarget CreateRenderTarget() => throw new NotImplementedException(); - - public void Invalidate(Rect rect) => throw new NotImplementedException(); - - public Point PointToClient(PixelPoint p) => throw new NotImplementedException(); - - public PixelPoint PointToScreen(Point p) => throw new NotImplementedException(); - } - private void RaisePointerPressed(Button button, int clickCount, MouseButton mouseButton, Point position) { _helper.Down(button, mouseButton, position, clickCount: clickCount); From 94efefedb91fcf45dc947b78d7b6b8b243c86b83 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 29 May 2023 22:49:10 -0400 Subject: [PATCH 14/60] Fix gesture tests --- .../Input/GesturesTests.cs | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index c3630c36b7..14fa242146 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -24,13 +24,13 @@ namespace Avalonia.Base.UnitTests.Input public void Tapped_Should_Follow_Pointer_Pressed_Released() { Border border = new Border(); - var decorator = new Decorator + var root = new TestRoot { Child = border }; var result = new List(); - AddHandlers(decorator, border, result, false); + AddHandlers(root, border, result, false); _mouse.Click(border); @@ -41,13 +41,13 @@ namespace Avalonia.Base.UnitTests.Input public void Tapped_Should_Be_Raised_Even_When_Pressed_Released_Handled() { Border border = new Border(); - var decorator = new Decorator + var root = new TestRoot { Child = border }; var result = new List(); - AddHandlers(decorator, border, result, true); + AddHandlers(root, border, result, true); _mouse.Click(border); @@ -58,13 +58,13 @@ namespace Avalonia.Base.UnitTests.Input public void Tapped_Should_Not_Be_Raised_For_Middle_Button() { Border border = new Border(); - var decorator = new Decorator + var root = new TestRoot { Child = border }; var raised = false; - decorator.AddHandler(Gestures.TappedEvent, (_, _) => raised = true); + root.AddHandler(Gestures.TappedEvent, (_, _) => raised = true); _mouse.Click(border, MouseButton.Middle); @@ -75,13 +75,13 @@ namespace Avalonia.Base.UnitTests.Input public void Tapped_Should_Not_Be_Raised_For_Right_Button() { Border border = new Border(); - var decorator = new Decorator + var root = new TestRoot { Child = border }; var raised = false; - decorator.AddHandler(Gestures.TappedEvent, (_, _) => raised = true); + root.AddHandler(Gestures.TappedEvent, (_, _) => raised = true); _mouse.Click(border, MouseButton.Right); @@ -92,13 +92,13 @@ namespace Avalonia.Base.UnitTests.Input public void RightTapped_Should_Be_Raised_For_Right_Button() { Border border = new Border(); - var decorator = new Decorator + var root = new TestRoot { Child = border }; var raised = false; - decorator.AddHandler(Gestures.RightTappedEvent, (_, _) => raised = true); + root.AddHandler(Gestures.RightTappedEvent, (_, _) => raised = true); _mouse.Click(border, MouseButton.Right); @@ -109,13 +109,13 @@ namespace Avalonia.Base.UnitTests.Input public void DoubleTapped_Should_Follow_Pointer_Pressed_Released_Pressed() { Border border = new Border(); - var decorator = new Decorator + var root = new TestRoot { Child = border }; var result = new List(); - AddHandlers(decorator, border, result, false); + AddHandlers(root, border, result, false); _mouse.Click(border); _mouse.Down(border, clickCount: 2); @@ -127,13 +127,13 @@ namespace Avalonia.Base.UnitTests.Input public void DoubleTapped_Should_Be_Raised_Even_When_Pressed_Released_Handled() { Border border = new Border(); - var decorator = new Decorator + var root = new TestRoot { Child = border }; var result = new List(); - AddHandlers(decorator, border, result, true); + AddHandlers(root, border, result, true); _mouse.Click(border); _mouse.Down(border, clickCount: 2); @@ -145,13 +145,13 @@ namespace Avalonia.Base.UnitTests.Input public void DoubleTapped_Should_Not_Be_Raised_For_Middle_Button() { Border border = new Border(); - var decorator = new Decorator + var root = new TestRoot { Child = border }; var raised = false; - decorator.AddHandler(Gestures.DoubleTappedEvent, (_, _) => raised = true); + root.AddHandler(Gestures.DoubleTappedEvent, (_, _) => raised = true); _mouse.Click(border, MouseButton.Middle); _mouse.Down(border, MouseButton.Middle, clickCount: 2); @@ -163,13 +163,13 @@ namespace Avalonia.Base.UnitTests.Input public void DoubleTapped_Should_Not_Be_Raised_For_Right_Button() { Border border = new Border(); - var decorator = new Decorator + var root = new TestRoot { Child = border }; var raised = false; - decorator.AddHandler(Gestures.DoubleTappedEvent, (_, _) => raised = true); + root.AddHandler(Gestures.DoubleTappedEvent, (_, _) => raised = true); _mouse.Click(border, MouseButton.Right); _mouse.Down(border, MouseButton.Right, clickCount: 2); @@ -191,13 +191,13 @@ namespace Avalonia.Base.UnitTests.Input Border border = new Border(); Gestures.SetIsHoldWithMouseEnabled(border, true); - var decorator = new Decorator + var root = new TestRoot { Child = border }; HoldingState holding = HoldingState.Cancelled; - decorator.AddHandler(Gestures.HoldingEvent, (_, e) => holding = e.HoldingState); + root.AddHandler(Gestures.HoldingEvent, (_, e) => holding = e.HoldingState); _mouse.Down(border); Assert.False(holding != HoldingState.Cancelled); @@ -227,13 +227,13 @@ namespace Avalonia.Base.UnitTests.Input Border border = new Border(); Gestures.SetIsHoldWithMouseEnabled(border, true); - var decorator = new Decorator + var root = new TestRoot { Child = border }; var raised = false; - decorator.AddHandler(Gestures.HoldingEvent, (_, e) => raised = e.HoldingState == HoldingState.Started); + root.AddHandler(Gestures.HoldingEvent, (_, e) => raised = e.HoldingState == HoldingState.Started); _mouse.Down(border); Assert.False(raised); @@ -262,13 +262,13 @@ namespace Avalonia.Base.UnitTests.Input Border border = new Border(); Gestures.SetIsHoldWithMouseEnabled(border, true); - var decorator = new Decorator + var root = new TestRoot { Child = border }; var raised = false; - decorator.AddHandler(Gestures.HoldingEvent, (_, e) => raised = e.HoldingState == HoldingState.Completed); + root.AddHandler(Gestures.HoldingEvent, (_, e) => raised = e.HoldingState == HoldingState.Completed); _mouse.Down(border); Assert.False(raised); @@ -297,13 +297,13 @@ namespace Avalonia.Base.UnitTests.Input Border border = new Border(); Gestures.SetIsHoldWithMouseEnabled(border, true); - var decorator = new Decorator + var root = new TestRoot { Child = border }; var cancelled = false; - decorator.AddHandler(Gestures.HoldingEvent, (_, e) => cancelled = e.HoldingState == HoldingState.Cancelled); + root.AddHandler(Gestures.HoldingEvent, (_, e) => cancelled = e.HoldingState == HoldingState.Cancelled); _mouse.Down(border); Assert.False(cancelled); @@ -333,13 +333,13 @@ namespace Avalonia.Base.UnitTests.Input Border border = new Border(); Gestures.SetIsHoldWithMouseEnabled(border, true); - var decorator = new Decorator + var root = new TestRoot() { Child = border }; var cancelled = false; - decorator.AddHandler(Gestures.HoldingEvent, (_, e) => cancelled = e.HoldingState == HoldingState.Cancelled); + root.AddHandler(Gestures.HoldingEvent, (_, e) => cancelled = e.HoldingState == HoldingState.Cancelled); _mouse.Down(border); @@ -369,13 +369,13 @@ namespace Avalonia.Base.UnitTests.Input Border border = new Border(); Gestures.SetIsHoldWithMouseEnabled(border, true); - var decorator = new Decorator + var testRoot = new TestRoot { Child = border }; var raised = false; - decorator.AddHandler(Gestures.HoldingEvent, (_, e) => raised = e.HoldingState == HoldingState.Completed); + testRoot.AddHandler(Gestures.HoldingEvent, (_, e) => raised = e.HoldingState == HoldingState.Completed); var secondMouse = new MouseTestHelper(); @@ -392,12 +392,12 @@ namespace Avalonia.Base.UnitTests.Input } private static void AddHandlers( - Decorator decorator, + TestRoot root, Border border, IList result, bool markHandled) { - decorator.AddHandler(InputElement.PointerPressedEvent, (_, e) => + root.AddHandler(InputElement.PointerPressedEvent, (_, e) => { result.Add("dp"); @@ -407,7 +407,7 @@ namespace Avalonia.Base.UnitTests.Input } }); - decorator.AddHandler(InputElement.PointerReleasedEvent, (_, e) => + root.AddHandler(InputElement.PointerReleasedEvent, (_, e) => { result.Add("dr"); @@ -420,8 +420,8 @@ namespace Avalonia.Base.UnitTests.Input border.AddHandler(InputElement.PointerPressedEvent, (_, _) => result.Add("bp")); border.AddHandler(InputElement.PointerReleasedEvent, (_, _) => result.Add("br")); - decorator.AddHandler(Gestures.TappedEvent, (_, _) => result.Add("dt")); - decorator.AddHandler(Gestures.DoubleTappedEvent, (_, _) => result.Add("ddt")); + root.AddHandler(Gestures.TappedEvent, (_, _) => result.Add("dt")); + root.AddHandler(Gestures.DoubleTappedEvent, (_, _) => result.Add("ddt")); border.AddHandler(Gestures.TappedEvent, (_, _) => result.Add("bt")); border.AddHandler(Gestures.DoubleTappedEvent, (_, _) => result.Add("bdt")); } @@ -438,13 +438,13 @@ namespace Avalonia.Base.UnitTests.Input Background = new SolidColorBrush(Colors.Red) }; border.GestureRecognizers.Add(new PinchGestureRecognizer()); - var decorator = new Decorator + var root = new TestRoot { Child = border }; var raised = false; - decorator.AddHandler(Gestures.PinchEvent, (_, _) => raised = true); + root.AddHandler(Gestures.PinchEvent, (_, _) => raised = true); var firstPoint = new Point(5, 5); var secondPoint = new Point(10, 10); @@ -466,13 +466,13 @@ namespace Avalonia.Base.UnitTests.Input Background = new SolidColorBrush(Colors.Red) }; border.GestureRecognizers.Add(new PinchGestureRecognizer()); - var decorator = new Decorator + var root = new TestRoot { Child = border }; var raised = false; - decorator.AddHandler(Gestures.PinchEvent, (_, _) => raised = true); + root.AddHandler(Gestures.PinchEvent, (_, _) => raised = true); var firstPoint = new Point(5, 5); var secondPoint = new Point(10, 10); @@ -502,13 +502,13 @@ namespace Avalonia.Base.UnitTests.Input CanVerticallyScroll = true, ScrollStartDistance = 50 }); - var decorator = new Decorator + var root = new TestRoot { Child = border }; var raised = false; - decorator.AddHandler(Gestures.ScrollGestureEvent, (_, _) => raised = true); + root.AddHandler(Gestures.ScrollGestureEvent, (_, _) => raised = true); var firstTouch = new TouchTestHelper(); From 9ba579a15a8cf0151130af2c841bfd4ba032448e Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 29 May 2023 23:00:39 -0400 Subject: [PATCH 15/60] Update documentation a bit --- src/Avalonia.Base/Input/IInputRoot.cs | 2 +- src/Avalonia.Controls/Application.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Input/IInputRoot.cs b/src/Avalonia.Base/Input/IInputRoot.cs index 8e2a5d6932..2be5847220 100644 --- a/src/Avalonia.Base/Input/IInputRoot.cs +++ b/src/Avalonia.Base/Input/IInputRoot.cs @@ -23,7 +23,7 @@ namespace Avalonia.Input IFocusManager? FocusManager { get; } /// - /// Represents a contract for accessing platform-specific settings and information. + /// Represents a contract for accessing top-level platform-specific settings. /// /// /// PlatformSettings can be null only if window wasn't initialized yet. diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 1187ada8a7..30791551f5 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -181,7 +181,7 @@ namespace Avalonia public IApplicationLifetime? ApplicationLifetime { get; set; } /// - /// Represents a contract for accessing platform-specific settings and information. + /// Represents a contract for accessing global platform-specific settings. /// /// /// PlatformSettings can be null only if application wasn't initialized yet. From d267d06a08e3576700b998bf8202cbc6662b6573 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 30 May 2023 09:01:31 -0400 Subject: [PATCH 16/60] Move PlatformHotkeyConfiguration to IPlatformSettings --- .../Platform/PlatformHotkeyConfiguration.cs | 9 ------ .../Platform/DefaultPlatformSettings.cs | 7 ++++- .../Platform/IPlatformSettings.cs | 6 ++++ src/Avalonia.Controls.DataGrid/DataGrid.cs | 30 +++++++++---------- .../DataGridColumn.cs | 2 +- .../DataGridColumnHeader.cs | 2 +- .../Utils/KeyboardHelper.cs | 12 ++++---- src/Avalonia.Controls/ContextMenu.cs | 2 +- src/Avalonia.Controls/Control.cs | 2 +- .../Flyouts/PopupFlyoutBase.cs | 3 +- src/Avalonia.Controls/ListBox.cs | 5 ++-- src/Avalonia.Controls/MaskedTextBox.cs | 3 +- src/Avalonia.Controls/SelectableTextBlock.cs | 2 +- src/Avalonia.Controls/TextBox.cs | 8 ++--- src/Avalonia.Controls/TopLevel.cs | 2 +- src/Avalonia.Controls/TreeView.cs | 5 ++-- src/Avalonia.Native/AvaloniaNativePlatform.cs | 16 +++++----- .../TreeViewTests.cs | 6 ++-- .../Avalonia.UnitTests/UnitTestApplication.cs | 3 +- 19 files changed, 65 insertions(+), 60 deletions(-) diff --git a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs index a4cf90e221..2eed0980fb 100644 --- a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs +++ b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs @@ -8,15 +8,6 @@ namespace Avalonia.Input.Platform /// public sealed class PlatformHotkeyConfiguration { - /// - /// Retrieves shared static instance of PlatformHotkeyConfiguration. - /// - /// - /// Return value might be null, when application wasn't yet initialized. - /// - public static PlatformHotkeyConfiguration? Instance => - AvaloniaLocator.Current.GetService(); - [PrivateApi] public PlatformHotkeyConfiguration() : this(KeyModifiers.Control) { diff --git a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs index 54528efec1..a4d14b0084 100644 --- a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs +++ b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs @@ -1,7 +1,9 @@ using System; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Media; using Avalonia.Metadata; +using Avalonia.VisualTree; namespace Avalonia.Platform { @@ -30,7 +32,10 @@ namespace Avalonia.Platform public virtual TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(500); public virtual TimeSpan HoldWaitDuration => TimeSpan.FromMilliseconds(300); - + + public PlatformHotkeyConfiguration HotkeyConfiguration => + AvaloniaLocator.Current.GetRequiredService(); + public virtual PlatformColorValues GetColorValues() { return new PlatformColorValues diff --git a/src/Avalonia.Base/Platform/IPlatformSettings.cs b/src/Avalonia.Base/Platform/IPlatformSettings.cs index c5d46cb7e6..46980c6d51 100644 --- a/src/Avalonia.Base/Platform/IPlatformSettings.cs +++ b/src/Avalonia.Base/Platform/IPlatformSettings.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Metadata; namespace Avalonia.Platform @@ -37,6 +38,11 @@ namespace Avalonia.Platform /// TimeSpan HoldWaitDuration { get; } + /// + /// Get a configuration for platform-specific hotkeys in an Avalonia application. + /// + PlatformHotkeyConfiguration HotkeyConfiguration { get; } + /// /// Gets current system color values including dark mode and accent colors. /// diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index bfcd4750e3..7ade9f2437 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -2660,25 +2660,25 @@ namespace Avalonia.Controls internal bool ProcessDownKey(KeyEventArgs e) { - KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift); + KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); return ProcessDownKeyInternal(shift, ctrl); } internal bool ProcessEndKey(KeyEventArgs e) { - KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift); + KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); return ProcessEndKey(shift, ctrl); } internal bool ProcessEnterKey(KeyEventArgs e) { - KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift); + KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); return ProcessEnterKey(shift, ctrl); } internal bool ProcessHomeKey(KeyEventArgs e) { - KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift); + KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); return ProcessHomeKey(shift, ctrl); } @@ -2718,25 +2718,25 @@ namespace Avalonia.Controls internal bool ProcessLeftKey(KeyEventArgs e) { - KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift); + KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); return ProcessLeftKey(shift, ctrl); } internal bool ProcessNextKey(KeyEventArgs e) { - KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift); + KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); return ProcessNextKey(shift, ctrl); } internal bool ProcessPriorKey(KeyEventArgs e) { - KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift); + KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); return ProcessPriorKey(shift, ctrl); } internal bool ProcessRightKey(KeyEventArgs e) { - KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift); + KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); return ProcessRightKey(shift, ctrl); } @@ -2854,7 +2854,7 @@ namespace Avalonia.Controls internal bool ProcessUpKey(KeyEventArgs e) { - KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift); + KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); return ProcessUpKey(shift, ctrl); } @@ -3124,13 +3124,13 @@ namespace Avalonia.Controls //TODO: Ensure right button is checked for internal bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit) { - KeyboardHelper.GetMetaKeyState(pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift); + KeyboardHelper.GetMetaKeyState(this, pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift); return UpdateStateOnMouseRightButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl); } //TODO: Ensure left button is checked for internal bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit) { - KeyboardHelper.GetMetaKeyState(pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift); + KeyboardHelper.GetMetaKeyState(this, pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift); return UpdateStateOnMouseLeftButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl); } @@ -4654,7 +4654,7 @@ namespace Avalonia.Controls private bool ProcessAKey(KeyEventArgs e) { - KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift, out bool alt); + KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift, out bool alt); if (ctrl && !shift && !alt && SelectionMode == DataGridSelectionMode.Extended) { @@ -4923,7 +4923,7 @@ namespace Avalonia.Controls private bool ProcessF2Key(KeyEventArgs e) { - KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift); + KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); if (!shift && !ctrl && _editingColumnIndex == -1 && CurrentColumnIndex != -1 && GetRowSelection(CurrentSlot) && @@ -5280,7 +5280,7 @@ namespace Avalonia.Controls private bool ProcessTabKey(KeyEventArgs e) { - KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift); + KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); return ProcessTabKey(e, shift, ctrl); } @@ -6099,7 +6099,7 @@ namespace Avalonia.Controls /// Whether or not the DataGrid handled the key press. private bool ProcessCopyKey(KeyModifiers modifiers) { - KeyboardHelper.GetMetaKeyState(modifiers, out bool ctrl, out bool shift, out bool alt); + KeyboardHelper.GetMetaKeyState(this, modifiers, out bool ctrl, out bool shift, out bool alt); if (ctrl && !shift && !alt && ClipboardCopyMode != DataGridClipboardCopyMode.None && SelectedItems.Count > 0) { diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index d1e1efdd85..96d072260c 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -703,7 +703,7 @@ namespace Avalonia.Controls public void ClearSort() { //InvokeProcessSort is already validating if sorting is possible - _headerCell?.InvokeProcessSort(KeyboardHelper.GetPlatformCtrlOrCmdKeyModifier()); + _headerCell?.InvokeProcessSort(KeyboardHelper.GetPlatformCtrlOrCmdKeyModifier(OwningGrid)); } /// diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index ef1e84c745..0755864c25 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -241,7 +241,7 @@ namespace Avalonia.Controls DataGrid owningGrid = OwningGrid; DataGridSortDescription newSort; - KeyboardHelper.GetMetaKeyState(keyModifiers, out bool ctrl, out bool shift); + KeyboardHelper.GetMetaKeyState(this, keyModifiers, out bool ctrl, out bool shift); DataGridSortDescription sort = OwningColumn.GetSortDescription(); IDataGridCollectionView collectionView = owningGrid.DataConnection.CollectionView; diff --git a/src/Avalonia.Controls.DataGrid/Utils/KeyboardHelper.cs b/src/Avalonia.Controls.DataGrid/Utils/KeyboardHelper.cs index 78b349fd23..bcb30888dc 100644 --- a/src/Avalonia.Controls.DataGrid/Utils/KeyboardHelper.cs +++ b/src/Avalonia.Controls.DataGrid/Utils/KeyboardHelper.cs @@ -10,22 +10,22 @@ namespace Avalonia.Controls.Utils { internal static class KeyboardHelper { - public static void GetMetaKeyState(KeyModifiers modifiers, out bool ctrlOrCmd, out bool shift) + public static void GetMetaKeyState(Control target, KeyModifiers modifiers, out bool ctrlOrCmd, out bool shift) { - ctrlOrCmd = modifiers.HasFlag(GetPlatformCtrlOrCmdKeyModifier()); + ctrlOrCmd = modifiers.HasFlag(GetPlatformCtrlOrCmdKeyModifier(target)); shift = modifiers.HasFlag(KeyModifiers.Shift); } - public static void GetMetaKeyState(KeyModifiers modifiers, out bool ctrlOrCmd, out bool shift, out bool alt) + public static void GetMetaKeyState(Control target, KeyModifiers modifiers, out bool ctrlOrCmd, out bool shift, out bool alt) { - ctrlOrCmd = modifiers.HasFlag(GetPlatformCtrlOrCmdKeyModifier()); + ctrlOrCmd = modifiers.HasFlag(GetPlatformCtrlOrCmdKeyModifier(target)); shift = modifiers.HasFlag(KeyModifiers.Shift); alt = modifiers.HasFlag(KeyModifiers.Alt); } - public static KeyModifiers GetPlatformCtrlOrCmdKeyModifier() + public static KeyModifiers GetPlatformCtrlOrCmdKeyModifier(Control target) { - var keymap = PlatformHotkeyConfiguration.Instance; + var keymap = TopLevel.GetTopLevel(target)!.PlatformSettings!.HotkeyConfiguration; return keymap.CommandModifiers; } } diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 238ccd091c..e3a419d7b3 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -405,7 +405,7 @@ namespace Avalonia.Controls { if (IsOpen) { - var keymap = PlatformHotkeyConfiguration.Instance; + var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration; if (keymap?.OpenContextMenu.Any(k => k.Matches(e)) == true && !CancelClosing()) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 4a9737a267..6e0063c9ec 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -480,7 +480,7 @@ namespace Avalonia.Controls if (e.Source == this && !e.Handled) { - var keymap = PlatformHotkeyConfiguration.Instance?.OpenContextMenu; + var keymap = Application.Current!.PlatformSettings?.HotkeyConfiguration.OpenContextMenu; if (keymap is null) { diff --git a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs index c94200ac8b..c3cf1f4897 100644 --- a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs @@ -9,6 +9,7 @@ using Avalonia.Input.Raw; using Avalonia.Layout; using Avalonia.Logging; using Avalonia.Reactive; +using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives { @@ -392,7 +393,7 @@ namespace Avalonia.Controls.Primitives && IsOpen && Target?.ContextFlyout == this) { - var keymap = PlatformHotkeyConfiguration.Instance; + var keymap = Application.Current!.PlatformSettings?.HotkeyConfiguration; if (keymap?.OpenContextMenu.Any(k => k.Matches(e)) == true) { diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index cd3c72c3a4..220903cc04 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -9,6 +9,7 @@ using Avalonia.Controls.Selection; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Input.Platform; +using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -127,7 +128,7 @@ namespace Avalonia.Controls protected override void OnKeyDown(KeyEventArgs e) { - var hotkeys = PlatformHotkeyConfiguration.Instance; + var hotkeys = Application.Current!.PlatformSettings?.HotkeyConfiguration; var ctrl = hotkeys is not null && e.KeyModifiers.HasAllFlags(hotkeys.CommandModifiers); if (!ctrl && @@ -165,7 +166,7 @@ namespace Avalonia.Controls internal bool UpdateSelectionFromPointerEvent(Control source, PointerEventArgs e) { - var hotkeys = PlatformHotkeyConfiguration.Instance; + var hotkeys = Application.Current!.PlatformSettings?.HotkeyConfiguration; var toggle = hotkeys is not null && e.KeyModifiers.HasAllFlags(hotkeys.CommandModifiers); return UpdateSelectionFromEventSource( diff --git a/src/Avalonia.Controls/MaskedTextBox.cs b/src/Avalonia.Controls/MaskedTextBox.cs index fa4b8b2fb4..10eae5e7c0 100644 --- a/src/Avalonia.Controls/MaskedTextBox.cs +++ b/src/Avalonia.Controls/MaskedTextBox.cs @@ -7,6 +7,7 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Styling; +using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -204,7 +205,7 @@ namespace Avalonia.Controls return; } - var keymap = PlatformHotkeyConfiguration.Instance; + var keymap = Application.Current!.PlatformSettings?.HotkeyConfiguration; bool Match(List gestures) => gestures.Any(g => g.Matches(e)); diff --git a/src/Avalonia.Controls/SelectableTextBlock.cs b/src/Avalonia.Controls/SelectableTextBlock.cs index 973f086e2a..5e21681024 100644 --- a/src/Avalonia.Controls/SelectableTextBlock.cs +++ b/src/Avalonia.Controls/SelectableTextBlock.cs @@ -198,7 +198,7 @@ namespace Avalonia.Controls var handled = false; var modifiers = e.KeyModifiers; - var keymap = PlatformHotkeyConfiguration.Instance!; + var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration; bool Match(List gestures) => gestures.Any(g => g.Matches(e)); diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 2479622b31..5f3ba2ae11 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -32,17 +32,17 @@ namespace Avalonia.Controls /// /// Gets a platform-specific for the Cut action /// - public static KeyGesture? CutGesture { get; } = PlatformHotkeyConfiguration.Instance?.Cut.FirstOrDefault(); + public static KeyGesture? CutGesture => Application.Current?.PlatformSettings?.HotkeyConfiguration.Cut.FirstOrDefault(); /// /// Gets a platform-specific for the Copy action /// - public static KeyGesture? CopyGesture { get; } = PlatformHotkeyConfiguration.Instance?.Copy.FirstOrDefault(); + public static KeyGesture? CopyGesture => Application.Current?.PlatformSettings?.HotkeyConfiguration.Copy.FirstOrDefault(); /// /// Gets a platform-specific for the Paste action /// - public static KeyGesture? PasteGesture { get; } = PlatformHotkeyConfiguration.Instance?.Paste.FirstOrDefault(); + public static KeyGesture? PasteGesture => Application.Current?.PlatformSettings?.HotkeyConfiguration.Paste.FirstOrDefault(); /// /// Defines the property @@ -1100,7 +1100,7 @@ namespace Avalonia.Controls var handled = false; var modifiers = e.KeyModifiers; - var keymap = PlatformHotkeyConfiguration.Instance!; + var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration; bool Match(List gestures) => gestures.Any(g => g.Matches(e)); bool DetectSelection() => e.KeyModifiers.HasAllFlags(keymap.SelectionModifiers); diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 5da82813e2..1324cd858a 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -250,7 +250,7 @@ namespace Avalonia.Controls if (e is RawKeyEventArgs rawKeyEventArgs && rawKeyEventArgs.Type == RawKeyEventType.KeyDown) { - var keymap = PlatformHotkeyConfiguration.Instance?.Back; + var keymap = PlatformSettings?.HotkeyConfiguration.Back; if (keymap != null) { diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index d449590bd8..7bf8d3bb68 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -571,7 +571,7 @@ namespace Avalonia.Controls if (!e.Handled) { - var keymap = PlatformHotkeyConfiguration.Instance; + var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration; bool Match(List? gestures) => gestures?.Any(g => g.Matches(e)) ?? false; if (this.SelectionMode == SelectionMode.Multiple && Match(keymap?.SelectAll)) @@ -652,11 +652,12 @@ namespace Avalonia.Controls if (point.Properties.IsLeftButtonPressed || point.Properties.IsRightButtonPressed) { + var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration; e.Handled = UpdateSelectionFromEventSource( e.Source, true, e.KeyModifiers.HasAllFlags(KeyModifiers.Shift), - e.KeyModifiers.HasAllFlags(PlatformHotkeyConfiguration.Instance!.CommandModifiers), + e.KeyModifiers.HasAllFlags(keymap.CommandModifiers), point.Properties.IsRightButtonPressed); } } diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index a1ab90b3f7..b32382084d 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -108,20 +108,18 @@ namespace Avalonia.Native .Bind().ToConstant(this) .Bind().ToConstant(new ClipboardImpl(_factory.CreateClipboard())) .Bind().ToConstant(new DefaultRenderTimer(60)) - .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt)) .Bind().ToConstant(new MacOSMountedVolumeInfoProvider()) .Bind().ToConstant(new AvaloniaNativeDragSource(_factory)) .Bind().ToConstant(applicationPlatform) .Bind().ToConstant(new MacOSNativeMenuCommands(_factory.CreateApplicationCommands())); - var hotkeys = PlatformHotkeyConfiguration.Instance; - if (hotkeys is not null) - { - hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers)); - hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); - hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers)); - hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); - } + var hotkeys = new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt); + hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers)); + hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); + hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers)); + hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); + + AvaloniaLocator.CurrentMutable.Bind().ToConstant(hotkeys); if (_options.UseGpu) { diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index f5e7992203..11e9349ffe 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1045,7 +1045,7 @@ namespace Avalonia.Controls.UnitTests var data = CreateTestTreeData(); var target = CreateTarget(data: data, multiSelect: true); var rootNode = data[0]; - var keymap = PlatformHotkeyConfiguration.Instance!; + var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration; var selectAllGesture = keymap.SelectAll.First(); var keyEvent = new KeyEventArgs @@ -1075,7 +1075,7 @@ namespace Avalonia.Controls.UnitTests ClickContainer(fromContainer, KeyModifiers.None); ClickContainer(toContainer, KeyModifiers.Shift); - var keymap = PlatformHotkeyConfiguration.Instance!; + var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration; var selectAllGesture = keymap.SelectAll.First(); var keyEvent = new KeyEventArgs @@ -1105,7 +1105,7 @@ namespace Avalonia.Controls.UnitTests ClickContainer(fromContainer, KeyModifiers.None); ClickContainer(toContainer, KeyModifiers.Shift); - var keymap = PlatformHotkeyConfiguration.Instance!; + var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration; var selectAllGesture = keymap.SelectAll.First(); var keyEvent = new KeyEventArgs diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index 870e6391d0..5f7f3f20ec 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -77,7 +77,8 @@ namespace Avalonia.UnitTests .Bind().ToConstant(Services.DispatcherImpl) .Bind().ToConstant(Services.StandardCursorFactory) .Bind().ToConstant(Services.WindowingPlatform) - .Bind().ToSingleton(); + .Bind().ToSingleton() + .Bind().ToSingleton(); // This is a hack to make tests work, we need to refactor the way font manager is registered // See https://github.com/AvaloniaUI/Avalonia/issues/10081 From eb05dac696f332bb0ca7861aae8b673db1eb5244 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 30 May 2023 11:45:20 -0400 Subject: [PATCH 17/60] Remove IKeyboardDevice/IPointerDevice/IDragDropDevice from public API, as they are not really used anywhere --- src/Avalonia.Base/Input/IKeyboardDevice.cs | 2 +- src/Avalonia.Base/Input/IMouseDevice.cs | 2 +- src/Avalonia.Base/Input/IPenDevice.cs | 5 ++++- src/Avalonia.Base/Input/IPointerDevice.cs | 2 +- src/Avalonia.Base/Input/KeyEventArgs.cs | 2 -- src/Avalonia.Base/Input/KeyboardDevice.cs | 2 -- src/Avalonia.Base/Input/Raw/IDragDropDevice.cs | 2 +- src/Avalonia.Base/Input/TextInputEventArgs.cs | 4 +--- .../Primitives/SelectingItemsControlTests.cs | 2 -- 9 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Base/Input/IKeyboardDevice.cs b/src/Avalonia.Base/Input/IKeyboardDevice.cs index 172b58068c..dfab1cc83d 100644 --- a/src/Avalonia.Base/Input/IKeyboardDevice.cs +++ b/src/Avalonia.Base/Input/IKeyboardDevice.cs @@ -43,7 +43,7 @@ namespace Avalonia.Input PenBarrelButton = 2048 } - [NotClientImplementable] + [PrivateApi] public interface IKeyboardDevice : IInputDevice { } diff --git a/src/Avalonia.Base/Input/IMouseDevice.cs b/src/Avalonia.Base/Input/IMouseDevice.cs index 00c436bf21..4fd1ccad22 100644 --- a/src/Avalonia.Base/Input/IMouseDevice.cs +++ b/src/Avalonia.Base/Input/IMouseDevice.cs @@ -5,7 +5,7 @@ namespace Avalonia.Input /// /// Represents a mouse device. /// - [NotClientImplementable] + [PrivateApi] public interface IMouseDevice : IPointerDevice { } diff --git a/src/Avalonia.Base/Input/IPenDevice.cs b/src/Avalonia.Base/Input/IPenDevice.cs index 1cc0fcf76d..a6cb669f51 100644 --- a/src/Avalonia.Base/Input/IPenDevice.cs +++ b/src/Avalonia.Base/Input/IPenDevice.cs @@ -1,8 +1,11 @@ -namespace Avalonia.Input +using Avalonia.Metadata; + +namespace Avalonia.Input { /// /// Represents a pen/stylus device. /// + [PrivateApi] public interface IPenDevice : IPointerDevice { diff --git a/src/Avalonia.Base/Input/IPointerDevice.cs b/src/Avalonia.Base/Input/IPointerDevice.cs index e0aebda9c5..afcea9e4cb 100644 --- a/src/Avalonia.Base/Input/IPointerDevice.cs +++ b/src/Avalonia.Base/Input/IPointerDevice.cs @@ -3,7 +3,7 @@ using Avalonia.Metadata; namespace Avalonia.Input { - [NotClientImplementable] + [PrivateApi] public interface IPointerDevice : IInputDevice { /// diff --git a/src/Avalonia.Base/Input/KeyEventArgs.cs b/src/Avalonia.Base/Input/KeyEventArgs.cs index 9fa097c4b1..4de0c2d233 100644 --- a/src/Avalonia.Base/Input/KeyEventArgs.cs +++ b/src/Avalonia.Base/Input/KeyEventArgs.cs @@ -5,8 +5,6 @@ namespace Avalonia.Input { public class KeyEventArgs : RoutedEventArgs { - public IKeyboardDevice? Device { get; init; } - public Key Key { get; init; } public KeyModifiers KeyModifiers { get; init; } diff --git a/src/Avalonia.Base/Input/KeyboardDevice.cs b/src/Avalonia.Base/Input/KeyboardDevice.cs index a81bc6b2e0..c5e3ef5bf0 100644 --- a/src/Avalonia.Base/Input/KeyboardDevice.cs +++ b/src/Avalonia.Base/Input/KeyboardDevice.cs @@ -192,7 +192,6 @@ namespace Avalonia.Input KeyEventArgs ev = new KeyEventArgs { RoutedEvent = routedEvent, - Device = this, Key = keyInput.Key, KeyModifiers = keyInput.Modifiers.ToKeyModifiers(), Source = element, @@ -241,7 +240,6 @@ namespace Avalonia.Input { var ev = new TextInputEventArgs() { - Device = this, Text = text.Text, Source = element, RoutedEvent = InputElement.TextInputEvent diff --git a/src/Avalonia.Base/Input/Raw/IDragDropDevice.cs b/src/Avalonia.Base/Input/Raw/IDragDropDevice.cs index 3bcc9fadd3..5473b5a9e5 100644 --- a/src/Avalonia.Base/Input/Raw/IDragDropDevice.cs +++ b/src/Avalonia.Base/Input/Raw/IDragDropDevice.cs @@ -2,7 +2,7 @@ namespace Avalonia.Input.Raw { - [NotClientImplementable] + [PrivateApi] public interface IDragDropDevice : IInputDevice { } diff --git a/src/Avalonia.Base/Input/TextInputEventArgs.cs b/src/Avalonia.Base/Input/TextInputEventArgs.cs index cda0103749..f4acd694a0 100644 --- a/src/Avalonia.Base/Input/TextInputEventArgs.cs +++ b/src/Avalonia.Base/Input/TextInputEventArgs.cs @@ -4,8 +4,6 @@ namespace Avalonia.Input { public class TextInputEventArgs : RoutedEventArgs { - public IKeyboardDevice? Device { get; set; } - - public string? Text { get; set; } + public string? Text { get; init; } } } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index e456f25238..9cc068006b 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -2141,7 +2141,6 @@ namespace Avalonia.Controls.UnitTests.Primitives target.RaiseEvent(new TextInputEventArgs { RoutedEvent = InputElement.TextInputEvent, - Device = KeyboardDevice.Instance, Text = "Foo" }); @@ -2152,7 +2151,6 @@ namespace Avalonia.Controls.UnitTests.Primitives target.RaiseEvent(new TextInputEventArgs { RoutedEvent = InputElement.TextInputEvent, - Device = KeyboardDevice.Instance, Text = "Foo" }); From aaf13944c8ba28b6423c21e5a02ee386e6d85e50 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 30 May 2023 19:45:43 -0700 Subject: [PATCH 18/60] Update BclStorageFile.cs --- src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs index f9f02ec339..fed5328b94 100644 --- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs +++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs @@ -59,12 +59,13 @@ internal class BclStorageFile : IStorageBookmarkFile public Task OpenReadAsync() { - return Task.FromResult(FileInfo.OpenRead()); + var stream = new FileStream(FileInfo.FullName, FileMode.Create, FileAccess.Write, FileShare.None); + return Task.FromResult(stream); } public Task OpenWriteAsync() { - return Task.FromResult(FileInfo.OpenWrite()); + return Task.FromResult(File.Create(FileInfo.FullName)); } public virtual Task SaveBookmarkAsync() From 6f6ec9b79d219d2c96783cda4918ac598169aee9 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 30 May 2023 22:49:49 -0400 Subject: [PATCH 19/60] Update BclStorageFile.cs --- src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs index fed5328b94..0c455ab511 100644 --- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs +++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs @@ -59,13 +59,13 @@ internal class BclStorageFile : IStorageBookmarkFile public Task OpenReadAsync() { - var stream = new FileStream(FileInfo.FullName, FileMode.Create, FileAccess.Write, FileShare.None); - return Task.FromResult(stream); + return Task.FromResult(FileInfo.OpenRead()); } public Task OpenWriteAsync() { - return Task.FromResult(File.Create(FileInfo.FullName)); + var stream = new FileStream(FileInfo.FullName, FileMode.Create, FileAccess.Write, FileShare.Write); + return Task.FromResult(stream); } public virtual Task SaveBookmarkAsync() From 9971c6de22a0bec4b6c34f1622d1c9c6970a325c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 31 May 2023 11:16:51 +0200 Subject: [PATCH 20/60] Make some interfaces/classes a PrivateApi. Instead of internal. Required to allow people to port existing unit tests without having to rewrite using Headless. --- src/Avalonia.Base/Layout/ILayoutManager.cs | 4 ++-- src/Avalonia.Base/Layout/LayoutManager.cs | 5 +++-- src/Avalonia.Base/Rendering/IRenderRoot.cs | 4 ++-- src/Avalonia.Base/Rendering/IRenderer.cs | 6 ++++-- src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs | 4 +++- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Base/Layout/ILayoutManager.cs b/src/Avalonia.Base/Layout/ILayoutManager.cs index 063c8e99d2..5035d3a48d 100644 --- a/src/Avalonia.Base/Layout/ILayoutManager.cs +++ b/src/Avalonia.Base/Layout/ILayoutManager.cs @@ -6,8 +6,8 @@ namespace Avalonia.Layout /// /// Manages measuring and arranging of controls. /// - [NotClientImplementable] - internal interface ILayoutManager : IDisposable + [PrivateApi] + public interface ILayoutManager : IDisposable { /// /// Raised when the layout manager completes a layout pass. diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs index 6d698ae5ce..c64c2714c3 100644 --- a/src/Avalonia.Base/Layout/LayoutManager.cs +++ b/src/Avalonia.Base/Layout/LayoutManager.cs @@ -2,9 +2,9 @@ using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using Avalonia.Logging; using Avalonia.Media; +using Avalonia.Metadata; using Avalonia.Rendering; using Avalonia.Threading; using Avalonia.Utilities; @@ -16,7 +16,8 @@ namespace Avalonia.Layout /// /// Manages measuring and arranging of controls. /// - internal class LayoutManager : ILayoutManager, IDisposable + [PrivateApi] + public class LayoutManager : ILayoutManager, IDisposable { private const int MaxPasses = 10; private readonly Layoutable _owner; diff --git a/src/Avalonia.Base/Rendering/IRenderRoot.cs b/src/Avalonia.Base/Rendering/IRenderRoot.cs index 371d34b1f4..820840afbc 100644 --- a/src/Avalonia.Base/Rendering/IRenderRoot.cs +++ b/src/Avalonia.Base/Rendering/IRenderRoot.cs @@ -16,9 +16,9 @@ namespace Avalonia.Rendering /// /// Gets the renderer for the window. /// - internal IRenderer Renderer { get; } + public IRenderer Renderer { get; } - internal IHitTester HitTester { get; } + public IHitTester HitTester { get; } /// /// The scaling factor to use in rendering. diff --git a/src/Avalonia.Base/Rendering/IRenderer.cs b/src/Avalonia.Base/Rendering/IRenderer.cs index 4cb79d803d..227ff53897 100644 --- a/src/Avalonia.Base/Rendering/IRenderer.cs +++ b/src/Avalonia.Base/Rendering/IRenderer.cs @@ -9,7 +9,8 @@ namespace Avalonia.Rendering /// /// Defines the interface for a renderer. /// - internal interface IRenderer : IDisposable + [PrivateApi] + public interface IRenderer : IDisposable { /// /// Gets a value indicating whether the renderer should draw specific diagnostics. @@ -73,7 +74,8 @@ namespace Avalonia.Rendering Compositor Compositor { get; } } - internal interface IHitTester + [PrivateApi] + public interface IHitTester { /// /// Hit tests a location to find the visuals at the specified point. diff --git a/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs index fcccd9c752..552ecb14ff 100644 --- a/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs +++ b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs @@ -1,11 +1,13 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Rendering { /// /// Provides data for the event. /// - internal class SceneInvalidatedEventArgs : EventArgs + [PrivateApi] + public class SceneInvalidatedEventArgs : EventArgs { /// /// Initializes a new instance of the class. From d99697f4ae211ffbe5ddd16a5f73ac641a0251de Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 31 May 2023 12:58:21 +0100 Subject: [PATCH 21/60] fixes the overlay popup glitch where it renders at 0,0 before applying the offet. use the media context api to sync with layout and render. --- src/Avalonia.Base/Media/MediaContext.cs | 9 +++++++-- src/Avalonia.Controls/Primitives/OverlayPopupHost.cs | 8 ++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Media/MediaContext.cs b/src/Avalonia.Base/Media/MediaContext.cs index c261dd80b8..a41107360a 100644 --- a/src/Avalonia.Base/Media/MediaContext.cs +++ b/src/Avalonia.Base/Media/MediaContext.cs @@ -212,7 +212,12 @@ internal partial class MediaContext : ICompositorScheduler } while (count > 0); } - + + /// + /// Executes the callback in the layout pass that will occur + /// immediately before the next rendered frame. + /// + /// Code to execute. public void BeginInvokeOnRender(Action callback) { if (_invokeOnRenderCallbacks == null) @@ -224,4 +229,4 @@ internal partial class MediaContext : ICompositorScheduler if (!_isRendering) ScheduleRender(true); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs index 2236d87dd1..5dd0f23e8e 100644 --- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs @@ -120,11 +120,11 @@ namespace Avalonia.Controls.Primitives void IManagedPopupPositionerPopup.MoveAndResize(Point devicePoint, Size virtualSize) { _lastRequestedPosition = devicePoint; - Dispatcher.UIThread.Post(() => + MediaContext.Instance.BeginInvokeOnRender(() => { - OverlayLayer.SetLeft(this, _lastRequestedPosition.X); - OverlayLayer.SetTop(this, _lastRequestedPosition.Y); - }, DispatcherPriority.Render); + Canvas.SetLeft(this, _lastRequestedPosition.X); + Canvas.SetTop(this, _lastRequestedPosition.Y); + }); } double IManagedPopupPositionerPopup.Scaling => 1; From 2c9a1fbeb68283a58ec94ab9a6a46bbe06efa4cd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 31 May 2023 13:08:27 +0100 Subject: [PATCH 22/60] fix issue loading when changing between targetframeworks and targetframework. --- packages/Avalonia/Avalonia.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index 8f1b39ae12..a4101afac1 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -5,7 +5,7 @@ - + all From 864ef9b3d9c48dc3df6f2f650c87f4f2740faa0b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 31 May 2023 13:20:50 +0100 Subject: [PATCH 23/60] improve comment. --- src/Avalonia.Base/Media/MediaContext.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Media/MediaContext.cs b/src/Avalonia.Base/Media/MediaContext.cs index a41107360a..05872a3e50 100644 --- a/src/Avalonia.Base/Media/MediaContext.cs +++ b/src/Avalonia.Base/Media/MediaContext.cs @@ -214,8 +214,8 @@ internal partial class MediaContext : ICompositorScheduler } /// - /// Executes the callback in the layout pass that will occur - /// immediately before the next rendered frame. + /// Executes the callback in the next iteration of the current UI-thread + /// render loop / layout pass that. /// /// Code to execute. public void BeginInvokeOnRender(Action callback) From 42fccd1af633a8c840e7aaeea13e62c7c1f3b6ec Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 25 May 2023 11:58:11 +0000 Subject: [PATCH 24/60] track currently active gesture in the pointer --- .../GestureRecognizerCollection.cs | 6 +++++ src/Avalonia.Base/Input/IPointer.cs | 1 + src/Avalonia.Base/Input/Pointer.cs | 25 ++++++++++++++++++- .../PullToRefresh/RefreshVisualizer.cs | 3 --- .../DefaultMenuInteractionHandlerTests.cs | 1 + 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs index 3b9b2d0de6..8101e79af9 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs @@ -114,7 +114,13 @@ namespace Avalonia.Input.GestureRecognizers void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer) { + var p = pointer as Pointer; + if (p != null && p.CapturedGestureRecognizer != null && recognizer != p.CapturedGestureRecognizer) + return; + pointer.Capture(_inputElement); + p?.CaptureGestureRecognizer(recognizer); + _pointerGrabs![pointer] = recognizer; foreach (var r in _recognizers!) { diff --git a/src/Avalonia.Base/Input/IPointer.cs b/src/Avalonia.Base/Input/IPointer.cs index 52605bb6ae..050adbabaa 100644 --- a/src/Avalonia.Base/Input/IPointer.cs +++ b/src/Avalonia.Base/Input/IPointer.cs @@ -1,3 +1,4 @@ +using Avalonia.Input.GestureRecognizers; using Avalonia.Metadata; namespace Avalonia.Input diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs index 4713364f00..a600624276 100644 --- a/src/Avalonia.Base/Input/Pointer.cs +++ b/src/Avalonia.Base/Input/Pointer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Avalonia.Input.GestureRecognizers; using Avalonia.VisualTree; namespace Avalonia.Input @@ -52,6 +53,9 @@ namespace Avalonia.Input if (Captured is Visual v3) v3.DetachedFromVisualTree += OnCaptureDetached; + + if (Captured == null) + CaptureGestureRecognizer(null); } static IInputElement? GetNextCapture(Visual parent) @@ -69,6 +73,25 @@ namespace Avalonia.Input public PointerType Type { get; } public bool IsPrimary { get; } - public void Dispose() => Capture(null); + + /// + /// Gets the gesture recognizer that is currently capturing by the pointer, if any. + /// + internal IGestureRecognizer? CapturedGestureRecognizer { get; private set; } + + public void Dispose() + { + Capture(null); + } + + /// + /// Captures pointer input to the specified gesture recognizer. + /// + /// The gesture recognizer. + /// + internal void CaptureGestureRecognizer(IGestureRecognizer? gestureRecognizer) + { + CapturedGestureRecognizer = gestureRecognizer; + } } } diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs index 5c7071a161..39ff8e3a92 100644 --- a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs +++ b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs @@ -238,7 +238,6 @@ namespace Avalonia.Controls visualizerVisual.Offset = IsPullDirectionVertical ? new Vector3(visualizerVisual.Offset.X, 0, 0) : new Vector3(0, visualizerVisual.Offset.Y, 0); - visual.Offset = default; _content.InvalidateMeasure(); break; case RefreshVisualizerState.Interacting: @@ -452,8 +451,6 @@ namespace Avalonia.Controls _interactionRatioSubscription = RefreshInfoProvider.GetObservable(RefreshInfoProvider.InteractionRatioProperty) .Subscribe(InteractionRatioObserver); - var visual = RefreshInfoProvider.Visual; - _executingRatio = RefreshInfoProvider.ExecutionRatio; } else diff --git a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs index 506a62525c..3259ec1a4c 100644 --- a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs @@ -2,6 +2,7 @@ using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Input; +using Avalonia.Input.GestureRecognizers; using Avalonia.Interactivity; using Avalonia.VisualTree; using Moq; From 250959acc863124dd978729d1fb55b141932ac0d Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 25 May 2023 15:15:39 +0000 Subject: [PATCH 25/60] notify current gesture recognizer that it's lost capture --- src/Avalonia.Base/Input/Pointer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs index a600624276..30c32ae48c 100644 --- a/src/Avalonia.Base/Input/Pointer.cs +++ b/src/Avalonia.Base/Input/Pointer.cs @@ -91,6 +91,9 @@ namespace Avalonia.Input /// internal void CaptureGestureRecognizer(IGestureRecognizer? gestureRecognizer) { + if (CapturedGestureRecognizer != gestureRecognizer) + CapturedGestureRecognizer?.PointerCaptureLost(this); + CapturedGestureRecognizer = gestureRecognizer; } } From 8c0dfbaf08f84e1c810d1870aca47ae6e387b298 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 26 May 2023 11:32:57 +0000 Subject: [PATCH 26/60] rework gesture routing #11522 --- .../GestureRecognizerCollection.cs | 50 +++++-------------- .../GestureRecognizers/IGestureRecognizer.cs | 1 + .../PinchGestureRecognizer.cs | 4 +- .../PullGestureRecognizer.cs | 6 ++- .../ScrollGestureRecognizer.cs | 6 ++- src/Avalonia.Base/Input/InputElement.cs | 46 ++++++++++++----- src/Avalonia.Base/Input/MouseDevice.cs | 36 ++++++++++++- src/Avalonia.Base/Input/PenDevice.cs | 35 ++++++++++++- src/Avalonia.Base/Input/Pointer.cs | 5 +- src/Avalonia.Base/Input/PointerEventArgs.cs | 10 ++++ src/Avalonia.Base/Input/TouchDevice.cs | 35 ++++++++++--- src/Avalonia.Controls/Primitives/Thumb.cs | 2 + 12 files changed, 171 insertions(+), 65 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs index 8101e79af9..8ec9cb7301 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs @@ -10,8 +10,6 @@ namespace Avalonia.Input.GestureRecognizers { private readonly IInputElement _inputElement; private List? _recognizers; - private Dictionary? _pointerGrabs; - public GestureRecognizerCollection(IInputElement inputElement) { @@ -24,7 +22,6 @@ namespace Avalonia.Input.GestureRecognizers { // We initialize the collection when the first recognizer is added _recognizers = new List(); - _pointerGrabs = new Dictionary(); } _recognizers.Add(recognizer); @@ -57,8 +54,6 @@ namespace Avalonia.Input.GestureRecognizers return false; foreach (var r in _recognizers) { - if (e.Handled) - break; r.PointerPressed(e); } @@ -69,17 +64,15 @@ namespace Avalonia.Input.GestureRecognizers { if (_recognizers == null) return false; - if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture)) + var pointer = e.Pointer as Pointer; + + foreach (var r in _recognizers) { - capture.PointerReleased(e); + if (pointer?.CapturedGestureRecognizer != null) + break; + + r.PointerReleased(e); } - else - foreach (var r in _recognizers) - { - if (e.Handled) - break; - r.PointerReleased(e); - } return e.Handled; } @@ -87,41 +80,24 @@ namespace Avalonia.Input.GestureRecognizers { if (_recognizers == null) return false; - if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture)) - { - capture.PointerMoved(e); - } - else - foreach (var r in _recognizers) - { - if (e.Handled) - break; - r.PointerMoved(e); - } - return e.Handled; - } + var pointer = e.Pointer as Pointer; - internal void HandlePointerCaptureLost(PointerCaptureLostEventArgs e) - { - if (_recognizers == null) - return; - _pointerGrabs!.Remove(e.Pointer); foreach (var r in _recognizers) { - r.PointerCaptureLost(e.Pointer); + if (pointer?.CapturedGestureRecognizer != null) + break; + + r.PointerMoved(e); } + return e.Handled; } void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer) { var p = pointer as Pointer; - if (p != null && p.CapturedGestureRecognizer != null && recognizer != p.CapturedGestureRecognizer) - return; - pointer.Capture(_inputElement); p?.CaptureGestureRecognizer(recognizer); - _pointerGrabs![pointer] = recognizer; foreach (var r in _recognizers!) { if (r != recognizer) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs index c1d9ae5304..51c36a93de 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs @@ -2,6 +2,7 @@ namespace Avalonia.Input.GestureRecognizers { public interface IGestureRecognizer { + IInputElement? Target { get; } void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions); void PointerPressed(PointerPressedEventArgs e); void PointerReleased(PointerReleasedEventArgs e); diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs index 3b83d0cb87..4390167e98 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -2,7 +2,7 @@ namespace Avalonia.Input { - public class PinchGestureRecognizer : StyledElement, IGestureRecognizer + public class PinchGestureRecognizer : AvaloniaObject, IGestureRecognizer { private IInputElement? _target; private IGestureRecognizerActionsDispatcher? _actions; @@ -13,6 +13,8 @@ namespace Avalonia.Input private Point _secondPoint; private Point _origin; + public IInputElement? Target => _target; + public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) { _target = target; diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs index 6784677520..7391cdbaca 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs @@ -1,10 +1,9 @@ using System; -using System.Diagnostics; using Avalonia.Input.GestureRecognizers; namespace Avalonia.Input { - public class PullGestureRecognizer : StyledElement, IGestureRecognizer + public class PullGestureRecognizer : AvaloniaObject, IGestureRecognizer { internal static int MinPullDetectionSize = 50; @@ -27,6 +26,8 @@ namespace Avalonia.Input set => SetValue(PullDirectionProperty, value); } + public IInputElement? Target => _target; + public PullGestureRecognizer(PullDirection pullDirection) { PullDirection = pullDirection; @@ -54,6 +55,7 @@ namespace Avalonia.Input { var currentPosition = e.GetPosition(visual); _actions!.Capture(e.Pointer, this); + e.PreventGestureRecognition(); Vector delta = default; switch (PullDirection) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index b510d44e63..3206c3903a 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -91,7 +91,9 @@ namespace Avalonia.Input.GestureRecognizers { get => _scrollStartDistance; set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value); - } + } + + public IInputElement? Target => _target; public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) { @@ -132,6 +134,8 @@ namespace Avalonia.Input.GestureRecognizers _trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? ScrollStartDistance : -ScrollStartDistance)); _actions!.Capture(e.Pointer, this); + + e.PreventGestureRecognition(); } } diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index 68131e5bf7..46f543d25b 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -225,6 +225,11 @@ namespace Avalonia.Input PointerReleasedEvent.AddClassHandler((x, e) => x.OnPointerReleased(e)); PointerCaptureLostEvent.AddClassHandler((x, e) => x.OnPointerCaptureLost(e)); PointerWheelChangedEvent.AddClassHandler((x, e) => x.OnPointerWheelChanged(e)); + + // Gesture only handlers + PointerMovedEvent.AddClassHandler((x, e) => x.OnGesturePointerMoved(e), handledEventsToo: true); + PointerPressedEvent.AddClassHandler((x, e) => x.OnGesturePointerPressed(e), handledEventsToo: true); + PointerReleasedEvent.AddClassHandler((x, e) => x.OnGesturePointerReleased(e), handledEventsToo: true); } public InputElement() @@ -583,10 +588,6 @@ namespace Avalonia.Input /// The event args. protected virtual void OnPointerMoved(PointerEventArgs e) { - if (_gestureRecognizers?.HandlePointerMoved(e) == true) - { - e.Handled = true; - } } /// @@ -595,10 +596,6 @@ namespace Avalonia.Input /// The event args. protected virtual void OnPointerPressed(PointerPressedEventArgs e) { - if (_gestureRecognizers?.HandlePointerPressed(e) == true) - { - e.Handled = true; - } } /// @@ -607,10 +604,33 @@ namespace Avalonia.Input /// The event args. protected virtual void OnPointerReleased(PointerReleasedEventArgs e) { - if (_gestureRecognizers?.HandlePointerReleased(e) == true) - { - e.Handled = true; - } + } + + private void OnGesturePointerReleased(PointerReleasedEventArgs e) + { + if (!e.IsGestureRecognitionSkipped) + if (_gestureRecognizers?.HandlePointerReleased(e) == true) + { + e.Handled = true; + } + } + + private void OnGesturePointerPressed(PointerPressedEventArgs e) + { + if (!e.IsGestureRecognitionSkipped) + if (_gestureRecognizers?.HandlePointerPressed(e) == true) + { + e.Handled = true; + } + } + + private void OnGesturePointerMoved(PointerEventArgs e) + { + if (!e.IsGestureRecognitionSkipped) + if (_gestureRecognizers?.HandlePointerMoved(e) == true) + { + e.Handled = true; + } } /// @@ -619,7 +639,7 @@ namespace Avalonia.Input /// The event args. protected virtual void OnPointerCaptureLost(PointerCaptureLostEventArgs e) { - _gestureRecognizers?.HandlePointerCaptureLost(e); + } /// diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs index 69c7a83900..bb8313106f 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/src/Avalonia.Base/Input/MouseDevice.cs @@ -163,7 +163,22 @@ namespace Avalonia.Input device = device ?? throw new ArgumentNullException(nameof(device)); root = root ?? throw new ArgumentNullException(nameof(root)); - var source = _pointer.Captured ?? hitTest; + IInputElement source; + if (_pointer.CapturedGestureRecognizer is { } gestureRecognizer) + { + source = gestureRecognizer.Target ?? hitTest; + + if(source != null) + { + var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)root, + p, timestamp, properties, inputModifiers, intermediatePoints); + gestureRecognizer.PointerMoved(e); + + return e.Handled; + } + } + + source = _pointer.Captured ?? hitTest; if (source is object) { @@ -174,6 +189,7 @@ namespace Avalonia.Input return e.Handled; } + return false; } @@ -183,7 +199,22 @@ namespace Avalonia.Input device = device ?? throw new ArgumentNullException(nameof(device)); root = root ?? throw new ArgumentNullException(nameof(root)); - var source = _pointer.Captured ?? hitTest; + IInputElement source; + if (_pointer.CapturedGestureRecognizer is { } gestureRecognizer) + { + source = gestureRecognizer.Target ?? hitTest; + + if (source != null) + { + var e = new PointerReleasedEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers, + _lastMouseDownButton); + gestureRecognizer.PointerReleased(e); + + return e.Handled; + } + } + + source = _pointer.Captured ?? hitTest; if (source is not null) { @@ -192,6 +223,7 @@ namespace Avalonia.Input source?.RaiseEvent(e); _pointer.Capture(null); + _pointer.CaptureGestureRecognizer(null); _lastMouseDownButton = default; return e.Handled; } diff --git a/src/Avalonia.Base/Input/PenDevice.cs b/src/Avalonia.Base/Input/PenDevice.cs index d5ccfdb18d..fcf7d31e03 100644 --- a/src/Avalonia.Base/Input/PenDevice.cs +++ b/src/Avalonia.Base/Input/PenDevice.cs @@ -114,7 +114,22 @@ namespace Avalonia.Input KeyModifiers inputModifiers, IInputElement? hitTest, Lazy?>? intermediatePoints) { - var source = pointer.Captured ?? hitTest; + IInputElement source; + if (pointer.CapturedGestureRecognizer is { } gestureRecognizer) + { + source = gestureRecognizer.Target ?? hitTest; + + if (source != null) + { + var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, (Visual)root, + p, timestamp, properties, inputModifiers, intermediatePoints); + gestureRecognizer.PointerMoved(e); + + return e.Handled; + } + } + + source = pointer.Captured ?? hitTest; if (source is not null) { @@ -132,7 +147,22 @@ namespace Avalonia.Input IInputElement root, Point p, PointerPointProperties properties, KeyModifiers inputModifiers, IInputElement? hitTest) { - var source = pointer.Captured ?? hitTest; + IInputElement source; + if (pointer.CapturedGestureRecognizer is { } gestureRecognizer) + { + source = gestureRecognizer.Target ?? hitTest; + + if (source != null) + { + var e = new PointerReleasedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, + _lastMouseDownButton); + gestureRecognizer.PointerReleased(e); + + return e.Handled; + } + } + + source = pointer.Captured ?? hitTest; if (source is not null) { @@ -141,6 +171,7 @@ namespace Avalonia.Input source.RaiseEvent(e); pointer.Capture(null); + pointer.CaptureGestureRecognizer(null); _lastMouseDownButton = default; return e.Handled; } diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs index 30c32ae48c..3abda3637e 100644 --- a/src/Avalonia.Base/Input/Pointer.cs +++ b/src/Avalonia.Base/Input/Pointer.cs @@ -54,7 +54,7 @@ namespace Avalonia.Input if (Captured is Visual v3) v3.DetachedFromVisualTree += OnCaptureDetached; - if (Captured == null) + if (Captured != null) CaptureGestureRecognizer(null); } @@ -94,6 +94,9 @@ namespace Avalonia.Input if (CapturedGestureRecognizer != gestureRecognizer) CapturedGestureRecognizer?.PointerCaptureLost(this); + if (gestureRecognizer != null) + Capture(null); + CapturedGestureRecognizer = gestureRecognizer; } } diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs index 9c24e5c314..cd742cc4e4 100644 --- a/src/Avalonia.Base/Input/PointerEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerEventArgs.cs @@ -58,6 +58,8 @@ namespace Avalonia.Input /// public ulong Timestamp { get; } + internal bool IsGestureRecognitionSkipped { get; private set; } + /// /// Gets a value that indicates which key modifiers were active at the time that the pointer event was initiated. /// @@ -121,6 +123,14 @@ namespace Avalonia.Input return points; } + /// + /// Prevents this event from being handled by other gesture recognizers in the route + /// + public void PreventGestureRecognition() + { + IsGestureRecognitionSkipped = true; + } + /// /// Returns the current pointer point properties /// diff --git a/src/Avalonia.Base/Input/TouchDevice.cs b/src/Avalonia.Base/Input/TouchDevice.cs index 74c5837b84..c5b372defa 100644 --- a/src/Avalonia.Base/Input/TouchDevice.cs +++ b/src/Avalonia.Base/Input/TouchDevice.cs @@ -52,6 +52,7 @@ namespace Avalonia.Input } var target = pointer.Captured ?? args.Root; + var gestureTarget = pointer.CapturedGestureRecognizer?.Target; var updateKind = args.Type.ToUpdateKind(); var keyModifier = args.InputModifiers.ToKeyModifiers(); @@ -95,10 +96,19 @@ namespace Avalonia.Input _pointers.Remove(args.RawPointerId); using (pointer) { - target.RaiseEvent(new PointerReleasedEventArgs(target, pointer, - (Visual)args.Root, args.Position, ev.Timestamp, - new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind), - keyModifier, MouseButton.Left)); + target = gestureTarget ?? target; + var e = new PointerReleasedEventArgs(target, pointer, + (Visual)args.Root, args.Position, ev.Timestamp, + new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind), + keyModifier, MouseButton.Left); + if (gestureTarget != null) + { + pointer?.CapturedGestureRecognizer?.PointerReleased(e); + } + else + { + target.RaiseEvent(e); + } } } @@ -106,15 +116,28 @@ namespace Avalonia.Input { _pointers.Remove(args.RawPointerId); using (pointer) + { pointer.Capture(null); + pointer.CaptureGestureRecognizer(null); + } } if (args.Type == RawPointerEventType.TouchUpdate) { - target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, (Visual)args.Root, + target = gestureTarget ?? target; + var e = new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, (Visual)args.Root, args.Position, ev.Timestamp, new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind), - keyModifier, args.IntermediatePoints)); + keyModifier, args.IntermediatePoints); + + if (gestureTarget != null) + { + pointer?.CapturedGestureRecognizer?.PointerMoved(e); + } + else + { + target.RaiseEvent(e); + } } } diff --git a/src/Avalonia.Controls/Primitives/Thumb.cs b/src/Avalonia.Controls/Primitives/Thumb.cs index c5d73dc00f..7299300cc7 100644 --- a/src/Avalonia.Controls/Primitives/Thumb.cs +++ b/src/Avalonia.Controls/Primitives/Thumb.cs @@ -113,6 +113,8 @@ namespace Avalonia.Controls.Primitives PseudoClasses.Add(":pressed"); + e.PreventGestureRecognition(); + RaiseEvent(ev); } From 5ad9e4ea4ed478875ff46d4669b29800018a15da Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 26 May 2023 12:16:17 +0000 Subject: [PATCH 27/60] fix tests --- src/Avalonia.Base/Input/MouseDevice.cs | 10 +++++----- src/Avalonia.Base/Input/PenDevice.cs | 9 +++++---- src/Avalonia.Base/Input/TouchDevice.cs | 6 +++--- tests/Avalonia.UnitTests/TouchTestHelper.cs | 20 ++++++++++++++++---- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs index bb8313106f..328d5e945f 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/src/Avalonia.Base/Input/MouseDevice.cs @@ -7,7 +7,7 @@ using Avalonia.Metadata; using Avalonia.Platform; using Avalonia.Utilities; using Avalonia.VisualTree; - +using Avalonia.Input.GestureRecognizers; #pragma warning disable CS0618 namespace Avalonia.Input @@ -163,8 +163,8 @@ namespace Avalonia.Input device = device ?? throw new ArgumentNullException(nameof(device)); root = root ?? throw new ArgumentNullException(nameof(root)); - IInputElement source; - if (_pointer.CapturedGestureRecognizer is { } gestureRecognizer) + IInputElement? source; + if (_pointer.CapturedGestureRecognizer is IGestureRecognizer gestureRecognizer) { source = gestureRecognizer.Target ?? hitTest; @@ -199,8 +199,8 @@ namespace Avalonia.Input device = device ?? throw new ArgumentNullException(nameof(device)); root = root ?? throw new ArgumentNullException(nameof(root)); - IInputElement source; - if (_pointer.CapturedGestureRecognizer is { } gestureRecognizer) + IInputElement? source; + if (_pointer.CapturedGestureRecognizer is IGestureRecognizer gestureRecognizer) { source = gestureRecognizer.Target ?? hitTest; diff --git a/src/Avalonia.Base/Input/PenDevice.cs b/src/Avalonia.Base/Input/PenDevice.cs index fcf7d31e03..a183dbbaf8 100644 --- a/src/Avalonia.Base/Input/PenDevice.cs +++ b/src/Avalonia.Base/Input/PenDevice.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using Avalonia.Input.GestureRecognizers; using Avalonia.Input.Raw; using Avalonia.Interactivity; using Avalonia.Metadata; @@ -114,8 +115,8 @@ namespace Avalonia.Input KeyModifiers inputModifiers, IInputElement? hitTest, Lazy?>? intermediatePoints) { - IInputElement source; - if (pointer.CapturedGestureRecognizer is { } gestureRecognizer) + IInputElement? source; + if (pointer.CapturedGestureRecognizer is IGestureRecognizer gestureRecognizer) { source = gestureRecognizer.Target ?? hitTest; @@ -147,8 +148,8 @@ namespace Avalonia.Input IInputElement root, Point p, PointerPointProperties properties, KeyModifiers inputModifiers, IInputElement? hitTest) { - IInputElement source; - if (pointer.CapturedGestureRecognizer is { } gestureRecognizer) + IInputElement? source; + if (pointer.CapturedGestureRecognizer is IGestureRecognizer gestureRecognizer) { source = gestureRecognizer.Target ?? hitTest; diff --git a/src/Avalonia.Base/Input/TouchDevice.cs b/src/Avalonia.Base/Input/TouchDevice.cs index c5b372defa..02f3479e95 100644 --- a/src/Avalonia.Base/Input/TouchDevice.cs +++ b/src/Avalonia.Base/Input/TouchDevice.cs @@ -117,15 +117,15 @@ namespace Avalonia.Input _pointers.Remove(args.RawPointerId); using (pointer) { - pointer.Capture(null); - pointer.CaptureGestureRecognizer(null); + pointer?.Capture(null); + pointer?.CaptureGestureRecognizer(null); } } if (args.Type == RawPointerEventType.TouchUpdate) { target = gestureTarget ?? target; - var e = new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, (Visual)args.Root, + var e = new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer!, (Visual)args.Root, args.Position, ev.Timestamp, new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind), keyModifier, args.IntermediatePoints); diff --git a/tests/Avalonia.UnitTests/TouchTestHelper.cs b/tests/Avalonia.UnitTests/TouchTestHelper.cs index db70f570a2..703cb45b96 100644 --- a/tests/Avalonia.UnitTests/TouchTestHelper.cs +++ b/tests/Avalonia.UnitTests/TouchTestHelper.cs @@ -27,8 +27,13 @@ namespace Avalonia.UnitTests public void Move(Interactive target, Interactive source, in Point position, KeyModifiers modifiers = default) { - target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)target, position, - Timestamp(), PointerPointProperties.None, modifiers)); + var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)target, position, + Timestamp(), PointerPointProperties.None, modifiers); + if (_pointer.CapturedGestureRecognizer != null) + _pointer.CapturedGestureRecognizer.PointerMoved(e); + else + target.RaiseEvent(e); + } public void Up(Interactive target, Point position = default, KeyModifiers modifiers = default) @@ -36,9 +41,16 @@ namespace Avalonia.UnitTests public void Up(Interactive target, Interactive source, Point position = default, KeyModifiers modifiers = default) { - source.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (Visual)target, position, Timestamp(), PointerPointProperties.None, - modifiers, MouseButton.None)); + var e = new PointerReleasedEventArgs(source, _pointer, (Visual)target, position, Timestamp(), PointerPointProperties.None, + modifiers, MouseButton.None); + + if (_pointer.CapturedGestureRecognizer != null) + _pointer.CapturedGestureRecognizer.PointerReleased(e); + else + source.RaiseEvent(e); + _pointer.Capture(null); + _pointer.CaptureGestureRecognizer(null); } public void Tap(Interactive target, Point position = default, KeyModifiers modifiers = default) From 0042c19121f185a0895f1e3f6572b2706b72fca5 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 29 May 2023 15:48:48 +0000 Subject: [PATCH 28/60] replace IGestureRecognizer with abstrac GestureRecognizer. remove related obsolete interfaces. --- .../GestureRecognizers/GestureRecognizer.cs | 23 +++++++++++++++ .../GestureRecognizerCollection.cs | 29 +++++-------------- .../GestureRecognizers/IGestureRecognizer.cs | 24 --------------- .../PinchGestureRecognizer.cs | 20 ++++++------- .../PullGestureRecognizer.cs | 18 +++++------- .../ScrollGestureRecognizer.cs | 20 ++++++------- src/Avalonia.Base/Input/MouseDevice.cs | 24 ++++++--------- src/Avalonia.Base/Input/PenDevice.cs | 22 +++++--------- src/Avalonia.Base/Input/Pointer.cs | 4 +-- 9 files changed, 75 insertions(+), 109 deletions(-) create mode 100644 src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs delete mode 100644 src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs new file mode 100644 index 0000000000..fd6748dee7 --- /dev/null +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Input.GestureRecognizers +{ + public abstract class GestureRecognizer : StyledElement + { + public abstract IInputElement? Target { get; } + public abstract void Initialize(IInputElement target); + public abstract void PointerPressed(PointerPressedEventArgs e); + public abstract void PointerReleased(PointerReleasedEventArgs e); + public abstract void PointerMoved(PointerEventArgs e); + public abstract void PointerCaptureLost(IPointer pointer); + + protected void Capture(IPointer pointer) + { + (pointer as Pointer)?.CaptureGestureRecognizer(this); + } + } +} diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs index 8ec9cb7301..e7c6513c43 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs @@ -6,26 +6,26 @@ using Avalonia.Reactive; namespace Avalonia.Input.GestureRecognizers { - public class GestureRecognizerCollection : IReadOnlyCollection, IGestureRecognizerActionsDispatcher + public class GestureRecognizerCollection : IReadOnlyCollection { private readonly IInputElement _inputElement; - private List? _recognizers; + private List? _recognizers; public GestureRecognizerCollection(IInputElement inputElement) { _inputElement = inputElement; } - public void Add(IGestureRecognizer recognizer) + public void Add(GestureRecognizer recognizer) { if (_recognizers == null) { // We initialize the collection when the first recognizer is added - _recognizers = new List(); + _recognizers = new List(); } _recognizers.Add(recognizer); - recognizer.Initialize(_inputElement, this); + recognizer.Initialize(_inputElement); // Hacks to make bindings work @@ -38,16 +38,15 @@ namespace Avalonia.Input.GestureRecognizers } } - static readonly List s_Empty = new List(); + static readonly List s_Empty = new List(); - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() => _recognizers?.GetEnumerator() ?? s_Empty.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public int Count => _recognizers?.Count ?? 0; - internal bool HandlePointerPressed(PointerPressedEventArgs e) { if (_recognizers == null) @@ -91,19 +90,5 @@ namespace Avalonia.Input.GestureRecognizers } return e.Handled; } - - void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer) - { - var p = pointer as Pointer; - - p?.CaptureGestureRecognizer(recognizer); - - foreach (var r in _recognizers!) - { - if (r != recognizer) - r.PointerCaptureLost(pointer); - } - } - } } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs deleted file mode 100644 index 51c36a93de..0000000000 --- a/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Avalonia.Input.GestureRecognizers -{ - public interface IGestureRecognizer - { - IInputElement? Target { get; } - void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions); - void PointerPressed(PointerPressedEventArgs e); - void PointerReleased(PointerReleasedEventArgs e); - void PointerMoved(PointerEventArgs e); - void PointerCaptureLost(IPointer pointer); - } - - public interface IGestureRecognizerActionsDispatcher - { - void Capture(IPointer pointer, IGestureRecognizer recognizer); - } - - public enum GestureRecognizerResult - { - None, - Capture, - ReleaseCapture - } -} diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs index 4390167e98..133fd76947 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -2,10 +2,9 @@ namespace Avalonia.Input { - public class PinchGestureRecognizer : AvaloniaObject, IGestureRecognizer + public class PinchGestureRecognizer : GestureRecognizer { private IInputElement? _target; - private IGestureRecognizerActionsDispatcher? _actions; private float _initialDistance; private IPointer? _firstContact; private Point _firstPoint; @@ -13,12 +12,11 @@ namespace Avalonia.Input private Point _secondPoint; private Point _origin; - public IInputElement? Target => _target; + public override IInputElement? Target => _target; - public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) + public override void Initialize(IInputElement target) { _target = target; - _actions = actions; } private void OnPointerPressed(object? sender, PointerPressedEventArgs e) @@ -31,12 +29,12 @@ namespace Avalonia.Input PointerReleased(e); } - public void PointerCaptureLost(IPointer pointer) + public override void PointerCaptureLost(IPointer pointer) { RemoveContact(pointer); } - public void PointerMoved(PointerEventArgs e) + public override void PointerMoved(PointerEventArgs e) { if (_target != null && _target is Visual visual) { @@ -67,7 +65,7 @@ namespace Avalonia.Input } } - public void PointerPressed(PointerPressedEventArgs e) + public override void PointerPressed(PointerPressedEventArgs e) { if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) { @@ -94,13 +92,13 @@ namespace Avalonia.Input _origin = new Point((_firstPoint.X + _secondPoint.X) / 2.0f, (_firstPoint.Y + _secondPoint.Y) / 2.0f); - _actions!.Capture(_firstContact, this); - _actions!.Capture(_secondContact, this); + Capture(_firstContact); + Capture(_secondContact); } } } - public void PointerReleased(PointerReleasedEventArgs e) + public override void PointerReleased(PointerReleasedEventArgs e) { RemoveContact(e.Pointer); } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs index 7391cdbaca..f6ab1c4508 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs @@ -3,12 +3,11 @@ using Avalonia.Input.GestureRecognizers; namespace Avalonia.Input { - public class PullGestureRecognizer : AvaloniaObject, IGestureRecognizer + public class PullGestureRecognizer : GestureRecognizer { internal static int MinPullDetectionSize = 50; private IInputElement? _target; - private IGestureRecognizerActionsDispatcher? _actions; private Point _initialPosition; private int _gestureId; private IPointer? _tracking; @@ -26,7 +25,7 @@ namespace Avalonia.Input set => SetValue(PullDirectionProperty, value); } - public IInputElement? Target => _target; + public override IInputElement? Target => _target; public PullGestureRecognizer(PullDirection pullDirection) { @@ -35,13 +34,12 @@ namespace Avalonia.Input public PullGestureRecognizer() { } - public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) + public override void Initialize(IInputElement target) { _target = target; - _actions = actions; } - public void PointerCaptureLost(IPointer pointer) + public override void PointerCaptureLost(IPointer pointer) { if (_tracking == pointer) { @@ -49,12 +47,12 @@ namespace Avalonia.Input } } - public void PointerMoved(PointerEventArgs e) + public override void PointerMoved(PointerEventArgs e) { if (_tracking == e.Pointer && _target is Visual visual) { var currentPosition = e.GetPosition(visual); - _actions!.Capture(e.Pointer, this); + Capture(e.Pointer); e.PreventGestureRecognition(); Vector delta = default; @@ -94,7 +92,7 @@ namespace Avalonia.Input } } - public void PointerPressed(PointerPressedEventArgs e) + public override void PointerPressed(PointerPressedEventArgs e) { if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) { @@ -129,7 +127,7 @@ namespace Avalonia.Input } } - public void PointerReleased(PointerReleasedEventArgs e) + public override void PointerReleased(PointerReleasedEventArgs e) { if (_tracking == e.Pointer && _pullInProgress) { diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 3206c3903a..ecc72c8f60 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -4,7 +4,7 @@ using Avalonia.Threading; namespace Avalonia.Input.GestureRecognizers { - public class ScrollGestureRecognizer : AvaloniaObject, IGestureRecognizer + public class ScrollGestureRecognizer : GestureRecognizer { // Pixels per second speed that is considered to be the stop of inertial scroll internal const double InertialScrollSpeedEnd = 5; @@ -19,7 +19,6 @@ namespace Avalonia.Input.GestureRecognizers private Point _trackedRootPoint; private IPointer? _tracking; private IInputElement? _target; - private IGestureRecognizerActionsDispatcher? _actions; private int _gestureId; private Point _pointerPressedPoint; private VelocityTracker? _velocityTracker; @@ -93,15 +92,14 @@ namespace Avalonia.Input.GestureRecognizers set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value); } - public IInputElement? Target => _target; + public override IInputElement? Target => _target; - public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) + public override void Initialize(IInputElement target) { _target = target; - _actions = actions; } - public void PointerPressed(PointerPressedEventArgs e) + public override void PointerPressed(PointerPressedEventArgs e) { if (e.Pointer.IsPrimary && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) @@ -112,8 +110,8 @@ namespace Avalonia.Input.GestureRecognizers _trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)_target); } } - - public void PointerMoved(PointerEventArgs e) + + public override void PointerMoved(PointerEventArgs e) { if (e.Pointer == _tracking) { @@ -133,7 +131,7 @@ namespace Avalonia.Input.GestureRecognizers _trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? ScrollStartDistance : -ScrollStartDistance), _trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? ScrollStartDistance : -ScrollStartDistance)); - _actions!.Capture(e.Pointer, this); + Capture(e.Pointer); e.PreventGestureRecognition(); } @@ -153,7 +151,7 @@ namespace Avalonia.Input.GestureRecognizers } } - public void PointerCaptureLost(IPointer pointer) + public override void PointerCaptureLost(IPointer pointer) { if (pointer == _tracking) EndGesture(); } @@ -173,7 +171,7 @@ namespace Avalonia.Input.GestureRecognizers } - public void PointerReleased(PointerReleasedEventArgs e) + public override void PointerReleased(PointerReleasedEventArgs e) { if (e.Pointer == _tracking && _scrolling) { diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs index 328d5e945f..4da8062897 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/src/Avalonia.Base/Input/MouseDevice.cs @@ -163,14 +163,11 @@ namespace Avalonia.Input device = device ?? throw new ArgumentNullException(nameof(device)); root = root ?? throw new ArgumentNullException(nameof(root)); - IInputElement? source; - if (_pointer.CapturedGestureRecognizer is IGestureRecognizer gestureRecognizer) + if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) { - source = gestureRecognizer.Target ?? hitTest; - - if(source != null) + if(gestureRecognizer.Target != null) { - var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)root, + var e = new PointerEventArgs(InputElement.PointerMovedEvent, gestureRecognizer.Target, _pointer, (Visual)root, p, timestamp, properties, inputModifiers, intermediatePoints); gestureRecognizer.PointerMoved(e); @@ -178,7 +175,7 @@ namespace Avalonia.Input } } - source = _pointer.Captured ?? hitTest; + var source = _pointer.Captured ?? hitTest; if (source is object) { @@ -198,15 +195,12 @@ namespace Avalonia.Input { device = device ?? throw new ArgumentNullException(nameof(device)); root = root ?? throw new ArgumentNullException(nameof(root)); - - IInputElement? source; - if (_pointer.CapturedGestureRecognizer is IGestureRecognizer gestureRecognizer) + + if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) { - source = gestureRecognizer.Target ?? hitTest; - - if (source != null) + if (gestureRecognizer.Target != null) { - var e = new PointerReleasedEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers, + var e = new PointerReleasedEventArgs(gestureRecognizer.Target, _pointer, (Visual)root, p, timestamp, props, inputModifiers, _lastMouseDownButton); gestureRecognizer.PointerReleased(e); @@ -214,7 +208,7 @@ namespace Avalonia.Input } } - source = _pointer.Captured ?? hitTest; + var source = _pointer.Captured ?? hitTest; if (source is not null) { diff --git a/src/Avalonia.Base/Input/PenDevice.cs b/src/Avalonia.Base/Input/PenDevice.cs index a183dbbaf8..fab0f1320c 100644 --- a/src/Avalonia.Base/Input/PenDevice.cs +++ b/src/Avalonia.Base/Input/PenDevice.cs @@ -115,14 +115,11 @@ namespace Avalonia.Input KeyModifiers inputModifiers, IInputElement? hitTest, Lazy?>? intermediatePoints) { - IInputElement? source; - if (pointer.CapturedGestureRecognizer is IGestureRecognizer gestureRecognizer) + if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) { - source = gestureRecognizer.Target ?? hitTest; - - if (source != null) + if (gestureRecognizer.Target != null) { - var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, (Visual)root, + var e = new PointerEventArgs(InputElement.PointerMovedEvent, gestureRecognizer.Target, pointer, (Visual)root, p, timestamp, properties, inputModifiers, intermediatePoints); gestureRecognizer.PointerMoved(e); @@ -130,7 +127,7 @@ namespace Avalonia.Input } } - source = pointer.Captured ?? hitTest; + var source = pointer.Captured ?? hitTest; if (source is not null) { @@ -148,14 +145,11 @@ namespace Avalonia.Input IInputElement root, Point p, PointerPointProperties properties, KeyModifiers inputModifiers, IInputElement? hitTest) { - IInputElement? source; - if (pointer.CapturedGestureRecognizer is IGestureRecognizer gestureRecognizer) + if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) { - source = gestureRecognizer.Target ?? hitTest; - - if (source != null) + if (gestureRecognizer.Target != null) { - var e = new PointerReleasedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, + var e = new PointerReleasedEventArgs(gestureRecognizer.Target, pointer, (Visual)root, p, timestamp, properties, inputModifiers, _lastMouseDownButton); gestureRecognizer.PointerReleased(e); @@ -163,7 +157,7 @@ namespace Avalonia.Input } } - source = pointer.Captured ?? hitTest; + var source = pointer.Captured ?? hitTest; if (source is not null) { diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs index 3abda3637e..65c0d7f538 100644 --- a/src/Avalonia.Base/Input/Pointer.cs +++ b/src/Avalonia.Base/Input/Pointer.cs @@ -77,7 +77,7 @@ namespace Avalonia.Input /// /// Gets the gesture recognizer that is currently capturing by the pointer, if any. /// - internal IGestureRecognizer? CapturedGestureRecognizer { get; private set; } + internal GestureRecognizer? CapturedGestureRecognizer { get; private set; } public void Dispose() { @@ -89,7 +89,7 @@ namespace Avalonia.Input /// /// The gesture recognizer. /// - internal void CaptureGestureRecognizer(IGestureRecognizer? gestureRecognizer) + internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer) { if (CapturedGestureRecognizer != gestureRecognizer) CapturedGestureRecognizer?.PointerCaptureLost(this); From 0ac73d1a734bd4b63dbf2bf80265af3d5d80c58d Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 30 May 2023 09:11:09 +0000 Subject: [PATCH 29/60] remove GestureRecognizer.Initialize --- .../GestureRecognizers/GestureRecognizer.cs | 12 +++-------- .../GestureRecognizerCollection.cs | 2 +- .../PinchGestureRecognizer.cs | 16 ++++----------- .../PullGestureRecognizer.cs | 17 ++++------------ .../ScrollGestureRecognizer.cs | 20 ++++++------------- 5 files changed, 18 insertions(+), 49 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs index fd6748dee7..c7f40b4468 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs @@ -1,15 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Avalonia.Input.GestureRecognizers +namespace Avalonia.Input.GestureRecognizers { public abstract class GestureRecognizer : StyledElement { - public abstract IInputElement? Target { get; } - public abstract void Initialize(IInputElement target); + protected internal IInputElement? Target { get; internal set; } + public abstract void PointerPressed(PointerPressedEventArgs e); public abstract void PointerReleased(PointerReleasedEventArgs e); public abstract void PointerMoved(PointerEventArgs e); diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs index e7c6513c43..d8c2d5a21b 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs @@ -25,7 +25,7 @@ namespace Avalonia.Input.GestureRecognizers } _recognizers.Add(recognizer); - recognizer.Initialize(_inputElement); + recognizer.Target = _inputElement; // Hacks to make bindings work diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs index 133fd76947..b5342ae180 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -4,7 +4,6 @@ namespace Avalonia.Input { public class PinchGestureRecognizer : GestureRecognizer { - private IInputElement? _target; private float _initialDistance; private IPointer? _firstContact; private Point _firstPoint; @@ -12,13 +11,6 @@ namespace Avalonia.Input private Point _secondPoint; private Point _origin; - public override IInputElement? Target => _target; - - public override void Initialize(IInputElement target) - { - _target = target; - } - private void OnPointerPressed(object? sender, PointerPressedEventArgs e) { PointerPressed(e); @@ -36,7 +28,7 @@ namespace Avalonia.Input public override void PointerMoved(PointerEventArgs e) { - if (_target != null && _target is Visual visual) + if (Target != null && Target is Visual visual) { if(_firstContact == e.Pointer) { @@ -58,7 +50,7 @@ namespace Avalonia.Input var scale = distance / _initialDistance; var pinchEventArgs = new PinchEventArgs(scale, _origin); - _target?.RaiseEvent(pinchEventArgs); + Target?.RaiseEvent(pinchEventArgs); e.Handled = pinchEventArgs.Handled; } @@ -67,7 +59,7 @@ namespace Avalonia.Input public override void PointerPressed(PointerPressedEventArgs e) { - if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) + if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) { if (_firstContact == null) { @@ -118,7 +110,7 @@ namespace Avalonia.Input _secondContact = null; } - _target?.RaiseEvent(new PinchEndedEventArgs()); + Target?.RaiseEvent(new PinchEndedEventArgs()); } } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs index f6ab1c4508..4d463203e6 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs @@ -6,8 +6,6 @@ namespace Avalonia.Input public class PullGestureRecognizer : GestureRecognizer { internal static int MinPullDetectionSize = 50; - - private IInputElement? _target; private Point _initialPosition; private int _gestureId; private IPointer? _tracking; @@ -25,8 +23,6 @@ namespace Avalonia.Input set => SetValue(PullDirectionProperty, value); } - public override IInputElement? Target => _target; - public PullGestureRecognizer(PullDirection pullDirection) { PullDirection = pullDirection; @@ -34,11 +30,6 @@ namespace Avalonia.Input public PullGestureRecognizer() { } - public override void Initialize(IInputElement target) - { - _target = target; - } - public override void PointerCaptureLost(IPointer pointer) { if (_tracking == pointer) @@ -49,7 +40,7 @@ namespace Avalonia.Input public override void PointerMoved(PointerEventArgs e) { - if (_tracking == e.Pointer && _target is Visual visual) + if (_tracking == e.Pointer && Target is Visual visual) { var currentPosition = e.GetPosition(visual); Capture(e.Pointer); @@ -86,7 +77,7 @@ namespace Avalonia.Input _pullInProgress = true; var pullEventArgs = new PullGestureEventArgs(_gestureId, delta, PullDirection); - _target?.RaiseEvent(pullEventArgs); + Target?.RaiseEvent(pullEventArgs); e.Handled = pullEventArgs.Handled; } @@ -94,7 +85,7 @@ namespace Avalonia.Input public override void PointerPressed(PointerPressedEventArgs e) { - if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) + if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) { var position = e.GetPosition(visual); @@ -141,7 +132,7 @@ namespace Avalonia.Input _initialPosition = default; _pullInProgress = false; - _target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection)); + Target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection)); } } } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index ecc72c8f60..2f2afd5289 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -18,7 +18,6 @@ namespace Avalonia.Input.GestureRecognizers private bool _scrolling; private Point _trackedRootPoint; private IPointer? _tracking; - private IInputElement? _target; private int _gestureId; private Point _pointerPressedPoint; private VelocityTracker? _velocityTracker; @@ -91,13 +90,6 @@ namespace Avalonia.Input.GestureRecognizers get => _scrollStartDistance; set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value); } - - public override IInputElement? Target => _target; - - public override void Initialize(IInputElement target) - { - _target = target; - } public override void PointerPressed(PointerPressedEventArgs e) { @@ -107,7 +99,7 @@ namespace Avalonia.Input.GestureRecognizers EndGesture(); _tracking = e.Pointer; _gestureId = ScrollGestureEventArgs.GetNextFreeId(); - _trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)_target); + _trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)Target); } } @@ -115,7 +107,7 @@ namespace Avalonia.Input.GestureRecognizers { if (e.Pointer == _tracking) { - var rootPoint = e.GetPosition((Visual?)_target); + var rootPoint = e.GetPosition((Visual?)Target); if (!_scrolling) { if (CanHorizontallyScroll && Math.Abs(_trackedRootPoint.X - rootPoint.X) > ScrollStartDistance) @@ -145,7 +137,7 @@ namespace Avalonia.Input.GestureRecognizers _lastMoveTimestamp = e.Timestamp; _trackedRootPoint = rootPoint; - _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); + Target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); e.Handled = true; } } @@ -163,7 +155,7 @@ namespace Avalonia.Input.GestureRecognizers { _inertia = default; _scrolling = false; - _target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId)); + Target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId)); _gestureId = 0; _lastMoveTimestamp = null; } @@ -190,7 +182,7 @@ namespace Avalonia.Input.GestureRecognizers var savedGestureId = _gestureId; var st = Stopwatch.StartNew(); var lastTime = TimeSpan.Zero; - _target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia)); + Target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia)); DispatcherTimer.Run(() => { // Another gesture has started, finish the current one @@ -205,7 +197,7 @@ namespace Avalonia.Input.GestureRecognizers var speed = _inertia * Math.Pow(InertialResistance, st.Elapsed.TotalSeconds); var distance = speed * elapsedSinceLastTick.TotalSeconds; var scrollGestureEventArgs = new ScrollGestureEventArgs(_gestureId, distance); - _target!.RaiseEvent(scrollGestureEventArgs); + Target!.RaiseEvent(scrollGestureEventArgs); if (!scrollGestureEventArgs.Handled || scrollGestureEventArgs.ShouldEndScrollGesture) { From 9b41e8e8f820fdc7c3c7c6bcb59535f0122f0a48 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 30 May 2023 10:07:15 +0000 Subject: [PATCH 30/60] make pointer handler protected internal --- .../Input/GestureRecognizers/GestureRecognizer.cs | 8 ++++---- .../Input/GestureRecognizers/PinchGestureRecognizer.cs | 8 ++++---- .../Input/GestureRecognizers/PullGestureRecognizer.cs | 8 ++++---- .../GestureRecognizers/ScrollGestureRecognizer.cs | 10 +++++----- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs index c7f40b4468..e96c397d11 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs @@ -4,10 +4,10 @@ { protected internal IInputElement? Target { get; internal set; } - public abstract void PointerPressed(PointerPressedEventArgs e); - public abstract void PointerReleased(PointerReleasedEventArgs e); - public abstract void PointerMoved(PointerEventArgs e); - public abstract void PointerCaptureLost(IPointer pointer); + protected internal abstract void PointerPressed(PointerPressedEventArgs e); + protected internal abstract void PointerReleased(PointerReleasedEventArgs e); + protected internal abstract void PointerMoved(PointerEventArgs e); + protected internal abstract void PointerCaptureLost(IPointer pointer); protected void Capture(IPointer pointer) { diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs index b5342ae180..b12c2cc701 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -21,12 +21,12 @@ namespace Avalonia.Input PointerReleased(e); } - public override void PointerCaptureLost(IPointer pointer) + protected internal override void PointerCaptureLost(IPointer pointer) { RemoveContact(pointer); } - public override void PointerMoved(PointerEventArgs e) + protected internal override void PointerMoved(PointerEventArgs e) { if (Target != null && Target is Visual visual) { @@ -57,7 +57,7 @@ namespace Avalonia.Input } } - public override void PointerPressed(PointerPressedEventArgs e) + protected internal override void PointerPressed(PointerPressedEventArgs e) { if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) { @@ -90,7 +90,7 @@ namespace Avalonia.Input } } - public override void PointerReleased(PointerReleasedEventArgs e) + protected internal override void PointerReleased(PointerReleasedEventArgs e) { RemoveContact(e.Pointer); } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs index 4d463203e6..5358eb4cda 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs @@ -30,7 +30,7 @@ namespace Avalonia.Input public PullGestureRecognizer() { } - public override void PointerCaptureLost(IPointer pointer) + protected internal override void PointerCaptureLost(IPointer pointer) { if (_tracking == pointer) { @@ -38,7 +38,7 @@ namespace Avalonia.Input } } - public override void PointerMoved(PointerEventArgs e) + protected internal override void PointerMoved(PointerEventArgs e) { if (_tracking == e.Pointer && Target is Visual visual) { @@ -83,7 +83,7 @@ namespace Avalonia.Input } } - public override void PointerPressed(PointerPressedEventArgs e) + protected internal override void PointerPressed(PointerPressedEventArgs e) { if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) { @@ -118,7 +118,7 @@ namespace Avalonia.Input } } - public override void PointerReleased(PointerReleasedEventArgs e) + protected internal override void PointerReleased(PointerReleasedEventArgs e) { if (_tracking == e.Pointer && _pullInProgress) { diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 2f2afd5289..80c0583d90 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -90,8 +90,8 @@ namespace Avalonia.Input.GestureRecognizers get => _scrollStartDistance; set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value); } - - public override void PointerPressed(PointerPressedEventArgs e) + + protected internal override void PointerPressed(PointerPressedEventArgs e) { if (e.Pointer.IsPrimary && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) @@ -103,7 +103,7 @@ namespace Avalonia.Input.GestureRecognizers } } - public override void PointerMoved(PointerEventArgs e) + protected internal override void PointerMoved(PointerEventArgs e) { if (e.Pointer == _tracking) { @@ -143,7 +143,7 @@ namespace Avalonia.Input.GestureRecognizers } } - public override void PointerCaptureLost(IPointer pointer) + protected internal override void PointerCaptureLost(IPointer pointer) { if (pointer == _tracking) EndGesture(); } @@ -163,7 +163,7 @@ namespace Avalonia.Input.GestureRecognizers } - public override void PointerReleased(PointerReleasedEventArgs e) + protected internal override void PointerReleased(PointerReleasedEventArgs e) { if (e.Pointer == _tracking && _scrolling) { From abcad6e78ad3ee908ffa1a88202a22f4bf347c41 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 31 May 2023 18:46:00 +0600 Subject: [PATCH 31/60] Added InterpolatingTransitionBase --- .../Animation/InterpolatingTransitionBase.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/Avalonia.Base/Animation/InterpolatingTransitionBase.cs diff --git a/src/Avalonia.Base/Animation/InterpolatingTransitionBase.cs b/src/Avalonia.Base/Animation/InterpolatingTransitionBase.cs new file mode 100644 index 0000000000..44e3129b76 --- /dev/null +++ b/src/Avalonia.Base/Animation/InterpolatingTransitionBase.cs @@ -0,0 +1,28 @@ +using System; +using Avalonia.Animation.Animators; + +namespace Avalonia.Animation; + +/// +/// The base class for user-defined transition that are doing simple value interpolation +/// +public abstract class InterpolatingTransitionBase : Transition +{ + class Animator : Animator + { + private readonly InterpolatingTransitionBase _parent; + + public Animator(InterpolatingTransitionBase parent) + { + _parent = parent; + } + + public override T Interpolate(double progress, T oldValue, T newValue) => + _parent.Interpolate(progress, oldValue, newValue); + } + + protected abstract T Interpolate(double progress, T from, T to); + + internal override IObservable DoTransition(IObservable progress, T oldValue, T newValue) => + new AnimatorTransitionObservable(new Animator(this), progress, Easing, oldValue, newValue); +} \ No newline at end of file From c12fe17315dade003858ea1359704e7ff64b9bfb Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 31 May 2023 12:50:03 +0000 Subject: [PATCH 32/60] Added internal helpers for protected gesture events --- .../GestureRecognizers/GestureRecognizer.cs | 28 ++++++++++++-- .../GestureRecognizerCollection.cs | 6 +-- .../PinchGestureRecognizer.cs | 8 ++-- .../PullGestureRecognizer.cs | 8 ++-- .../ScrollGestureRecognizer.cs | 8 ++-- src/Avalonia.Base/Input/MouseDevice.cs | 38 +++++-------------- src/Avalonia.Base/Input/PenDevice.cs | 38 +++++-------------- src/Avalonia.Base/Input/Pointer.cs | 2 +- src/Avalonia.Base/Input/TouchDevice.cs | 4 +- tests/Avalonia.UnitTests/TouchTestHelper.cs | 4 +- 10 files changed, 64 insertions(+), 80 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs index e96c397d11..2cb97b4707 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs @@ -4,10 +4,30 @@ { protected internal IInputElement? Target { get; internal set; } - protected internal abstract void PointerPressed(PointerPressedEventArgs e); - protected internal abstract void PointerReleased(PointerReleasedEventArgs e); - protected internal abstract void PointerMoved(PointerEventArgs e); - protected internal abstract void PointerCaptureLost(IPointer pointer); + protected abstract void PointerPressed(PointerPressedEventArgs e); + protected abstract void PointerReleased(PointerReleasedEventArgs e); + protected abstract void PointerMoved(PointerEventArgs e); + protected abstract void PointerCaptureLost(IPointer pointer); + + internal void PointerPressedInternal(PointerPressedEventArgs e) + { + PointerPressed(e); + } + + internal void PointerReleasedInternal(PointerReleasedEventArgs e) + { + PointerReleased(e); + } + + internal void PointerMovedInternal(PointerEventArgs e) + { + PointerMoved(e); + } + + internal void PointerCaptureLostInternal(IPointer pointer) + { + PointerCaptureLost(pointer); + } protected void Capture(IPointer pointer) { diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs index d8c2d5a21b..05dce8214b 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs @@ -53,7 +53,7 @@ namespace Avalonia.Input.GestureRecognizers return false; foreach (var r in _recognizers) { - r.PointerPressed(e); + r.PointerPressedInternal(e); } return e.Handled; @@ -70,7 +70,7 @@ namespace Avalonia.Input.GestureRecognizers if (pointer?.CapturedGestureRecognizer != null) break; - r.PointerReleased(e); + r.PointerReleasedInternal(e); } return e.Handled; } @@ -86,7 +86,7 @@ namespace Avalonia.Input.GestureRecognizers if (pointer?.CapturedGestureRecognizer != null) break; - r.PointerMoved(e); + r.PointerMovedInternal(e); } return e.Handled; } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs index b12c2cc701..b02c82a066 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -21,12 +21,12 @@ namespace Avalonia.Input PointerReleased(e); } - protected internal override void PointerCaptureLost(IPointer pointer) + protected override void PointerCaptureLost(IPointer pointer) { RemoveContact(pointer); } - protected internal override void PointerMoved(PointerEventArgs e) + protected override void PointerMoved(PointerEventArgs e) { if (Target != null && Target is Visual visual) { @@ -57,7 +57,7 @@ namespace Avalonia.Input } } - protected internal override void PointerPressed(PointerPressedEventArgs e) + protected override void PointerPressed(PointerPressedEventArgs e) { if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) { @@ -90,7 +90,7 @@ namespace Avalonia.Input } } - protected internal override void PointerReleased(PointerReleasedEventArgs e) + protected override void PointerReleased(PointerReleasedEventArgs e) { RemoveContact(e.Pointer); } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs index 5358eb4cda..57faf5bfd8 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs @@ -30,7 +30,7 @@ namespace Avalonia.Input public PullGestureRecognizer() { } - protected internal override void PointerCaptureLost(IPointer pointer) + protected override void PointerCaptureLost(IPointer pointer) { if (_tracking == pointer) { @@ -38,7 +38,7 @@ namespace Avalonia.Input } } - protected internal override void PointerMoved(PointerEventArgs e) + protected override void PointerMoved(PointerEventArgs e) { if (_tracking == e.Pointer && Target is Visual visual) { @@ -83,7 +83,7 @@ namespace Avalonia.Input } } - protected internal override void PointerPressed(PointerPressedEventArgs e) + protected override void PointerPressed(PointerPressedEventArgs e) { if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) { @@ -118,7 +118,7 @@ namespace Avalonia.Input } } - protected internal override void PointerReleased(PointerReleasedEventArgs e) + protected override void PointerReleased(PointerReleasedEventArgs e) { if (_tracking == e.Pointer && _pullInProgress) { diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 80c0583d90..beecfcc3ce 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -91,7 +91,7 @@ namespace Avalonia.Input.GestureRecognizers set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value); } - protected internal override void PointerPressed(PointerPressedEventArgs e) + protected override void PointerPressed(PointerPressedEventArgs e) { if (e.Pointer.IsPrimary && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) @@ -103,7 +103,7 @@ namespace Avalonia.Input.GestureRecognizers } } - protected internal override void PointerMoved(PointerEventArgs e) + protected override void PointerMoved(PointerEventArgs e) { if (e.Pointer == _tracking) { @@ -143,7 +143,7 @@ namespace Avalonia.Input.GestureRecognizers } } - protected internal override void PointerCaptureLost(IPointer pointer) + protected override void PointerCaptureLost(IPointer pointer) { if (pointer == _tracking) EndGesture(); } @@ -163,7 +163,7 @@ namespace Avalonia.Input.GestureRecognizers } - protected internal override void PointerReleased(PointerReleasedEventArgs e) + protected override void PointerReleased(PointerReleasedEventArgs e) { if (e.Pointer == _tracking && _scrolling) { diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs index 4da8062897..db333dbd8b 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/src/Avalonia.Base/Input/MouseDevice.cs @@ -163,26 +163,17 @@ namespace Avalonia.Input device = device ?? throw new ArgumentNullException(nameof(device)); root = root ?? throw new ArgumentNullException(nameof(root)); - if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) - { - if(gestureRecognizer.Target != null) - { - var e = new PointerEventArgs(InputElement.PointerMovedEvent, gestureRecognizer.Target, _pointer, (Visual)root, - p, timestamp, properties, inputModifiers, intermediatePoints); - gestureRecognizer.PointerMoved(e); - - return e.Handled; - } - } - - var source = _pointer.Captured ?? hitTest; + var source = _pointer.CapturedGestureRecognizer?.Target ?? _pointer.Captured ?? hitTest; if (source is object) { var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)root, p, timestamp, properties, inputModifiers, intermediatePoints); - source.RaiseEvent(e); + if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) + gestureRecognizer.PointerMovedInternal(e); + else + source.RaiseEvent(e); return e.Handled; } @@ -195,27 +186,18 @@ namespace Avalonia.Input { device = device ?? throw new ArgumentNullException(nameof(device)); root = root ?? throw new ArgumentNullException(nameof(root)); - - if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) - { - if (gestureRecognizer.Target != null) - { - var e = new PointerReleasedEventArgs(gestureRecognizer.Target, _pointer, (Visual)root, p, timestamp, props, inputModifiers, - _lastMouseDownButton); - gestureRecognizer.PointerReleased(e); - - return e.Handled; - } - } - var source = _pointer.Captured ?? hitTest; + var source = _pointer.CapturedGestureRecognizer?.Target ?? _pointer.Captured ?? hitTest; if (source is not null) { var e = new PointerReleasedEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers, _lastMouseDownButton); - source?.RaiseEvent(e); + if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) + gestureRecognizer.PointerReleasedInternal(e); + else + source?.RaiseEvent(e); _pointer.Capture(null); _pointer.CaptureGestureRecognizer(null); _lastMouseDownButton = default; diff --git a/src/Avalonia.Base/Input/PenDevice.cs b/src/Avalonia.Base/Input/PenDevice.cs index fab0f1320c..09bf18d3fd 100644 --- a/src/Avalonia.Base/Input/PenDevice.cs +++ b/src/Avalonia.Base/Input/PenDevice.cs @@ -115,26 +115,17 @@ namespace Avalonia.Input KeyModifiers inputModifiers, IInputElement? hitTest, Lazy?>? intermediatePoints) { - if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) - { - if (gestureRecognizer.Target != null) - { - var e = new PointerEventArgs(InputElement.PointerMovedEvent, gestureRecognizer.Target, pointer, (Visual)root, - p, timestamp, properties, inputModifiers, intermediatePoints); - gestureRecognizer.PointerMoved(e); - - return e.Handled; - } - } - - var source = pointer.Captured ?? hitTest; + var source = pointer.CapturedGestureRecognizer?.Target ?? pointer.Captured ?? hitTest; if (source is not null) { var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, intermediatePoints); - source.RaiseEvent(e); + if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) + gestureRecognizer.PointerMovedInternal(e); + else + source.RaiseEvent(e); return e.Handled; } @@ -145,26 +136,17 @@ namespace Avalonia.Input IInputElement root, Point p, PointerPointProperties properties, KeyModifiers inputModifiers, IInputElement? hitTest) { - if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) - { - if (gestureRecognizer.Target != null) - { - var e = new PointerReleasedEventArgs(gestureRecognizer.Target, pointer, (Visual)root, p, timestamp, properties, inputModifiers, - _lastMouseDownButton); - gestureRecognizer.PointerReleased(e); - - return e.Handled; - } - } - - var source = pointer.Captured ?? hitTest; + var source = pointer.CapturedGestureRecognizer?.Target ?? pointer.Captured ?? hitTest; if (source is not null) { var e = new PointerReleasedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, _lastMouseDownButton); - source.RaiseEvent(e); + if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) + gestureRecognizer.PointerReleasedInternal(e); + else + source.RaiseEvent(e); pointer.Capture(null); pointer.CaptureGestureRecognizer(null); _lastMouseDownButton = default; diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs index 65c0d7f538..91358712a0 100644 --- a/src/Avalonia.Base/Input/Pointer.cs +++ b/src/Avalonia.Base/Input/Pointer.cs @@ -92,7 +92,7 @@ namespace Avalonia.Input internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer) { if (CapturedGestureRecognizer != gestureRecognizer) - CapturedGestureRecognizer?.PointerCaptureLost(this); + CapturedGestureRecognizer?.PointerCaptureLostInternal(this); if (gestureRecognizer != null) Capture(null); diff --git a/src/Avalonia.Base/Input/TouchDevice.cs b/src/Avalonia.Base/Input/TouchDevice.cs index 02f3479e95..8868e966f0 100644 --- a/src/Avalonia.Base/Input/TouchDevice.cs +++ b/src/Avalonia.Base/Input/TouchDevice.cs @@ -103,7 +103,7 @@ namespace Avalonia.Input keyModifier, MouseButton.Left); if (gestureTarget != null) { - pointer?.CapturedGestureRecognizer?.PointerReleased(e); + pointer?.CapturedGestureRecognizer?.PointerReleasedInternal(e); } else { @@ -132,7 +132,7 @@ namespace Avalonia.Input if (gestureTarget != null) { - pointer?.CapturedGestureRecognizer?.PointerMoved(e); + pointer?.CapturedGestureRecognizer?.PointerMovedInternal(e); } else { diff --git a/tests/Avalonia.UnitTests/TouchTestHelper.cs b/tests/Avalonia.UnitTests/TouchTestHelper.cs index 703cb45b96..574599d1ad 100644 --- a/tests/Avalonia.UnitTests/TouchTestHelper.cs +++ b/tests/Avalonia.UnitTests/TouchTestHelper.cs @@ -30,7 +30,7 @@ namespace Avalonia.UnitTests var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)target, position, Timestamp(), PointerPointProperties.None, modifiers); if (_pointer.CapturedGestureRecognizer != null) - _pointer.CapturedGestureRecognizer.PointerMoved(e); + _pointer.CapturedGestureRecognizer.PointerMovedInternal(e); else target.RaiseEvent(e); @@ -45,7 +45,7 @@ namespace Avalonia.UnitTests modifiers, MouseButton.None); if (_pointer.CapturedGestureRecognizer != null) - _pointer.CapturedGestureRecognizer.PointerReleased(e); + _pointer.CapturedGestureRecognizer.PointerReleasedInternal(e); else source.RaiseEvent(e); From b34f8321e7be03d00187e0746318abfe19e520da Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 31 May 2023 17:39:09 +0100 Subject: [PATCH 33/60] implement msbuild property to give access to internal apis. --- packages/Avalonia/Avalonia.targets | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/Avalonia/Avalonia.targets b/packages/Avalonia/Avalonia.targets index 50306f2cdc..0961c82b5c 100644 --- a/packages/Avalonia/Avalonia.targets +++ b/packages/Avalonia/Avalonia.targets @@ -1,3 +1,19 @@ + + + + + + + + + + + + + + + + From a87cd48ae1aaf0b1a7e8c990d5a4a8b68ebfdf5a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 31 May 2023 17:45:02 +0100 Subject: [PATCH 34/60] use our own targets file for private apis. --- packages/Avalonia/Avalonia.targets | 17 +-------------- packages/Avalonia/AvaloniaPrivateApis.targets | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 16 deletions(-) create mode 100644 packages/Avalonia/AvaloniaPrivateApis.targets diff --git a/packages/Avalonia/Avalonia.targets b/packages/Avalonia/Avalonia.targets index 0961c82b5c..eacd287b59 100644 --- a/packages/Avalonia/Avalonia.targets +++ b/packages/Avalonia/Avalonia.targets @@ -1,19 +1,4 @@ - - - - - - - - - - - - - - - - + diff --git a/packages/Avalonia/AvaloniaPrivateApis.targets b/packages/Avalonia/AvaloniaPrivateApis.targets new file mode 100644 index 0000000000..fdf60a24e5 --- /dev/null +++ b/packages/Avalonia/AvaloniaPrivateApis.targets @@ -0,0 +1,21 @@ + + + net6.0 + netstandard2.0 + + + + + + + + + + + + + + + + + From a9e6f335c763f53565d5dcdfdd7a80c5de544b36 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 31 May 2023 17:49:45 +0100 Subject: [PATCH 35/60] add a warning. --- packages/Avalonia/AvaloniaPrivateApis.targets | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/Avalonia/AvaloniaPrivateApis.targets b/packages/Avalonia/AvaloniaPrivateApis.targets index fdf60a24e5..8d44c7bbbf 100644 --- a/packages/Avalonia/AvaloniaPrivateApis.targets +++ b/packages/Avalonia/AvaloniaPrivateApis.targets @@ -17,5 +17,6 @@ + From c8676f275b38d3bd44389cf59f9fc907d57beb88 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 31 May 2023 18:31:35 +0100 Subject: [PATCH 36/60] fix targets file. --- packages/Avalonia/AvaloniaPrivateApis.targets | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/Avalonia/AvaloniaPrivateApis.targets b/packages/Avalonia/AvaloniaPrivateApis.targets index 8d44c7bbbf..a98fecce40 100644 --- a/packages/Avalonia/AvaloniaPrivateApis.targets +++ b/packages/Avalonia/AvaloniaPrivateApis.targets @@ -1,7 +1,7 @@ - net6.0 - netstandard2.0 + net6.0 + netstandard2.0 @@ -13,9 +13,9 @@ - - - + + + From 59fb1536a6a1c02a4b775f2f333e1432a5f53203 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 31 May 2023 18:37:12 +0100 Subject: [PATCH 37/60] better warning message --- packages/Avalonia/AvaloniaPrivateApis.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/Avalonia/AvaloniaPrivateApis.targets b/packages/Avalonia/AvaloniaPrivateApis.targets index a98fecce40..ec93dcb5dc 100644 --- a/packages/Avalonia/AvaloniaPrivateApis.targets +++ b/packages/Avalonia/AvaloniaPrivateApis.targets @@ -17,6 +17,6 @@ - + From 6f598fff06873d6f39b92cdc27b17177f7f7686e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 31 May 2023 18:40:30 +0100 Subject: [PATCH 38/60] fix referencing unstable apis. --- packages/Avalonia/AvaloniaPrivateApis.targets | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/Avalonia/AvaloniaPrivateApis.targets b/packages/Avalonia/AvaloniaPrivateApis.targets index ec93dcb5dc..5f6d19773c 100644 --- a/packages/Avalonia/AvaloniaPrivateApis.targets +++ b/packages/Avalonia/AvaloniaPrivateApis.targets @@ -13,9 +13,9 @@ - - - + + + From c142c5a0e722a6a5e2853d5be0173d70b80d5870 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 31 May 2023 18:43:19 +0100 Subject: [PATCH 39/60] prefix the property name --- packages/Avalonia/AvaloniaPrivateApis.targets | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/Avalonia/AvaloniaPrivateApis.targets b/packages/Avalonia/AvaloniaPrivateApis.targets index 5f6d19773c..95c08520a9 100644 --- a/packages/Avalonia/AvaloniaPrivateApis.targets +++ b/packages/Avalonia/AvaloniaPrivateApis.targets @@ -1,8 +1,4 @@ - - net6.0 - netstandard2.0 - @@ -12,9 +8,13 @@ + + net6.0 + netstandard2.0 + - - + + From 7e0c578f3e27e0e7cc4629129325c594ceeac182 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 31 May 2023 18:45:03 +0100 Subject: [PATCH 40/60] fix bug --- packages/Avalonia/AvaloniaPrivateApis.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/Avalonia/AvaloniaPrivateApis.targets b/packages/Avalonia/AvaloniaPrivateApis.targets index 95c08520a9..368d69322d 100644 --- a/packages/Avalonia/AvaloniaPrivateApis.targets +++ b/packages/Avalonia/AvaloniaPrivateApis.targets @@ -15,7 +15,7 @@ - + From 036e6c3c4f9cf6d240aed91f25c0cf16037acc0c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 31 May 2023 18:50:55 +0100 Subject: [PATCH 41/60] add error if user is publishing a library on nuget. --- packages/Avalonia/AvaloniaPrivateApis.targets | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/Avalonia/AvaloniaPrivateApis.targets b/packages/Avalonia/AvaloniaPrivateApis.targets index 368d69322d..e7807aeb78 100644 --- a/packages/Avalonia/AvaloniaPrivateApis.targets +++ b/packages/Avalonia/AvaloniaPrivateApis.targets @@ -18,5 +18,7 @@ + From 0dc6b6b042e46f270799cd30a22e5be5cd4530ec Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 31 May 2023 19:45:57 +0100 Subject: [PATCH 42/60] ensure packing error does not occur on normal builds. --- packages/Avalonia/AvaloniaPrivateApis.targets | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/Avalonia/AvaloniaPrivateApis.targets b/packages/Avalonia/AvaloniaPrivateApis.targets index e7807aeb78..5343559c07 100644 --- a/packages/Avalonia/AvaloniaPrivateApis.targets +++ b/packages/Avalonia/AvaloniaPrivateApis.targets @@ -1,5 +1,5 @@ - + @@ -7,7 +7,11 @@ - + + + + net6.0 netstandard2.0 @@ -18,7 +22,5 @@ - From 51da158ad6a4a6f289a661b234d88c8520a71528 Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Wed, 31 May 2023 21:10:34 +0200 Subject: [PATCH 43/60] Fix DBus Tray Icon when the org.kde.StatusNotifierWatcher service is unavailable --- src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs index 9abf4f97a7..61e46b651e 100644 --- a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs +++ b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs @@ -60,9 +60,9 @@ namespace Avalonia.FreeDesktop { try { - _serviceWatchDisposable = await _dBus!.WatchNameOwnerChangedAsync((_, x) => OnNameChange(x.Item2)); + _serviceWatchDisposable = await _dBus!.WatchNameOwnerChangedAsync((_, x) => OnNameChange(x.Item1, x.Item3)); var nameOwner = await _dBus.GetNameOwnerAsync("org.kde.StatusNotifierWatcher"); - OnNameChange(nameOwner); + OnNameChange("org.kde.StatusNotifierWatcher", nameOwner); } catch { @@ -72,9 +72,9 @@ namespace Avalonia.FreeDesktop } } - private void OnNameChange(string? newOwner) + private void OnNameChange(string name, string? newOwner) { - if (_isDisposed || _connection is null) + if (_isDisposed || _connection is null || name != "org.kde.StatusNotifierWatcher") return; if (!_serviceConnected & newOwner is not null) From c650d835221a80d4c18776856abf11feef0968fc Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 31 May 2023 20:51:40 +0100 Subject: [PATCH 44/60] build services fix. --- packages/Avalonia/Avalonia.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index a4101afac1..3f313ead1b 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -5,7 +5,7 @@ - + all From fb9baae0125525856292857cd699a54626039828 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 31 May 2023 10:43:31 +0200 Subject: [PATCH 45/60] Fixed ManagedDispatcherImpl wait --- .../Platform/ManagedDispatcherImpl.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs b/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs index fdc098777a..c8225f775b 100644 --- a/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs +++ b/src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs @@ -99,9 +99,15 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl continue; } - if (_nextTimer != null) + TimeSpan? nextTimer; + lock (_lock) + { + nextTimer = _nextTimer; + } + + if (nextTimer != null) { - var waitFor = _clock.Elapsed - _nextTimer.Value; + var waitFor = nextTimer.Value - _clock.Elapsed; if (waitFor.TotalMilliseconds < 1) continue; _wakeup.WaitOne(waitFor); @@ -112,4 +118,4 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl registration.Dispose(); } -} \ No newline at end of file +} From af343bdf027c9b56e8d92ffcafcdb1d3347514b8 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 31 May 2023 18:28:33 +0200 Subject: [PATCH 46/60] Implemented a proper framebuffer for RemoteServerTopLevelImpl --- .../Offscreen/OffscreenTopLevelImpl.cs | 19 +-- .../RemoteServerTopLevelImpl.Framebuffer.cs | 116 +++++++++++++ .../Remote/Server/RemoteServerTopLevelImpl.cs | 154 ++++++++---------- .../Remote/PreviewerWindowImpl.cs | 2 +- .../Remote/PreviewerWindowingPlatform.cs | 2 +- 5 files changed, 190 insertions(+), 103 deletions(-) create mode 100644 src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index f379120638..106ac8dff5 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -1,14 +1,10 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using Avalonia.Input; using Avalonia.Input.Raw; -using Avalonia.Media; using Avalonia.Metadata; using Avalonia.Platform; -using Avalonia.Rendering; using Avalonia.Rendering.Composition; -using Avalonia.Threading; namespace Avalonia.Controls.Embedding.Offscreen { @@ -17,7 +13,6 @@ namespace Avalonia.Controls.Embedding.Offscreen { private double _scaling = 1; private Size _clientSize; - private ManualRenderTimer _manualRenderTimer = new(); public IInputRoot? InputRoot { get; private set; } public bool IsDisposed { get; private set; } @@ -27,21 +22,10 @@ namespace Avalonia.Controls.Embedding.Offscreen IsDisposed = true; } - class ManualRenderTimer : IRenderTimer - { - static Stopwatch St = Stopwatch.StartNew(); - public event Action? Tick; - public bool RunsInBackground => false; - public void TriggerTick() => Tick?.Invoke(St.Elapsed); - } - public Compositor Compositor { get; } public OffscreenTopLevelImplBase() - { - Compositor = new Compositor(new RenderLoop(_manualRenderTimer), null, false, - MediaContext.Instance, false); - } + => Compositor = new Compositor(null); public abstract IEnumerable Surfaces { get; } @@ -76,7 +60,6 @@ namespace Avalonia.Controls.Embedding.Offscreen public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } - /// public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1); public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot; diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs new file mode 100644 index 0000000000..361c3033b5 --- /dev/null +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs @@ -0,0 +1,116 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; +using Avalonia.Platform; +using Avalonia.Remote.Protocol.Viewport; +using PlatformPixelFormat = Avalonia.Platform.PixelFormat; +using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat; + +namespace Avalonia.Controls.Remote.Server +{ + internal partial class RemoteServerTopLevelImpl + { + private enum FrameStatus + { + NotRendered, + Rendered, + CopiedToMessage + } + + private sealed class Framebuffer + { + public static Framebuffer Empty { get; } = + new(ProtocolPixelFormat.Rgba8888, default, new Vector(96.0, 96.0)); + + private readonly object _dataLock = new(); + private readonly byte[] _data; // for rendering only + private readonly byte[] _dataCopy; // for messages only + private FrameStatus _status = FrameStatus.NotRendered; + + public Framebuffer(ProtocolPixelFormat format, Size clientSize, Vector dpi) + { + var frameSize = PixelSize.FromSizeWithDpi(clientSize, dpi); + if (frameSize.Width <= 0 || frameSize.Height <= 0) + frameSize = PixelSize.Empty; + + var bpp = format == ProtocolPixelFormat.Rgb565 ? 2 : 4; + var stride = frameSize.Width * bpp; + var dataLength = Math.Max(0, stride * frameSize.Height); + + Format = format; + FrameSize = frameSize; + ClientSize = clientSize; + Dpi = dpi; + + (Stride, _data, _dataCopy) = dataLength > 0 ? + (stride, new byte[dataLength], new byte[dataLength]) : + (0, Array.Empty(), Array.Empty()); + } + + public ProtocolPixelFormat Format { get; } + + public Size ClientSize { get; } + + public Vector Dpi { get; } + + public PixelSize FrameSize { get; } + + public int Stride { get; } + + public FrameStatus GetStatus() + { + lock (_dataLock) + return _status; + } + + public ILockedFramebuffer Lock(Action onUnlocked) + { + var handle = GCHandle.Alloc(_data, GCHandleType.Pinned); + Monitor.Enter(_dataLock); + + try + { + return new LockedFramebuffer( + handle.AddrOfPinnedObject(), + FrameSize, + Stride, + Dpi, + new PlatformPixelFormat((PixelFormatEnum)Format), + () => + { + handle.Free(); + Array.Copy(_data, _dataCopy, _data.Length); + _status = FrameStatus.Rendered; + Monitor.Exit(_dataLock); + onUnlocked(); + }); + } + catch + { + handle.Free(); + Monitor.Exit(_dataLock); + throw; + } + } + + /// The returned message must be kept around, as it contains a shared buffer. + public FrameMessage ToMessage(long sequenceId) + { + lock (_dataLock) + _status = FrameStatus.CopiedToMessage; + + return new FrameMessage + { + SequenceId = sequenceId, + Data = _dataCopy, + Format = Format, + Width = FrameSize.Width, + Height = FrameSize.Height, + Stride = Stride, + DpiX = Dpi.X, + DpiY = Dpi.Y + }; + } + } + } +} diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index 8e4123a790..f8fc1957a4 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Runtime.InteropServices; using Avalonia.Controls.Embedding.Offscreen; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input; @@ -11,7 +10,6 @@ using Avalonia.Platform; using Avalonia.Remote.Protocol; using Avalonia.Remote.Protocol.Input; using Avalonia.Remote.Protocol.Viewport; -using Avalonia.Rendering; using Avalonia.Threading; using Key = Avalonia.Input.Key; using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat; @@ -20,28 +18,29 @@ using ProtocolMouseButton = Avalonia.Remote.Protocol.Input.MouseButton; namespace Avalonia.Controls.Remote.Server { [Unstable] - internal class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface, ITopLevelImpl + internal partial class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface, ITopLevelImpl { private readonly IAvaloniaRemoteTransportConnection _transport; - private LockedFramebuffer? _framebuffer; private readonly object _lock = new(); + private readonly Action _sendLastFrameIfNeeded; + private readonly Action _renderAndSendFrameIfNeeded; + private Framebuffer _framebuffer = Framebuffer.Empty; private long _lastSentFrame = -1; private long _lastReceivedFrame = -1; private long _nextFrameNumber = 1; private ClientViewportAllocatedMessage? _pendingAllocation; - private bool _queuedNextRender; - private bool _inRender; - private Vector _dpi = new Vector(96, 96); - private ProtocolPixelFormat[]? _supportedFormats; + private Vector _dpi = new(96, 96); + private ProtocolPixelFormat? _format; public RemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport) { + _sendLastFrameIfNeeded = SendLastFrameIfNeeded; + _renderAndSendFrameIfNeeded = RenderAndSendFrameIfNeeded; + _transport = transport; _transport.OnMessage += OnMessage; KeyboardDevice = AvaloniaLocator.Current.GetRequiredService(); - QueueNextRender(); - Compositor.AfterCommit += QueueNextRender; } private static RawPointerEventType GetAvaloniaEventType(ProtocolMouseButton button, bool pressed) @@ -118,23 +117,23 @@ namespace Avalonia.Controls.Remote.Server { _lastReceivedFrame = Math.Max(lastFrame.SequenceId, _lastReceivedFrame); } - Dispatcher.UIThread.Post(RenderIfNeeded); + Dispatcher.UIThread.Post(_sendLastFrameIfNeeded); } - if(obj is ClientRenderInfoMessage renderInfo) + if (obj is ClientRenderInfoMessage renderInfo) { - lock(_lock) + lock (_lock) { _dpi = new Vector(renderInfo.DpiX, renderInfo.DpiY); - _queuedNextRender = true; } - - Dispatcher.UIThread.Post(RenderIfNeeded); + Dispatcher.UIThread.Post(_renderAndSendFrameIfNeeded); } if (obj is ClientSupportedPixelFormatsMessage supportedFormats) { lock (_lock) - _supportedFormats = supportedFormats.Formats; - Dispatcher.UIThread.Post(RenderIfNeeded); + { + _format = TryGetValidPixelFormat(supportedFormats.Formats); + } + Dispatcher.UIThread.Post(_renderAndSendFrameIfNeeded); } if (obj is MeasureViewportMessage measure) Dispatcher.UIThread.Post(() => @@ -161,7 +160,7 @@ namespace Avalonia.Controls.Remote.Server } _dpi = new Vector(allocation.DpiX, allocation.DpiY); ClientSize = new Size(allocation.Width, allocation.Height); - RenderIfNeeded(); + RenderAndSendFrameIfNeeded(); }); _pendingAllocation = allocated; @@ -250,10 +249,24 @@ namespace Avalonia.Controls.Remote.Server } } + private static ProtocolPixelFormat? TryGetValidPixelFormat(ProtocolPixelFormat[]? formats) + { + if (formats is not null) + { + foreach (var format in formats) + { + if (format is >= 0 and <= ProtocolPixelFormat.MaxValue) + return format; + } + } + + return null; + } + protected void SetDpi(Vector dpi) { _dpi = dpi; - RenderIfNeeded(); + RenderAndSendFrameIfNeeded(); } protected virtual Size Measure(Size constraint) @@ -265,88 +278,63 @@ namespace Avalonia.Controls.Remote.Server public override IEnumerable Surfaces => new[] { this }; - private FrameMessage RenderFrame(int width, int height, ProtocolPixelFormat? format) + private Framebuffer GetOrCreateFramebuffer() { - var scalingX = _dpi.X / 96.0; - var scalingY = _dpi.Y / 96.0; - - width = (int)(width * scalingX); - height = (int)(height * scalingY); - - var fmt = format ?? ProtocolPixelFormat.Rgba8888; - var bpp = fmt == ProtocolPixelFormat.Rgb565 ? 2 : 4; - var data = new byte[width * height * bpp]; - var handle = GCHandle.Alloc(data, GCHandleType.Pinned); - - try - { - if (width > 0 && height > 0) - { - _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), new PixelSize(width, height), width * bpp, _dpi, new((PixelFormatEnum)fmt), - null); - Paint?.Invoke(new Rect(0, 0, width, height)); - } - } - finally + lock (_lock) { - _framebuffer = null; - handle.Free(); + if (_format is not { } format) + _framebuffer = Framebuffer.Empty; + else if (_framebuffer.Format != format || _framebuffer.ClientSize != ClientSize || _framebuffer.Dpi != _dpi) + _framebuffer = new Framebuffer(format, ClientSize, _dpi); + + return _framebuffer; } - return new FrameMessage - { - Data = data, - Format = fmt, - Width = width, - Height = height, - Stride = width * bpp, - DpiX = _dpi.X, - DpiY = _dpi.Y - }; } public ILockedFramebuffer Lock() - { - if (_framebuffer == null) - throw new InvalidOperationException("Paint was not requested, wait for Paint event"); - return _framebuffer; - } + => GetOrCreateFramebuffer().Lock(_sendLastFrameIfNeeded); - protected void RenderIfNeeded() + private void SendLastFrameIfNeeded() { - lock (_lock) - { - if (_lastReceivedFrame != _lastSentFrame || !_queuedNextRender || _supportedFormats == null) - return; + if (IsDisposed) + return; - } - - var format = ProtocolPixelFormat.Rgba8888; - foreach(var fmt in _supportedFormats) - if (fmt <= ProtocolPixelFormat.MaxValue) - { - format = fmt; - break; - } + Framebuffer framebuffer; + long sequenceId; - _inRender = true; - var frame = RenderFrame((int) ClientSize.Width, (int) ClientSize.Height, format); lock (_lock) { + // Ideally we should only send a frame if its status is Rendered: since the renderer might not be + // initialized at the start, we're sending black frames in this case. However, this was the historical + // behavior and some external programs are depending on receiving a frame asap. + if (_lastReceivedFrame != _lastSentFrame || _framebuffer.GetStatus() == FrameStatus.CopiedToMessage) + return; + + framebuffer = _framebuffer; _lastSentFrame = _nextFrameNumber++; - frame.SequenceId = _lastSentFrame; - _queuedNextRender = false; + sequenceId = _lastSentFrame; } - _inRender = false; - _transport.Send(frame); + + _transport.Send(framebuffer.ToMessage(sequenceId)); } - private void QueueNextRender() + protected void RenderAndSendFrameIfNeeded() { - if (!_inRender && !IsDisposed) + if (IsDisposed) + return; + + lock (_lock) { - _queuedNextRender = true; - DispatcherTimer.RunOnce(RenderIfNeeded, TimeSpan.FromMilliseconds(2), DispatcherPriority.Background); + if (_lastReceivedFrame != _lastSentFrame || _format is null) + return; } + + var framebuffer = GetOrCreateFramebuffer(); + + if (framebuffer.Stride > 0) + Paint?.Invoke(new Rect(framebuffer.ClientSize)); + + SendLastFrameIfNeeded(); } public override IMouseDevice MouseDevice { get; } = new MouseDevice(); diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index e0fcf8e530..5fe9f203e7 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -67,7 +67,7 @@ namespace Avalonia.DesignerSupport.Remote Height = clientSize.Height }); ClientSize = clientSize; - RenderIfNeeded(); + RenderAndSendFrameIfNeeded(); } public void Move(PixelPoint point) diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs index d2787e73ae..ba9dd592ce 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs @@ -52,7 +52,7 @@ namespace Avalonia.DesignerSupport.Remote .Bind().ToConstant(Keyboard) .Bind().ToSingleton() .Bind().ToConstant(new ManagedDispatcherImpl(null)) - .Bind().ToConstant(new DefaultRenderTimer(60)) + .Bind().ToConstant(new UiThreadRenderTimer(60)) .Bind().ToConstant(instance) .Bind().ToSingleton() .Bind().ToSingleton(); From c45d49942bc87831b7242bf49901117d4bb38fef Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 31 May 2023 19:22:13 +0200 Subject: [PATCH 47/60] RemoteServerTopLevelImpl cleanup --- .../Remote/Server/RemoteServerTopLevelImpl.cs | 221 +++++++++--------- 1 file changed, 108 insertions(+), 113 deletions(-) diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index f8fc1957a4..632e7fa8a3 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -111,45 +111,38 @@ namespace Avalonia.Controls.Remote.Server { lock (_lock) { - if (obj is FrameReceivedMessage lastFrame) + switch (obj) { - lock (_lock) - { + case FrameReceivedMessage lastFrame: _lastReceivedFrame = Math.Max(lastFrame.SequenceId, _lastReceivedFrame); - } - Dispatcher.UIThread.Post(_sendLastFrameIfNeeded); - } - if (obj is ClientRenderInfoMessage renderInfo) - { - lock (_lock) - { + Dispatcher.UIThread.Post(_sendLastFrameIfNeeded); + break; + + case ClientRenderInfoMessage renderInfo: _dpi = new Vector(renderInfo.DpiX, renderInfo.DpiY); - } - Dispatcher.UIThread.Post(_renderAndSendFrameIfNeeded); - } - if (obj is ClientSupportedPixelFormatsMessage supportedFormats) - { - lock (_lock) - { + Dispatcher.UIThread.Post(_renderAndSendFrameIfNeeded); + break; + + case ClientSupportedPixelFormatsMessage supportedFormats: _format = TryGetValidPixelFormat(supportedFormats.Formats); - } - Dispatcher.UIThread.Post(_renderAndSendFrameIfNeeded); - } - if (obj is MeasureViewportMessage measure) - Dispatcher.UIThread.Post(() => - { - var m = Measure(new Size(measure.Width, measure.Height)); - _transport.Send(new MeasureViewportMessage + Dispatcher.UIThread.Post(_renderAndSendFrameIfNeeded); + break; + + case MeasureViewportMessage measure: + Dispatcher.UIThread.Post(() => { - Width = m.Width, - Height = m.Height + var m = Measure(new Size(measure.Width, measure.Height)); + _transport.Send(new MeasureViewportMessage + { + Width = m.Width, + Height = m.Height + }); }); - }); - if (obj is ClientViewportAllocatedMessage allocated) - { - lock (_lock) - { + break; + + case ClientViewportAllocatedMessage allocated: if (_pendingAllocation == null) + { Dispatcher.UIThread.Post(() => { ClientViewportAllocatedMessage allocation; @@ -158,93 +151,95 @@ namespace Avalonia.Controls.Remote.Server allocation = _pendingAllocation!; _pendingAllocation = null; } + _dpi = new Vector(allocation.DpiX, allocation.DpiY); ClientSize = new Size(allocation.Width, allocation.Height); RenderAndSendFrameIfNeeded(); }); + } _pendingAllocation = allocated; - } - } - if(obj is PointerMovedEventMessage pointer) - { - Dispatcher.UIThread.Post(() => - { - Input?.Invoke(new RawPointerEventArgs( - MouseDevice, - 0, - InputRoot!, - RawPointerEventType.Move, - new Point(pointer.X, pointer.Y), - GetAvaloniaRawInputModifiers(pointer.Modifiers))); - }, DispatcherPriority.Input); - } - if(obj is PointerPressedEventMessage pressed) - { - Dispatcher.UIThread.Post(() => - { - Input?.Invoke(new RawPointerEventArgs( - MouseDevice, - 0, - InputRoot!, - GetAvaloniaEventType(pressed.Button, true), - new Point(pressed.X, pressed.Y), - GetAvaloniaRawInputModifiers(pressed.Modifiers))); - }, DispatcherPriority.Input); - } - if (obj is PointerReleasedEventMessage released) - { - Dispatcher.UIThread.Post(() => - { - Input?.Invoke(new RawPointerEventArgs( - MouseDevice, - 0, - InputRoot!, - GetAvaloniaEventType(released.Button, false), - new Point(released.X, released.Y), - GetAvaloniaRawInputModifiers(released.Modifiers))); - }, DispatcherPriority.Input); - } - if(obj is ScrollEventMessage scroll) - { - Dispatcher.UIThread.Post(() => - { - Input?.Invoke(new RawMouseWheelEventArgs( - MouseDevice, - 0, - InputRoot!, - new Point(scroll.X, scroll.Y), - new Vector(scroll.DeltaX, scroll.DeltaY), - GetAvaloniaRawInputModifiers(scroll.Modifiers))); - }, DispatcherPriority.Input); - } - if(obj is KeyEventMessage key) - { - Dispatcher.UIThread.Post(() => - { - Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); - - Input?.Invoke(new RawKeyEventArgs( - KeyboardDevice, - 0, - InputRoot!, - key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, - (Key)key.Key, - GetAvaloniaRawInputModifiers(key.Modifiers))); - }, DispatcherPriority.Input); - } - if(obj is TextInputEventMessage text) - { - Dispatcher.UIThread.Post(() => - { - Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); - - Input?.Invoke(new RawTextInputEventArgs( - KeyboardDevice, - 0, - InputRoot!, - text.Text)); - }, DispatcherPriority.Input); + break; + + case PointerMovedEventMessage pointer: + Dispatcher.UIThread.Post(() => + { + Input?.Invoke(new RawPointerEventArgs( + MouseDevice, + 0, + InputRoot!, + RawPointerEventType.Move, + new Point(pointer.X, pointer.Y), + GetAvaloniaRawInputModifiers(pointer.Modifiers))); + }, DispatcherPriority.Input); + break; + + case PointerPressedEventMessage pressed: + Dispatcher.UIThread.Post(() => + { + Input?.Invoke(new RawPointerEventArgs( + MouseDevice, + 0, + InputRoot!, + GetAvaloniaEventType(pressed.Button, true), + new Point(pressed.X, pressed.Y), + GetAvaloniaRawInputModifiers(pressed.Modifiers))); + }, DispatcherPriority.Input); + break; + + case PointerReleasedEventMessage released: + Dispatcher.UIThread.Post(() => + { + Input?.Invoke(new RawPointerEventArgs( + MouseDevice, + 0, + InputRoot!, + GetAvaloniaEventType(released.Button, false), + new Point(released.X, released.Y), + GetAvaloniaRawInputModifiers(released.Modifiers))); + }, DispatcherPriority.Input); + break; + + case ScrollEventMessage scroll: + Dispatcher.UIThread.Post(() => + { + Input?.Invoke(new RawMouseWheelEventArgs( + MouseDevice, + 0, + InputRoot!, + new Point(scroll.X, scroll.Y), + new Vector(scroll.DeltaX, scroll.DeltaY), + GetAvaloniaRawInputModifiers(scroll.Modifiers))); + }, DispatcherPriority.Input); + break; + + case KeyEventMessage key: + Dispatcher.UIThread.Post(() => + { + Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); + + Input?.Invoke(new RawKeyEventArgs( + KeyboardDevice, + 0, + InputRoot!, + key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, + (Key)key.Key, + GetAvaloniaRawInputModifiers(key.Modifiers))); + }, DispatcherPriority.Input); + break; + + case TextInputEventMessage text: + Dispatcher.UIThread.Post(() => + { + Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); + + Input?.Invoke(new RawTextInputEventArgs( + KeyboardDevice, + 0, + InputRoot!, + text.Text)); + }, DispatcherPriority.Input); + break; } } } From 4adf0b68cb32e4c968f006bc08d3179e6134a6b7 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 31 May 2023 23:17:30 +0200 Subject: [PATCH 48/60] Fixed previewer render scaling --- .../RemoteServerTopLevelImpl.Framebuffer.cs | 32 +++++++++---------- .../Remote/Server/RemoteServerTopLevelImpl.cs | 20 +++++------- .../Remote/PreviewerWindowImpl.cs | 6 +++- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs index 361c3033b5..593adfb225 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs @@ -19,17 +19,18 @@ namespace Avalonia.Controls.Remote.Server private sealed class Framebuffer { - public static Framebuffer Empty { get; } = - new(ProtocolPixelFormat.Rgba8888, default, new Vector(96.0, 96.0)); + public static Framebuffer Empty { get; } = new(ProtocolPixelFormat.Rgba8888, default, 1.0); + private readonly double _dpi; + private readonly PixelSize _frameSize; private readonly object _dataLock = new(); private readonly byte[] _data; // for rendering only private readonly byte[] _dataCopy; // for messages only private FrameStatus _status = FrameStatus.NotRendered; - public Framebuffer(ProtocolPixelFormat format, Size clientSize, Vector dpi) + public Framebuffer(ProtocolPixelFormat format, Size clientSize, double renderScaling) { - var frameSize = PixelSize.FromSizeWithDpi(clientSize, dpi); + var frameSize = PixelSize.FromSize(clientSize, renderScaling); if (frameSize.Width <= 0 || frameSize.Height <= 0) frameSize = PixelSize.Empty; @@ -37,10 +38,11 @@ namespace Avalonia.Controls.Remote.Server var stride = frameSize.Width * bpp; var dataLength = Math.Max(0, stride * frameSize.Height); + _dpi = renderScaling * 96.0; + _frameSize = frameSize; Format = format; - FrameSize = frameSize; ClientSize = clientSize; - Dpi = dpi; + RenderScaling = renderScaling; (Stride, _data, _dataCopy) = dataLength > 0 ? (stride, new byte[dataLength], new byte[dataLength]) : @@ -51,9 +53,7 @@ namespace Avalonia.Controls.Remote.Server public Size ClientSize { get; } - public Vector Dpi { get; } - - public PixelSize FrameSize { get; } + public double RenderScaling { get; } public int Stride { get; } @@ -72,9 +72,9 @@ namespace Avalonia.Controls.Remote.Server { return new LockedFramebuffer( handle.AddrOfPinnedObject(), - FrameSize, + _frameSize, Stride, - Dpi, + new Vector(_dpi, _dpi), new PlatformPixelFormat((PixelFormatEnum)Format), () => { @@ -93,7 +93,7 @@ namespace Avalonia.Controls.Remote.Server } } - /// The returned message must be kept around, as it contains a shared buffer. + /// The returned message must NOT be kept around, as it contains a shared buffer. public FrameMessage ToMessage(long sequenceId) { lock (_dataLock) @@ -104,11 +104,11 @@ namespace Avalonia.Controls.Remote.Server SequenceId = sequenceId, Data = _dataCopy, Format = Format, - Width = FrameSize.Width, - Height = FrameSize.Height, + Width = _frameSize.Width, + Height = _frameSize.Height, Stride = Stride, - DpiX = Dpi.X, - DpiY = Dpi.Y + DpiX = _dpi, + DpiY = _dpi }; } } diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index 632e7fa8a3..49af6a71a0 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -29,7 +29,6 @@ namespace Avalonia.Controls.Remote.Server private long _lastReceivedFrame = -1; private long _nextFrameNumber = 1; private ClientViewportAllocatedMessage? _pendingAllocation; - private Vector _dpi = new(96, 96); private ProtocolPixelFormat? _format; public RemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport) @@ -119,8 +118,11 @@ namespace Avalonia.Controls.Remote.Server break; case ClientRenderInfoMessage renderInfo: - _dpi = new Vector(renderInfo.DpiX, renderInfo.DpiY); - Dispatcher.UIThread.Post(_renderAndSendFrameIfNeeded); + Dispatcher.UIThread.Post(() => + { + RenderScaling = renderInfo.DpiX / 96.0; + RenderAndSendFrameIfNeeded(); + }); break; case ClientSupportedPixelFormatsMessage supportedFormats: @@ -152,7 +154,7 @@ namespace Avalonia.Controls.Remote.Server _pendingAllocation = null; } - _dpi = new Vector(allocation.DpiX, allocation.DpiY); + RenderScaling = allocation.DpiX / 96.0; ClientSize = new Size(allocation.Width, allocation.Height); RenderAndSendFrameIfNeeded(); }); @@ -258,12 +260,6 @@ namespace Avalonia.Controls.Remote.Server return null; } - protected void SetDpi(Vector dpi) - { - _dpi = dpi; - RenderAndSendFrameIfNeeded(); - } - protected virtual Size Measure(Size constraint) { var l = (Layoutable) InputRoot!; @@ -279,8 +275,8 @@ namespace Avalonia.Controls.Remote.Server { if (_format is not { } format) _framebuffer = Framebuffer.Empty; - else if (_framebuffer.Format != format || _framebuffer.ClientSize != ClientSize || _framebuffer.Dpi != _dpi) - _framebuffer = new Framebuffer(format, ClientSize, _dpi); + else if (_framebuffer.Format != format || _framebuffer.ClientSize != ClientSize || _framebuffer.RenderScaling != RenderScaling) + _framebuffer = new Framebuffer(format, ClientSize, RenderScaling); return _framebuffer; } diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 5fe9f203e7..b6c0c3ae3d 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -53,7 +53,11 @@ namespace Avalonia.DesignerSupport.Remote // In previewer mode we completely ignore client-side viewport size if (obj is ClientViewportAllocatedMessage alloc) { - Dispatcher.UIThread.Post(() => SetDpi(new Vector(alloc.DpiX, alloc.DpiY))); + Dispatcher.UIThread.Post(() => + { + RenderScaling = alloc.DpiX / 96.0; + RenderAndSendFrameIfNeeded(); + }); return; } base.OnMessage(transport, obj); From f4f3670c384d312e4d31161e637aff3f1ddf2f05 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 1 Jun 2023 07:32:21 +0200 Subject: [PATCH 49/60] Remove redundant using --- src/Avalonia.Controls/TextBox.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 873febd421..3016dc8239 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -17,7 +17,6 @@ using Avalonia.Controls.Metadata; using Avalonia.Media.TextFormatting; using Avalonia.Automation.Peers; using Avalonia.Threading; -using Avalonia.Collections; namespace Avalonia.Controls { From 3d9ef67c493144dbacb4347c487bce36ee77e5dd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 1 Jun 2023 15:42:51 +0600 Subject: [PATCH 50/60] Fixed _requestedCommits enumeration --- src/Avalonia.Base/Media/MediaContext.Compositor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Media/MediaContext.Compositor.cs b/src/Avalonia.Base/Media/MediaContext.Compositor.cs index 9bdd77960d..4ddc2ea9eb 100644 --- a/src/Avalonia.Base/Media/MediaContext.Compositor.cs +++ b/src/Avalonia.Base/Media/MediaContext.Compositor.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Avalonia.Platform; using Avalonia.Rendering.Composition; @@ -78,7 +79,7 @@ partial class MediaContext // Nothing to do, and there are no pending commits return false; - foreach (var c in _requestedCommits) + foreach (var c in _requestedCommits.ToArray()) CommitCompositor(c); _requestedCommits.Clear(); From 685682c5dba9ae69afd8a36215ad5ebd013b037b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 1 Jun 2023 15:43:15 +0600 Subject: [PATCH 51/60] Fixed race condition in Compositor.Commit --- src/Avalonia.Base/Rendering/Composition/Compositor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index dde9dcd6fb..5838811e9e 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs @@ -130,7 +130,7 @@ namespace Avalonia.Rendering.Composition Dispatcher.UIThread.VerifyAccess(); using var noPump = NonPumpingLockHelper.Use(); - _nextCommit ??= new(); + var commit = _nextCommit ??= new(); (_invokeBeforeCommitRead, _invokeBeforeCommitWrite) = (_invokeBeforeCommitWrite, _invokeBeforeCommitRead); while (_invokeBeforeCommitRead.Count > 0) @@ -188,7 +188,7 @@ namespace Avalonia.Rendering.Composition }, TaskContinuationOptions.ExecuteSynchronously); _nextCommit = null; - return _pendingBatch; + return commit; } } From 7808da21603f63e8d3974cc701bae3a758cd966b Mon Sep 17 00:00:00 2001 From: rabbitism Date: Fri, 2 Jun 2023 12:25:56 +0800 Subject: [PATCH 52/60] feat: attach transitions after ToggleSwitch loaded. --- src/Avalonia.Controls/ToggleSwitch.cs | 34 +++++++++++++++++-- .../Controls/ToggleSwitch.xaml | 21 ++++++------ .../Controls/ToggleSwitch.xaml | 18 +++++++--- 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Controls/ToggleSwitch.cs b/src/Avalonia.Controls/ToggleSwitch.cs index a68a022e67..989c3cb261 100644 --- a/src/Avalonia.Controls/ToggleSwitch.cs +++ b/src/Avalonia.Controls/ToggleSwitch.cs @@ -1,4 +1,5 @@ -using Avalonia.Controls.Metadata; +using Avalonia.Animation; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; @@ -42,6 +43,10 @@ namespace Avalonia.Controls x.UpdateKnobPos(x.IsChecked.Value); } }); + KnobTransitionsProperty.Changed.AddClassHandler((x, e) => + { + x.AssignTransitions(); + }); } /// @@ -68,6 +73,8 @@ namespace Avalonia.Controls public static readonly StyledProperty OnContentTemplateProperty = AvaloniaProperty.Register(nameof(OnContentTemplate)); + public static readonly StyledProperty KnobTransitionsProperty = AvaloniaProperty.Register(nameof(KnobTransitions)); + /// /// Gets or Sets the Content that is displayed when in the On State. /// @@ -116,6 +123,14 @@ namespace Avalonia.Controls set { SetValue(OnContentTemplateProperty, value); } } + public Transitions KnobTransitions + { + get { return GetValue(KnobTransitionsProperty); } + set { SetValue(KnobTransitionsProperty, value); } + } + + + private void OffContentChanged(AvaloniaPropertyChangedEventArgs e) { if (e.OldValue is ILogical oldChild) @@ -176,8 +191,23 @@ namespace Avalonia.Controls { UpdateKnobPos(IsChecked.Value); } + } - + + protected override void OnLoaded() + { + base.OnLoaded(); + AssignTransitions(); + } + + private void AssignTransitions() + { + if (_knobsPanel != null) + { + _knobsPanel.Transitions = KnobTransitions; + } + } + private void KnobsPanel_PointerPressed(object? sender, Input.PointerPressedEventArgs e) { _switchStartPoint = e.GetPosition(_switchKnob); diff --git a/src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml index 47d7953096..63c11f6ff8 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml @@ -28,6 +28,14 @@ + + + + + @@ -134,16 +142,9 @@ - - diff --git a/src/Avalonia.Themes.Simple/Controls/ToggleSwitch.xaml b/src/Avalonia.Themes.Simple/Controls/ToggleSwitch.xaml index a51aeb9ee6..1178c96419 100644 --- a/src/Avalonia.Themes.Simple/Controls/ToggleSwitch.xaml +++ b/src/Avalonia.Themes.Simple/Controls/ToggleSwitch.xaml @@ -46,6 +46,14 @@ + + + + + - - - - - + + + - - - - - -