From e3ab47247065c89d05935d9a3ec0d846bf75ab79 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 13 Dec 2022 12:17:10 +0000 Subject: [PATCH 01/57] set rounded corners when client is extended --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 8 ++++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index a7cca5b0f3..58133c21f8 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -820,6 +820,14 @@ namespace Avalonia.Win32.Interop DWMWA_LAST }; + public enum DwmWindowCornerPreference : uint + { + DWMWCP_DEFAULT = 0, + DWMWCP_DONOTROUND, + DWMWCP_ROUND, + DWMWCP_ROUNDSMALL + } + public enum MapVirtualKeyMapTypes : uint { MAPVK_VK_TO_VSC = 0x00, diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9e11959101..ec4f9d8381 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -1035,6 +1035,12 @@ namespace Avalonia.Win32 { var margins = UpdateExtendMargins(); DwmExtendFrameIntoClientArea(_hwnd, ref margins); + + unsafe + { + int cornerPreference = (int)DwmWindowCornerPreference.DWMWCP_ROUND; + DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPreference, sizeof(int)); + } } else { From 987bc200f089a7bec91c1169f423cb211439b831 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 13 Dec 2022 12:21:02 +0000 Subject: [PATCH 02/57] restore attribute to defaul when client area is restored --- src/Windows/Avalonia.Win32/WindowImpl.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index ec4f9d8381..54635d11cd 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -1051,6 +1051,12 @@ namespace Avalonia.Win32 _extendedMargins = new Thickness(); Resize(new Size(rcWindow.Width / RenderScaling, rcWindow.Height / RenderScaling), PlatformResizeReason.Layout); + + unsafe + { + int cornerPreference = (int)DwmWindowCornerPreference.DWMWCP_DEFAULT; + DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPreference, sizeof(int)); + } } if (!_isClientAreaExtended || (_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) && From 2f431b957043b53f98ecde0231fa3a36290416cf Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 14 Dec 2022 12:38:32 +0100 Subject: [PATCH 03/57] fix: Warning CS0436 --- src/Avalonia.X11/Avalonia.X11.csproj | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj index c762349c1c..061c516ef6 100644 --- a/src/Avalonia.X11/Avalonia.X11.csproj +++ b/src/Avalonia.X11/Avalonia.X11.csproj @@ -10,7 +10,12 @@ - - - + + + + + + + + From cfb8a896fa7010333d5c4e7ec82c34868ba5a639 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 17 Dec 2022 11:49:29 -0500 Subject: [PATCH 04/57] Modernize ComboBox properties --- src/Avalonia.Controls/ComboBox.cs | 71 ++++++++++++++++--------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index f02df2e9c1..564a2e478e 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -95,10 +95,9 @@ namespace Avalonia.Controls { ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); FocusableProperty.OverrideDefaultValue(true); - SelectedItemProperty.Changed.AddClassHandler((x, e) => x.SelectedItemChanged(e)); - KeyDownEvent.AddClassHandler((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel); IsTextSearchEnabledProperty.OverrideDefaultValue(true); - IsDropDownOpenProperty.Changed.AddClassHandler((x, e) => x.DropdownChanged(e)); + + KeyDownEvent.AddClassHandler((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel); } /// @@ -106,8 +105,8 @@ namespace Avalonia.Controls /// public bool IsDropDownOpen { - get { return _isDropDownOpen; } - set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); } + get => _isDropDownOpen; + set => SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); } /// @@ -115,8 +114,8 @@ namespace Avalonia.Controls /// public double MaxDropDownHeight { - get { return GetValue(MaxDropDownHeightProperty); } - set { SetValue(MaxDropDownHeightProperty, value); } + get => GetValue(MaxDropDownHeightProperty); + set => SetValue(MaxDropDownHeightProperty, value); } /// @@ -124,8 +123,8 @@ namespace Avalonia.Controls /// protected object? SelectionBoxItem { - get { return _selectionBoxItem; } - set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); } + get => _selectionBoxItem; + set => SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); } /// @@ -133,8 +132,8 @@ namespace Avalonia.Controls /// public string? PlaceholderText { - get { return GetValue(PlaceholderTextProperty); } - set { SetValue(PlaceholderTextProperty, value); } + get => GetValue(PlaceholderTextProperty); + set => SetValue(PlaceholderTextProperty, value); } /// @@ -142,8 +141,8 @@ namespace Avalonia.Controls /// public IBrush? PlaceholderForeground { - get { return GetValue(PlaceholderForegroundProperty); } - set { SetValue(PlaceholderForegroundProperty, value); } + get => GetValue(PlaceholderForegroundProperty); + set => SetValue(PlaceholderForegroundProperty, value); } /// @@ -151,8 +150,8 @@ namespace Avalonia.Controls /// public ItemVirtualizationMode VirtualizationMode { - get { return GetValue(VirtualizationModeProperty); } - set { SetValue(VirtualizationModeProperty, value); } + get => GetValue(VirtualizationModeProperty); + set => SetValue(VirtualizationModeProperty, value); } /// @@ -160,8 +159,8 @@ namespace Avalonia.Controls /// public HorizontalAlignment HorizontalContentAlignment { - get { return GetValue(HorizontalContentAlignmentProperty); } - set { SetValue(HorizontalContentAlignmentProperty, value); } + get => GetValue(HorizontalContentAlignmentProperty); + set => SetValue(HorizontalContentAlignmentProperty, value); } /// @@ -169,8 +168,8 @@ namespace Avalonia.Controls /// public VerticalAlignment VerticalContentAlignment { - get { return GetValue(VerticalContentAlignmentProperty); } - set { SetValue(VerticalContentAlignmentProperty, value); } + get => GetValue(VerticalContentAlignmentProperty); + set => SetValue(VerticalContentAlignmentProperty, value); } /// @@ -182,6 +181,7 @@ namespace Avalonia.Controls ComboBoxItem.ContentTemplateProperty); } + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); @@ -234,7 +234,7 @@ namespace Avalonia.Controls } // This part of code is needed just to acquire initial focus, subsequent focus navigation will be done by ItemsControl. else if (IsDropDownOpen && SelectedIndex < 0 && ItemCount > 0 && - (e.Key == Key.Up || e.Key == Key.Down) && IsFocused == true) + (e.Key == Key.Up || e.Key == Key.Down) && IsFocused == true) { var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c)); if (firstChild != null) @@ -304,12 +304,11 @@ namespace Avalonia.Controls e.Handled = true; } } + PseudoClasses.Set(pcPressed, false); base.OnPointerReleased(e); - } - /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { @@ -324,6 +323,22 @@ namespace Avalonia.Controls _popup.Closed += PopupClosed; } + /// + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + if (change.Property == SelectedItemProperty) + { + UpdateSelectionBoxItem(change.NewValue); + TryFocusSelectedItem(); + } + else if (change.Property == IsDropDownOpenProperty) + { + PseudoClasses.Set(pcDropdownOpen, change.GetNewValue()); + } + + base.OnPropertyChanged(change); + } + protected override AutomationPeer OnCreateAutomationPeer() { return new ComboBoxAutomationPeer(this); @@ -384,12 +399,6 @@ namespace Avalonia.Controls } } - private void SelectedItemChanged(AvaloniaPropertyChangedEventArgs e) - { - UpdateSelectionBoxItem(e.NewValue); - TryFocusSelectedItem(); - } - private void TryFocusSelectedItem() { var selectedIndex = SelectedIndex; @@ -489,11 +498,5 @@ namespace Avalonia.Controls MoveSelection(NavigationDirection.Previous, WrapSelection); } } - - private void DropdownChanged(AvaloniaPropertyChangedEventArgs e) - { - bool newValue = e.GetNewValue(); - PseudoClasses.Set(pcDropdownOpen, newValue); - } } } From 9b24a5bed64d6d5695fa073f8468481339b74af4 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 17 Dec 2022 11:53:17 -0500 Subject: [PATCH 05/57] Remove duplicate OnKeyDown handler --- src/Avalonia.Controls/ComboBox.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 564a2e478e..f3179a4644 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -96,8 +96,6 @@ namespace Avalonia.Controls ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); FocusableProperty.OverrideDefaultValue(true); IsTextSearchEnabledProperty.OverrideDefaultValue(true); - - KeyDownEvent.AddClassHandler((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel); } /// From 53a78131f54440ff863804636545d79882d203fb Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 17 Dec 2022 13:23:26 -0500 Subject: [PATCH 06/57] Add DropDownOpened and DropDownClosed events to ComboBox --- src/Avalonia.Controls/ComboBox.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index f3179a4644..c17b42b684 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -98,6 +98,16 @@ namespace Avalonia.Controls IsTextSearchEnabledProperty.OverrideDefaultValue(true); } + /// + /// Occurs after the drop-down (popup) list of the closes. + /// + public event EventHandler? DropDownClosed; + + /// + /// Occurs after the drop-down (popup) list of the opens. + /// + public event EventHandler? DropDownOpened; + /// /// Gets or sets a value indicating whether the dropdown is currently open. /// @@ -358,6 +368,8 @@ namespace Avalonia.Controls { Focus(); } + + DropDownClosed?.Invoke(this, EventArgs.Empty); } private void PopupOpened(object? sender, EventArgs e) @@ -387,6 +399,8 @@ namespace Avalonia.Controls } UpdateFlowDirection(); + + DropDownOpened?.Invoke(this, EventArgs.Empty); } private void IsVisibleChanged(bool isVisible) From 6497e49c267b28f365bbc735fa25de8b0609f610 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 18 Dec 2022 12:14:19 -0500 Subject: [PATCH 07/57] Open/Close ComboBox drop-down with Enter and Space --- src/Avalonia.Controls/ComboBox.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index f02df2e9c1..a0085c92ca 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -213,7 +213,12 @@ namespace Avalonia.Controls IsDropDownOpen = false; e.Handled = true; } - else if (IsDropDownOpen && e.Key == Key.Enter) + else if (!IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Space)) + { + IsDropDownOpen = true; + e.Handled = true; + } + else if (IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Space)) { SelectFocusedItem(); IsDropDownOpen = false; From b2cf36fc5969b610cb64014821750af4090493e1 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 21 Dec 2022 11:40:46 +0000 Subject: [PATCH 08/57] make ScrollGestureRecognizer.ScrollStartDistance a property --- .../ScrollGestureRecognizer.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 7bcb81767d..02c7e7e4a3 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -16,6 +16,7 @@ namespace Avalonia.Input.GestureRecognizers private bool _canHorizontallyScroll; private bool _canVerticallyScroll; private int _gestureId; + private int _scrollStartDistance = 30; // Movement per second private Vector _inertia; @@ -38,6 +39,15 @@ namespace Avalonia.Input.GestureRecognizers nameof(CanVerticallyScroll), o => o.CanVerticallyScroll, (o, v) => o.CanVerticallyScroll = v); + + /// + /// Defines the property. + /// + public static readonly DirectProperty ScrollStartDistanceProperty = + AvaloniaProperty.RegisterDirect( + nameof(ScrollStartDistance), + o => o.ScrollStartDistance, + (o, v) => o.ScrollStartDistance = v); /// /// Gets or sets a value indicating whether the content can be scrolled horizontally. @@ -56,6 +66,15 @@ namespace Avalonia.Input.GestureRecognizers get => _canVerticallyScroll; set => SetAndRaise(CanVerticallyScrollProperty, ref _canVerticallyScroll, value); } + + /// + /// Gets or sets a value indicating the distance to move the pointer before scrolling is started + /// + public int ScrollStartDistance + { + get => _scrollStartDistance; + set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value); + } public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) @@ -75,9 +94,6 @@ namespace Avalonia.Input.GestureRecognizers _trackedRootPoint = e.GetPosition((Visual?)_target); } } - - // Arbitrary chosen value, probably need to move that to platform settings or something - private const double ScrollStartDistance = 30; // Pixels per second speed that is considered to be the stop of inertial scroll private const double InertialScrollSpeedEnd = 5; From 8e2f9d53461a4478bf1022dc61c437284542ddae Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 21 Dec 2022 18:06:17 +0000 Subject: [PATCH 09/57] And Pinch gensture --- .../PinchGestureRecognizer.cs | 131 ++++++++++++++++++ .../ScrollGestureRecognizer.cs | 4 +- src/Avalonia.Base/Input/Gestures.cs | 8 ++ src/Avalonia.Base/Input/PinchEventArgs.cs | 24 ++++ 4 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs create mode 100644 src/Avalonia.Base/Input/PinchEventArgs.cs diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs new file mode 100644 index 0000000000..57bd76c5da --- /dev/null +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -0,0 +1,131 @@ +using Avalonia.Input.GestureRecognizers; + +namespace Avalonia.Input +{ + public class PinchGestureRecognizer : StyledElement, IGestureRecognizer + { + private IInputElement? _target; + private IGestureRecognizerActionsDispatcher? _actions; + private float _initialDistance; + private IPointer? _firstContact; + private Point _firstPoint; + private IPointer? _secondContact; + private Point _secondPoint; + private Point _origin; + + public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) + { + _target = target; + _actions = actions; + + _target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); + _target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); + } + + private void OnPointerPressed(object? sender, PointerPressedEventArgs e) + { + PointerPressed(e); + } + + private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) + { + PointerReleased(e); + } + + public void PointerCaptureLost(IPointer pointer) + { + RemoveContact(pointer); + } + + public void PointerMoved(PointerEventArgs e) + { + if (_target != null && _target is Visual visual) + { + if(_firstContact == e.Pointer) + { + _firstPoint = e.GetPosition(visual); + } + else if (_secondContact == e.Pointer) + { + _secondPoint = e.GetPosition(visual); + } + else + { + return; + } + + if (_firstContact != null && _secondContact != null) + { + var distance = GetDistance(_firstPoint, _secondPoint); + + var scale = distance / _initialDistance; + + _target?.RaiseEvent(new PinchEventArgs(scale, _origin)); + } + } + } + + public void PointerPressed(PointerPressedEventArgs e) + { + if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) + { + if (_firstContact == null) + { + _firstContact = e.Pointer; + _firstPoint = e.GetPosition(visual); + + return; + } + else if (_secondContact == null && _firstContact != e.Pointer) + { + _secondContact = e.Pointer; + _secondPoint = e.GetPosition(visual); + } + else + { + return; + } + + if (_firstContact != null && _secondContact != null) + { + _initialDistance = GetDistance(_firstPoint, _secondPoint); + + _origin = new Point((_firstPoint.X + _secondPoint.X) / 2.0f, (_firstPoint.Y + _secondPoint.Y) / 2.0f); + + _actions!.Capture(_firstContact, this); + _actions!.Capture(_secondContact, this); + } + } + } + + public void PointerReleased(PointerReleasedEventArgs e) + { + RemoveContact(e.Pointer); + } + + private void RemoveContact(IPointer pointer) + { + if (_firstContact == pointer || _secondContact == pointer) + { + if (_secondContact == pointer) + { + _secondContact = null; + } + + if (_firstContact == pointer) + { + _firstContact = _secondContact; + + _secondContact = null; + } + _target?.RaiseEvent(new PinchEndedEventArgs()); + } + } + + private float GetDistance(Point a, Point b) + { + var length = _secondPoint - _firstPoint; + return (float)new Vector(length.X, length.Y).Length; + } + } +} diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 02c7e7e4a3..7dc4ab3f2e 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -68,7 +68,7 @@ namespace Avalonia.Input.GestureRecognizers } /// - /// Gets or sets a value indicating the distance to move the pointer before scrolling is started + /// Gets or sets a value indicating the distance the pointer moves before scrolling is started /// public int ScrollStartDistance { @@ -96,7 +96,7 @@ namespace Avalonia.Input.GestureRecognizers } // Pixels per second speed that is considered to be the stop of inertial scroll - private const double InertialScrollSpeedEnd = 5; + private const double InertialScrollSpeedEnd = 0; public void PointerMoved(PointerEventArgs e) { diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 1ea88fe824..b4d5feaf3b 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -46,6 +46,14 @@ namespace Avalonia.Input private static readonly WeakReference s_lastPress = new WeakReference(null); private static Point s_lastPressPoint; + public static readonly RoutedEvent PinchEvent = + RoutedEvent.Register( + "PinchEvent", RoutingStrategies.Bubble, typeof(Gestures)); + + public static readonly RoutedEvent PinchEndedEvent = + RoutedEvent.Register( + "PinchEndedEvent", RoutingStrategies.Bubble, typeof(Gestures)); + public static readonly RoutedEvent PullGestureEvent = RoutedEvent.Register( "PullGesture", RoutingStrategies.Bubble, typeof(Gestures)); diff --git a/src/Avalonia.Base/Input/PinchEventArgs.cs b/src/Avalonia.Base/Input/PinchEventArgs.cs new file mode 100644 index 0000000000..31c760eb51 --- /dev/null +++ b/src/Avalonia.Base/Input/PinchEventArgs.cs @@ -0,0 +1,24 @@ +using Avalonia.Interactivity; + +namespace Avalonia.Input +{ + public class PinchEventArgs : RoutedEventArgs + { + public PinchEventArgs(double scale, Point scaleOrigin) : base(Gestures.PinchEvent) + { + Scale = scale; + ScaleOrigin = scaleOrigin; + } + + public double Scale { get; } = 1; + + public Point ScaleOrigin { get; } + } + + public class PinchEndedEventArgs : RoutedEventArgs + { + public PinchEndedEventArgs() : base(Gestures.PinchEndedEvent) + { + } + } +} From b9827c39cec113f02c586ad259860ffe8fb8dc21 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 21 Dec 2022 18:06:28 +0000 Subject: [PATCH 10/57] add Gestures sample page --- samples/ControlCatalog/MainView.xaml | 3 + samples/ControlCatalog/Pages/GesturePage.cs | 214 ++++++++++++++++++ samples/ControlCatalog/Pages/GesturePage.xaml | 117 ++++++++++ 3 files changed, 334 insertions(+) create mode 100644 samples/ControlCatalog/Pages/GesturePage.cs create mode 100644 samples/ControlCatalog/Pages/GesturePage.xaml diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 4a5f5bc96c..9e0fb3f852 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -92,6 +92,9 @@ + + + diff --git a/samples/ControlCatalog/Pages/GesturePage.cs b/samples/ControlCatalog/Pages/GesturePage.cs new file mode 100644 index 0000000000..d5d016ed1a --- /dev/null +++ b/samples/ControlCatalog/Pages/GesturePage.cs @@ -0,0 +1,214 @@ +using System; +using System.Diagnostics; +using System.Numerics; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.LogicalTree; +using Avalonia.Markup.Xaml; +using Avalonia.Rendering.Composition; +using Avalonia.VisualTree; + +namespace ControlCatalog.Pages +{ + public class GesturePage : UserControl + { + private bool _isInit; + private float _currentScale; + + public GesturePage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + if(_isInit) + { + return; + } + + _isInit = true; + + SetPullHandlers(this.Find("TopPullZone"), false); + SetPullHandlers(this.Find("BottomPullZone"), true); + SetPullHandlers(this.Find("RightPullZone"), true); + SetPullHandlers(this.Find("LeftPullZone"), false); + + var image = this.Find("PinchImage"); + SetPinchHandlers(image); + + var reset = this.Find + + From 8a48d58e5b23a36188801bd79ed3298908440adf Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 21 Dec 2022 18:06:56 +0000 Subject: [PATCH 11/57] remove redundant pointer event handled in pull gesture --- .../PullGestureRecognizer.cs | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs index fedd07ec32..23bab13fc8 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs @@ -1,15 +1,19 @@ -using Avalonia.Input.GestureRecognizers; +using System; +using Avalonia.Input.GestureRecognizers; namespace Avalonia.Input { public class PullGestureRecognizer : StyledElement, IGestureRecognizer { + internal static int MinPullDetectionSize = 50; + private IInputElement? _target; private IGestureRecognizerActionsDispatcher? _actions; private Point _initialPosition; private int _gestureId; private IPointer? _tracking; private PullDirection _pullDirection; + private bool _pullInProgress; /// /// Defines the property. @@ -31,23 +35,12 @@ namespace Avalonia.Input PullDirection = pullDirection; } + public PullGestureRecognizer() { } + public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) { _target = target; _actions = actions; - - _target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); - _target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); - } - - private void OnPointerPressed(object? sender, PointerPressedEventArgs e) - { - PointerPressed(e); - } - - private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) - { - PointerReleased(e); } public void PointerCaptureLost(IPointer pointer) @@ -94,6 +87,7 @@ namespace Avalonia.Input break; } + _pullInProgress = true; _target?.RaiseEvent(new PullGestureEventArgs(_gestureId, delta, PullDirection)); } } @@ -111,16 +105,16 @@ namespace Avalonia.Input switch (PullDirection) { case PullDirection.TopToBottom: - canPull = position.Y < bounds.Height * 0.1; + canPull = position.Y < Math.Max(MinPullDetectionSize, bounds.Height * 0.1); break; case PullDirection.BottomToTop: - canPull = position.Y > bounds.Height - (bounds.Height * 0.1); + canPull = position.Y > Math.Min(bounds.Height - MinPullDetectionSize, bounds.Height - (bounds.Height * 0.1)); break; case PullDirection.LeftToRight: - canPull = position.X < bounds.Width * 0.1; + canPull = position.X < Math.Max(MinPullDetectionSize, bounds.Width * 0.1); break; case PullDirection.RightToLeft: - canPull = position.X > bounds.Width - (bounds.Width * 0.1); + canPull = position.X > Math.Min(bounds.Width - MinPullDetectionSize, bounds.Width - (bounds.Width * 0.1)); break; } @@ -135,7 +129,7 @@ namespace Avalonia.Input public void PointerReleased(PointerReleasedEventArgs e) { - if (_tracking == e.Pointer) + if (_tracking == e.Pointer && _pullInProgress) { EndPull(); } @@ -145,6 +139,7 @@ namespace Avalonia.Input { _tracking = null; _initialPosition = default; + _pullInProgress = false; _target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection)); } From f09a901e48f59e5dcb9e46348f2e96a959e20eca Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 21 Dec 2022 18:20:38 +0000 Subject: [PATCH 12/57] cleanup --- samples/ControlCatalog/Pages/GesturePage.cs | 2 -- .../Input/GestureRecognizers/PinchGestureRecognizer.cs | 3 --- 2 files changed, 5 deletions(-) diff --git a/samples/ControlCatalog/Pages/GesturePage.cs b/samples/ControlCatalog/Pages/GesturePage.cs index d5d016ed1a..ee10f21317 100644 --- a/samples/ControlCatalog/Pages/GesturePage.cs +++ b/samples/ControlCatalog/Pages/GesturePage.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Numerics; using Avalonia; using Avalonia.Controls; @@ -7,7 +6,6 @@ using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Markup.Xaml; using Avalonia.Rendering.Composition; -using Avalonia.VisualTree; namespace ControlCatalog.Pages { diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs index 57bd76c5da..eea7c3b7d1 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -17,9 +17,6 @@ namespace Avalonia.Input { _target = target; _actions = actions; - - _target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); - _target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); } private void OnPointerPressed(object? sender, PointerPressedEventArgs e) From 8a78f134c57abf490a755ec2f3db112b9547b0a3 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 21 Dec 2022 20:47:28 -0500 Subject: [PATCH 13/57] Remove JetBrains.Annotations from the repo --- Avalonia.sln | 1 - build/JetBrains.Annotations.props | 5 --- nukebuild/DotNetConfigHelper.cs | 1 - .../Animation/AnimationInstance`1.cs | 1 - src/Avalonia.Base/Avalonia.Base.csproj | 1 - src/Avalonia.Base/Contract.cs | 34 ------------------- src/Avalonia.Base/Data/InstancedBinding.cs | 4 +-- .../Input/Raw/RawInputEventArgs.cs | 6 ++-- .../Input/Raw/RawPointerEventArgs.cs | 6 ---- src/Avalonia.Base/Media/DrawingGroup.cs | 7 ++-- src/Avalonia.Base/Media/Typeface.cs | 3 +- src/Avalonia.Base/PixelVector.cs | 2 -- src/Avalonia.Base/Visual.cs | 5 ++- .../Avalonia.Controls.ColorPicker.csproj | 1 - .../Avalonia.Controls.DataGrid.csproj | 1 - .../DataGridColumn.cs | 4 +-- .../DataGridRows.cs | 1 - .../Primitives/DataGridFrozenGrid.cs | 2 -- .../Avalonia.Controls.csproj | 1 - .../Utils/BorderRenderHelper.cs | 1 - src/Avalonia.Controls/WindowBase.cs | 1 - .../LinuxMountedVolumeInfoProvider.cs | 3 +- src/Avalonia.Native/AvaloniaNativePlatform.cs | 23 +++++++------ src/Avalonia.Native/Helpers.cs | 1 - .../MacOSMountedVolumeInfoProvider.cs | 1 - src/Avalonia.X11/NativeDialogs/Gtk.cs | 1 - src/Avalonia.X11/X11Info.cs | 1 - src/Avalonia.X11/X11Screens.cs | 1 - src/Avalonia.X11/X11Window.Ime.cs | 3 +- .../DrmOutputOptions.cs | 1 - .../LinuxFramebufferPlatform.cs | 32 ++++++++--------- .../Output/DrmOutput.cs | 3 +- .../CompiledBindings/CommandAccessorPlugin.cs | 4 +-- .../PropertyInfoAccessorFactory.cs | 14 +++----- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 6 ++-- src/Skia/Avalonia.Skia/GlyphRunImpl.cs | 4 +-- .../Avalonia.Direct2D1.csproj | 1 - src/Windows/Avalonia.Win32/Win32Platform.cs | 5 +-- .../WindowsMountedVolumeInfoProvider.cs | 1 - src/iOS/Avalonia.iOS/AvaloniaView.Text.cs | 1 - .../Rendering/CompositorTestsBase.cs | 1 - .../Data/AccessorTestObject.cs | 1 - .../AvaloniaPropertyDictionaryBenchmarks.cs | 2 +- .../TreeViewTests.cs | 1 - .../Xaml/XamlIlTests.cs | 2 -- 45 files changed, 60 insertions(+), 141 deletions(-) delete mode 100644 build/JetBrains.Annotations.props delete mode 100644 src/Avalonia.Base/Contract.cs diff --git a/Avalonia.sln b/Avalonia.sln index e6898131b0..fc42a5d63b 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -100,7 +100,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\EmbedXaml.props = build\EmbedXaml.props build\HarfBuzzSharp.props = build\HarfBuzzSharp.props build\ImageSharp.props = build\ImageSharp.props - build\JetBrains.Annotations.props = build\JetBrains.Annotations.props build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props build\Microsoft.CSharp.props = build\Microsoft.CSharp.props build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props diff --git a/build/JetBrains.Annotations.props b/build/JetBrains.Annotations.props deleted file mode 100644 index 7bc12cbd84..0000000000 --- a/build/JetBrains.Annotations.props +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/nukebuild/DotNetConfigHelper.cs b/nukebuild/DotNetConfigHelper.cs index eca1e2684d..9d43261616 100644 --- a/nukebuild/DotNetConfigHelper.cs +++ b/nukebuild/DotNetConfigHelper.cs @@ -1,5 +1,4 @@ using System.Globalization; -using JetBrains.Annotations; using Nuke.Common.Tools.DotNet; // ReSharper disable ReturnValueOfPureMethodIsNotUsed diff --git a/src/Avalonia.Base/Animation/AnimationInstance`1.cs b/src/Avalonia.Base/Animation/AnimationInstance`1.cs index 0881fde988..6a6e69894b 100644 --- a/src/Avalonia.Base/Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Base/Animation/AnimationInstance`1.cs @@ -5,7 +5,6 @@ using Avalonia.Animation.Animators; using Avalonia.Animation.Utils; using Avalonia.Data; using Avalonia.Reactive; -using JetBrains.Annotations; namespace Avalonia.Animation { diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 0d3da66f7a..94a9ea8352 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -15,7 +15,6 @@ - diff --git a/src/Avalonia.Base/Contract.cs b/src/Avalonia.Base/Contract.cs deleted file mode 100644 index 27427700ac..0000000000 --- a/src/Avalonia.Base/Contract.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using JetBrains.Annotations; - -namespace Avalonia -{ - /// - /// A stub of Code Contract's Contract class. - /// - /// - /// It would be nice to use Code Contracts on Avalonia but last time I tried it slowed things - /// to a crawl and often crashed. Instead use the same signature for checking preconditions - /// in the hope that it might become usable at some point. - /// - public static class Contract - { - /// - /// Specifies a precondition. - /// - /// - /// The exception to throw if is false. - /// - /// The precondition. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("condition:false=>stop")] - public static void Requires(bool condition) where TException : Exception, new() - { - if (!condition) - { - throw new TException(); - } - } - } -} diff --git a/src/Avalonia.Base/Data/InstancedBinding.cs b/src/Avalonia.Base/Data/InstancedBinding.cs index a349486bf8..4a1e2660de 100644 --- a/src/Avalonia.Base/Data/InstancedBinding.cs +++ b/src/Avalonia.Base/Data/InstancedBinding.cs @@ -28,11 +28,9 @@ namespace Avalonia.Data /// public InstancedBinding(ISubject subject, BindingMode mode, BindingPriority priority) { - Contract.Requires(subject != null); - Mode = mode; Priority = priority; - Value = subject; + Value = subject ?? throw new ArgumentNullException(nameof(subject)); } private InstancedBinding(object? value, BindingMode mode, BindingPriority priority) diff --git a/src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs b/src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs index 3a5ae1340f..cf4d37b6da 100644 --- a/src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs +++ b/src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs @@ -21,11 +21,9 @@ namespace Avalonia.Input.Raw /// The root from which the event originates. public RawInputEventArgs(IInputDevice device, ulong timestamp, IInputRoot root) { - device = device ?? throw new ArgumentNullException(nameof(device)); - - Device = device; + Device = device ?? throw new ArgumentNullException(nameof(device)); Timestamp = timestamp; - Root = root; + Root = root ?? throw new ArgumentNullException(nameof(root)); } /// diff --git a/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs b/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs index 854dd4b83b..ebedc9e6e8 100644 --- a/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs +++ b/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs @@ -53,9 +53,6 @@ namespace Avalonia.Input.Raw RawInputModifiers inputModifiers) : base(device, timestamp, root) { - Contract.Requires(device != null); - Contract.Requires(root != null); - Point = new RawPointerPoint(); Position = position; Type = type; @@ -80,9 +77,6 @@ namespace Avalonia.Input.Raw RawInputModifiers inputModifiers) : base(device, timestamp, root) { - Contract.Requires(device != null); - Contract.Requires(root != null); - Point = point; Type = type; InputModifiers = inputModifiers; diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs index e71f568207..4a46d89153 100644 --- a/src/Avalonia.Base/Media/DrawingGroup.cs +++ b/src/Avalonia.Base/Media/DrawingGroup.cs @@ -461,9 +461,10 @@ namespace Avalonia.Media if (_rootDrawing == null) { - // When a DrawingGroup is set, it should be made the root if - // a root drawing didnt exist. - Contract.Requires(_currentDrawingGroup == null); + if (_currentDrawingGroup != null) + { + throw new NotSupportedException("When a DrawingGroup is set, it should be made the root if a root drawing didnt exist."); + } // If this is the first Drawing being added, avoid creating a DrawingGroup // and set this drawing as the root drawing. This optimizes the common diff --git a/src/Avalonia.Base/Media/Typeface.cs b/src/Avalonia.Base/Media/Typeface.cs index e6047bf96c..1e744c30c8 100644 --- a/src/Avalonia.Base/Media/Typeface.cs +++ b/src/Avalonia.Base/Media/Typeface.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using JetBrains.Annotations; namespace Avalonia.Media { @@ -17,7 +16,7 @@ namespace Avalonia.Media /// The font style. /// The font weight. /// The font stretch. - public Typeface([NotNull] FontFamily fontFamily, + public Typeface(FontFamily fontFamily, FontStyle style = FontStyle.Normal, FontWeight weight = FontWeight.Normal, FontStretch stretch = FontStretch.Normal) diff --git a/src/Avalonia.Base/PixelVector.cs b/src/Avalonia.Base/PixelVector.cs index 79e7b94c21..0e156190a4 100644 --- a/src/Avalonia.Base/PixelVector.cs +++ b/src/Avalonia.Base/PixelVector.cs @@ -1,7 +1,6 @@ using System; using System.Globalization; using Avalonia.Animation.Animators; -using JetBrains.Annotations; namespace Avalonia { @@ -135,7 +134,6 @@ namespace Avalonia /// /// The other vector. /// True if vectors are nearly equal. - [Pure] public bool NearlyEquals(PixelVector other) { const float tolerance = float.Epsilon; diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index ddaea01b05..5930df5483 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -367,7 +367,10 @@ namespace Avalonia /// The drawing context. public virtual void Render(DrawingContext context) { - Contract.Requires(context != null); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } } /// diff --git a/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj b/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj index f3f8e4c82c..97f9efe8fa 100644 --- a/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj +++ b/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj @@ -17,7 +17,6 @@ - diff --git a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj index 6369961f0f..efa38e49a7 100644 --- a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj +++ b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj @@ -14,7 +14,6 @@ - diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index 2b715d6a9f..75101dc487 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -641,7 +641,7 @@ namespace Avalonia.Controls public Control GetCellContent(DataGridRow dataGridRow) { - Contract.Requires(dataGridRow != null); + dataGridRow = dataGridRow ?? throw new ArgumentNullException(nameof(dataGridRow)); if (OwningGrid == null) { throw DataGridError.DataGrid.NoOwningGrid(GetType()); @@ -659,7 +659,7 @@ namespace Avalonia.Controls public Control GetCellContent(object dataItem) { - Contract.Requires(dataItem != null); + dataItem = dataItem ?? throw new ArgumentNullException(nameof(dataItem)); if (OwningGrid == null) { throw DataGridError.DataGrid.NoOwningGrid(GetType()); diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs index 6c997f62c1..4d3bccee70 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs @@ -16,7 +16,6 @@ using System.Diagnostics; using System.Linq; using Avalonia.Data; using Avalonia.Styling; -using JetBrains.Annotations; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs index 9feca71cda..4fd0ca3d26 100644 --- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs +++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs @@ -26,7 +26,6 @@ namespace Avalonia.Controls.Primitives /// true if the grid is frozen; otherwise, false. The default is true. public static bool GetIsFrozen(Control element) { - Contract.Requires(element != null); return element.GetValue(IsFrozenProperty); } @@ -38,7 +37,6 @@ namespace Avalonia.Controls.Primitives /// is null. public static void SetIsFrozen(Control element, bool value) { - Contract.Requires(element != null); element.SetValue(IsFrozenProperty, value); } } diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 2710ac5cc2..cf4beab6d4 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -7,7 +7,6 @@ - diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs index eb9f38894d..6239a5120d 100644 --- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs +++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs @@ -4,7 +4,6 @@ using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Platform; using Avalonia.Utilities; -using JetBrains.Annotations; namespace Avalonia.Controls.Utils { diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index b71dc6df44..65325aac92 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -8,7 +8,6 @@ using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Platform; -using JetBrains.Annotations; namespace Avalonia.Controls { diff --git a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs index b69ea68a76..4624a9c340 100644 --- a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs +++ b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs @@ -9,8 +9,7 @@ namespace Avalonia.FreeDesktop { public IDisposable Listen(ObservableCollection mountedDrives) { - Contract.Requires(mountedDrives != null); - return new LinuxMountedVolumeInfoListener(ref mountedDrives!); + return new LinuxMountedVolumeInfoListener(ref mountedDrives); } } } diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index a5b2ea30cc..6d5925c0ae 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -9,23 +9,23 @@ using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Composition; -using JetBrains.Annotations; using MicroCom.Runtime; +#nullable enable namespace Avalonia.Native { class AvaloniaNativePlatform : IWindowingPlatform { private readonly IAvaloniaNativeFactory _factory; - private AvaloniaNativePlatformOptions _options; - private AvaloniaNativeGlPlatformGraphics _platformGl; + private AvaloniaNativePlatformOptions? _options; + private AvaloniaNativeGlPlatformGraphics? _platformGl; [DllImport("libAvaloniaNative")] static extern IntPtr CreateAvaloniaNative(); internal static readonly KeyboardDevice KeyboardDevice = new KeyboardDevice(); - [CanBeNull] internal static Compositor Compositor { get; private set; } - [CanBeNull] internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; } + internal static Compositor? Compositor { get; private set; } + internal static PlatformRenderInterfaceContextManager? RenderInterface { get; private set; } public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options) { @@ -63,7 +63,7 @@ namespace Avalonia.Native public void SetupApplicationName() { - if (!string.IsNullOrWhiteSpace(Application.Current.Name)) + if (!string.IsNullOrWhiteSpace(Application.Current!.Name)) { _factory.MacOptions.SetApplicationTitle(Application.Current.Name); } @@ -118,10 +118,13 @@ namespace Avalonia.Native AvaloniaLocator.CurrentMutable.Bind().ToConstant(renderLoop); var hotkeys = AvaloniaLocator.Current.GetService(); - 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)); + 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)); + } if (_options.UseGpu) { diff --git a/src/Avalonia.Native/Helpers.cs b/src/Avalonia.Native/Helpers.cs index 764ff789dc..6270cd30a0 100644 --- a/src/Avalonia.Native/Helpers.cs +++ b/src/Avalonia.Native/Helpers.cs @@ -1,5 +1,4 @@ using Avalonia.Native.Interop; -using JetBrains.Annotations; namespace Avalonia.Native { diff --git a/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs b/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs index 92b2915e2e..1907a3d129 100644 --- a/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs +++ b/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs @@ -72,7 +72,6 @@ namespace Avalonia.Native { public IDisposable Listen(ObservableCollection mountedDrives) { - Contract.Requires(mountedDrives != null); return new MacOSMountedVolumeInfoListener(mountedDrives); } } diff --git a/src/Avalonia.X11/NativeDialogs/Gtk.cs b/src/Avalonia.X11/NativeDialogs/Gtk.cs index d5eae037a9..4e56ae73cf 100644 --- a/src/Avalonia.X11/NativeDialogs/Gtk.cs +++ b/src/Avalonia.X11/NativeDialogs/Gtk.cs @@ -3,7 +3,6 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Avalonia.Platform.Interop; -using JetBrains.Annotations; // ReSharper disable IdentifierTypo namespace Avalonia.X11.NativeDialogs diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs index 13dc460f45..72f3bf3137 100644 --- a/src/Avalonia.X11/X11Info.cs +++ b/src/Avalonia.X11/X11Info.cs @@ -2,7 +2,6 @@ using System; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; -using JetBrains.Annotations; using static Avalonia.X11.XLib; // ReSharper disable UnusedAutoPropertyAccessor.Local namespace Avalonia.X11 diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs index ba6029b350..87899b11ed 100644 --- a/src/Avalonia.X11/X11Screens.cs +++ b/src/Avalonia.X11/X11Screens.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Runtime.InteropServices; using Avalonia.Platform; using static Avalonia.X11.XLib; -using JetBrains.Annotations; namespace Avalonia.X11 { diff --git a/src/Avalonia.X11/X11Window.Ime.cs b/src/Avalonia.X11/X11Window.Ime.cs index 257580a5ec..e6066c7964 100644 --- a/src/Avalonia.X11/X11Window.Ime.cs +++ b/src/Avalonia.X11/X11Window.Ime.cs @@ -7,7 +7,6 @@ using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Platform.Interop; -using JetBrains.Annotations; using static Avalonia.X11.XLib; namespace Avalonia.X11 @@ -206,7 +205,7 @@ namespace Avalonia.X11 // This class is used to attach the text value of the key to an asynchronously dispatched KeyDown event class RawKeyEventArgsWithText : RawKeyEventArgs { - public RawKeyEventArgsWithText([NotNull] IKeyboardDevice device, ulong timestamp, [NotNull] IInputRoot root, + public RawKeyEventArgsWithText(IKeyboardDevice device, ulong timestamp, IInputRoot root, RawKeyEventType type, Key key, RawInputModifiers modifiers, string text) : base(device, timestamp, root, type, key, modifiers) { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs index ce843952e7..3439f0edae 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs @@ -1,6 +1,5 @@ using Avalonia.LinuxFramebuffer.Output; using Avalonia.Media; -using JetBrains.Annotations; namespace Avalonia.LinuxFramebuffer { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 38498951f8..e2e9ec8fb3 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -17,7 +17,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Avalonia.Threading; -using JetBrains.Annotations; +#nullable enable namespace Avalonia.LinuxFramebuffer { @@ -26,9 +26,9 @@ namespace Avalonia.LinuxFramebuffer IOutputBackend _fb; private static readonly Stopwatch St = Stopwatch.StartNew(); internal static uint Timestamp => (uint)St.ElapsedTicks; - public static InternalPlatformThreadingInterface Threading; + public static InternalPlatformThreadingInterface? Threading; - internal static Compositor Compositor { get; private set; } + internal static Compositor? Compositor { get; private set; } LinuxFramebufferPlatform(IOutputBackend backend) @@ -60,7 +60,7 @@ namespace Avalonia.LinuxFramebuffer } - internal static LinuxFramebufferLifetime Initialize(T builder, IOutputBackend outputBackend, IInputBackend inputBackend) where T : AppBuilderBase, new() + internal static LinuxFramebufferLifetime Initialize(T builder, IOutputBackend outputBackend, IInputBackend? inputBackend) where T : AppBuilderBase, new() { var platform = new LinuxFramebufferPlatform(outputBackend); builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev"); @@ -71,8 +71,8 @@ namespace Avalonia.LinuxFramebuffer class LinuxFramebufferLifetime : IControlledApplicationLifetime, ISingleViewApplicationLifetime { private readonly IOutputBackend _fb; - [CanBeNull] private readonly IInputBackend _inputBackend; - private TopLevel _topLevel; + private readonly IInputBackend? _inputBackend; + private TopLevel? _topLevel; private readonly CancellationTokenSource _cts = new CancellationTokenSource(); public CancellationToken Token => _cts.Token; @@ -81,15 +81,15 @@ namespace Avalonia.LinuxFramebuffer _fb = fb; } - public LinuxFramebufferLifetime(IOutputBackend fb, IInputBackend input) + public LinuxFramebufferLifetime(IOutputBackend fb, IInputBackend? input) { _fb = fb; _inputBackend = input; } - public Control MainView + public Control? MainView { - get => (Control)_topLevel?.Content; + get => (Control?)_topLevel?.Content; set { if (_topLevel == null) @@ -119,8 +119,8 @@ namespace Avalonia.LinuxFramebuffer } public int ExitCode { get; private set; } - public event EventHandler Startup; - public event EventHandler Exit; + public event EventHandler? Startup; + public event EventHandler? Exit; public void Start(string[] args) { @@ -140,19 +140,19 @@ namespace Avalonia.LinuxFramebuffer public static class LinuxFramebufferPlatformExtensions { - public static int StartLinuxFbDev(this T builder, string[] args, string fbdev = null, double scaling = 1, IInputBackend inputBackend = default) + public static int StartLinuxFbDev(this T builder, string[] args, string? fbdev = null, double scaling = 1, IInputBackend? inputBackend = default) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling }, inputBackend); - public static int StartLinuxFbDev(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling, IInputBackend inputBackend = default) + public static int StartLinuxFbDev(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling, IInputBackend? inputBackend = default) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling }, inputBackend); - public static int StartLinuxDrm(this T builder, string[] args, string card = null, double scaling = 1, IInputBackend inputBackend = default) + public static int StartLinuxDrm(this T builder, string[] args, string? card = null, double scaling = 1, IInputBackend? inputBackend = default) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card) { Scaling = scaling }, inputBackend); - public static int StartLinuxDrm(this T builder, string[] args, string card = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null, IInputBackend inputBackend = default) + public static int StartLinuxDrm(this T builder, string[] args, string? card = null, bool connectorsForceProbe = false, DrmOutputOptions? options = null, IInputBackend? inputBackend = default) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card, connectorsForceProbe, options), inputBackend); - public static int StartLinuxDirect(this T builder, string[] args, IOutputBackend outputBackend, IInputBackend inputBackend = default) + public static int StartLinuxDirect(this T builder, string[] args, IOutputBackend outputBackend, IInputBackend? inputBackend = default) where T : AppBuilderBase, new() { var lifetime = LinuxFramebufferPlatform.Initialize(builder, outputBackend, inputBackend); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 22dd407791..d61dcd4f91 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -8,7 +8,6 @@ using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; using Avalonia.Platform.Interop; -using JetBrains.Annotations; using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods; using static Avalonia.LinuxFramebuffer.Output.LibDrm; using static Avalonia.LinuxFramebuffer.Output.LibDrm.GbmColorFormats; @@ -50,7 +49,7 @@ namespace Avalonia.LinuxFramebuffer.Output _outputOptions = options; Init(card, resources, connector, modeInfo); } - public DrmOutput(string path = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null) + public DrmOutput(string path = null, bool connectorsForceProbe = false, DrmOutputOptions? options = null) { if(options != null) _outputOptions = options; diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs index 3084964d44..4478a79d27 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs @@ -44,9 +44,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public CommandAccessor(WeakReference reference, Action execute, Func canExecute, ISet dependsOnProperties) { - Contract.Requires(reference != null); - - _reference = reference; + _reference = reference ?? throw new ArgumentNullException(nameof(reference)); _dependsOnProperties = dependsOnProperties; _command = new Command(reference, execute, canExecute); diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs index ef11b06369..81f1224650 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs @@ -28,11 +28,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public AvaloniaPropertyAccessor(WeakReference reference, AvaloniaProperty property) { - Contract.Requires(reference != null); - Contract.Requires(property != null); - - _reference = reference; - _property = property; + _reference = reference ?? throw new ArgumentNullException(nameof(reference));; + _property = property ?? throw new ArgumentNullException(nameof(property));; } public AvaloniaObject Instance @@ -77,11 +74,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public InpcPropertyAccessor(WeakReference reference, IPropertyInfo property) { - Contract.Requires(reference != null); - Contract.Requires(property != null); - - _reference = reference; - _property = property; + _reference = reference ?? throw new ArgumentNullException(nameof(reference)); + _property = property ?? throw new ArgumentNullException(nameof(property)); } public override Type PropertyType => _property.PropertyType; diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index a7bec62366..48e8c761eb 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -10,7 +10,6 @@ using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.Utilities; using Avalonia.Utilities; using Avalonia.Media.Imaging; -using JetBrains.Annotations; using SkiaSharp; namespace Avalonia.Skia @@ -675,13 +674,14 @@ namespace Avalonia.Skia } } - [CanBeNull] - public object GetFeature(Type t) +#nullable enable + public object? GetFeature(Type t) { if (t == typeof(ISkiaSharpApiLeaseFeature)) return new SkiaLeaseFeature(this); return null; } +#nullable restore /// /// Configure paint wrapper for using gradient brush. diff --git a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs index bdc3d075cf..484fd9f219 100644 --- a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs @@ -1,8 +1,8 @@ using System; using Avalonia.Metadata; using Avalonia.Platform; -using JetBrains.Annotations; using SkiaSharp; +#nullable enable namespace Avalonia.Skia { @@ -10,7 +10,7 @@ namespace Avalonia.Skia [Unstable] public class GlyphRunImpl : IGlyphRunImpl { - public GlyphRunImpl([NotNull] SKTextBlob textBlob) + public GlyphRunImpl(SKTextBlob textBlob) { TextBlob = textBlob ?? throw new ArgumentNullException (nameof (textBlob)); } diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj index fb7831415d..5fecaef100 100644 --- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj +++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj @@ -17,7 +17,6 @@ - diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 3f220f0f09..3f16b49772 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -19,11 +19,11 @@ using Avalonia.Threading; using Avalonia.Utilities; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; -using JetBrains.Annotations; using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia { +#nullable enable public static class Win32ApplicationExtensions { public static T UseWin32( @@ -106,9 +106,10 @@ namespace Avalonia /// /// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu /// - [CanBeNull] public IPlatformGraphics CustomPlatformGraphics { get; set; } + public IPlatformGraphics? CustomPlatformGraphics { get; set; } } } +#nullable restore namespace Avalonia.Win32 { diff --git a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoProvider.cs b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoProvider.cs index e1b5f5a3a0..4f4e0b9293 100644 --- a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoProvider.cs +++ b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoProvider.cs @@ -8,7 +8,6 @@ namespace Avalonia.Win32 { public IDisposable Listen(ObservableCollection mountedDrives) { - Contract.Requires(mountedDrives != null); return new WindowsMountedVolumeInfoListener(mountedDrives); } } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs b/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs index fb0857e472..a1836f3ce4 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs @@ -1,6 +1,5 @@ #nullable enable using Avalonia.Input.TextInput; -using JetBrains.Annotations; using UIKit; namespace Avalonia.iOS; diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs index d407a09b06..7f4e160000 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs @@ -18,7 +18,6 @@ using Avalonia.Rendering.Composition; using Avalonia.Threading; using Avalonia.UnitTests; using Avalonia.VisualTree; -using JetBrains.Annotations; using Xunit; namespace Avalonia.Base.UnitTests.Rendering; diff --git a/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs b/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs index 0039f5670c..9ee3b03e0d 100644 --- a/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs +++ b/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs @@ -1,6 +1,5 @@ using System.ComponentModel; using System.Runtime.CompilerServices; -using JetBrains.Annotations; namespace Avalonia.Benchmarks.Data { diff --git a/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs b/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs index d43d6bd48b..e160c8dfa8 100644 --- a/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs +++ b/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs @@ -172,7 +172,7 @@ internal sealed class AvaloniaPropertyValueStoreOld internal class MockProperty : StyledProperty { - public MockProperty([JetBrains.Annotations.NotNull] string name) : base(name, typeof(object), new StyledPropertyMetadata()) + public MockProperty(string name) : base(name, typeof(object), new StyledPropertyMetadata()) { } } diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index cd38bf556a..81936711ef 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -17,7 +17,6 @@ using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; -using JetBrains.Annotations; using Moq; using Xunit; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs index 4b32a8cdca..f42f787117 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs @@ -15,7 +15,6 @@ using Avalonia.Styling; using Avalonia.Threading; using Avalonia.UnitTests; using Avalonia.VisualTree; -using JetBrains.Annotations; using Xunit; namespace Avalonia.Markup.Xaml.UnitTests @@ -389,7 +388,6 @@ namespace Avalonia.Markup.Xaml.UnitTests public bool IsPressed { get; set; } public event PropertyChangedEventHandler PropertyChanged; - [NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); From b3eab25240033ffac063166652741809a22083d8 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 21 Dec 2022 21:00:11 -0500 Subject: [PATCH 14/57] Fix tests build --- tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs b/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs index 9ee3b03e0d..cd323ededb 100644 --- a/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs +++ b/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs @@ -37,7 +37,6 @@ namespace Avalonia.Benchmarks.Data { } - [NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); From 586c3350dccc35834117c58006d5aedd76190ad7 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 22 Dec 2022 12:54:37 +0000 Subject: [PATCH 15/57] adds tests --- .../ScrollGestureRecognizer.cs | 2 +- .../Input/GesturesTests.cs | 98 +++++++++++++++++++ tests/Avalonia.UnitTests/TouchTestHelper.cs | 53 ++++++++++ 3 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.UnitTests/TouchTestHelper.cs diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 7dc4ab3f2e..64fe275547 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -96,7 +96,7 @@ namespace Avalonia.Input.GestureRecognizers } // Pixels per second speed that is considered to be the stop of inertial scroll - private const double InertialScrollSpeedEnd = 0; + private const double InertialScrollSpeedEnd = 5; public void PointerMoved(PointerEventArgs e) { diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 508eff5f9d..59085a21ce 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Input.GestureRecognizers; +using Avalonia.Media; using Avalonia.UnitTests; using Xunit; @@ -201,5 +203,101 @@ namespace Avalonia.Base.UnitTests.Input border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt")); border.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("bdt")); } + + [Fact] + public void Pinched_Should_Not_Be_Raised_For_Same_Pointer() + { + var touch = new TouchTestHelper(); + + Border border = new Border() + { + Width = 100, + Height = 100, + Background = new SolidColorBrush(Colors.Red) + }; + border.GestureRecognizers.Add(new PinchGestureRecognizer()); + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.PinchEvent, (s, e) => raised = true); + + var firstPoint = new Point(5, 5); + var secondPoint = new Point(10, 10); + + touch.Down(border, position: firstPoint); + touch.Down(border, position: secondPoint); + touch.Down(border, position: new Point(20, 20)); + + Assert.False(raised); + } + + [Fact] + public void Pinched_Should_Be_Raised_For_Two_Pointers_Moving() + { + Border border = new Border() + { + Width = 100, + Height = 100, + Background = new SolidColorBrush(Colors.Red) + }; + border.GestureRecognizers.Add(new PinchGestureRecognizer()); + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.PinchEvent, (s, e) => raised = true); + + var firstPoint = new Point(5, 5); + var secondPoint = new Point(10, 10); + + var firstTouch = new TouchTestHelper(); + var secondTouch = new TouchTestHelper(); + + firstTouch.Down(border, position: firstPoint); + secondTouch.Down(border, position: secondPoint); + secondTouch.Move(border, position: new Point(20, 20)); + + Assert.True(raised); + } + + [Fact] + public void Scrolling_Should_Start_After_Start_Distance_Is_Exceded() + { + Border border = new Border() + { + Width = 100, + Height = 100, + Background = new SolidColorBrush(Colors.Red) + }; + border.GestureRecognizers.Add(new ScrollGestureRecognizer() + { + CanHorizontallyScroll = true, + CanVerticallyScroll = true, + ScrollStartDistance = 50 + }); + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.ScrollGestureEvent, (s, e) => raised = true); + + var firstTouch = new TouchTestHelper(); + + firstTouch.Down(border, position: new Point(5, 5)); + firstTouch.Move(border, position: new Point(20, 20)); + + Assert.False(raised); + + firstTouch.Move(border, position: new Point(70, 20)); + + Assert.True(raised); + } } } diff --git a/tests/Avalonia.UnitTests/TouchTestHelper.cs b/tests/Avalonia.UnitTests/TouchTestHelper.cs new file mode 100644 index 0000000000..db70f570a2 --- /dev/null +++ b/tests/Avalonia.UnitTests/TouchTestHelper.cs @@ -0,0 +1,53 @@ +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.VisualTree; + +namespace Avalonia.UnitTests +{ + public class TouchTestHelper + { + private readonly Pointer _pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Touch, true); + private ulong _nextStamp = 1; + private ulong Timestamp() => _nextStamp++; + public IInputElement Captured => _pointer.Captured; + + public void Down(Interactive target, Point position = default, KeyModifiers modifiers = default) + { + Down(target, target, position, modifiers); + } + + public void Down(Interactive target, Interactive source, Point position = default, KeyModifiers modifiers = default) + { + _pointer.Capture((IInputElement)target); + source.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (Visual)source, position, Timestamp(), PointerPointProperties.None, + modifiers)); + } + + public void Move(Interactive target, in Point position, KeyModifiers modifiers = default) => Move(target, target, position, modifiers); + + 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)); + } + + public void Up(Interactive target, Point position = default, KeyModifiers modifiers = default) + => Up(target, target, position, modifiers); + + 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)); + _pointer.Capture(null); + } + + public void Tap(Interactive target, Point position = default, KeyModifiers modifiers = default) + => Tap(target, target, position, modifiers); + + public void Tap(Interactive target, Interactive source, Point position = default, KeyModifiers modifiers = default) + { + Down(target, source, position, modifiers); + Up(target, source, position, modifiers); + } + } +} From 9f633b84118ea447c54d12d218dcb638c75f586b Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 22 Dec 2022 16:33:20 -0500 Subject: [PATCH 16/57] Fix OnPlatform-like extensions with a single input parameter --- .../AvaloniaXamlIlOptionMarkupExtensionTransformer.cs | 1 + .../MarkupExtensions/OptionsMarkupExtensionTests.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs index 5004e594f7..13de455b96 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs @@ -366,6 +366,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor foreach (var branch in ExtensionNodeContainer.Branches) { var next = codeGen.DefineLabel(); + codeGen.Emit(OpCodes.Nop); if (branch.HasContext) { codeGen.Ldloc(context.ContextLocal); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OptionsMarkupExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OptionsMarkupExtensionTests.cs index 2d1f961743..0c67f385e1 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OptionsMarkupExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OptionsMarkupExtensionTests.cs @@ -434,7 +434,7 @@ public class OptionsMarkupExtensionTests : XamlTestBase "; + Text='{local:OptionsMarkupExtensionNoServiceProvider OptionB=""Im Option 2"", OptionA=""Im Option 1""}' />"; var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml); From ab64b3347005ad62c980e0b209d9721828288641 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 22 Dec 2022 16:33:45 -0500 Subject: [PATCH 17/57] Remove IRuntimePlatform.OperatingSystem API and replace with trimmable OperatingSystem API --- .../Pages/PlatformInfoPage.xaml.cs | 5 +- .../PlatformInformationViewModel.cs | 4 +- src/Avalonia.Base/Avalonia.Base.csproj | 3 + .../Compatibility/OperatingSystem.cs | 26 +++++++ src/Avalonia.Base/Logging/LogArea.cs | 5 ++ .../Platform/IRuntimePlatform.cs | 19 ----- .../Platform/StandardRuntimePlatform.cs | 40 +--------- .../StandardRuntimePlatformServices.cs | 13 +--- .../AppBuilderDesktopExtensions.cs | 17 +++-- src/Avalonia.OpenGL/Egl/EglInterface.cs | 6 +- .../BrowserRuntimePlatform.cs | 7 +- .../MarkupExtensions/OnFormFactorExtension.cs | 4 +- .../MarkupExtensions/OnPlatformExtension.cs | 43 +++++++---- .../Helpers/PixelFormatHelper.cs | 8 +- .../OnPlatformExtensionTests.cs | 75 ------------------- 15 files changed, 101 insertions(+), 174 deletions(-) create mode 100644 src/Avalonia.Base/Compatibility/OperatingSystem.cs delete mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs diff --git a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs index 1f37451782..6325af2688 100644 --- a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs +++ b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs @@ -1,5 +1,8 @@ -using Avalonia.Controls; +using System; +using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.Markup.Xaml.MarkupExtensions; +using Avalonia.Media.Immutable; using ControlCatalog.ViewModels; namespace ControlCatalog.Pages diff --git a/samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs b/samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs index e4f6c3ac73..60f1f84cc9 100644 --- a/samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs +++ b/samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs @@ -1,3 +1,5 @@ +using System; +using System.Runtime.InteropServices; using Avalonia; using Avalonia.Platform; using MiniMvvm; @@ -13,7 +15,7 @@ public class PlatformInformationViewModel : ViewModelBase if (runtimeInfo is { } info) { - if (info.IsBrowser) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"))) { if (info.IsDesktop) { diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 94a9ea8352..d6f1542687 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -28,10 +28,13 @@ + + + diff --git a/src/Avalonia.Base/Compatibility/OperatingSystem.cs b/src/Avalonia.Base/Compatibility/OperatingSystem.cs new file mode 100644 index 0000000000..838f7da8b2 --- /dev/null +++ b/src/Avalonia.Base/Compatibility/OperatingSystem.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Compatibility +{ + internal sealed class OperatingSystemEx + { +#if NET6_0_OR_GREATER + public static bool IsWindows() => OperatingSystem.IsWindows(); + public static bool IsMacOS() => OperatingSystem.IsMacOS(); + public static bool IsLinux() => OperatingSystem.IsLinux(); + public static bool IsAndroid() => OperatingSystem.IsAndroid(); + public static bool IsIOS() => OperatingSystem.IsIOS(); + public static bool IsBrowser() => OperatingSystem.IsBrowser(); + public static bool IsOSPlatform(string platform) => OperatingSystem.IsOSPlatform(platform); +#else + public static bool IsWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + public static bool IsMacOS() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + public static bool IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + public static bool IsAndroid() => IsOSPlatform("ANDROID"); + public static bool IsIOS() => IsOSPlatform("IOS"); + public static bool IsBrowser() => IsOSPlatform("BROWSER"); + public static bool IsOSPlatform(string platform) => RuntimeInformation.IsOSPlatform(OSPlatform.Create(platform)); +#endif + } +} diff --git a/src/Avalonia.Base/Logging/LogArea.cs b/src/Avalonia.Base/Logging/LogArea.cs index 972a9a1e9d..f15e87da1b 100644 --- a/src/Avalonia.Base/Logging/LogArea.cs +++ b/src/Avalonia.Base/Logging/LogArea.cs @@ -35,6 +35,11 @@ namespace Avalonia.Logging /// public const string Control = nameof(Control); + /// + /// The log event comes from Win32 Platform. + /// + public const string Platform = nameof(Platform); + /// /// The log event comes from Win32 Platform. /// diff --git a/src/Avalonia.Base/Platform/IRuntimePlatform.cs b/src/Avalonia.Base/Platform/IRuntimePlatform.cs index 91d2a1e0cf..64b504c479 100644 --- a/src/Avalonia.Base/Platform/IRuntimePlatform.cs +++ b/src/Avalonia.Base/Platform/IRuntimePlatform.cs @@ -23,29 +23,10 @@ namespace Avalonia.Platform [Unstable] public record struct RuntimePlatformInfo { - public OperatingSystemType OperatingSystem { get; set; } - public FormFactorType FormFactor => IsDesktop ? FormFactorType.Desktop : IsMobile ? FormFactorType.Mobile : FormFactorType.Unknown; public bool IsDesktop { get; set; } public bool IsMobile { get; set; } - public bool IsBrowser { get; set; } - public bool IsCoreClr { get; set; } - public bool IsMono { get; set; } - public bool IsDotNetFramework { get; set; } - public bool IsUnix { get; set; } - } - - [Unstable] - public enum OperatingSystemType - { - Unknown, - WinNT, - Linux, - OSX, - Android, - iOS, - Browser } [Unstable] diff --git a/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs b/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs index 4df9e8e917..ddb492adcf 100644 --- a/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs +++ b/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs @@ -1,6 +1,6 @@ using System; -using System.Runtime.InteropServices; using System.Threading; +using Avalonia.Compatibility; using Avalonia.Platform.Internal; namespace Avalonia.Platform @@ -14,42 +14,10 @@ namespace Avalonia.Platform public IUnmanagedBlob AllocBlob(int size) => new UnmanagedBlob(size); - private static readonly Lazy Info = new(() => + private static readonly Lazy Info = new(() => new RuntimePlatformInfo { - OperatingSystemType os; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - os = OperatingSystemType.OSX; - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - os = OperatingSystemType.Linux; - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - os = OperatingSystemType.WinNT; - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("Android"))) - os = OperatingSystemType.Android; - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("iOS"))) - os = OperatingSystemType.iOS; - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("Browser"))) - os = OperatingSystemType.Browser; - else - throw new Exception("Unknown OS platform " + RuntimeInformation.OSDescription); - - // Source: https://github.com/dotnet/runtime/blob/main/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs - var isCoreClr = Environment.Version.Major >= 5 || RuntimeInformation.FrameworkDescription.StartsWith(".NET Core", StringComparison.OrdinalIgnoreCase); - var isMonoRuntime = Type.GetType("Mono.Runtime") != null; - var isFramework = !isCoreClr && RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework", StringComparison.OrdinalIgnoreCase); - - return new RuntimePlatformInfo - { - IsCoreClr = isCoreClr, - IsDotNetFramework = isFramework, - IsMono = isMonoRuntime, - - IsDesktop = os is OperatingSystemType.Linux or OperatingSystemType.OSX or OperatingSystemType.WinNT, - IsMobile = os is OperatingSystemType.Android or OperatingSystemType.iOS, - IsUnix = os is OperatingSystemType.Linux or OperatingSystemType.OSX or OperatingSystemType.Android, - IsBrowser = os == OperatingSystemType.Browser, - OperatingSystem = os, - }; + IsDesktop = OperatingSystemEx.IsWindows() || OperatingSystemEx.IsMacOS() || OperatingSystemEx.IsLinux(), + IsMobile = OperatingSystemEx.IsAndroid() || OperatingSystemEx.IsAndroid() }); diff --git a/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs b/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs index 65d6733399..0a36b4c9dd 100644 --- a/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs +++ b/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs @@ -1,4 +1,5 @@ using System.Reflection; +using Avalonia.Compatibility; using Avalonia.Platform.Internal; using Avalonia.Platform.Interop; @@ -18,15 +19,9 @@ namespace Avalonia.Platform #if NET6_0_OR_GREATER new Net6Loader() #else - standardPlatform.GetRuntimeInfo().OperatingSystem switch - { - OperatingSystemType.WinNT => (IDynamicLibraryLoader)new Win32Loader(), - OperatingSystemType.OSX => new UnixLoader(), - OperatingSystemType.Linux => new UnixLoader(), - OperatingSystemType.Android => new UnixLoader(), - // iOS, WASM, ... - _ => new NotSupportedLoader() - } + OperatingSystemEx.IsWindows() ? (IDynamicLibraryLoader)new Win32Loader() + : OperatingSystemEx.IsMacOS() || OperatingSystemEx.IsLinux() || OperatingSystemEx.IsAndroid() ? new UnixLoader() + : new NotSupportedLoader() #endif ); } diff --git a/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs b/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs index 7c4a489328..d89c62c063 100644 --- a/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs +++ b/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs @@ -1,5 +1,6 @@ +using Avalonia.Compatibility; using Avalonia.Controls; -using Avalonia.Platform; +using Avalonia.Logging; namespace Avalonia { @@ -8,8 +9,6 @@ namespace Avalonia public static TAppBuilder UsePlatformDetect(this TAppBuilder builder) where TAppBuilder : AppBuilderBase, new() { - var os = builder.RuntimePlatform.GetRuntimeInfo().OperatingSystem; - // We don't have the ability to load every assembly right now, so we are // stuck with manual configuration here // Helpers are extracted to separate methods to take the advantage of the fact @@ -17,21 +16,27 @@ namespace Avalonia // Additionally, by having a hard reference to each assembly, // we verify that the assemblies are in the final .deps.json file // so .NET Core knows where to load the assemblies from,. - if (os == OperatingSystemType.WinNT) + if (OperatingSystemEx.IsWindows()) { LoadWin32(builder); LoadSkia(builder); } - else if(os==OperatingSystemType.OSX) + else if(OperatingSystemEx.IsMacOS()) { LoadAvaloniaNative(builder); LoadSkia(builder); } - else + else if (OperatingSystemEx.IsLinux()) { LoadX11(builder); LoadSkia(builder); } + else + { + Logger.TryGet(LogEventLevel.Warning, LogArea.Platform)?.Log(builder, + "Avalonia.Desktop package was referenced on non-desktop platform or it isn't supported"); + } + return builder; } diff --git a/src/Avalonia.OpenGL/Egl/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs index ad4b55a686..a913c05996 100644 --- a/src/Avalonia.OpenGL/Egl/EglInterface.cs +++ b/src/Avalonia.OpenGL/Egl/EglInterface.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using Avalonia.Compatibility; using Avalonia.Platform; using Avalonia.Platform.Interop; using Avalonia.SourceGenerator; @@ -24,10 +25,9 @@ namespace Avalonia.OpenGL.Egl static Func Load() { - var os = AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem; - if(os == OperatingSystemType.Linux) + if(OperatingSystemEx.IsLinux()) return Load("libEGL.so.1"); - if (os == OperatingSystemType.Android) + if (OperatingSystemEx.IsAndroid()) return Load("libEGL.so"); throw new PlatformNotSupportedException(); diff --git a/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs b/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs index 0abc7703da..67bb040410 100644 --- a/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs +++ b/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs @@ -11,12 +11,11 @@ internal class BrowserRuntimePlatform : StandardRuntimePlatform { private static readonly Lazy Info = new(() => { + var isMobile = AvaloniaModule.IsMobile(); var result = new RuntimePlatformInfo { - IsCoreClr = true, // WASM browser is always CoreCLR - IsBrowser = true, // BrowserRuntimePlatform only runs on Browser. - OperatingSystem = OperatingSystemType.Browser, - IsMobile = AvaloniaModule.IsMobile() + IsMobile = isMobile, + IsDesktop = !isMobile }; return result; diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs index 51e09eef71..c2f1100eff 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs @@ -6,7 +6,7 @@ using Avalonia.Platform; namespace Avalonia.Markup.Xaml.MarkupExtensions; -public class OnFormFactorExtension : OnFormFactorExtensionBase +public sealed class OnFormFactorExtension : OnFormFactorExtensionBase { public OnFormFactorExtension() { @@ -24,7 +24,7 @@ public class OnFormFactorExtension : OnFormFactorExtensionBase } } -public class OnFormFactorExtension : OnFormFactorExtensionBase> +public sealed class OnFormFactorExtension : OnFormFactorExtensionBase> { public OnFormFactorExtension() { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs index 1ac7a522f1..1c20020978 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs @@ -1,11 +1,11 @@ #nullable enable using System; +using Avalonia.Compatibility; using Avalonia.Metadata; -using Avalonia.Platform; namespace Avalonia.Markup.Xaml.MarkupExtensions; -public class OnPlatformExtension : OnPlatformExtensionBase +public sealed class OnPlatformExtension : OnPlatformExtensionBase { public OnPlatformExtension() { @@ -17,13 +17,13 @@ public class OnPlatformExtension : OnPlatformExtensionBase Default = defaultValue; } - public static bool ShouldProvideOption(IServiceProvider serviceProvider, OperatingSystemType option) + public static bool ShouldProvideOption(string option) { - return serviceProvider.GetService().GetRuntimeInfo().OperatingSystem == option; + return ShouldProvideOptionInternal(option); } } -public class OnPlatformExtension : OnPlatformExtensionBase> +public sealed class OnPlatformExtension : OnPlatformExtensionBase> { public OnPlatformExtension() { @@ -35,9 +35,9 @@ public class OnPlatformExtension : OnPlatformExtensionBase().GetRuntimeInfo().OperatingSystem == option; + return ShouldProvideOptionInternal(option); } } @@ -47,27 +47,44 @@ public abstract class OnPlatformExtensionBase : IAddChild [MarkupExtensionDefaultOption] public TReturn? Default { get; set; } - [MarkupExtensionOption(OperatingSystemType.WinNT)] + [MarkupExtensionOption("WINDOWS")] public TReturn? Windows { get; set; } - [MarkupExtensionOption(OperatingSystemType.OSX)] + [MarkupExtensionOption("OSX")] // ReSharper disable once InconsistentNaming public TReturn? macOS { get; set; } - [MarkupExtensionOption(OperatingSystemType.Linux)] + [MarkupExtensionOption("LINUX")] public TReturn? Linux { get; set; } - [MarkupExtensionOption(OperatingSystemType.Android)] + [MarkupExtensionOption("ANDROID")] public TReturn? Android { get; set; } - [MarkupExtensionOption(OperatingSystemType.iOS)] + [MarkupExtensionOption("IOS")] // ReSharper disable once InconsistentNaming public TReturn? iOS { get; set; } - [MarkupExtensionOption(OperatingSystemType.Browser)] + [MarkupExtensionOption("BROWSER")] public TReturn? Browser { get; set; } // Required for the compiler, will be replaced with actual method compile time. public object ProvideValue() { return this; } void IAddChild.AddChild(TOn child) {} + + private protected static bool ShouldProvideOptionInternal(string option) + { + // Instead of using OperatingSystem.IsOSPlatform(string) we use specific "Is***" methods so whole method can be trimmed by the mono linked. + // Keep in mind it works only with const "option" parameter. + // IsOSPlatform might work better with trimming in the future, so it should be re-visited after .NET 8/9. + return option switch + { + "WINDOWS" => OperatingSystemEx.IsWindows(), + "OSX" => OperatingSystemEx.IsMacOS(), + "LINUX" => OperatingSystemEx.IsLinux(), + "ANDROID" => OperatingSystemEx.IsAndroid(), + "IOS" => OperatingSystemEx.IsIOS(), + "BROWSER" => OperatingSystemEx.IsBrowser(), + _ => OperatingSystemEx.IsOSPlatform(option) + }; + } } diff --git a/src/Skia/Avalonia.Skia/Helpers/PixelFormatHelper.cs b/src/Skia/Avalonia.Skia/Helpers/PixelFormatHelper.cs index 9236bdee8d..84a1972e01 100644 --- a/src/Skia/Avalonia.Skia/Helpers/PixelFormatHelper.cs +++ b/src/Skia/Avalonia.Skia/Helpers/PixelFormatHelper.cs @@ -1,4 +1,5 @@ -using Avalonia.Platform; +using Avalonia.Compatibility; +using Avalonia.Platform; using SkiaSharp; namespace Avalonia.Skia.Helpers @@ -18,10 +19,7 @@ namespace Avalonia.Skia.Helpers var colorType = format?.ToSkColorType() ?? SKImageInfo.PlatformColorType; // TODO: This looks like some leftover hack - var runtimePlatform = AvaloniaLocator.Current?.GetService(); - var runtime = runtimePlatform?.GetRuntimeInfo(); - - if (runtime?.IsDesktop == true && runtime.Value.OperatingSystem == OperatingSystemType.Linux) + if (OperatingSystemEx.IsLinux()) { colorType = SKColorType.Bgra8888; } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs deleted file mode 100644 index 1d37378010..0000000000 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Platform; -using Xunit; - -namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions; - -public class OnPlatformExtensionTests : XamlTestBase -{ - [Fact] - public void Should_Resolve_Default_Value() - { - using (AvaloniaLocator.EnterScope()) - { - AvaloniaLocator.CurrentMutable.Bind() - .ToConstant(new TestRuntimePlatform(OperatingSystemType.Unknown)); - - var xaml = @" - - -"; - - var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); - var textBlock = (TextBlock)userControl.Content!; - - Assert.Equal("Hello World", textBlock.Text); - } - } - - [Theory] - [InlineData(OperatingSystemType.WinNT, "Im Windows")] - [InlineData(OperatingSystemType.OSX, "Im macOS")] - [InlineData(OperatingSystemType.Linux, "Im Linux")] - [InlineData(OperatingSystemType.Android, "Im Android")] - [InlineData(OperatingSystemType.iOS, "Im iOS")] - [InlineData(OperatingSystemType.Browser, "Im Browser")] - [InlineData(OperatingSystemType.Unknown, "Default value")] - public void Should_Resolve_Expected_Value_Per_Platform(OperatingSystemType currentPlatform, string expectedResult) - { - using (AvaloniaLocator.EnterScope()) - { - AvaloniaLocator.CurrentMutable.Bind() - .ToConstant(new TestRuntimePlatform(currentPlatform)); - - var xaml = @" - - -"; - - var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); - var textBlock = (TextBlock)userControl.Content!; - - Assert.Equal(expectedResult, textBlock.Text); - } - } - - private class TestRuntimePlatform : StandardRuntimePlatform - { - private readonly OperatingSystemType _operatingSystemType; - - public TestRuntimePlatform(OperatingSystemType operatingSystemType) - { - _operatingSystemType = operatingSystemType; - } - - public override RuntimePlatformInfo GetRuntimeInfo() - { - return new RuntimePlatformInfo() { OperatingSystem = _operatingSystemType }; - } - } -} From c2e00428dff6e0ea326ac993bfbbb71fcfad2909 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 22 Dec 2022 22:41:09 -0500 Subject: [PATCH 18/57] Remove AppBuilderBase and generics --- .../Avalonia.Android/AndroidPlatform.cs | 2 +- .../AvaloniaSplashActivity.cs | 2 +- src/Avalonia.Controls/AppBuilder.cs | 239 ++++++++++++++++- src/Avalonia.Controls/AppBuilderBase.cs | 244 ------------------ .../ClassicDesktopStyleApplicationLifetime.cs | 5 +- src/Avalonia.Controls/LoggingExtensions.cs | 6 +- .../Remote/RemoteDesignerEntryPoint.cs | 6 +- .../AppBuilderDesktopExtensions.cs | 15 +- .../ManagedFileDialogExtensions.cs | 7 +- .../HeadlessVncPlatformExtensions.cs | 5 +- .../AvaloniaHeadlessPlatform.cs | 3 +- .../AvaloniaNativePlatformExtensions.cs | 3 +- .../AppBuilderExtensions.cs | 3 +- src/Avalonia.X11/X11Platform.cs | 2 +- .../BlazorSingleViewLifetime.cs | 7 +- .../BrowserSingleViewLifetime.cs | 12 +- .../LinuxFramebufferPlatform.cs | 30 +-- .../SkiaApplicationExtensions.cs | 3 +- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 2 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 4 +- src/iOS/Avalonia.iOS/Platform.cs | 2 +- .../Media/Fonts/FontFamilyLoaderTests.cs | 2 +- tests/Avalonia.UnitTests/TestServices.cs | 14 +- 23 files changed, 285 insertions(+), 333 deletions(-) delete mode 100644 src/Avalonia.Controls/AppBuilderBase.cs diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 75856e4b52..01b48ffd89 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -15,7 +15,7 @@ namespace Avalonia { public static class AndroidApplicationExtensions { - public static T UseAndroid(this T builder) where T : AppBuilderBase, new() + public static AppBuilder UseAndroid(this AppBuilder builder) { return builder .UseWindowingSubsystem(() => AndroidPlatform.Initialize(), "Android") diff --git a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs index 5b5ebd1bd9..ec26ee5599 100644 --- a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs @@ -1,6 +1,6 @@ using Android.OS; using AndroidX.AppCompat.App; -using AndroidX.Lifecycle; +using Avalonia.Controls; namespace Avalonia.Android { diff --git a/src/Avalonia.Controls/AppBuilder.cs b/src/Avalonia.Controls/AppBuilder.cs index 5bcd87162e..cf79fcd1a8 100644 --- a/src/Avalonia.Controls/AppBuilder.cs +++ b/src/Avalonia.Controls/AppBuilder.cs @@ -1,4 +1,8 @@ -using Avalonia.Controls; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Linq; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Platform; namespace Avalonia @@ -6,15 +10,244 @@ namespace Avalonia /// /// Initializes platform-specific services for an . /// - public sealed class AppBuilder : AppBuilderBase + public class AppBuilder { + private static bool s_setupWasAlreadyCalled; + private Action? _optionsInitializers; + private Func? _appFactory; + private IApplicationLifetime? _lifetime; + + /// + /// Gets or sets the instance. + /// + public IRuntimePlatform RuntimePlatform { get; set; } + + /// + /// Gets or sets a method to call the initialize the runtime platform services (e. g. AssetLoader) + /// + public Action RuntimePlatformServicesInitializer { get; private set; } + + /// + /// Gets the instance being initialized. + /// + public Application? Instance { get; private set; } + + /// + /// Gets the type of the Instance (even if it's not created yet) + /// + public Type? ApplicationType { get; private set; } + + /// + /// Gets or sets a method to call the initialize the windowing subsystem. + /// + public Action? WindowingSubsystemInitializer { get; private set; } + + /// + /// Gets the name of the currently selected windowing subsystem. + /// + public string? WindowingSubsystemName { get; private set; } + + /// + /// Gets or sets a method to call the initialize the windowing subsystem. + /// + public Action? RenderingSubsystemInitializer { get; private set; } + + /// + /// Gets the name of the currently selected rendering subsystem. + /// + public string? RenderingSubsystemName { get; private set; } + + /// + /// Gets or sets a method to call after the is setup. + /// + public Action AfterSetupCallback { get; private set; } = builder => { }; + + + public Action AfterPlatformServicesSetupCallback { get; private set; } = builder => { }; + /// /// Initializes a new instance of the class. /// public AppBuilder() - : base(new StandardRuntimePlatform(), + : this(new StandardRuntimePlatform(), builder => StandardRuntimePlatformServices.Register(builder.ApplicationType?.Assembly)) { } + + /// + /// Initializes a new instance of the class. + /// + protected AppBuilder(IRuntimePlatform platform, Action platformServices) + { + RuntimePlatform = platform; + RuntimePlatformServicesInitializer = () => platformServices(this); + } + + /// + /// Begin configuring an . + /// + /// The subclass of to configure. + /// An instance. + public static AppBuilder Configure() + where TApp : Application, new() + { + return new AppBuilder() + { + ApplicationType = typeof(TApp), + // Needed for CoreRT compatibility + _appFactory = () => new TApp() + }; + } + + /// + /// Begin configuring an . + /// + /// Factory function for . + /// The subclass of to configure. + /// is useful for passing of dependencies to . + /// An instance. + public static AppBuilder Configure(Func appFactory) + where TApp : Application + { + return new AppBuilder() + { + ApplicationType = typeof(TApp), + _appFactory = appFactory + }; + } + + protected AppBuilder Self => this; + + public AppBuilder AfterSetup(Action callback) + { + AfterSetupCallback = (Action)Delegate.Combine(AfterSetupCallback, callback); + return Self; + } + + + public AppBuilder AfterPlatformServicesSetup(Action callback) + { + AfterPlatformServicesSetupCallback = (Action)Delegate.Combine(AfterPlatformServicesSetupCallback, callback); + return Self; + } + + public delegate void AppMainDelegate(Application app, string[] args); + + public void Start(AppMainDelegate main, string[] args) + { + Setup(); + main(Instance!, args); + } + + /// + /// Sets up the platform-specific services for the application, but does not run it. + /// + /// + public AppBuilder SetupWithoutStarting() + { + Setup(); + return Self; + } + + /// + /// Sets up the platform-specific services for the application and initialized it with a particular lifetime, but does not run it. + /// + /// + /// + public AppBuilder SetupWithLifetime(IApplicationLifetime lifetime) + { + _lifetime = lifetime; + Setup(); + return Self; + } + + /// + /// Specifies a windowing subsystem to use. + /// + /// The method to call to initialize the windowing subsystem. + /// The name of the windowing subsystem. + /// An instance. + public AppBuilder UseWindowingSubsystem(Action initializer, string name = "") + { + WindowingSubsystemInitializer = initializer; + WindowingSubsystemName = name; + return Self; + } + + /// + /// Specifies a rendering subsystem to use. + /// + /// The method to call to initialize the rendering subsystem. + /// The name of the rendering subsystem. + /// An instance. + public AppBuilder UseRenderingSubsystem(Action initializer, string name = "") + { + RenderingSubsystemInitializer = initializer; + RenderingSubsystemName = name; + return Self; + } + + /// + /// Configures platform-specific options + /// + public AppBuilder With(T options) + { + _optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind().ToConstant(options); }; + return Self; + } + + /// + /// Configures platform-specific options + /// + public AppBuilder With(Func options) + { + _optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind().ToFunc(options); }; + return Self; + } + + /// + /// Sets up the platform-specific services for the . + /// + private void Setup() + { + if (RuntimePlatformServicesInitializer == null) + { + throw new InvalidOperationException("No runtime platform services configured."); + } + + if (WindowingSubsystemInitializer == null) + { + throw new InvalidOperationException("No windowing system configured."); + } + + if (RenderingSubsystemInitializer == null) + { + throw new InvalidOperationException("No rendering system configured."); + } + + if (_appFactory == null) + { + throw new InvalidOperationException("No Application factory configured."); + } + + if (s_setupWasAlreadyCalled) + { + throw new InvalidOperationException("Setup was already called on one of AppBuilder instances"); + } + + s_setupWasAlreadyCalled = true; + _optionsInitializers?.Invoke(); + RuntimePlatformServicesInitializer(); + RenderingSubsystemInitializer(); + WindowingSubsystemInitializer(); + AfterPlatformServicesSetupCallback(Self); + Instance = _appFactory(); + Instance.ApplicationLifetime = _lifetime; + AvaloniaLocator.CurrentMutable.BindToSelf(Instance); + Instance.RegisterServices(); + Instance.Initialize(); + AfterSetupCallback(Self); + Instance.OnFrameworkInitializationCompleted(); + } } } diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs deleted file mode 100644 index a100d38d78..0000000000 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ /dev/null @@ -1,244 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Linq; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Platform; - -namespace Avalonia.Controls -{ - /// - /// Base class for initializing platform-specific services for an . - /// - /// The type of the AppBuilder class itself. - public abstract class AppBuilderBase where TAppBuilder : AppBuilderBase, new() - { - private static bool s_setupWasAlreadyCalled; - private Action? _optionsInitializers; - private Func? _appFactory; - private IApplicationLifetime? _lifetime; - - /// - /// Gets or sets the instance. - /// - public IRuntimePlatform RuntimePlatform { get; set; } - - /// - /// Gets or sets a method to call the initialize the runtime platform services (e. g. AssetLoader) - /// - public Action RuntimePlatformServicesInitializer { get; private set; } - - /// - /// Gets the instance being initialized. - /// - public Application? Instance { get; private set; } - - /// - /// Gets the type of the Instance (even if it's not created yet) - /// - public Type? ApplicationType { get; private set; } - - /// - /// Gets or sets a method to call the initialize the windowing subsystem. - /// - public Action? WindowingSubsystemInitializer { get; private set; } - - /// - /// Gets the name of the currently selected windowing subsystem. - /// - public string? WindowingSubsystemName { get; private set; } - - /// - /// Gets or sets a method to call the initialize the windowing subsystem. - /// - public Action? RenderingSubsystemInitializer { get; private set; } - - /// - /// Gets the name of the currently selected rendering subsystem. - /// - public string? RenderingSubsystemName { get; private set; } - - /// - /// Gets or sets a method to call after the is setup. - /// - public Action AfterSetupCallback { get; private set; } = builder => { }; - - - public Action AfterPlatformServicesSetupCallback { get; private set; } = builder => { }; - - protected AppBuilderBase(IRuntimePlatform platform, Action platformServices) - { - RuntimePlatform = platform; - RuntimePlatformServicesInitializer = () => platformServices((TAppBuilder)this); - } - - /// - /// Begin configuring an . - /// - /// The subclass of to configure. - /// An instance. - public static TAppBuilder Configure() - where TApp : Application, new() - { - return new TAppBuilder() - { - ApplicationType = typeof(TApp), - // Needed for CoreRT compatibility - _appFactory = () => new TApp() - }; - } - - /// - /// Begin configuring an . - /// - /// Factory function for . - /// The subclass of to configure. - /// is useful for passing of dependencies to . - /// An instance. - public static TAppBuilder Configure(Func appFactory) - where TApp : Application - { - return new TAppBuilder() - { - ApplicationType = typeof(TApp), - _appFactory = appFactory - }; - } - - protected TAppBuilder Self => (TAppBuilder)this; - - public TAppBuilder AfterSetup(Action callback) - { - AfterSetupCallback = (Action)Delegate.Combine(AfterSetupCallback, callback); - return Self; - } - - - public TAppBuilder AfterPlatformServicesSetup(Action callback) - { - AfterPlatformServicesSetupCallback = (Action)Delegate.Combine(AfterPlatformServicesSetupCallback, callback); - return Self; - } - - public delegate void AppMainDelegate(Application app, string[] args); - - public void Start(AppMainDelegate main, string[] args) - { - Setup(); - main(Instance!, args); - } - - /// - /// Sets up the platform-specific services for the application, but does not run it. - /// - /// - public TAppBuilder SetupWithoutStarting() - { - Setup(); - return Self; - } - - /// - /// Sets up the platform-specific services for the application and initialized it with a particular lifetime, but does not run it. - /// - /// - /// - public TAppBuilder SetupWithLifetime(IApplicationLifetime lifetime) - { - _lifetime = lifetime; - Setup(); - return Self; - } - - /// - /// Specifies a windowing subsystem to use. - /// - /// The method to call to initialize the windowing subsystem. - /// The name of the windowing subsystem. - /// An instance. - public TAppBuilder UseWindowingSubsystem(Action initializer, string name = "") - { - WindowingSubsystemInitializer = initializer; - WindowingSubsystemName = name; - return Self; - } - - /// - /// Specifies a rendering subsystem to use. - /// - /// The method to call to initialize the rendering subsystem. - /// The name of the rendering subsystem. - /// An instance. - public TAppBuilder UseRenderingSubsystem(Action initializer, string name = "") - { - RenderingSubsystemInitializer = initializer; - RenderingSubsystemName = name; - return Self; - } - - protected virtual bool CheckSetup => true; - - /// - /// Configures platform-specific options - /// - public TAppBuilder With(T options) - { - _optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind().ToConstant(options); }; - return Self; - } - - /// - /// Configures platform-specific options - /// - public TAppBuilder With(Func options) - { - _optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind().ToFunc(options); }; - return Self; - } - - /// - /// Sets up the platform-specific services for the . - /// - private void Setup() - { - if (RuntimePlatformServicesInitializer == null) - { - throw new InvalidOperationException("No runtime platform services configured."); - } - - if (WindowingSubsystemInitializer == null) - { - throw new InvalidOperationException("No windowing system configured."); - } - - if (RenderingSubsystemInitializer == null) - { - throw new InvalidOperationException("No rendering system configured."); - } - - if (_appFactory == null) - { - throw new InvalidOperationException("No Application factory configured."); - } - - if (s_setupWasAlreadyCalled && CheckSetup) - { - throw new InvalidOperationException("Setup was already called on one of AppBuilder instances"); - } - - s_setupWasAlreadyCalled = true; - _optionsInitializers?.Invoke(); - RuntimePlatformServicesInitializer(); - RenderingSubsystemInitializer(); - WindowingSubsystemInitializer(); - AfterPlatformServicesSetupCallback(Self); - Instance = _appFactory(); - Instance.ApplicationLifetime = _lifetime; - AvaloniaLocator.CurrentMutable.BindToSelf(Instance); - Instance.RegisterServices(); - Instance.Initialize(); - AfterSetupCallback(Self); - Instance.OnFrameworkInitializationCompleted(); - } - } -} diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index 757db96799..fde401fb01 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -200,9 +200,8 @@ namespace Avalonia { public static class ClassicDesktopStyleApplicationLifetimeExtensions { - public static int StartWithClassicDesktopLifetime( - this T builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) - where T : AppBuilderBase, new() + public static int StartWithClassicDesktopLifetime( + this AppBuilder builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) { var lifetime = new ClassicDesktopStyleApplicationLifetime() { diff --git a/src/Avalonia.Controls/LoggingExtensions.cs b/src/Avalonia.Controls/LoggingExtensions.cs index ef14d0b477..f909ba8714 100644 --- a/src/Avalonia.Controls/LoggingExtensions.cs +++ b/src/Avalonia.Controls/LoggingExtensions.cs @@ -8,16 +8,14 @@ namespace Avalonia /// /// Logs Avalonia events to the sink. /// - /// The application class type. /// The app builder instance. /// The minimum level to log. /// The areas to log. Valid values are listed in . /// The app builder instance. - public static T LogToTrace( - this T builder, + public static AppBuilder LogToTrace( + this AppBuilder builder, LogEventLevel level = LogEventLevel.Warning, params string[] areas) - where T : AppBuilderBase, new() { Logger.Sink = new TraceLogSink(level, areas); return builder; diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs index 543d07f958..85605ccd9d 100644 --- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -134,12 +134,12 @@ namespace Avalonia.DesignerSupport.Remote IAvaloniaRemoteTransportConnection ConfigureApp(IAvaloniaRemoteTransportConnection transport, CommandLineArgs args, object obj); } - class AppInitializer : IAppInitializer where T : AppBuilderBase, new() + class AppInitializer : IAppInitializer { public IAvaloniaRemoteTransportConnection ConfigureApp(IAvaloniaRemoteTransportConnection transport, CommandLineArgs args, object obj) { - var builder = (AppBuilderBase)obj; + var builder = (AppBuilder)obj; if (args.Method == Methods.AvaloniaRemote) builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport)); if (args.Method == Methods.Html) @@ -191,7 +191,7 @@ namespace Avalonia.DesignerSupport.Remote Log($"Obtaining AppBuilder instance from {builderMethod.DeclaringType.FullName}.{builderMethod.Name}"); var appBuilder = builderMethod.Invoke(null, null); Log($"Initializing application in design mode"); - var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer<>).MakeGenericType(appBuilder.GetType())); + var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer)); transport = initializer.ConfigureApp(transport, args, appBuilder); s_transport = transport; transport.OnMessage += OnTransportMessage; diff --git a/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs b/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs index 7c4a489328..195af7b46e 100644 --- a/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs +++ b/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs @@ -5,8 +5,7 @@ namespace Avalonia { public static class AppBuilderDesktopExtensions { - public static TAppBuilder UsePlatformDetect(this TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() + public static AppBuilder UsePlatformDetect(this AppBuilder builder) { var os = builder.RuntimePlatform.GetRuntimeInfo().OperatingSystem; @@ -35,19 +34,15 @@ namespace Avalonia return builder; } - static void LoadAvaloniaNative(TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() + static void LoadAvaloniaNative(AppBuilder builder) => builder.UseAvaloniaNative(); - static void LoadWin32(TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() + static void LoadWin32(AppBuilder builder) => builder.UseWin32(); - static void LoadX11(TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() + static void LoadX11(AppBuilder builder) => builder.UseX11(); - static void LoadSkia(TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() + static void LoadSkia(AppBuilder builder) => builder.UseSkia(); } } diff --git a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs index fe2dd99100..bc5945441c 100644 --- a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs +++ b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs @@ -24,16 +24,15 @@ namespace Avalonia.Dialogs } } - public static TAppBuilder UseManagedSystemDialogs(this TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() + public static AppBuilder UseManagedSystemDialogs(this AppBuilder builder) { builder.AfterSetup(_ => AvaloniaLocator.CurrentMutable.Bind().ToSingleton>()); return builder; } - public static TAppBuilder UseManagedSystemDialogs(this TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() where TWindow : Window, new() + public static AppBuilder UseManagedSystemDialogs(this AppBuilder builder) + where TWindow : Window, new() { builder.AfterSetup(_ => AvaloniaLocator.CurrentMutable.Bind().ToSingleton>()); diff --git a/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs b/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs index cc7d5ef30d..b6bb69b05d 100644 --- a/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs +++ b/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs @@ -11,11 +11,10 @@ namespace Avalonia { public static class HeadlessVncPlatformExtensions { - public static int StartWithHeadlessVncPlatform( - this T builder, + public static int StartWithHeadlessVncPlatform( + this AppBuilder builder, string host, int port, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) - where T : AppBuilderBase, new() { var tcpServer = new TcpListener(host == null ? IPAddress.Loopback : IPAddress.Parse(host), port); tcpServer.Start(); diff --git a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs index 8da10fa59b..e7cba13ce0 100644 --- a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs +++ b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs @@ -94,8 +94,7 @@ namespace Avalonia.Headless public static class AvaloniaHeadlessPlatformExtensions { - public static T UseHeadless(this T builder, AvaloniaHeadlessPlatformOptions opts) - where T : AppBuilderBase, new() + public static AppBuilder UseHeadless(this AppBuilder builder, AvaloniaHeadlessPlatformOptions opts) { if(opts.UseHeadlessDrawing) builder.UseRenderingSubsystem(HeadlessPlatformRenderInterface.Initialize, "Headless"); diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 189f45d7c8..6613fc09be 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -6,8 +6,7 @@ namespace Avalonia { public static class AvaloniaNativePlatformExtensions { - public static T UseAvaloniaNative(this T builder) - where T : AppBuilderBase, new() + public static AppBuilder UseAvaloniaNative(this AppBuilder builder) { builder.UseWindowingSubsystem(() => { diff --git a/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs index 359da3d7c2..3fde580160 100644 --- a/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs +++ b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs @@ -12,8 +12,7 @@ namespace Avalonia.ReactiveUI /// scheduler, an activation for view fetcher, a template binding hook. Remember /// to call this method if you are using ReactiveUI in your application. /// - public static TAppBuilder UseReactiveUI(this TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() => + public static AppBuilder UseReactiveUI(this AppBuilder builder) => builder.AfterPlatformServicesSetup(_ => Locator.RegisterResolverCallbackChanged(() => { if (Locator.CurrentMutable is null) diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index e44b5ded14..9692e0a384 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -303,7 +303,7 @@ namespace Avalonia } public static class AvaloniaX11PlatformExtensions { - public static T UseX11(this T builder) where T : AppBuilderBase, new() + public static AppBuilder UseX11(this AppBuilder builder) { builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize(AvaloniaLocator.Current.GetService() ?? diff --git a/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs b/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs index 2432dc29a3..5e101f7f17 100644 --- a/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs +++ b/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs @@ -8,14 +8,13 @@ namespace Avalonia.Browser.Blazor; [SupportedOSPlatform("browser")] public static class WebAppBuilder { - public static T SetupWithSingleViewLifetime( - this T builder) - where T : AppBuilderBase, new() + public static AppBuilder SetupWithSingleViewLifetime( + this AppBuilder builder) { return builder.SetupWithLifetime(new BlazorSingleViewLifetime()); } - public static T UseBlazor(this T builder) where T : AppBuilderBase, new() + public static AppBuilder UseBlazor(this AppBuilder builder) { return builder .UseBrowser() diff --git a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs index b6c766b2a5..3adcb8e539 100644 --- a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs +++ b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs @@ -2,8 +2,6 @@ using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using System.Runtime.Versioning; -using Avalonia.Browser.Skia; -using Avalonia.Platform; namespace Avalonia.Browser; @@ -27,9 +25,8 @@ public class BrowserPlatformOptions [SupportedOSPlatform("browser")] public static class WebAppBuilder { - public static T SetupBrowserApp( - this T builder, string mainDivId) - where T : AppBuilderBase, new() + public static AppBuilder SetupBrowserApp( + this AppBuilder builder, string mainDivId) { var lifetime = new BrowserSingleViewLifetime(); @@ -42,9 +39,8 @@ public static class WebAppBuilder .SetupWithLifetime(lifetime); } - public static T UseBrowser( - this T builder) - where T : AppBuilderBase, new() + public static AppBuilder UseBrowser( + this AppBuilder builder) { return builder .UseWindowingSubsystem(BrowserWindowingPlatform.Register) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index e2e9ec8fb3..4202ba821f 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Threading; +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Embedding; @@ -12,11 +13,9 @@ using Avalonia.LinuxFramebuffer.Input; using Avalonia.LinuxFramebuffer.Input.EvDev; using Avalonia.LinuxFramebuffer.Input.LibInput; using Avalonia.LinuxFramebuffer.Output; -using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Composition; -using Avalonia.Threading; #nullable enable namespace Avalonia.LinuxFramebuffer @@ -60,7 +59,7 @@ namespace Avalonia.LinuxFramebuffer } - internal static LinuxFramebufferLifetime Initialize(T builder, IOutputBackend outputBackend, IInputBackend? inputBackend) where T : AppBuilderBase, new() + internal static LinuxFramebufferLifetime Initialize(AppBuilder builder, IOutputBackend outputBackend, IInputBackend? inputBackend) { var platform = new LinuxFramebufferPlatform(outputBackend); builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev"); @@ -140,20 +139,17 @@ namespace Avalonia.LinuxFramebuffer public static class LinuxFramebufferPlatformExtensions { - public static int StartLinuxFbDev(this T builder, string[] args, string? fbdev = null, double scaling = 1, IInputBackend? inputBackend = default) - where T : AppBuilderBase, new() => - StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling }, inputBackend); - public static int StartLinuxFbDev(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling, IInputBackend? inputBackend = default) - where T : AppBuilderBase, new() => - StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling }, inputBackend); - - public static int StartLinuxDrm(this T builder, string[] args, string? card = null, double scaling = 1, IInputBackend? inputBackend = default) - where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card) { Scaling = scaling }, inputBackend); - public static int StartLinuxDrm(this T builder, string[] args, string? card = null, bool connectorsForceProbe = false, DrmOutputOptions? options = null, IInputBackend? inputBackend = default) - where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card, connectorsForceProbe, options), inputBackend); - - public static int StartLinuxDirect(this T builder, string[] args, IOutputBackend outputBackend, IInputBackend? inputBackend = default) - where T : AppBuilderBase, new() + public static int StartLinuxFbDev(this AppBuilder builder, string[] args, string? fbdev = null, double scaling = 1, IInputBackend? inputBackend = default) + => StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling }, inputBackend); + public static int StartLinuxFbDev(this AppBuilder builder, string[] args, string fbdev, PixelFormat? format, double scaling, IInputBackend? inputBackend = default) + => StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling }, inputBackend); + + public static int StartLinuxDrm(this AppBuilder builder, string[] args, string? card = null, double scaling = 1, IInputBackend? inputBackend = default) + => StartLinuxDirect(builder, args, new DrmOutput(card) { Scaling = scaling }, inputBackend); + public static int StartLinuxDrm(this AppBuilder builder, string[] args, string? card = null, bool connectorsForceProbe = false, DrmOutputOptions? options = null, IInputBackend? inputBackend = default) + => StartLinuxDirect(builder, args, new DrmOutput(card, connectorsForceProbe, options), inputBackend); + + public static int StartLinuxDirect(this AppBuilder builder, string[] args, IOutputBackend outputBackend, IInputBackend? inputBackend = default) { var lifetime = LinuxFramebufferPlatform.Initialize(builder, outputBackend, inputBackend); builder.SetupWithLifetime(lifetime); diff --git a/src/Skia/Avalonia.Skia/SkiaApplicationExtensions.cs b/src/Skia/Avalonia.Skia/SkiaApplicationExtensions.cs index 014298ce83..3e4fd7b385 100644 --- a/src/Skia/Avalonia.Skia/SkiaApplicationExtensions.cs +++ b/src/Skia/Avalonia.Skia/SkiaApplicationExtensions.cs @@ -12,10 +12,9 @@ namespace Avalonia /// /// Enable Skia renderer. /// - /// Builder type. /// Builder. /// Configure builder. - public static T UseSkia(this T builder) where T : AppBuilderBase, new() + public static AppBuilder UseSkia(this AppBuilder builder) { return builder.UseRenderingSubsystem(() => SkiaPlatform.Initialize( AvaloniaLocator.Current.GetService() ?? new SkiaOptions()), diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index a5f77230b7..5887ba2172 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -16,7 +16,7 @@ namespace Avalonia { public static class Direct2DApplicationExtensions { - public static T UseDirect2D1(this T builder) where T : AppBuilderBase, new() + public static AppBuilder UseDirect2D1(this AppBuilder builder) { builder.UseRenderingSubsystem(Direct2D1.Direct2D1Platform.Initialize, "Direct2D1"); return builder; diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 3f16b49772..93d16b5768 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -26,9 +26,7 @@ namespace Avalonia #nullable enable public static class Win32ApplicationExtensions { - public static T UseWin32( - this T builder) - where T : AppBuilderBase, new() + public static AppBuilder UseWin32(this AppBuilder builder) { return builder.UseWindowingSubsystem( () => Win32.Win32Platform.Initialize( diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index eb0a55734a..63025f7f0a 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -12,7 +12,7 @@ namespace Avalonia { public static class IOSApplicationExtensions { - public static T UseiOS(this T builder) where T : AppBuilderBase, new() + public static AppBuilder UseiOS(this AppBuilder builder) { return builder .UseWindowingSubsystem(iOS.Platform.Register, "iOS") diff --git a/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs b/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs index 1bb2300724..aa042ffec8 100644 --- a/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs @@ -117,7 +117,7 @@ namespace Avalonia.Base.UnitTests.Media.Fonts private static IDisposable StartWithResources(params (string, string)[] assets) { var assetLoader = new MockAssetLoader(assets); - var services = new TestServices(assetLoader: assetLoader, platform: new AppBuilder().RuntimePlatform); + var services = new TestServices(assetLoader: assetLoader, platform: new StandardRuntimePlatform()); return UnitTestApplication.Start(services); } } diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index c421adaf21..8f132433ec 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -20,7 +20,7 @@ namespace Avalonia.UnitTests { public static readonly TestServices StyledWindow = new TestServices( assetLoader: new AssetLoader(), - platform: new AppBuilder().RuntimePlatform, + platform: new StandardRuntimePlatform(), renderInterface: new MockPlatformRenderInterface(), standardCursorFactory: Mock.Of(), theme: () => CreateSimpleTheme(), @@ -169,16 +169,4 @@ namespace Avalonia.UnitTests y => y.Open() == Mock.Of())); } } - - public class AppBuilder : AppBuilderBase - { - public AppBuilder() - : base(new StandardRuntimePlatform(), - builder => StandardRuntimePlatformServices.Register(builder.Instance?.GetType() - ?.GetTypeInfo().Assembly)) - { - } - - protected override bool CheckSetup => false; - } } From d3836a40ea9d63f2923813c3f01a0a8db333e496 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 23 Dec 2022 09:47:56 -0500 Subject: [PATCH 19/57] Remove Lazy usage where it's not needed anymore --- src/Avalonia.Base/Platform/StandardRuntimePlatform.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs b/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs index ddb492adcf..48fff4fc32 100644 --- a/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs +++ b/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs @@ -14,13 +14,13 @@ namespace Avalonia.Platform public IUnmanagedBlob AllocBlob(int size) => new UnmanagedBlob(size); - private static readonly Lazy Info = new(() => new RuntimePlatformInfo + private static readonly RuntimePlatformInfo s_info = new() { IsDesktop = OperatingSystemEx.IsWindows() || OperatingSystemEx.IsMacOS() || OperatingSystemEx.IsLinux(), IsMobile = OperatingSystemEx.IsAndroid() || OperatingSystemEx.IsAndroid() - }); + }; - public virtual RuntimePlatformInfo GetRuntimeInfo() => Info.Value; + public virtual RuntimePlatformInfo GetRuntimeInfo() => s_info; } } From e5d816c8055e918b0ea86bfaa4506a72ba370b2f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 23 Dec 2022 11:14:34 -0500 Subject: [PATCH 20/57] Fix copy paste condition --- src/Avalonia.Base/Platform/StandardRuntimePlatform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs b/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs index 48fff4fc32..8352d794d0 100644 --- a/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs +++ b/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs @@ -17,7 +17,7 @@ namespace Avalonia.Platform private static readonly RuntimePlatformInfo s_info = new() { IsDesktop = OperatingSystemEx.IsWindows() || OperatingSystemEx.IsMacOS() || OperatingSystemEx.IsLinux(), - IsMobile = OperatingSystemEx.IsAndroid() || OperatingSystemEx.IsAndroid() + IsMobile = OperatingSystemEx.IsAndroid() || OperatingSystemEx.IsIOS() }; From 11b0de3481b8e9950b42fee8fc1869def260e397 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 8 Dec 2022 14:28:33 +0000 Subject: [PATCH 21/57] add hold gesture recognizer --- .../HoldGestureRecognizer.cs | 124 ++++++++++++++++++ src/Avalonia.Base/Input/Gestures.cs | 4 + .../Input/HoldGestureEventArgs.cs | 31 +++++ src/Avalonia.Controls/Control.cs | 19 +++ 4 files changed, 178 insertions(+) create mode 100644 src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs create mode 100644 src/Avalonia.Base/Input/HoldGestureEventArgs.cs diff --git a/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs new file mode 100644 index 0000000000..02fd800baf --- /dev/null +++ b/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs @@ -0,0 +1,124 @@ +using System.Timers; +using Avalonia.Input.GestureRecognizers; +using Avalonia.Threading; + +namespace Avalonia.Input +{ + public class HoldGestureRecognizer : StyledElement, IGestureRecognizer + { + private const int Tolerance = 30; + private IInputElement? _target; + private IGestureRecognizerActionsDispatcher? _actions; + private int _gestureId; + private IPointer? _tracking; + private PointerPressedEventArgs? _pointerEventArgs; + private Rect _trackingBounds; + private Timer? _holdTimer; + private bool _elasped; + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHoldWithMouseEnabledProperty = + AvaloniaProperty.Register( + nameof(IsHoldWithMouseEnabled)); + + /// + /// Gets or sets whether to detect hold from the mouse + /// + public bool IsHoldWithMouseEnabled + { + get => GetValue(IsHoldWithMouseEnabledProperty); + set => SetValue(IsHoldWithMouseEnabledProperty, value); + } + + public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) + { + _target = target; + _actions = actions; + + _target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); + _target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); + + _holdTimer = new Timer(300); + _holdTimer.AutoReset = false; + _holdTimer.Elapsed += HoldTimer_Elapsed; + } + + private async void HoldTimer_Elapsed(object? sender, ElapsedEventArgs e) + { + _elasped = true; + _holdTimer?.Stop(); + + if(_tracking != null) + { + await Dispatcher.UIThread.InvokeAsync(() => _target?.RaiseEvent(new HoldGestureEventArgs(_gestureId, _pointerEventArgs, HoldingState.Started))); + } + } + + private void OnPointerPressed(object? sender, PointerPressedEventArgs e) + { + PointerPressed(e); + } + + private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) + { + PointerReleased(e); + } + + public void PointerCaptureLost(IPointer pointer) + { + if (_tracking == pointer) + { + EndHold(!_elasped); + } + } + + public void PointerMoved(PointerEventArgs e) + { + if (_tracking == e.Pointer && _target is Visual visual) + { + var currentPosition = e.GetPosition(visual); + + if (!_trackingBounds.Contains(currentPosition)) + { + EndHold(true); + } + } + } + + public void PointerPressed(PointerPressedEventArgs e) + { + if (_target != null && _target is Visual visual && (IsHoldWithMouseEnabled || e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) + { + _elasped = false; + var position = e.GetPosition(visual); + _gestureId = HoldGestureEventArgs.GetNextFreeId(); + _tracking = e.Pointer; + _pointerEventArgs = e; + + _trackingBounds = new Rect(position.X - Tolerance / 2, position.Y - Tolerance / 2, Tolerance, Tolerance); + + _holdTimer?.Start(); + } + } + + public void PointerReleased(PointerReleasedEventArgs e) + { + if (_tracking == e.Pointer) + { + EndHold(!_elasped); + } + } + + private void EndHold(bool cancelled) + { + _holdTimer?.Stop(); + + _tracking = null; + _trackingBounds = default; + + _target?.RaiseEvent(new HoldGestureEventArgs(_gestureId, _pointerEventArgs, cancelled ? HoldingState.Cancelled : HoldingState.Completed)); + } + } +} diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index b4d5feaf3b..1c2220baf4 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -58,6 +58,10 @@ namespace Avalonia.Input RoutedEvent.Register( "PullGesture", RoutingStrategies.Bubble, typeof(Gestures)); + public static readonly RoutedEvent HoldGestureEvent = + RoutedEvent.Register( + "HoldGesture", RoutingStrategies.Bubble, typeof(Gestures)); + public static readonly RoutedEvent PullGestureEndedEvent = RoutedEvent.Register( "PullGestureEnded", RoutingStrategies.Bubble, typeof(Gestures)); diff --git a/src/Avalonia.Base/Input/HoldGestureEventArgs.cs b/src/Avalonia.Base/Input/HoldGestureEventArgs.cs new file mode 100644 index 0000000000..63a508b1f4 --- /dev/null +++ b/src/Avalonia.Base/Input/HoldGestureEventArgs.cs @@ -0,0 +1,31 @@ +using System; +using Avalonia.Interactivity; + +namespace Avalonia.Input +{ + public class HoldGestureEventArgs : RoutedEventArgs + { + public int Id { get; } + public Vector Delta { get; } + public HoldingState HoldingState { get; } + public PointerEventArgs? PointerEventArgs { get; } + + private static int _nextId = 1; + + internal static int GetNextFreeId() => _nextId++; + + public HoldGestureEventArgs(int id, PointerEventArgs? pointerEventArgs, HoldingState holdingState) : base(Gestures.HoldGestureEvent) + { + Id = id; + HoldingState = holdingState; + PointerEventArgs = pointerEventArgs; + } + } + + public enum HoldingState + { + Started, + Completed, + Cancelled, + } +} diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 88c9823952..de91fb83c9 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -216,6 +216,12 @@ namespace Avalonia.Controls /// bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; + public Control() + { + // Add a default HoldGestureRecognizer + GestureRecognizers.Add(new HoldGestureRecognizer()); + } + /// void ISetterValue.Initialize(ISetter setter) { @@ -366,16 +372,29 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTreeCore(e); + AddHandler(Gestures.HoldGestureEvent, OnHoldEvent); + InitializeIfNeeded(); ScheduleOnLoadedCore(); } + private void OnHoldEvent(object? sender, HoldGestureEventArgs e) + { + if(e.HoldingState == HoldingState.Started) + { + // Trigger ContentRequest when hold starts + RaiseEvent(new ContextRequestedEventArgs(e.PointerEventArgs!)); + } + } + /// protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTreeCore(e); + RemoveHandler(Gestures.HoldGestureEvent, OnHoldEvent); + OnUnloadedCore(); } From 8945e1b77c78223b4172f419c5d916593f88c62d Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 9 Dec 2022 13:48:14 +0000 Subject: [PATCH 22/57] drop hold gesture recognizer --- .../HoldGestureRecognizer.cs | 124 ------------------ src/Avalonia.Base/Input/Gestures.cs | 93 ++++++++++++- .../Input/HoldGestureEventArgs.cs | 31 ----- .../Input/HoldingRoutedEventArgs.cs | 22 ++++ src/Avalonia.Base/Input/InputElement.cs | 46 +++++++ src/Avalonia.Controls/Control.cs | 18 +-- 6 files changed, 163 insertions(+), 171 deletions(-) delete mode 100644 src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs delete mode 100644 src/Avalonia.Base/Input/HoldGestureEventArgs.cs create mode 100644 src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs diff --git a/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs deleted file mode 100644 index 02fd800baf..0000000000 --- a/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System.Timers; -using Avalonia.Input.GestureRecognizers; -using Avalonia.Threading; - -namespace Avalonia.Input -{ - public class HoldGestureRecognizer : StyledElement, IGestureRecognizer - { - private const int Tolerance = 30; - private IInputElement? _target; - private IGestureRecognizerActionsDispatcher? _actions; - private int _gestureId; - private IPointer? _tracking; - private PointerPressedEventArgs? _pointerEventArgs; - private Rect _trackingBounds; - private Timer? _holdTimer; - private bool _elasped; - - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHoldWithMouseEnabledProperty = - AvaloniaProperty.Register( - nameof(IsHoldWithMouseEnabled)); - - /// - /// Gets or sets whether to detect hold from the mouse - /// - public bool IsHoldWithMouseEnabled - { - get => GetValue(IsHoldWithMouseEnabledProperty); - set => SetValue(IsHoldWithMouseEnabledProperty, value); - } - - public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) - { - _target = target; - _actions = actions; - - _target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); - _target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); - - _holdTimer = new Timer(300); - _holdTimer.AutoReset = false; - _holdTimer.Elapsed += HoldTimer_Elapsed; - } - - private async void HoldTimer_Elapsed(object? sender, ElapsedEventArgs e) - { - _elasped = true; - _holdTimer?.Stop(); - - if(_tracking != null) - { - await Dispatcher.UIThread.InvokeAsync(() => _target?.RaiseEvent(new HoldGestureEventArgs(_gestureId, _pointerEventArgs, HoldingState.Started))); - } - } - - private void OnPointerPressed(object? sender, PointerPressedEventArgs e) - { - PointerPressed(e); - } - - private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) - { - PointerReleased(e); - } - - public void PointerCaptureLost(IPointer pointer) - { - if (_tracking == pointer) - { - EndHold(!_elasped); - } - } - - public void PointerMoved(PointerEventArgs e) - { - if (_tracking == e.Pointer && _target is Visual visual) - { - var currentPosition = e.GetPosition(visual); - - if (!_trackingBounds.Contains(currentPosition)) - { - EndHold(true); - } - } - } - - public void PointerPressed(PointerPressedEventArgs e) - { - if (_target != null && _target is Visual visual && (IsHoldWithMouseEnabled || e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) - { - _elasped = false; - var position = e.GetPosition(visual); - _gestureId = HoldGestureEventArgs.GetNextFreeId(); - _tracking = e.Pointer; - _pointerEventArgs = e; - - _trackingBounds = new Rect(position.X - Tolerance / 2, position.Y - Tolerance / 2, Tolerance, Tolerance); - - _holdTimer?.Start(); - } - } - - public void PointerReleased(PointerReleasedEventArgs e) - { - if (_tracking == e.Pointer) - { - EndHold(!_elasped); - } - } - - private void EndHold(bool cancelled) - { - _holdTimer?.Stop(); - - _tracking = null; - _trackingBounds = default; - - _target?.RaiseEvent(new HoldGestureEventArgs(_gestureId, _pointerEventArgs, cancelled ? HoldingState.Cancelled : HoldingState.Completed)); - } - } -} diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 1c2220baf4..06d90f1c27 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -1,6 +1,8 @@ using System; +using System.Threading; using Avalonia.Interactivity; using Avalonia.Platform; +using Avalonia.Threading; using Avalonia.VisualTree; namespace Avalonia.Input @@ -8,6 +10,16 @@ namespace Avalonia.Input public static class Gestures { private static bool s_isDoubleTapped = false; + private static bool s_isHolding; + private static CancellationTokenSource? s_holdCancellationToken; + + /* /// + /// Defines the property. + /// + public static readonly AttachedProperty IsHoldWithMouseEnabledProperty = + AvaloniaProperty.RegisterAttached( + "IsHoldWithMouseEnabled");*/ + public static readonly RoutedEvent TappedEvent = RoutedEvent.Register( "Tapped", RoutingStrategies.Bubble, @@ -45,6 +57,7 @@ namespace Avalonia.Input private static readonly WeakReference s_lastPress = new WeakReference(null); private static Point s_lastPressPoint; + private static IPointer? s_lastPointer; public static readonly RoutedEvent PinchEvent = RoutedEvent.Register( @@ -58,9 +71,9 @@ namespace Avalonia.Input RoutedEvent.Register( "PullGesture", RoutingStrategies.Bubble, typeof(Gestures)); - public static readonly RoutedEvent HoldGestureEvent = - RoutedEvent.Register( - "HoldGesture", RoutingStrategies.Bubble, typeof(Gestures)); + public static readonly RoutedEvent HoldingEvent = + RoutedEvent.Register( + "Holding", RoutingStrategies.Bubble, typeof(Gestures)); public static readonly RoutedEvent PullGestureEndedEvent = RoutedEvent.Register( @@ -70,6 +83,7 @@ namespace Avalonia.Input { InputElement.PointerPressedEvent.RouteFinished.Subscribe(PointerPressed); InputElement.PointerReleasedEvent.RouteFinished.Subscribe(PointerReleased); + InputElement.PointerMovedEvent.RouteFinished.Subscribe(PointerMoved); } public static void AddTappedHandler(Interactive element, EventHandler handler) @@ -114,11 +128,38 @@ namespace Avalonia.Input var e = (PointerPressedEventArgs)ev; var visual = (Visual)ev.Source; + if(s_lastPointer != null) + { + if(s_isHolding && ev.Source is Interactive i) + { + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled)); + } + s_holdCancellationToken?.Cancel(); + s_holdCancellationToken?.Dispose(); + s_holdCancellationToken = null; + + s_lastPointer = null; + } + + s_isHolding = false; + if (e.ClickCount % 2 == 1) { s_isDoubleTapped = false; s_lastPress.SetTarget(ev.Source); + s_lastPointer = e.Pointer; s_lastPressPoint = e.GetPosition((Visual)ev.Source); + s_holdCancellationToken = new CancellationTokenSource(); + var token = s_holdCancellationToken.Token; + var settings = AvaloniaLocator.Current.GetService(); + DispatcherTimer.RunOnce(() => + { + if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && ( e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled)) + { + s_isHolding = true; + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started)); + } + }, TimeSpan.FromMilliseconds(300)); } else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) { @@ -152,7 +193,12 @@ namespace Avalonia.Input if (tapRect.ContainsExclusive(point.Position)) { - if (e.InitialPressMouseButton == MouseButton.Right) + if(s_isHolding) + { + s_isHolding = false; + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed)); + } + else if (e.InitialPressMouseButton == MouseButton.Right) { i.RaiseEvent(new TappedEventArgs(RightTappedEvent, e)); } @@ -164,6 +210,45 @@ namespace Avalonia.Input } } } + + s_holdCancellationToken?.Cancel(); + s_holdCancellationToken?.Dispose(); + s_holdCancellationToken = null; + s_lastPointer = null; + } + } + + private static void PointerMoved(RoutedEventArgs ev) + { + if (ev.Route == RoutingStrategies.Bubble) + { + var e = (PointerEventArgs)ev; + if (s_lastPress.TryGetTarget(out var target)) + { + if (e.Pointer == s_lastPointer) + { + var point = e.GetCurrentPoint((Visual)target); + var settings = AvaloniaLocator.Current.GetService(); + 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)); + + if (tapRect.ContainsExclusive(point.Position)) + { + return; + } + } + + if (s_isHolding && ev.Source is Interactive i) + { + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled)); + } + } + + s_holdCancellationToken?.Cancel(); + s_holdCancellationToken?.Dispose(); + s_holdCancellationToken = null; + s_isHolding = false; } } } diff --git a/src/Avalonia.Base/Input/HoldGestureEventArgs.cs b/src/Avalonia.Base/Input/HoldGestureEventArgs.cs deleted file mode 100644 index 63a508b1f4..0000000000 --- a/src/Avalonia.Base/Input/HoldGestureEventArgs.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using Avalonia.Interactivity; - -namespace Avalonia.Input -{ - public class HoldGestureEventArgs : RoutedEventArgs - { - public int Id { get; } - public Vector Delta { get; } - public HoldingState HoldingState { get; } - public PointerEventArgs? PointerEventArgs { get; } - - private static int _nextId = 1; - - internal static int GetNextFreeId() => _nextId++; - - public HoldGestureEventArgs(int id, PointerEventArgs? pointerEventArgs, HoldingState holdingState) : base(Gestures.HoldGestureEvent) - { - Id = id; - HoldingState = holdingState; - PointerEventArgs = pointerEventArgs; - } - } - - public enum HoldingState - { - Started, - Completed, - Cancelled, - } -} diff --git a/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs new file mode 100644 index 0000000000..5826c5b994 --- /dev/null +++ b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs @@ -0,0 +1,22 @@ +using System; +using Avalonia.Interactivity; + +namespace Avalonia.Input +{ + public class HoldingRoutedEventArgs : RoutedEventArgs + { + public HoldingState HoldingState { get; } + + public HoldingRoutedEventArgs(HoldingState holdingState) : base(Gestures.HoldingEvent) + { + HoldingState = holdingState; + } + } + + public enum HoldingState + { + Started, + Completed, + Cancelled, + } +} diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index fa755277cc..7f2cbec299 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -45,6 +45,18 @@ namespace Avalonia.Input public static readonly StyledProperty CursorProperty = AvaloniaProperty.Register(nameof(Cursor), null, true); + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHoldingEnabledProperty = + AvaloniaProperty.Register(nameof(IsHoldingEnabled), true); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHoldWithMouseEnabledProperty = + AvaloniaProperty.Register(nameof(IsHoldWithMouseEnabled), false); + /// /// Defines the property. /// @@ -188,6 +200,11 @@ namespace Avalonia.Input /// public static readonly RoutedEvent TappedEvent = Gestures.TappedEvent; + /// + /// Defines the event. + /// + public static readonly RoutedEvent HoldingEvent = Gestures.HoldingEvent; + /// /// Defines the event. /// @@ -352,6 +369,15 @@ namespace Avalonia.Input add { AddHandler(TappedEvent, value); } remove { RemoveHandler(TappedEvent, value); } } + + /// + /// Occurs when a hold gesture occurs on the control. + /// + public event EventHandler? Holding + { + add { AddHandler(HoldingEvent, value); } + remove { RemoveHandler(HoldingEvent, value); } + } /// /// Occurs when a double-tap gesture occurs on the control. @@ -388,6 +414,26 @@ namespace Avalonia.Input get { return GetValue(CursorProperty); } set { SetValue(CursorProperty, value); } } + + /// + /// Gets or sets a value that determines whether the Holding event can originate + /// from that element. + /// + public bool IsHoldingEnabled + { + get { return GetValue(IsHoldingEnabledProperty); } + set { SetValue(IsHoldingEnabledProperty, value); } + } + + /// + /// Gets or sets a value that determines whether the Holding event can originate + /// from that element. + /// + public bool IsHoldWithMouseEnabled + { + get { return GetValue(IsHoldWithMouseEnabledProperty); } + set { SetValue(IsHoldWithMouseEnabledProperty, value); } + } /// /// Gets a value indicating whether keyboard focus is anywhere within the element or its visual tree child elements. diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index de91fb83c9..bb83f9e8bb 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -216,12 +216,6 @@ namespace Avalonia.Controls /// bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; - public Control() - { - // Add a default HoldGestureRecognizer - GestureRecognizers.Add(new HoldGestureRecognizer()); - } - /// void ISetterValue.Initialize(ISetter setter) { @@ -372,19 +366,19 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTreeCore(e); - AddHandler(Gestures.HoldGestureEvent, OnHoldEvent); + AddHandler(Gestures.HoldingEvent, OnHoldEvent); InitializeIfNeeded(); ScheduleOnLoadedCore(); } - private void OnHoldEvent(object? sender, HoldGestureEventArgs e) + private void OnHoldEvent(object? sender, HoldingRoutedEventArgs e) { - if(e.HoldingState == HoldingState.Started) + if(e.HoldingState == HoldingState.Completed) { - // Trigger ContentRequest when hold starts - RaiseEvent(new ContextRequestedEventArgs(e.PointerEventArgs!)); + // Trigger ContentRequest when hold is complete + RaiseEvent(new ContextRequestedEventArgs()); } } @@ -393,7 +387,7 @@ namespace Avalonia.Controls { base.OnDetachedFromVisualTreeCore(e); - RemoveHandler(Gestures.HoldGestureEvent, OnHoldEvent); + RemoveHandler(Gestures.HoldingEvent, OnHoldEvent); OnUnloadedCore(); } From c804fa00cfe323a00ca7fa6f9e1c91f4ae0f064e Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 9 Dec 2022 18:13:32 +0000 Subject: [PATCH 23/57] fix tests --- .../Input/GesturesTests.cs | 11 +++++++ .../Input/TouchDeviceTests.cs | 2 +- .../ButtonTests.cs | 3 ++ .../ComboBoxTests.cs | 4 +++ .../ListBoxTests_Single.cs | 13 +++++---- .../Mixins/PressedMixinTests.cs | 3 ++ .../Primitives/SelectingItemsControlTests.cs | 8 ++--- .../SelectingItemsControlTests_Multiple.cs | 29 ++++++++++--------- .../TreeViewTests.cs | 3 +- tests/Avalonia.UnitTests/TestServices.cs | 3 +- 10 files changed, 52 insertions(+), 27 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 59085a21ce..20d121756e 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -3,7 +3,9 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.GestureRecognizers; using Avalonia.Media; +using Avalonia.Platform; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Base.UnitTests.Input @@ -15,6 +17,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Follow_Pointer_Pressed_Released() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -32,6 +35,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Be_Raised_Even_When_Pressed_Released_Handled() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -49,6 +53,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Not_Be_Raised_For_Middle_Button() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -66,6 +71,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Not_Be_Raised_For_Right_Button() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -83,6 +89,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void RightTapped_Should_Be_Raised_For_Right_Button() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -100,6 +107,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Follow_Pointer_Pressed_Released_Pressed() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -118,6 +126,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Be_Raised_Even_When_Pressed_Released_Handled() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -136,6 +145,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Not_Be_Raised_For_Middle_Button() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -154,6 +164,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Not_Be_Raised_For_Right_Button() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { diff --git a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs index c0c0182622..36587ea222 100644 --- a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs @@ -207,7 +207,7 @@ namespace Avalonia.Input.UnitTests private IDisposable UnitTestApp(TimeSpan doubleClickTime = new TimeSpan()) { var unitTestApp = UnitTestApplication.Start( - new TestServices(inputManager: new InputManager())); + new TestServices(inputManager: new InputManager(), threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.GetDoubleTapTime(It.IsAny())).Returns(doubleClickTime); iSettingsMock.Setup(x => x.GetDoubleTapSize(It.IsAny())).Returns(new Size(16, 16)); diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 8bd51ec500..9cb83d3009 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -134,6 +134,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_Raises_Click() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); var pt = new Point(50, 50); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) @@ -166,6 +167,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) @@ -199,6 +201,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_With_RenderTransform_Raises_Click() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); var pt = new Point(150, 50); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 1a8061bfd9..0afcd9ed49 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -11,6 +11,8 @@ using Avalonia.Media; using Avalonia.VisualTree; using Avalonia.UnitTests; using Xunit; +using Moq; +using Avalonia.Platform; namespace Avalonia.Controls.UnitTests { @@ -21,6 +23,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_On_Control_Toggles_IsDropDownOpen() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new ComboBox { Items = new[] { "Foo", "Bar" }, @@ -41,6 +44,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_On_Control_PseudoClass() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new ComboBox { Items = new[] { "Foo", "Bar" }, diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index 03aac5e2f4..7f9b38018b 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -8,6 +8,7 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.LogicalTree; +using Avalonia.Platform; using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; @@ -115,7 +116,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Item_Should_Select_It() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -133,7 +134,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -153,7 +154,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Item_Should_Select_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -173,7 +174,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Deselect_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -195,7 +196,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It_When_SelectionMode_ToggleAlwaysSelected() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -216,7 +217,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Another_Item_Should_Select_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs b/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs index 0ff1f40121..74d46b18de 100644 --- a/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs @@ -1,5 +1,7 @@ using Avalonia.Controls.Mixins; +using Avalonia.Platform; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests.Mixins @@ -19,6 +21,7 @@ namespace Avalonia.Controls.UnitTests.Mixins [Fact] public void Setting_IsSelected_Should_Add_Selected_Class() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new TestControl(); _mouse.Down(target); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index e20c03f67f..c416a5de47 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -1118,7 +1118,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Setting_SelectedItem_With_Pointer_Should_Set_TabOnceActiveElement() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1140,7 +1140,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Removing_SelectedItem_Should_Clear_TabOnceActiveElement() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var items = new ObservableCollection(new[] { "Foo", "Bar", "Baz " }); @@ -1239,7 +1239,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1257,7 +1257,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Apply_Selected_Pseudoclass_To_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index bc6147d64c..7fa3839ed5 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -12,6 +12,7 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; +using Avalonia.Platform; using Avalonia.UnitTests; using Moq; using Xunit; @@ -703,7 +704,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Selecting_From_No_Selection_Selects_From_Start() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -726,7 +727,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_Raises_SelectionChanged_Events() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -780,7 +781,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_SelectedItem_With_Multiple_Selection_Active_Sets_SelectedItem_To_Next_Selection() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -810,7 +811,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_Non_SelectedItem_With_Multiple_Selection_Active_Leaves_SelectedItem_The_Same() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -838,7 +839,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Ctrl_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -862,7 +863,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Shift_Select_Correct_Item_When_Duplicates_Are_Present() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -886,7 +887,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Can_Shift_Select_All_Items_When_Duplicates_Are_Present() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -910,7 +911,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Selecting_Raises_SelectionChanged_Events() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -959,7 +960,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Duplicate_Items_Are_Added_To_SelectedItems_In_Order() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1189,7 +1190,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Left_Click_On_SelectedItem_Should_Clear_Existing_Selection() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1216,7 +1217,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1241,7 +1242,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1293,7 +1294,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Right_Click_Should_Not_Select_Multiple() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1316,7 +1317,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Right_Click_Should_Not_Select_Multiple() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 81936711ef..3e1e25faf5 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -15,6 +15,7 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.LogicalTree; +using Avalonia.Platform; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -1466,7 +1467,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var tree = CreateTestTreeData(); var target = new TreeView diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index 8f132433ec..a2096997c0 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -51,7 +51,8 @@ namespace Avalonia.UnitTests inputManager: new InputManager(), assetLoader: new AssetLoader(), renderInterface: new MockPlatformRenderInterface(), - fontManagerImpl: new MockFontManagerImpl(), + fontManagerImpl: new MockFontManagerImpl(), + threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true), textShaperImpl: new MockTextShaperImpl()); public static readonly TestServices TextServices = new TestServices( From 6791a2bca129ae179da39dad3f58c6b41818dbbd Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 12 Dec 2022 09:08:41 +0000 Subject: [PATCH 24/57] Add pointer properties to hold event args, fixup some docs. --- src/Avalonia.Base/Input/Gestures.cs | 18 ++++------ .../Input/HoldingRoutedEventArgs.cs | 33 +++++++++++++++++-- src/Avalonia.Base/Input/InputElement.cs | 3 +- src/Avalonia.Controls/Control.cs | 4 +-- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 06d90f1c27..b1fda35c28 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -13,13 +13,6 @@ namespace Avalonia.Input private static bool s_isHolding; private static CancellationTokenSource? s_holdCancellationToken; - /* /// - /// Defines the property. - /// - public static readonly AttachedProperty IsHoldWithMouseEnabledProperty = - AvaloniaProperty.RegisterAttached( - "IsHoldWithMouseEnabled");*/ - public static readonly RoutedEvent TappedEvent = RoutedEvent.Register( "Tapped", RoutingStrategies.Bubble, @@ -71,6 +64,9 @@ namespace Avalonia.Input RoutedEvent.Register( "PullGesture", RoutingStrategies.Bubble, typeof(Gestures)); + /// + /// Occurs when a user performs a press and hold gesture (with a single touch, mouse, or pen/stylus contact). + /// public static readonly RoutedEvent HoldingEvent = RoutedEvent.Register( "Holding", RoutingStrategies.Bubble, typeof(Gestures)); @@ -132,7 +128,7 @@ namespace Avalonia.Input { if(s_isHolding && ev.Source is Interactive i) { - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer.Type)); } s_holdCancellationToken?.Cancel(); s_holdCancellationToken?.Dispose(); @@ -157,7 +153,7 @@ namespace Avalonia.Input if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && ( e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled)) { s_isHolding = true; - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type)); } }, TimeSpan.FromMilliseconds(300)); } @@ -196,7 +192,7 @@ namespace Avalonia.Input if(s_isHolding) { s_isHolding = false; - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed, s_lastPressPoint, s_lastPointer!.Type)); } else if (e.InitialPressMouseButton == MouseButton.Right) { @@ -241,7 +237,7 @@ namespace Avalonia.Input if (s_isHolding && ev.Source is Interactive i) { - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type)); } } diff --git a/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs index 5826c5b994..b9a877b2ed 100644 --- a/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs +++ b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs @@ -5,18 +5,47 @@ namespace Avalonia.Input { public class HoldingRoutedEventArgs : RoutedEventArgs { + /// + /// Gets the state of the event. + /// public HoldingState HoldingState { get; } - - public HoldingRoutedEventArgs(HoldingState holdingState) : base(Gestures.HoldingEvent) + + /// + /// Gets the location of the touch, mouse, or pen/stylus contact. + /// + public Point Position { get; } + + /// + /// Gets the pointer type of the input source. + /// + public PointerType PointerType { get; } + + /// + /// Initializes a new instance of the class. + /// + public HoldingRoutedEventArgs(HoldingState holdingState, Point position, PointerType pointerType) : base(Gestures.HoldingEvent) { HoldingState = holdingState; + Position = position; + PointerType = pointerType; } } public enum HoldingState { + /// + /// A single contact has been detected and a time threshold is crossed without the contact being lifted, another contact detected, or another gesture started. + /// Started, + + /// + /// The single contact is lifted. + /// Completed, + + /// + /// An additional contact is detected or a subsequent gesture (such as a slide) is detected. + /// Cancelled, } } diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index 7f2cbec299..4a65134552 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -426,8 +426,7 @@ namespace Avalonia.Input } /// - /// Gets or sets a value that determines whether the Holding event can originate - /// from that element. + /// Enables or disables support for the press and hold gesture through the left button on a mouse. /// public bool IsHoldWithMouseEnabled { diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index bb83f9e8bb..26db431a0e 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -375,9 +375,9 @@ namespace Avalonia.Controls private void OnHoldEvent(object? sender, HoldingRoutedEventArgs e) { - if(e.HoldingState == HoldingState.Completed) + if(e.HoldingState == HoldingState.Started) { - // Trigger ContentRequest when hold is complete + // Trigger ContentRequest when hold has started RaiseEvent(new ContextRequestedEventArgs()); } } From 8516308e6335b63ac81d7ae57b966fe54c12ede5 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 22 Dec 2022 14:02:20 +0000 Subject: [PATCH 25/57] add HoldWaitDuration to IPlatformSettings --- src/Avalonia.Base/Input/Gestures.cs | 16 ++++++++++------ .../Platform/DefaultPlatformSettings.cs | 2 ++ src/Avalonia.Base/Platform/IPlatformSettings.cs | 2 ++ src/Windows/Avalonia.Win32/Win32Platform.cs | 2 ++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index b1fda35c28..a924b391ac 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -148,14 +148,18 @@ namespace Avalonia.Input s_holdCancellationToken = new CancellationTokenSource(); var token = s_holdCancellationToken.Token; var settings = AvaloniaLocator.Current.GetService(); - DispatcherTimer.RunOnce(() => + + if (settings != null) { - if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && ( e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled)) + DispatcherTimer.RunOnce(() => { - s_isHolding = true; - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type)); - } - }, TimeSpan.FromMilliseconds(300)); + if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && (e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled)) + { + s_isHolding = true; + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type)); + } + }, settings.HoldWaitDuration); + } } else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) { diff --git a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs index dd3e1a4cb1..d0b27b057f 100644 --- a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs +++ b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs @@ -26,5 +26,7 @@ namespace Avalonia.Platform }; } public TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(500); + + public TimeSpan HoldWaitDuration { get; set; } = TimeSpan.FromMilliseconds(300); } } diff --git a/src/Avalonia.Base/Platform/IPlatformSettings.cs b/src/Avalonia.Base/Platform/IPlatformSettings.cs index e7921883fd..7c4c1420eb 100644 --- a/src/Avalonia.Base/Platform/IPlatformSettings.cs +++ b/src/Avalonia.Base/Platform/IPlatformSettings.cs @@ -27,5 +27,7 @@ namespace Avalonia.Platform /// tap gesture. /// TimeSpan GetDoubleTapTime(PointerType type); + + TimeSpan HoldWaitDuration { get; set; } } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 93d16b5768..585a9cdf19 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -258,6 +258,8 @@ namespace Avalonia.Win32 public bool CurrentThreadIsLoopThread => _uiThread == Thread.CurrentThread; + public TimeSpan HoldWaitDuration { get; set; } = TimeSpan.FromMilliseconds(300); + public event Action Signaled; public event EventHandler ShutdownRequested; From f61c7f6cd949388e10faa0dc93818147e7ad4c3b Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 22 Dec 2022 14:03:56 +0000 Subject: [PATCH 26/57] Revert "fix tests" This reverts commit 584e18f78d29480890711b1149d946f61e0d8447. --- .../Input/GesturesTests.cs | 10 ------- .../Input/TouchDeviceTests.cs | 2 +- .../ButtonTests.cs | 3 -- .../ComboBoxTests.cs | 4 --- .../ListBoxTests_Single.cs | 13 ++++----- .../Mixins/PressedMixinTests.cs | 3 -- .../Primitives/SelectingItemsControlTests.cs | 8 ++--- .../SelectingItemsControlTests_Multiple.cs | 29 +++++++++---------- .../TreeViewTests.cs | 3 +- tests/Avalonia.UnitTests/TestServices.cs | 3 +- 10 files changed, 27 insertions(+), 51 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 20d121756e..fedcdcfb1e 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -5,7 +5,6 @@ using Avalonia.Input.GestureRecognizers; using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; -using Moq; using Xunit; namespace Avalonia.Base.UnitTests.Input @@ -17,7 +16,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Follow_Pointer_Pressed_Released() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -35,7 +33,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Be_Raised_Even_When_Pressed_Released_Handled() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -53,7 +50,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Not_Be_Raised_For_Middle_Button() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -71,7 +67,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Not_Be_Raised_For_Right_Button() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -89,7 +84,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void RightTapped_Should_Be_Raised_For_Right_Button() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -107,7 +101,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Follow_Pointer_Pressed_Released_Pressed() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -126,7 +119,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Be_Raised_Even_When_Pressed_Released_Handled() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -145,7 +137,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Not_Be_Raised_For_Middle_Button() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -164,7 +155,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Not_Be_Raised_For_Right_Button() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { diff --git a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs index 36587ea222..c0c0182622 100644 --- a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs @@ -207,7 +207,7 @@ namespace Avalonia.Input.UnitTests private IDisposable UnitTestApp(TimeSpan doubleClickTime = new TimeSpan()) { var unitTestApp = UnitTestApplication.Start( - new TestServices(inputManager: new InputManager(), threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); + new TestServices(inputManager: new InputManager())); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.GetDoubleTapTime(It.IsAny())).Returns(doubleClickTime); iSettingsMock.Setup(x => x.GetDoubleTapSize(It.IsAny())).Returns(new Size(16, 16)); diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 9cb83d3009..8bd51ec500 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -134,7 +134,6 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_Raises_Click() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); var pt = new Point(50, 50); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) @@ -167,7 +166,6 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) @@ -201,7 +199,6 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_With_RenderTransform_Raises_Click() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); var pt = new Point(150, 50); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 0afcd9ed49..1a8061bfd9 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -11,8 +11,6 @@ using Avalonia.Media; using Avalonia.VisualTree; using Avalonia.UnitTests; using Xunit; -using Moq; -using Avalonia.Platform; namespace Avalonia.Controls.UnitTests { @@ -23,7 +21,6 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_On_Control_Toggles_IsDropDownOpen() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new ComboBox { Items = new[] { "Foo", "Bar" }, @@ -44,7 +41,6 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_On_Control_PseudoClass() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new ComboBox { Items = new[] { "Foo", "Bar" }, diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index 7f9b38018b..03aac5e2f4 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -8,7 +8,6 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.LogicalTree; -using Avalonia.Platform; using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; @@ -116,7 +115,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Item_Should_Select_It() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -134,7 +133,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -154,7 +153,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Item_Should_Select_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -174,7 +173,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Deselect_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -196,7 +195,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It_When_SelectionMode_ToggleAlwaysSelected() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -217,7 +216,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Another_Item_Should_Select_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs b/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs index 74d46b18de..0ff1f40121 100644 --- a/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs @@ -1,7 +1,5 @@ using Avalonia.Controls.Mixins; -using Avalonia.Platform; using Avalonia.UnitTests; -using Moq; using Xunit; namespace Avalonia.Controls.UnitTests.Mixins @@ -21,7 +19,6 @@ namespace Avalonia.Controls.UnitTests.Mixins [Fact] public void Setting_IsSelected_Should_Add_Selected_Class() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new TestControl(); _mouse.Down(target); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index c416a5de47..e20c03f67f 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -1118,7 +1118,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Setting_SelectedItem_With_Pointer_Should_Set_TabOnceActiveElement() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1140,7 +1140,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Removing_SelectedItem_Should_Clear_TabOnceActiveElement() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var items = new ObservableCollection(new[] { "Foo", "Bar", "Baz " }); @@ -1239,7 +1239,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1257,7 +1257,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Apply_Selected_Pseudoclass_To_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index 7fa3839ed5..bc6147d64c 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -12,7 +12,6 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; -using Avalonia.Platform; using Avalonia.UnitTests; using Moq; using Xunit; @@ -704,7 +703,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Selecting_From_No_Selection_Selects_From_Start() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -727,7 +726,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_Raises_SelectionChanged_Events() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -781,7 +780,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_SelectedItem_With_Multiple_Selection_Active_Sets_SelectedItem_To_Next_Selection() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -811,7 +810,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_Non_SelectedItem_With_Multiple_Selection_Active_Leaves_SelectedItem_The_Same() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -839,7 +838,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Ctrl_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -863,7 +862,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Shift_Select_Correct_Item_When_Duplicates_Are_Present() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -887,7 +886,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Can_Shift_Select_All_Items_When_Duplicates_Are_Present() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -911,7 +910,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Selecting_Raises_SelectionChanged_Events() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -960,7 +959,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Duplicate_Items_Are_Added_To_SelectedItems_In_Order() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1190,7 +1189,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Left_Click_On_SelectedItem_Should_Clear_Existing_Selection() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1217,7 +1216,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1242,7 +1241,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1294,7 +1293,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Right_Click_Should_Not_Select_Multiple() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1317,7 +1316,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Right_Click_Should_Not_Select_Multiple() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 3e1e25faf5..81936711ef 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -15,7 +15,6 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.LogicalTree; -using Avalonia.Platform; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -1467,7 +1466,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var tree = CreateTestTreeData(); var target = new TreeView diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index a2096997c0..8f132433ec 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -51,8 +51,7 @@ namespace Avalonia.UnitTests inputManager: new InputManager(), assetLoader: new AssetLoader(), renderInterface: new MockPlatformRenderInterface(), - fontManagerImpl: new MockFontManagerImpl(), - threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true), + fontManagerImpl: new MockFontManagerImpl(), textShaperImpl: new MockTextShaperImpl()); public static readonly TestServices TextServices = new TestServices( From be843f699a677154d7acca6ce9eba4b287efda42 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 22 Dec 2022 14:19:01 +0000 Subject: [PATCH 27/57] add mock thread interface to touch device tests --- tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs index c0c0182622..36587ea222 100644 --- a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs @@ -207,7 +207,7 @@ namespace Avalonia.Input.UnitTests private IDisposable UnitTestApp(TimeSpan doubleClickTime = new TimeSpan()) { var unitTestApp = UnitTestApplication.Start( - new TestServices(inputManager: new InputManager())); + new TestServices(inputManager: new InputManager(), threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.GetDoubleTapTime(It.IsAny())).Returns(doubleClickTime); iSettingsMock.Setup(x => x.GetDoubleTapSize(It.IsAny())).Returns(new Size(16, 16)); From 29e7666a679e7994ba6d857543bb135753b494b1 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 22 Dec 2022 16:22:27 +0000 Subject: [PATCH 28/57] attempt at testing --- .../Input/GesturesTests.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index fedcdcfb1e..46df719d69 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -1,10 +1,13 @@ +using System; using System.Collections.Generic; +using System.Threading; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.GestureRecognizers; using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Base.UnitTests.Input @@ -170,6 +173,35 @@ namespace Avalonia.Base.UnitTests.Input Assert.False(raised); } + [Fact] + public void Hold_Should_Not_Be_Raised_For_Multiple_Contact() + { + using var app = UnitTestApplication.Start(TestServices.MockThreadingInterface); + + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = true); + + var secondMouse = new MouseTestHelper(); + + _mouse.Down(border, MouseButton.Left); + + Thread.Sleep(1000); + secondMouse.Down(border, MouseButton.Left); + + Assert.False(raised); + } + private static void AddHandlers( Decorator decorator, Border border, From 17765223976844e9ebb1908497bc8edc3d73506a Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 22 Dec 2022 11:59:33 -0500 Subject: [PATCH 29/57] Mock IPlatformThreadingInterface --- .../Input/GesturesTests.cs | 100 +++++++++++++++++- 1 file changed, 95 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 46df719d69..82aa3eab72 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -6,6 +6,7 @@ using Avalonia.Input; using Avalonia.Input.GestureRecognizers; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Threading; using Avalonia.UnitTests; using Moq; using Xunit; @@ -174,14 +175,52 @@ namespace Avalonia.Base.UnitTests.Input } [Fact] - public void Hold_Should_Not_Be_Raised_For_Multiple_Contact() + public void Hold_Is_Raised_When_Pointer_Pressed() { - using var app = UnitTestApplication.Start(TestServices.MockThreadingInterface); + using var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = e.HoldingState == HoldingState.Started); + + _mouse.Down(border); + Assert.False(raised); + + // Verify timer duration, but execute it immediately. + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + Assert.True(raised); + } + + [Fact] + public void Hold_Is_Not_Raised_When_Pointer_Released_Before_Timer() + { + using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); AvaloniaLocator.CurrentMutable.BindToSelf(this) - .Bind().ToConstant(iSettingsMock.Object); + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + Border border = new Border(); border.IsHoldWithMouseEnabled = true; var decorator = new Decorator @@ -190,18 +229,69 @@ namespace Avalonia.Base.UnitTests.Input }; var raised = false; - decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = true); + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = e.HoldingState == HoldingState.Completed); + + _mouse.Down(border); + Assert.False(raised); + + _mouse.Up(border); + Assert.False(raised); + + // Verify timer duration, but execute it immediately. + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + + Assert.False(raised); + } + + [Fact] + public void Hold_Should_Not_Be_Raised_For_Multiple_Contact() + { + using var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = e.HoldingState == HoldingState.Completed); var secondMouse = new MouseTestHelper(); _mouse.Down(border, MouseButton.Left); - Thread.Sleep(1000); + // Verify timer duration, but execute it immediately. + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + secondMouse.Down(border, MouseButton.Left); Assert.False(raised); } + private static IPlatformThreadingInterface CreatePlatformThreadingInterface(Action<(TimeSpan, Action)> callback) + { + var threadingInterface = new Mock(); + threadingInterface.SetupGet(p => p.CurrentThreadIsLoopThread).Returns(true); + threadingInterface.Setup(p => p + .StartTimer(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((_, t, a) => callback((t, a))); + return threadingInterface.Object; + } + private static void AddHandlers( Decorator decorator, Border border, From 08f17fa2068f09c36f1730c359eb97e717c4dfa7 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 23 Dec 2022 09:22:18 +0000 Subject: [PATCH 30/57] add more tests --- .../Input/GesturesTests.cs | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 82aa3eab72..33a01fc9cd 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Threading; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.GestureRecognizers; @@ -244,6 +243,78 @@ namespace Avalonia.Base.UnitTests.Input Assert.False(raised); } + + [Fact] + public void Hold_Is_Cancelled_When_Second_Contact_Is_Detected() + { + using var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + var cancelled = false; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => cancelled = e.HoldingState == HoldingState.Cancelled); + + _mouse.Down(border); + Assert.False(cancelled); + + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + + var secondMouse = new MouseTestHelper(); + + secondMouse.Down(border); + + Assert.True(cancelled); + } + + [Fact] + public void Hold_Is_Cancelled_When_Pointer_Moves() + { + using var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + var cancelled = false; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => cancelled = e.HoldingState == HoldingState.Cancelled); + + _mouse.Down(border); + Assert.False(cancelled); + + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + + _mouse.Move(border, position: new Point(10, 10)); + + Assert.True(cancelled); + } [Fact] public void Hold_Should_Not_Be_Raised_For_Multiple_Contact() From 306712e4cce1f63be61357a5f1c0456374bebf0c Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 23 Dec 2022 12:33:28 +0000 Subject: [PATCH 31/57] make IsHoldingEnabledProperty and IsHoldWithMouseEnabledProperty attached properties in Gestures --- src/Avalonia.Base/Input/Gestures.cs | 30 +++++++++++++++++++++++++ src/Avalonia.Base/Input/InputElement.cs | 26 ++++++++++----------- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index a924b391ac..a91e37177a 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -13,6 +13,18 @@ namespace Avalonia.Input private static bool s_isHolding; private static CancellationTokenSource? s_holdCancellationToken; + /// + /// Defines the IsHoldingEnabled attached property. + /// + public static readonly AttachedProperty IsHoldingEnabledProperty = + AvaloniaProperty.RegisterAttached("IsHoldingEnabled", typeof(Gestures), true); + + /// + /// Defines the IsHoldWithMouseEnabled attached property. + /// + public static readonly AttachedProperty IsHoldWithMouseEnabledProperty = + AvaloniaProperty.RegisterAttached("IsHoldWithMouseEnabled", typeof(Gestures), false); + public static readonly RoutedEvent TappedEvent = RoutedEvent.Register( "Tapped", RoutingStrategies.Bubble, @@ -75,6 +87,24 @@ namespace Avalonia.Input RoutedEvent.Register( "PullGestureEnded", RoutingStrategies.Bubble, typeof(Gestures)); + public static bool GetIsHoldingEnabled(StyledElement element) + { + return element.GetValue(IsHoldingEnabledProperty); + } + public static void SetIsHoldingEnabled(StyledElement element, bool value) + { + element.SetValue(IsHoldingEnabledProperty, value); + } + + public static bool GetIsHoldWithMouseEnabled(StyledElement element) + { + return element.GetValue(IsHoldWithMouseEnabledProperty); + } + public static void SetIsHoldWithMouseEnabled(StyledElement element, bool value) + { + element.SetValue(IsHoldWithMouseEnabledProperty, value); + } + static Gestures() { InputElement.PointerPressedEvent.RouteFinished.Subscribe(PointerPressed); diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index 4a65134552..e1835241bf 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -45,18 +45,6 @@ namespace Avalonia.Input public static readonly StyledProperty CursorProperty = AvaloniaProperty.Register(nameof(Cursor), null, true); - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHoldingEnabledProperty = - AvaloniaProperty.Register(nameof(IsHoldingEnabled), true); - - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHoldWithMouseEnabledProperty = - AvaloniaProperty.Register(nameof(IsHoldWithMouseEnabled), false); - /// /// Defines the property. /// @@ -209,7 +197,19 @@ namespace Avalonia.Input /// Defines the event. /// public static readonly RoutedEvent DoubleTappedEvent = Gestures.DoubleTappedEvent; - + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHoldingEnabledProperty = + Gestures.IsHoldingEnabledProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHoldWithMouseEnabledProperty = + Gestures.IsHoldWithMouseEnabledProperty.AddOwner(); + private bool _isEffectivelyEnabled = true; private bool _isFocused; private bool _isKeyboardFocusWithin; From 57255586c27a1e1296f3b01f18bc3e9598d2ac69 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 27 Dec 2022 12:50:25 +0000 Subject: [PATCH 32/57] update hold tests --- src/Avalonia.Base/Input/Gestures.cs | 8 +-- .../Input/GesturesTests.cs | 68 ++++++++++++++++--- 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index a91e37177a..9708ca2b48 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -267,11 +267,11 @@ namespace Avalonia.Input { return; } - } - if (s_isHolding && ev.Source is Interactive i) - { - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type)); + if (s_isHolding && ev.Source is Interactive i) + { + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type)); + } } } diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 33a01fc9cd..1b0c941082 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -174,7 +174,46 @@ namespace Avalonia.Base.UnitTests.Input } [Fact] - public void Hold_Is_Raised_When_Pointer_Pressed() + public void Hold_Should_Be_Raised_After_Hold_Duration() + { + using var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + iSettingsMock.Setup(x => x.GetTapSize(It.IsAny())).Returns(new Size(16, 16)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + HoldingState holding = HoldingState.Cancelled; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => holding = e.HoldingState); + + _mouse.Down(border); + Assert.False(holding != HoldingState.Cancelled); + + // Verify timer duration, but execute it immediately. + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + + Assert.True(holding == HoldingState.Started); + + _mouse.Up(border); + + Assert.True(holding == HoldingState.Completed); + } + + [Fact] + public void Hold_Should_Not_Raised_When_Pointer_Released_Before_Timer() { using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); @@ -199,16 +238,19 @@ namespace Avalonia.Base.UnitTests.Input _mouse.Down(border); Assert.False(raised); + _mouse.Up(border); + Assert.False(raised); + // Verify timer duration, but execute it immediately. var timer = Assert.Single(scheduledTimers); Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); timer.action(); - Assert.True(raised); + Assert.False(raised); } [Fact] - public void Hold_Is_Not_Raised_When_Pointer_Released_Before_Timer() + public void Hold_Should_Not_Raised_When_Pointer_Is_Moved_Before_Timer() { using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); @@ -232,8 +274,8 @@ namespace Avalonia.Base.UnitTests.Input _mouse.Down(border); Assert.False(raised); - - _mouse.Up(border); + + _mouse.Move(border, position: new Point(20, 20)); Assert.False(raised); // Verify timer duration, but execute it immediately. @@ -245,7 +287,7 @@ namespace Avalonia.Base.UnitTests.Input } [Fact] - public void Hold_Is_Cancelled_When_Second_Contact_Is_Detected() + public void Hold_Should_Be_Cancelled_When_Second_Contact_Is_Detected() { using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); @@ -282,11 +324,12 @@ namespace Avalonia.Base.UnitTests.Input } [Fact] - public void Hold_Is_Cancelled_When_Pointer_Moves() + public void Hold_Should_Be_Cancelled_When_Pointer_Moves_Too_Far() { using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + iSettingsMock.Setup(x => x.GetTapSize(It.IsAny())).Returns(new Size(16, 16)); AvaloniaLocator.CurrentMutable.BindToSelf(this) .Bind().ToConstant(iSettingsMock.Object); @@ -305,19 +348,22 @@ namespace Avalonia.Base.UnitTests.Input decorator.AddHandler(Gestures.HoldingEvent, (s, e) => cancelled = e.HoldingState == HoldingState.Cancelled); _mouse.Down(border); - Assert.False(cancelled); var timer = Assert.Single(scheduledTimers); Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); timer.action(); - _mouse.Move(border, position: new Point(10, 10)); + _mouse.Move(border, position: new Point(3, 3)); + + Assert.False(cancelled); + + _mouse.Move(border, position: new Point(20, 20)); Assert.True(cancelled); } - + [Fact] - public void Hold_Should_Not_Be_Raised_For_Multiple_Contact() + public void Hold_Should_Not_Be_Raised_For_Multiple_Contacts() { using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); From 66ca4ca6399f964c30e09672aca42fb7d016b106 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 28 Dec 2022 08:55:21 +0000 Subject: [PATCH 33/57] remove hold styled properties in InputElement --- src/Avalonia.Base/Input/Gestures.cs | 2 +- src/Avalonia.Base/Input/InputElement.cs | 31 ------------------- .../Input/GesturesTests.cs | 12 +++---- 3 files changed, 7 insertions(+), 38 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 9708ca2b48..54e61d89b2 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -183,7 +183,7 @@ namespace Avalonia.Input { DispatcherTimer.RunOnce(() => { - if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && (e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled)) + if (!token.IsCancellationRequested && e.Source is InputElement i && GetIsHoldingEnabled(i) && (e.Pointer.Type != PointerType.Mouse || GetIsHoldWithMouseEnabled(i))) { s_isHolding = true; i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type)); diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index e1835241bf..f233fdce51 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -198,18 +198,6 @@ namespace Avalonia.Input /// public static readonly RoutedEvent DoubleTappedEvent = Gestures.DoubleTappedEvent; - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHoldingEnabledProperty = - Gestures.IsHoldingEnabledProperty.AddOwner(); - - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHoldWithMouseEnabledProperty = - Gestures.IsHoldWithMouseEnabledProperty.AddOwner(); - private bool _isEffectivelyEnabled = true; private bool _isFocused; private bool _isKeyboardFocusWithin; @@ -414,25 +402,6 @@ namespace Avalonia.Input get { return GetValue(CursorProperty); } set { SetValue(CursorProperty, value); } } - - /// - /// Gets or sets a value that determines whether the Holding event can originate - /// from that element. - /// - public bool IsHoldingEnabled - { - get { return GetValue(IsHoldingEnabledProperty); } - set { SetValue(IsHoldingEnabledProperty, value); } - } - - /// - /// Enables or disables support for the press and hold gesture through the left button on a mouse. - /// - public bool IsHoldWithMouseEnabled - { - get { return GetValue(IsHoldWithMouseEnabledProperty); } - set { SetValue(IsHoldWithMouseEnabledProperty, value); } - } /// /// Gets a value indicating whether keyboard focus is anywhere within the element or its visual tree child elements. diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 1b0c941082..84ee35ba61 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -188,7 +188,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border @@ -226,7 +226,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border @@ -263,7 +263,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border @@ -300,7 +300,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border @@ -338,7 +338,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border @@ -376,7 +376,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border From 4780567438950cd9269cd84cbef36b41d870872f Mon Sep 17 00:00:00 2001 From: daniel Date: Wed, 28 Dec 2022 15:15:35 +0200 Subject: [PATCH 34/57] Don't reomve old content from logicaltree --- src/Avalonia.Controls/ContentControl.cs | 11 +++- .../TransitioningContentControl.cs | 7 +++ .../TransitioningContentControlTests.cs | 63 +++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs index b8a45e102f..d47a7a7809 100644 --- a/src/Avalonia.Controls/ContentControl.cs +++ b/src/Avalonia.Controls/ContentControl.cs @@ -116,14 +116,19 @@ namespace Avalonia.Controls return false; } - private void ContentChanged(AvaloniaPropertyChangedEventArgs e) + protected virtual void ContentChanged(AvaloniaPropertyChangedEventArgs e) { - if (e.OldValue is ILogical oldChild) + UpdateLogicalTree(e.OldValue, e.NewValue); + } + + protected void UpdateLogicalTree(object? toRemove, object? toAdd) + { + if (toRemove is ILogical oldChild) { LogicalChildren.Remove(oldChild); } - if (e.NewValue is ILogical newChild) + if (toAdd is ILogical newChild) { LogicalChildren.Add(newChild); } diff --git a/src/Avalonia.Controls/TransitioningContentControl.cs b/src/Avalonia.Controls/TransitioningContentControl.cs index 70b21b7248..545032befb 100644 --- a/src/Avalonia.Controls/TransitioningContentControl.cs +++ b/src/Avalonia.Controls/TransitioningContentControl.cs @@ -71,6 +71,11 @@ public class TransitioningContentControl : ContentControl } } + protected override void ContentChanged(AvaloniaPropertyChangedEventArgs e) + { + // We do nothing becuse we should not remove old Content until the animation is over + } + /// /// Updates the content with transitions. /// @@ -89,6 +94,8 @@ public class TransitioningContentControl : ContentControl if (PageTransition != null) await PageTransition.Start(this, null, true, localToken); + UpdateLogicalTree(CurrentContent, content); + if (localToken.IsCancellationRequested) { return; diff --git a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs new file mode 100644 index 0000000000..fa523d7f78 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs @@ -0,0 +1,63 @@ +using System; +using Avalonia.LogicalTree; +using Avalonia.UnitTests; +using Xunit; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Animation; + +namespace Avalonia.Controls.UnitTests +{ + public class TransitioningContentControlTests + { + [Fact] + public void Old_Content_Shuold_Be_Removed__From_Logical_Tree_After_Out_Animation() + { + var testTransition = new TestTransition(); + + var target = new TransitioningContentControl(); + target.PageTransition = testTransition; + + var root = new TestRoot() { Child = target }; + + var oldControl = new Control(); + var newControl = new Control(); + + target.Content = oldControl; + Threading.Dispatcher.UIThread.RunJobs(); + + Assert.Equal(target, oldControl.GetLogicalParent()); + Assert.Equal(null, newControl.GetLogicalParent()); + + testTransition.BeginTransition += isFrom => + { + // Old out + if (isFrom) + { + Assert.Equal(target, oldControl.GetLogicalParent()); + Assert.Equal(null, newControl.GetLogicalParent()); + } + // New in + else + { + Assert.Equal(null, oldControl.GetLogicalParent()); + Assert.Equal(target, newControl.GetLogicalParent()); + } + }; + + target.Content = newControl; + Threading.Dispatcher.UIThread.RunJobs(); + } + } + public class TestTransition : IPageTransition + { + public event Action BeginTransition; + + public Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken) + { + bool isFrom = from != null && to == null; + BeginTransition?.Invoke(isFrom); + return Task.CompletedTask; + } + } +} From 46eca014a2ba5cd4528ac755172c821695a43661 Mon Sep 17 00:00:00 2001 From: Martijn Sneijders Date: Wed, 28 Dec 2022 15:46:54 +0100 Subject: [PATCH 35/57] ScrollGestureRecognizer: fix skipping ScrollStartDistance on start of scroll; fix end scrolling too early using InertialScrollSpeedEnd; adding VelocityTracker that uses the last 20 pointermoves for calculating scroll velocity on pointerreleased. --- .../ScrollGestureRecognizer.cs | 43 +- .../GestureRecognizers/VelocityTracker.cs | 424 ++++++++++++++++++ 2 files changed, 454 insertions(+), 13 deletions(-) create mode 100644 src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 64fe275547..e2deea0fde 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -17,7 +17,9 @@ namespace Avalonia.Input.GestureRecognizers private bool _canVerticallyScroll; private int _gestureId; private int _scrollStartDistance = 30; - + private Point _pointerPressedPoint; + private VelocityTracker? _velocityTracker; + // Movement per second private Vector _inertia; private ulong? _lastMoveTimestamp; @@ -91,7 +93,7 @@ namespace Avalonia.Input.GestureRecognizers EndGesture(); _tracking = e.Pointer; _gestureId = ScrollGestureEventArgs.GetNextFreeId(); - _trackedRootPoint = e.GetPosition((Visual?)_target); + _trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)_target); } } @@ -111,6 +113,13 @@ namespace Avalonia.Input.GestureRecognizers _scrolling = true; if (_scrolling) { + _velocityTracker = new VelocityTracker(); // TODO: Should be platform specific -- this default tracker is for Android. + + // Correct _trackedRootPoint with ScrollStartDistance, so scrolling does not start with a skip of ScrollStartDistance + _trackedRootPoint = new Point( + _trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? _scrollStartDistance : -_scrollStartDistance), + _trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? _scrollStartDistance : -_scrollStartDistance)); + _actions!.Capture(e.Pointer, this); } } @@ -118,14 +127,11 @@ namespace Avalonia.Input.GestureRecognizers if (_scrolling) { var vector = _trackedRootPoint - rootPoint; - var elapsed = _lastMoveTimestamp.HasValue && _lastMoveTimestamp < e.Timestamp ? - TimeSpan.FromMilliseconds(e.Timestamp - _lastMoveTimestamp.Value) : - TimeSpan.Zero; - + + _velocityTracker?.AddPosition(TimeSpan.FromMilliseconds(e.Timestamp), _pointerPressedPoint - rootPoint); + _lastMoveTimestamp = e.Timestamp; _trackedRootPoint = rootPoint; - if (elapsed.TotalSeconds > 0) - _inertia = vector / elapsed.TotalSeconds; _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); e.Handled = true; } @@ -150,12 +156,14 @@ namespace Avalonia.Input.GestureRecognizers } } - - + + public void PointerReleased(PointerReleasedEventArgs e) { if (e.Pointer == _tracking && _scrolling) { + _inertia = _velocityTracker?.GetFlingVelocity().PixelsPerSecond ?? Vector.Zero; + e.Handled = true; if (_inertia == default || e.Timestamp == 0 @@ -183,9 +191,18 @@ namespace Avalonia.Input.GestureRecognizers var distance = speed * elapsedSinceLastTick.TotalSeconds; _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance)); - - - if (Math.Abs(speed.X) < InertialScrollSpeedEnd || Math.Abs(speed.Y) <= InertialScrollSpeedEnd) + // EndGesture using InertialScrollSpeedEnd only in the direction of scrolling + if (CanVerticallyScroll && CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd && Math.Abs(speed.Y) <= InertialScrollSpeedEnd) + { + EndGesture(); + return false; + } + else if (CanVerticallyScroll && Math.Abs(speed.Y) <= InertialScrollSpeedEnd) + { + EndGesture(); + return false; + } + else if (CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd) { EndGesture(); return false; diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs new file mode 100644 index 0000000000..909010d43d --- /dev/null +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -0,0 +1,424 @@ +// Code in this file is derived from +// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/velocity_tracker.dart + +//Copyright 2014 The Flutter Authors. All rights reserved. + +//Redistribution and use in source and binary forms, with or without modification, +//are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. + +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; +//OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using System; +using System.Diagnostics; + +namespace Avalonia.Input.GestureRecognizers +{ + // TODO: add 'IOSScrollViewFlingVelocityTracker' and 'MacOSScrollViewFlingVelocityTracker'? + + public readonly record struct Velocity(Vector PixelsPerSecond) + { + public Velocity ClampMagnitude(double minValue, double maxValue) + { + Debug.Assert(minValue >= 0.0); + Debug.Assert(maxValue >= 0.0 && maxValue >= minValue); + double valueSquared = PixelsPerSecond.SquaredLength; + if (valueSquared > maxValue * maxValue) + { + double length = PixelsPerSecond.Length; + return new Velocity(length != 0.0 ? (PixelsPerSecond / length) * maxValue : Vector.Zero); + // preventing double.NaN in Vector PixelsPerSecond is important -- if a NaN eventually gets into a + // ScrollGestureEventArgs it results in runtime errors. + } + if (valueSquared < minValue * minValue) + { + double length = PixelsPerSecond.Length; + return new Velocity(length != 0.0 ? (PixelsPerSecond / length) * minValue : Vector.Zero); + } + return this; + } + } + + /// A two dimensional velocity estimate. + /// + /// VelocityEstimates are computed by [VelocityTracker.getVelocityEstimate]. An + /// estimate's [confidence] measures how well the velocity tracker's position + /// data fit a straight line, [duration] is the time that elapsed between the + /// first and last position sample used to compute the velocity, and [offset] + /// is similarly the difference between the first and last positions. + /// + /// See also: + /// + /// * [VelocityTracker], which computes [VelocityEstimate]s. + /// * [Velocity], which encapsulates (just) a velocity vector and provides some + /// useful velocity operations. + public record VelocityEstimate(Vector PixelsPerSecond, double Confidence, TimeSpan Duration, Vector Offset); + + internal record struct PointAtTime(bool Valid, Vector Point, TimeSpan Time); + + /// Computes a pointer's velocity based on data from [PointerMoveEvent]s. + /// + /// The input data is provided by calling [addPosition]. Adding data is cheap. + /// + /// To obtain a velocity, call [getVelocity] or [getVelocityEstimate]. This will + /// compute the velocity based on the data added so far. Only call these when + /// you need to use the velocity, as they are comparatively expensive. + /// + /// The quality of the velocity estimation will be better if more data points + /// have been received. + public class VelocityTracker + { + private const int AssumePointerMoveStoppedMilliseconds = 40; + private const int HistorySize = 20; + private const int HorizonMilliseconds = 100; + private const int MinSampleSize = 3; + private const double MinFlingVelocity = 50.0; // Logical pixels / second (defined in flutter\lib\src\gesture\constants.dart) + private const double MaxFlingVelocity = 8000.0; + + private readonly PointAtTime[] _samples = new PointAtTime[HistorySize]; + private int _index = 0; + + /// + /// Adds a position as the given time to the tracker. + /// + /// + /// + public void AddPosition(TimeSpan time, Vector position) + { + _index++; + if (_index == HistorySize) + { + _index = 0; + } + _samples[_index] = new PointAtTime(true, position, time); + } + + /// Returns an estimate of the velocity of the object being tracked by the + /// tracker given the current information available to the tracker. + /// + /// Information is added using [addPosition]. + /// + /// Returns null if there is no data on which to base an estimate. + protected virtual VelocityEstimate? GetVelocityEstimate() + { + double[] x = new double[HistorySize]; + double[] y = new double[HistorySize]; + double[] w = new double[HistorySize]; + double[] time = new double[HistorySize]; + int sampleCount = 0; + int index = _index; + + var newestSample = _samples[index]; + if (!newestSample.Valid) + { + return null; + } + + var previousSample = newestSample; + var oldestSample = newestSample; + + // Starting with the most recent PointAtTime sample, iterate backwards while + // the samples represent continuous motion. + do + { + var sample = _samples[index]; + if (!sample.Valid) + { + break; + } + + double age = (newestSample.Time - sample.Time).TotalMilliseconds; + double delta = Math.Abs((sample.Time - previousSample.Time).TotalMilliseconds); + previousSample = sample; + if (age > HorizonMilliseconds || delta > AssumePointerMoveStoppedMilliseconds) + { + break; + } + + oldestSample = sample; + var position = sample.Point; + x[sampleCount] = position.X; + y[sampleCount] = position.Y; + w[sampleCount] = 1.0; + time[sampleCount] = -age; + index = (index == 0 ? HistorySize : index) - 1; + + sampleCount++; + } while (sampleCount < HistorySize); + + if (sampleCount >= MinSampleSize) + { + var xFit = LeastSquaresSolver.Solve(2, time.AsSpan(0, sampleCount), x.AsSpan(0, sampleCount), w.AsSpan(0, sampleCount)); + if (xFit != null) + { + var yFit = LeastSquaresSolver.Solve(2, time.AsSpan(0, sampleCount), y.AsSpan(0, sampleCount), w.AsSpan(0, sampleCount)); + if (yFit != null) + { + return new VelocityEstimate( // convert from pixels/ms to pixels/s + PixelsPerSecond: new Vector(xFit.Coefficients[1] * 1000, yFit.Coefficients[1] * 1000), + Confidence: xFit.Confidence * yFit.Confidence, + Duration: newestSample.Time - oldestSample.Time, + Offset: newestSample.Point - oldestSample.Point + ); + } + } + } + + // We're unable to make a velocity estimate but we did have at least one + // valid pointer position. + return new VelocityEstimate( + PixelsPerSecond: Vector.Zero, + Confidence: 1.0, + Duration: newestSample.Time - oldestSample.Time, + Offset: newestSample.Point - oldestSample.Point + ); + } + + /// + /// Computes the velocity of the pointer at the time of the last + /// provided data point. + /// + /// This can be expensive. Only call this when you need the velocity. + /// + /// Returns [Velocity.zero] if there is no data from which to compute an + /// estimate or if the estimated velocity is zero./// + /// + /// + public Velocity GetVelocity() + { + var estimate = GetVelocityEstimate(); + if (estimate == null || estimate.PixelsPerSecond.IsDefault) + { + return new Velocity(Vector.Zero); + } + return new Velocity(estimate.PixelsPerSecond); + } + + public virtual Velocity GetFlingVelocity() + { + return GetVelocity().ClampMagnitude(MinFlingVelocity, MaxFlingVelocity); + } + } + + /// An nth degree polynomial fit to a dataset. + internal class PolynomialFit + { + /// Creates a polynomial fit of the given degree. + /// + /// There are n + 1 coefficients in a fit of degree n. + internal PolynomialFit(int degree) + { + Coefficients = new double[degree + 1]; + } + + /// The polynomial coefficients of the fit. + public double[] Coefficients { get; } + + /// An indicator of the quality of the fit. + /// + /// Larger values indicate greater quality. + public double Confidence { get; set; } + } + + internal class LeastSquaresSolver + { + private const double PrecisionErrorTolerance = 1e-10; + + /// + /// Fits a polynomial of the given degree to the data points. + /// When there is not enough data to fit a curve null is returned. + /// + public static PolynomialFit? Solve(int degree, ReadOnlySpan x, ReadOnlySpan y, ReadOnlySpan w) + { + if (degree > x.Length) + { + // Not enough data to fit a curve. + return null; + } + + PolynomialFit result = new PolynomialFit(degree); + + // Shorthands for the purpose of notation equivalence to original C++ code. + int m = x.Length; + int n = degree + 1; + + // Expand the X vector to a matrix A, pre-multiplied by the weights. + _Matrix a = new _Matrix(n, m); + for (int h = 0; h < m; h += 1) + { + a.Set(0, h, w[h]); + for (int i = 1; i < n; i += 1) + { + a.Set(i, h, a.Get(i - 1, h) * x[h]); + } + } + + // Apply the Gram-Schmidt process to A to obtain its QR decomposition. + + // Orthonormal basis, column-major ordVectorer. + _Matrix q = new _Matrix(n, m); + // Upper triangular matrix, row-major order. + _Matrix r = new _Matrix(n, n); + for (int j = 0; j < n; j += 1) + { + for (int h = 0; h < m; h += 1) + { + q.Set(j, h, a.Get(j, h)); + } + for (int i = 0; i < j; i += 1) + { + double dot = q.GetRow(j) * q.GetRow(i); + for (int h = 0; h < m; h += 1) + { + q.Set(j, h, q.Get(j, h) - dot * q.Get(i, h)); + } + } + + double norm = q.GetRow(j).Norm(); + if (norm < PrecisionErrorTolerance) + { + // Vectors are linearly dependent or zero so no solution. + return null; + } + + double inverseNorm = 1.0 / norm; + for (int h = 0; h < m; h += 1) + { + q.Set(j, h, q.Get(j, h) * inverseNorm); + } + for (int i = 0; i < n; i += 1) + { + r.Set(j, i, i < j ? 0.0 : q.GetRow(j) * a.GetRow(i)); + } + } + + // Solve R B = Qt W Y to find B. This is easy because R is upper triangular. + // We just work from bottom-right to top-left calculating B's coefficients. + _Vector wy = new _Vector(m); + for (int h = 0; h < m; h += 1) + { + wy[h] = y[h] * w[h]; + } + for (int i = n - 1; i >= 0; i -= 1) + { + result.Coefficients[i] = q.GetRow(i) * wy; + for (int j = n - 1; j > i; j -= 1) + { + result.Coefficients[i] -= r.Get(i, j) * result.Coefficients[j]; + } + result.Coefficients[i] /= r.Get(i, i); + } + + // Calculate the coefficient of determination (confidence) as: + // 1 - (sumSquaredError / sumSquaredTotal) + // ...where sumSquaredError is the residual sum of squares (variance of the + // error), and sumSquaredTotal is the total sum of squares (variance of the + // data) where each has been weighted. + double yMean = 0.0; + for (int h = 0; h < m; h += 1) + { + yMean += y[h]; + } + yMean /= m; + + double sumSquaredError = 0.0; + double sumSquaredTotal = 0.0; + for (int h = 0; h < m; h += 1) + { + double term = 1.0; + double err = y[h] - result.Coefficients[0]; + for (int i = 1; i < n; i += 1) + { + term *= x[h]; + err -= term * result.Coefficients[i]; + } + sumSquaredError += w[h] * w[h] * err * err; + double v = y[h] - yMean; + sumSquaredTotal += w[h] * w[h] * v * v; + } + + result.Confidence = sumSquaredTotal <= PrecisionErrorTolerance ? 1.0 : + 1.0 - (sumSquaredError / sumSquaredTotal); + + return result; + } + + private readonly struct _Vector + { + private readonly int _offset; + private readonly int _length; + private readonly double[] _elements; + + internal _Vector(int size) + { + _offset = 0; + _length = size; + _elements = new double[size]; + } + + internal _Vector(double[] values, int offset, int length) + { + _offset = offset; + _length = length; + _elements = values; + } + + public double this[int i] + { + get => _elements[i + _offset]; + set => _elements[i + _offset] = value; + } + + public static double operator *(_Vector a, _Vector b) + { + double result = 0.0; + for (int i = 0; i < a._length; i += 1) + { + result += a[i] * b[i]; + } + return result; + } + + public double Norm() => Math.Sqrt(this * this); + } + + private readonly struct _Matrix + { + private readonly int _columns; + private readonly double[] _elements; + + internal _Matrix(int rows, int cols) + { + _columns = cols; + _elements = new double[rows * cols]; + } + + public double Get(int row, int col) => _elements[row * _columns + col]; + public void Set(int row, int col, double value) + { + _elements[row * _columns + col] = value; + } + + public _Vector GetRow(int row) => new(_elements, row * _columns, _columns); + } + } +} From 5f5f5ebc9b8dd0ed3a76a64f170325f410ac0cc6 Mon Sep 17 00:00:00 2001 From: daniel Date: Wed, 28 Dec 2022 20:07:31 +0200 Subject: [PATCH 36/57] try fix tests --- .../TransitioningContentControlTests.cs | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs index fa523d7f78..ac944352c2 100644 --- a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs @@ -13,40 +13,43 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Old_Content_Shuold_Be_Removed__From_Logical_Tree_After_Out_Animation() { - var testTransition = new TestTransition(); + using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) + { + var testTransition = new TestTransition(); - var target = new TransitioningContentControl(); - target.PageTransition = testTransition; + var target = new TransitioningContentControl(); + target.PageTransition = testTransition; - var root = new TestRoot() { Child = target }; + var root = new TestRoot() { Child = target }; - var oldControl = new Control(); - var newControl = new Control(); + var oldControl = new Control(); + var newControl = new Control(); - target.Content = oldControl; - Threading.Dispatcher.UIThread.RunJobs(); + target.Content = oldControl; + Threading.Dispatcher.UIThread.RunJobs(); - Assert.Equal(target, oldControl.GetLogicalParent()); - Assert.Equal(null, newControl.GetLogicalParent()); + Assert.Equal(target, oldControl.GetLogicalParent()); + Assert.Equal(null, newControl.GetLogicalParent()); - testTransition.BeginTransition += isFrom => - { - // Old out - if (isFrom) - { - Assert.Equal(target, oldControl.GetLogicalParent()); - Assert.Equal(null, newControl.GetLogicalParent()); - } - // New in - else + testTransition.BeginTransition += isFrom => { - Assert.Equal(null, oldControl.GetLogicalParent()); - Assert.Equal(target, newControl.GetLogicalParent()); - } - }; + // Old out + if (isFrom) + { + Assert.Equal(target, oldControl.GetLogicalParent()); + Assert.Equal(null, newControl.GetLogicalParent()); + } + // New in + else + { + Assert.Equal(null, oldControl.GetLogicalParent()); + Assert.Equal(target, newControl.GetLogicalParent()); + } + }; - target.Content = newControl; - Threading.Dispatcher.UIThread.RunJobs(); + target.Content = newControl; + Threading.Dispatcher.UIThread.RunJobs(); + } } } public class TestTransition : IPageTransition From 21f6307c379ff98f255dc5a141f5c5ad5e83a9ba Mon Sep 17 00:00:00 2001 From: daniel Date: Wed, 28 Dec 2022 20:21:20 +0200 Subject: [PATCH 37/57] again --- .../TransitioningContentControlTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs index ac944352c2..aaa1de4da4 100644 --- a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs @@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Old_Content_Shuold_Be_Removed__From_Logical_Tree_After_Out_Animation() { - using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) + using (UnitTestApplication.Start(TestServices.StyledWindow)) { var testTransition = new TestTransition(); From 8d5d382c2f134779d8f49e17a71064f664029e06 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 28 Dec 2022 23:52:34 -0500 Subject: [PATCH 38/57] Reuse GetVelocityEstimate arrays --- .../Input/GestureRecognizers/VelocityTracker.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index 909010d43d..526d36ba6c 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -94,6 +94,11 @@ namespace Avalonia.Input.GestureRecognizers private const double MinFlingVelocity = 50.0; // Logical pixels / second (defined in flutter\lib\src\gesture\constants.dart) private const double MaxFlingVelocity = 8000.0; + private static double[] x = new double[HistorySize]; + private static double[] y = new double[HistorySize]; + private static double[] w = new double[HistorySize]; + private static double[] time = new double[HistorySize]; + private readonly PointAtTime[] _samples = new PointAtTime[HistorySize]; private int _index = 0; @@ -120,10 +125,6 @@ namespace Avalonia.Input.GestureRecognizers /// Returns null if there is no data on which to base an estimate. protected virtual VelocityEstimate? GetVelocityEstimate() { - double[] x = new double[HistorySize]; - double[] y = new double[HistorySize]; - double[] w = new double[HistorySize]; - double[] time = new double[HistorySize]; int sampleCount = 0; int index = _index; From 4645dcaa12510fe32e35e735a228c9b95e349726 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 28 Dec 2022 23:53:54 -0500 Subject: [PATCH 39/57] Use C# indegers --- .../GestureRecognizers/VelocityTracker.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index 526d36ba6c..9e220bc85e 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -266,10 +266,10 @@ namespace Avalonia.Input.GestureRecognizers _Matrix a = new _Matrix(n, m); for (int h = 0; h < m; h += 1) { - a.Set(0, h, w[h]); + a[0, h] = w[h]; for (int i = 1; i < n; i += 1) { - a.Set(i, h, a.Get(i - 1, h) * x[h]); + a[i, h] = a[i - 1, h] * x[h]; } } @@ -283,14 +283,14 @@ namespace Avalonia.Input.GestureRecognizers { for (int h = 0; h < m; h += 1) { - q.Set(j, h, a.Get(j, h)); + q[j, h] = a[j, h]; } for (int i = 0; i < j; i += 1) { double dot = q.GetRow(j) * q.GetRow(i); for (int h = 0; h < m; h += 1) { - q.Set(j, h, q.Get(j, h) - dot * q.Get(i, h)); + q[j, h] = q[j, h] - dot * q[i, h]; } } @@ -304,7 +304,7 @@ namespace Avalonia.Input.GestureRecognizers double inverseNorm = 1.0 / norm; for (int h = 0; h < m; h += 1) { - q.Set(j, h, q.Get(j, h) * inverseNorm); + q[j, h] = q[j, h] * inverseNorm; } for (int i = 0; i < n; i += 1) { @@ -324,9 +324,9 @@ namespace Avalonia.Input.GestureRecognizers result.Coefficients[i] = q.GetRow(i) * wy; for (int j = n - 1; j > i; j -= 1) { - result.Coefficients[i] -= r.Get(i, j) * result.Coefficients[j]; + result.Coefficients[i] -= r[i, j] * result.Coefficients[j]; } - result.Coefficients[i] /= r.Get(i, i); + result.Coefficients[i] /= r[i, i]; } // Calculate the coefficient of determination (confidence) as: @@ -413,10 +413,10 @@ namespace Avalonia.Input.GestureRecognizers _elements = new double[rows * cols]; } - public double Get(int row, int col) => _elements[row * _columns + col]; - public void Set(int row, int col, double value) + public double this[int row, int col] { - _elements[row * _columns + col] = value; + get => _elements[row * _columns + col]; + set => _elements[row * _columns + col] = value; } public _Vector GetRow(int row) => new(_elements, row * _columns, _columns); From 6d6ec49dd3ff93e76625027efe2e4f3897321dd3 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 29 Dec 2022 00:09:58 -0500 Subject: [PATCH 40/57] Use Span instead of _Vector --- .../GestureRecognizers/VelocityTracker.cs | 59 ++++++------------- 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index 9e220bc85e..d290faf114 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -275,7 +275,7 @@ namespace Avalonia.Input.GestureRecognizers // Apply the Gram-Schmidt process to A to obtain its QR decomposition. - // Orthonormal basis, column-major ordVectorer. + // Orthonormal basis, column-major order Vector. _Matrix q = new _Matrix(n, m); // Upper triangular matrix, row-major order. _Matrix r = new _Matrix(n, n); @@ -287,14 +287,14 @@ namespace Avalonia.Input.GestureRecognizers } for (int i = 0; i < j; i += 1) { - double dot = q.GetRow(j) * q.GetRow(i); + double dot = Multiply(q.GetRow(j), q.GetRow(i)); for (int h = 0; h < m; h += 1) { q[j, h] = q[j, h] - dot * q[i, h]; } } - double norm = q.GetRow(j).Norm(); + double norm = Norm(q.GetRow(j)); if (norm < PrecisionErrorTolerance) { // Vectors are linearly dependent or zero so no solution. @@ -308,20 +308,21 @@ namespace Avalonia.Input.GestureRecognizers } for (int i = 0; i < n; i += 1) { - r.Set(j, i, i < j ? 0.0 : q.GetRow(j) * a.GetRow(i)); + r[j, i] = i < j ? 0.0 : Multiply(q.GetRow(j), a.GetRow(i)); } } // Solve R B = Qt W Y to find B. This is easy because R is upper triangular. // We just work from bottom-right to top-left calculating B's coefficients. - _Vector wy = new _Vector(m); + // "m" isn't expected to be bigger than HistorySize=20, so allocation on stack is safe. + Span wy = stackalloc double[m]; for (int h = 0; h < m; h += 1) { wy[h] = y[h] * w[h]; } for (int i = n - 1; i >= 0; i -= 1) { - result.Coefficients[i] = q.GetRow(i) * wy; + result.Coefficients[i] = Multiply(q.GetRow(i), wy); for (int j = n - 1; j > i; j -= 1) { result.Coefficients[i] -= r[i, j] * result.Coefficients[j]; @@ -363,43 +364,19 @@ namespace Avalonia.Input.GestureRecognizers return result; } - private readonly struct _Vector + private static double Multiply(Span v1, Span v2) { - private readonly int _offset; - private readonly int _length; - private readonly double[] _elements; - - internal _Vector(int size) - { - _offset = 0; - _length = size; - _elements = new double[size]; - } - - internal _Vector(double[] values, int offset, int length) - { - _offset = offset; - _length = length; - _elements = values; - } - - public double this[int i] - { - get => _elements[i + _offset]; - set => _elements[i + _offset] = value; - } - - public static double operator *(_Vector a, _Vector b) + double result = 0.0; + for (int i = 0; i < v1.Length; i += 1) { - double result = 0.0; - for (int i = 0; i < a._length; i += 1) - { - result += a[i] * b[i]; - } - return result; + result += v1[i] * v2[i]; } - - public double Norm() => Math.Sqrt(this * this); + return result; + } + + private static double Norm(Span v) + { + return Math.Sqrt(Multiply(v, v)); } private readonly struct _Matrix @@ -419,7 +396,7 @@ namespace Avalonia.Input.GestureRecognizers set => _elements[row * _columns + col] = value; } - public _Vector GetRow(int row) => new(_elements, row * _columns, _columns); + public Span GetRow(int row) => _elements.AsSpan(row * _columns, _columns); } } } From ebdf7df1624fe826211c0facb8968be19000c8c6 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 29 Dec 2022 00:21:00 -0500 Subject: [PATCH 41/57] Use stackalloc instead of reused arrays, so this code can potentially work with multitouch This reverts commit 8d5d382c2f134779d8f49e17a71064f664029e06. --- .../Input/GestureRecognizers/VelocityTracker.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index d290faf114..d4705bcdca 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -30,6 +30,7 @@ using System; using System.Diagnostics; +using Avalonia.Utilities; namespace Avalonia.Input.GestureRecognizers { @@ -94,11 +95,6 @@ namespace Avalonia.Input.GestureRecognizers private const double MinFlingVelocity = 50.0; // Logical pixels / second (defined in flutter\lib\src\gesture\constants.dart) private const double MaxFlingVelocity = 8000.0; - private static double[] x = new double[HistorySize]; - private static double[] y = new double[HistorySize]; - private static double[] w = new double[HistorySize]; - private static double[] time = new double[HistorySize]; - private readonly PointAtTime[] _samples = new PointAtTime[HistorySize]; private int _index = 0; @@ -125,6 +121,10 @@ namespace Avalonia.Input.GestureRecognizers /// Returns null if there is no data on which to base an estimate. protected virtual VelocityEstimate? GetVelocityEstimate() { + Span x = stackalloc double[HistorySize]; + Span y = stackalloc double[HistorySize]; + Span w = stackalloc double[HistorySize]; + Span time = stackalloc double[HistorySize]; int sampleCount = 0; int index = _index; @@ -168,10 +168,10 @@ namespace Avalonia.Input.GestureRecognizers if (sampleCount >= MinSampleSize) { - var xFit = LeastSquaresSolver.Solve(2, time.AsSpan(0, sampleCount), x.AsSpan(0, sampleCount), w.AsSpan(0, sampleCount)); + var xFit = LeastSquaresSolver.Solve(2, time.Slice(0, sampleCount), x.Slice(0, sampleCount), w.Slice(0, sampleCount)); if (xFit != null) { - var yFit = LeastSquaresSolver.Solve(2, time.AsSpan(0, sampleCount), y.AsSpan(0, sampleCount), w.AsSpan(0, sampleCount)); + var yFit = LeastSquaresSolver.Solve(2, time.Slice(0, sampleCount), y.Slice(0, sampleCount), w.Slice(0, sampleCount)); if (yFit != null) { return new VelocityEstimate( // convert from pixels/ms to pixels/s From 899dc69d7283d86c9d5189d5d4fa51e1d383b0e2 Mon Sep 17 00:00:00 2001 From: Martijn Sneijders Date: Thu, 29 Dec 2022 10:02:11 +0100 Subject: [PATCH 42/57] removed unnecessary comments --- .../ScrollGestureRecognizer.cs | 2 +- .../GestureRecognizers/VelocityTracker.cs | 31 ++----------------- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index e2deea0fde..cec98ec66b 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -113,7 +113,7 @@ namespace Avalonia.Input.GestureRecognizers _scrolling = true; if (_scrolling) { - _velocityTracker = new VelocityTracker(); // TODO: Should be platform specific -- this default tracker is for Android. + _velocityTracker = new VelocityTracker(); // Correct _trackedRootPoint with ScrollStartDistance, so scrolling does not start with a skip of ScrollStartDistance _trackedRootPoint = new Point( diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index d4705bcdca..9482542b45 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -1,40 +1,13 @@ // Code in this file is derived from // https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/velocity_tracker.dart -//Copyright 2014 The Flutter Authors. All rights reserved. - -//Redistribution and use in source and binary forms, with or without modification, -//are permitted provided that the following conditions are met: - -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following -// disclaimer in the documentation and/or other materials provided -// with the distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived -// from this software without specific prior written permission. - -//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -//LOSS OF USE, DATA, OR PROFITS; -//OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - using System; using System.Diagnostics; using Avalonia.Utilities; namespace Avalonia.Input.GestureRecognizers { - // TODO: add 'IOSScrollViewFlingVelocityTracker' and 'MacOSScrollViewFlingVelocityTracker'? + // Possible enhancement: add Flutter's 'IOSScrollViewFlingVelocityTracker' and 'MacOSScrollViewFlingVelocityTracker'? public readonly record struct Velocity(Vector PixelsPerSecond) { @@ -92,7 +65,7 @@ namespace Avalonia.Input.GestureRecognizers private const int HistorySize = 20; private const int HorizonMilliseconds = 100; private const int MinSampleSize = 3; - private const double MinFlingVelocity = 50.0; // Logical pixels / second (defined in flutter\lib\src\gesture\constants.dart) + private const double MinFlingVelocity = 50.0; // Logical pixels / second private const double MaxFlingVelocity = 8000.0; private readonly PointAtTime[] _samples = new PointAtTime[HistorySize]; From 9332fef8af52bc5eea6555354613187612eccbea Mon Sep 17 00:00:00 2001 From: Martijn Sneijders Date: Thu, 29 Dec 2022 10:05:11 +0100 Subject: [PATCH 43/57] flutter added to notice.md --- NOTICE.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/NOTICE.md b/NOTICE.md index e97fc654c9..bd26b65d70 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -303,3 +303,34 @@ https://github.com/chromium/chromium // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Flutter + +https://github.com/flutter/flutter + +//Copyright 2014 The Flutter Authors. All rights reserved. + +//Redistribution and use in source and binary forms, with or without modification, +//are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. + +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; +//OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 0ad6dec3a9e43e7533298f7166b6cd475c5ec781 Mon Sep 17 00:00:00 2001 From: Martijn Sneijders Date: Thu, 29 Dec 2022 10:17:51 +0100 Subject: [PATCH 44/57] VelocityTracker types marked as internal --- .../Input/GestureRecognizers/VelocityTracker.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index 9482542b45..f72ac61c4b 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -9,7 +9,7 @@ namespace Avalonia.Input.GestureRecognizers { // Possible enhancement: add Flutter's 'IOSScrollViewFlingVelocityTracker' and 'MacOSScrollViewFlingVelocityTracker'? - public readonly record struct Velocity(Vector PixelsPerSecond) + internal readonly record struct Velocity(Vector PixelsPerSecond) { public Velocity ClampMagnitude(double minValue, double maxValue) { @@ -45,7 +45,7 @@ namespace Avalonia.Input.GestureRecognizers /// * [VelocityTracker], which computes [VelocityEstimate]s. /// * [Velocity], which encapsulates (just) a velocity vector and provides some /// useful velocity operations. - public record VelocityEstimate(Vector PixelsPerSecond, double Confidence, TimeSpan Duration, Vector Offset); + internal record VelocityEstimate(Vector PixelsPerSecond, double Confidence, TimeSpan Duration, Vector Offset); internal record struct PointAtTime(bool Valid, Vector Point, TimeSpan Time); @@ -59,7 +59,7 @@ namespace Avalonia.Input.GestureRecognizers /// /// The quality of the velocity estimation will be better if more data points /// have been received. - public class VelocityTracker + internal class VelocityTracker { private const int AssumePointerMoveStoppedMilliseconds = 40; private const int HistorySize = 20; @@ -177,7 +177,7 @@ namespace Avalonia.Input.GestureRecognizers /// estimate or if the estimated velocity is zero./// /// /// - public Velocity GetVelocity() + internal Velocity GetVelocity() { var estimate = GetVelocityEstimate(); if (estimate == null || estimate.PixelsPerSecond.IsDefault) @@ -187,7 +187,7 @@ namespace Avalonia.Input.GestureRecognizers return new Velocity(estimate.PixelsPerSecond); } - public virtual Velocity GetFlingVelocity() + internal virtual Velocity GetFlingVelocity() { return GetVelocity().ClampMagnitude(MinFlingVelocity, MaxFlingVelocity); } From 157ac02dc661f2ddb23b615545ed825626eae9de Mon Sep 17 00:00:00 2001 From: Martijn Sneijders Date: Thu, 29 Dec 2022 10:58:26 +0100 Subject: [PATCH 45/57] _Matrix memory optimized using stack --- .../Input/GestureRecognizers/VelocityTracker.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index f72ac61c4b..206a73f436 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -236,7 +236,7 @@ namespace Avalonia.Input.GestureRecognizers int n = degree + 1; // Expand the X vector to a matrix A, pre-multiplied by the weights. - _Matrix a = new _Matrix(n, m); + _Matrix a = new _Matrix(m, stackalloc double[n * m]); for (int h = 0; h < m; h += 1) { a[0, h] = w[h]; @@ -249,9 +249,9 @@ namespace Avalonia.Input.GestureRecognizers // Apply the Gram-Schmidt process to A to obtain its QR decomposition. // Orthonormal basis, column-major order Vector. - _Matrix q = new _Matrix(n, m); + _Matrix q = new _Matrix(m, stackalloc double[n * m]); // Upper triangular matrix, row-major order. - _Matrix r = new _Matrix(n, n); + _Matrix r = new _Matrix(n, stackalloc double[n * n]); for (int j = 0; j < n; j += 1) { for (int h = 0; h < m; h += 1) @@ -352,15 +352,15 @@ namespace Avalonia.Input.GestureRecognizers return Math.Sqrt(Multiply(v, v)); } - private readonly struct _Matrix + private readonly ref struct _Matrix { private readonly int _columns; - private readonly double[] _elements; + private readonly Span _elements; - internal _Matrix(int rows, int cols) + internal _Matrix(int cols, Span elements) { _columns = cols; - _elements = new double[rows * cols]; + _elements = elements;// new double[rows * cols]; } public double this[int row, int col] @@ -369,7 +369,7 @@ namespace Avalonia.Input.GestureRecognizers set => _elements[row * _columns + col] = value; } - public Span GetRow(int row) => _elements.AsSpan(row * _columns, _columns); + public Span GetRow(int row) => _elements.Slice(row * _columns, _columns); } } } From c7c819295f82706d6f6ec0546ce9c83d1e095605 Mon Sep 17 00:00:00 2001 From: Martijn Sneijders Date: Thu, 29 Dec 2022 11:26:28 +0100 Subject: [PATCH 46/57] removed comment --- src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index 206a73f436..ce41aa6308 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -360,7 +360,7 @@ namespace Avalonia.Input.GestureRecognizers internal _Matrix(int cols, Span elements) { _columns = cols; - _elements = elements;// new double[rows * cols]; + _elements = elements; } public double this[int row, int col] From c099e2fd2c5e9924e98446bc34b0c79fb1870a7c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 30 Dec 2022 19:14:38 +0600 Subject: [PATCH 47/57] Use for(...) dst[c]=src[c] loop on arm due to a JIT bug --- .../Composition/Transport/BatchStream.cs | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs index a3cad3cebd..2dd15dfedc 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Server; @@ -27,6 +28,37 @@ public record struct BatchStreamSegment public int ElementCount { get; set; } } + +// Unsafe.ReadUnaligned/Unsafe.WriteUnaligned are broken on arm, +// see https://github.com/dotnet/runtime/issues/80068 +static unsafe class UnalignedMemoryHelper +{ + public static T ReadUnaligned(byte* src) where T : unmanaged + { +#if NET6_0_OR_GREATER + Unsafe.SkipInit(out var rv); +#else + T rv; +#endif + UnalignedMemcpy((byte*)&rv, src, Unsafe.SizeOf()); + return rv; + } + + public static void WriteUnaligned(byte* dst, T value) where T : unmanaged + { + UnalignedMemcpy(dst, (byte*)&value, Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static unsafe void UnalignedMemcpy(byte* dst, byte* src, int count) + { + for (var c = 0; c < count; c++) + { + dst[c] = src[c]; + } + } +} + internal class BatchStreamWriter : IDisposable { private readonly BatchStreamData _output; @@ -74,7 +106,15 @@ internal class BatchStreamWriter : IDisposable var size = Unsafe.SizeOf(); if (_currentDataSegment.Data == IntPtr.Zero || _currentDataSegment.ElementCount + size > _memoryPool.BufferSize) NextDataSegment(); - Unsafe.WriteUnaligned((byte*)_currentDataSegment.Data + _currentDataSegment.ElementCount, item); + var ptr = (byte*)_currentDataSegment.Data + _currentDataSegment.ElementCount; + + // Unsafe.ReadUnaligned/Unsafe.WriteUnaligned are broken on arm32, + // see https://github.com/dotnet/runtime/issues/80068 + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm) + UnalignedMemoryHelper.WriteUnaligned(ptr, item); + else + *(T*)ptr = item; + _currentDataSegment.ElementCount += size; } @@ -125,7 +165,16 @@ internal class BatchStreamReader : IDisposable if (_memoryOffset + size > _currentDataSegment.ElementCount) throw new InvalidOperationException("Attempted to read more memory then left in the current segment"); - var rv = Unsafe.ReadUnaligned((byte*)_currentDataSegment.Data + _memoryOffset); + var ptr = (byte*)_currentDataSegment.Data + _memoryOffset; + T rv; + + // Unsafe.ReadUnaligned/Unsafe.WriteUnaligned are broken on arm32, + // see https://github.com/dotnet/runtime/issues/80068 + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm) + rv = UnalignedMemoryHelper.ReadUnaligned(ptr); + else + rv = *(T*)ptr; + _memoryOffset += size; if (_memoryOffset == _currentDataSegment.ElementCount) { From 617c57376a34aa2511caa4b8d39283dbe4d9c801 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 30 Dec 2022 21:49:37 +0600 Subject: [PATCH 48/57] Use Unsafe.Read/WriteUnaligned for other platforms since it might be required for something like wasm --- .../Rendering/Composition/Transport/BatchStream.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs index 2dd15dfedc..5a88f0e91d 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs @@ -113,7 +113,7 @@ internal class BatchStreamWriter : IDisposable if (RuntimeInformation.ProcessArchitecture == Architecture.Arm) UnalignedMemoryHelper.WriteUnaligned(ptr, item); else - *(T*)ptr = item; + Unsafe.WriteUnaligned(ptr, item); _currentDataSegment.ElementCount += size; } @@ -173,7 +173,7 @@ internal class BatchStreamReader : IDisposable if (RuntimeInformation.ProcessArchitecture == Architecture.Arm) rv = UnalignedMemoryHelper.ReadUnaligned(ptr); else - rv = *(T*)ptr; + rv = Unsafe.ReadUnaligned(ptr); _memoryOffset += size; if (_memoryOffset == _currentDataSegment.ElementCount) From 50c21198b4032faa0b8d188383a003ff830ced9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Dec 2022 22:04:09 +0000 Subject: [PATCH 49/57] Bump flat and mocha Bumps [flat](https://github.com/hughsk/flat) to 5.0.2 and updates ancestor dependency [mocha](https://github.com/mochajs/mocha). These dependencies need to be updated together. Updates `flat` from 4.1.0 to 5.0.2 - [Release notes](https://github.com/hughsk/flat/releases) - [Commits](https://github.com/hughsk/flat/compare/4.1.0...5.0.2) Updates `mocha` from 8.1.3 to 8.4.0 - [Release notes](https://github.com/mochajs/mocha/releases) - [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md) - [Commits](https://github.com/mochajs/mocha/compare/v8.1.3...v8.4.0) --- updated-dependencies: - dependency-name: flat dependency-type: indirect - dependency-name: mocha dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- .../HtmlTransport/webapp/package-lock.json | 791 ++++++------------ .../Remote/HtmlTransport/webapp/package.json | 2 +- 2 files changed, 250 insertions(+), 543 deletions(-) diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json index eb57cfb8da..272be1c8cb 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json @@ -372,6 +372,12 @@ "csstype": "^3.0.2" } }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -405,9 +411,9 @@ } }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -444,18 +450,6 @@ "sprintf-js": "~1.0.2" } }, - "array.prototype.map": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", - "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.4" - } - }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -469,9 +463,9 @@ "dev": true }, "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, "brace-expansion": { @@ -538,9 +532,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -554,19 +548,19 @@ "dev": true }, "chokidar": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", - "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "dev": true, "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.1.2", + "fsevents": "~2.3.1", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.4.0" + "readdirp": "~3.5.0" } }, "clean-stack": { @@ -576,40 +570,46 @@ "dev": true }, "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" }, "dependencies": { "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.1" } } } @@ -708,15 +708,6 @@ "strip-bom": "^4.0.0" } }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -724,68 +715,23 @@ "dev": true }, "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, - "es-get-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", - "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", - "dev": true, - "requires": { - "es-abstract": "^1.17.4", - "has-symbols": "^1.0.1", - "is-arguments": "^1.0.4", - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, "es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -829,13 +775,10 @@ } }, "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - } + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true }, "foreground-child": { "version": "2.0.0", @@ -860,18 +803,12 @@ "dev": true }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "optional": true }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, "gensync": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", @@ -911,9 +848,9 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -937,27 +874,12 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, "hasha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz", @@ -1008,12 +930,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "dev": true - }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1023,51 +939,27 @@ "binary-extensions": "^2.0.0" } }, - "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", - "dev": true - }, - "is-callable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz", - "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==", - "dev": true - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" } }, - "is-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", - "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", - "dev": true - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -1075,24 +967,9 @@ "dev": true }, "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-set": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", - "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true }, "is-stream": { @@ -1101,21 +978,6 @@ "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -1128,12 +990,6 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1222,22 +1078,6 @@ "istanbul-lib-report": "^3.0.0" } }, - "iterate-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", - "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==", - "dev": true - }, - "iterate-value": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", - "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", - "dev": true, - "requires": { - "es-get-iterator": "^1.0.2", - "iterate-iterator": "^1.0.1" - } - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1339,36 +1179,91 @@ "dev": true }, "mocha": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.3.tgz", - "integrity": "sha512-ZbaYib4hT4PpF4bdSO2DohooKXIn4lDeiYqB+vTmCdr6l2woW0b6H3pf5x4sM5nwQMru9RvjjHYWVGltR50ZBw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", "dev": true, "requires": { + "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.4.2", - "debug": "4.1.1", - "diff": "4.0.2", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", "glob": "7.1.6", "growl": "1.10.5", "he": "1.2.0", - "js-yaml": "3.14.0", + "js-yaml": "4.0.0", "log-symbols": "4.0.0", "minimatch": "3.0.4", - "ms": "2.1.2", - "object.assign": "4.1.0", - "promise.allsettled": "1.0.2", - "serialize-javascript": "4.0.0", - "strip-json-comments": "3.0.1", - "supports-color": "7.1.0", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", "which": "2.0.2", "wide-align": "1.1.3", - "workerpool": "6.0.0", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.1" + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "moq.ts": { @@ -1386,6 +1281,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "nanoid": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "dev": true + }, "node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -1570,30 +1471,6 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1604,12 +1481,12 @@ } }, "p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" } }, "p-locate": { @@ -1679,9 +1556,9 @@ "dev": true }, "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, "pkg-dir": { @@ -1741,19 +1618,6 @@ "fromentries": "^1.2.0" } }, - "promise.allsettled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", - "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", - "dev": true, - "requires": { - "array.prototype.map": "^1.0.1", - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "iterate-value": "^1.0.0" - } - }, "prop-types": { "version": "15.7.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", @@ -1792,9 +1656,9 @@ "dev": true }, "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", "dev": true, "requires": { "picomatch": "^2.2.1" @@ -1858,9 +1722,9 @@ "dev": true }, "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -1947,30 +1811,10 @@ "strip-ansi": "^4.0.0" } }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", "dev": true, "requires": { "ansi-regex": "^3.0.0" @@ -1983,9 +1827,9 @@ "dev": true }, "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "supports-color": { @@ -2100,70 +1944,52 @@ } }, "workerpool": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz", - "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", "dev": true }, "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "dependencies": { "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.1" } } } @@ -2193,214 +2019,89 @@ "dev": true }, "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "dependencies": { "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { + "is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.1" } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true } } }, "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true }, "yargs-unparser": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.1.tgz", - "integrity": "sha512-qZV14lK9MWsGCmcr7u5oXGH0dbGqZAIxTDrWXZDo5zUr6b6iUmelNKO6x6R1dQT24AH3LgRxJpr8meWy2unolA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, "requires": { - "camelcase": "^5.3.1", - "decamelize": "^1.2.0", - "flat": "^4.1.0", - "is-plain-obj": "^1.1.0", - "yargs": "^14.2.3" + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "yargs": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", - "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^15.0.1" - } - }, - "yargs-parser": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", - "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, @@ -2409,6 +2110,12 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json index dbb12e192a..da3a748c39 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json @@ -16,7 +16,7 @@ "@types/mocha": "8.0.3", "@types/react": "^16.3.14", "chai": "^4.2.0", - "mocha": "^8.1.3", + "mocha": "^8.4.0", "moq.ts": "^6.4.0", "nyc": "^15.1.0", "react": "^16.3.2", From bad0160a2d74b30f041fbe474a5c81e29ee5020d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 31 Dec 2022 02:08:17 +0000 Subject: [PATCH 50/57] Bump json5 Bumps [json5](https://github.com/json5/json5) from 2.1.3 to 2.2.2. - [Release notes](https://github.com/json5/json5/releases) - [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md) - [Commits](https://github.com/json5/json5/compare/v2.1.3...v2.2.2) --- updated-dependencies: - dependency-name: json5 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- .../HtmlTransport/webapp/package-lock.json | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json index 272be1c8cb..7ae6c4c8d9 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json @@ -1101,13 +1101,10 @@ "dev": true }, "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", + "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==", + "dev": true }, "locate-path": { "version": "6.0.0", @@ -1172,12 +1169,6 @@ "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, "mocha": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", From fb88efce5f4b980a00581a1e2626eade90e39e89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 31 Dec 2022 06:29:27 +0000 Subject: [PATCH 51/57] Bump nanoid and mocha Bumps [nanoid](https://github.com/ai/nanoid) to 3.3.3 and updates ancestor dependency [mocha](https://github.com/mochajs/mocha). These dependencies need to be updated together. Updates `nanoid` from 3.1.20 to 3.3.3 - [Release notes](https://github.com/ai/nanoid/releases) - [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md) - [Commits](https://github.com/ai/nanoid/compare/3.1.20...3.3.3) Updates `mocha` from 8.4.0 to 10.2.0 - [Release notes](https://github.com/mochajs/mocha/releases) - [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md) - [Commits](https://github.com/mochajs/mocha/compare/v8.4.0...v10.2.0) --- updated-dependencies: - dependency-name: nanoid dependency-type: indirect - dependency-name: mocha dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- .../HtmlTransport/webapp/package-lock.json | 290 +++++++----------- .../Remote/HtmlTransport/webapp/package.json | 2 +- 2 files changed, 110 insertions(+), 182 deletions(-) diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json index 7ae6c4c8d9..0c0857b680 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json @@ -372,12 +372,6 @@ "csstype": "^3.0.2" } }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -395,9 +389,9 @@ "dev": true }, "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { @@ -548,19 +542,19 @@ "dev": true }, "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { - "anymatch": "~3.1.1", + "anymatch": "~3.1.2", "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "readdirp": "~3.6.0" } }, "clean-stack": { @@ -578,40 +572,6 @@ "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } } }, "color-convert": { @@ -868,12 +828,6 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -946,9 +900,9 @@ "dev": true }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "is-glob": { @@ -984,6 +938,12 @@ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -1128,12 +1088,13 @@ "dev": true }, "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "requires": { - "chalk": "^4.0.0" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" } }, "loose-envify": { @@ -1170,33 +1131,29 @@ } }, "mocha": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", - "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", "dev": true, "requires": { - "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", + "glob": "7.2.0", "he": "1.2.0", - "js-yaml": "4.0.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.1.20", - "serialize-javascript": "5.0.1", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.0", + "workerpool": "6.2.1", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -1209,9 +1166,9 @@ "dev": true }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -1231,15 +1188,60 @@ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, "js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { "argparse": "^2.0.1" } }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + } + } + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1273,9 +1275,9 @@ "dev": true }, "nanoid": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", - "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true }, "node-preload": { @@ -1647,9 +1649,9 @@ "dev": true }, "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "requires": { "picomatch": "^2.2.1" @@ -1713,9 +1715,9 @@ "dev": true }, "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -1793,22 +1795,23 @@ "dev": true }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^5.0.1" } }, "strip-bom": { @@ -1925,19 +1928,10 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, "workerpool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", - "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, "wrap-ansi": { @@ -1949,40 +1943,6 @@ "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } } }, "wrappy": { @@ -2024,38 +1984,6 @@ "yargs-parser": "^20.2.2" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json index da3a748c39..580c4059ba 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json @@ -16,7 +16,7 @@ "@types/mocha": "8.0.3", "@types/react": "^16.3.14", "chai": "^4.2.0", - "mocha": "^8.4.0", + "mocha": "^10.2.0", "moq.ts": "^6.4.0", "nyc": "^15.1.0", "react": "^16.3.2", From 64c12e9253914c4516a50333ae76eb3a00ff0273 Mon Sep 17 00:00:00 2001 From: Martijn Sneijders Date: Sat, 31 Dec 2022 14:57:59 +0100 Subject: [PATCH 52/57] [AndroidMotionEventsHelper] fixes eventTime to uptime android in milliseconds --- .../Platform/Specific/Helpers/AndroidMotionEventsHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs index 6d0e6be0ad..3d0f3b2652 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs @@ -38,7 +38,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers return null; } - var eventTime = (ulong)DateTime.Now.Millisecond; + var eventTime = (ulong)e.EventTime; var inputRoot = _view.InputRoot; var actionMasked = e.ActionMasked; var modifiers = GetModifiers(e.MetaState, e.ButtonState); From dcb4f759d16674078accdee887edf39568d499b2 Mon Sep 17 00:00:00 2001 From: daniel Date: Sun, 1 Jan 2023 13:58:14 +0200 Subject: [PATCH 53/57] Make FlowDirection's Path of CheckBox to fixed LTR --- src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml index 9abcf5d32b..28f8649e2d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml @@ -50,7 +50,8 @@ Opacity="0" Fill="{DynamicResource CheckBoxCheckGlyphForegroundUnchecked}" Stretch="Uniform" - VerticalAlignment="Center" /> + VerticalAlignment="Center" + FlowDirection="LeftToRight" /> @@ -146,7 +147,6 @@ - From 5a3d9f15743d6e5caf75e5e5fe96898fe0d30e1c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 1 Jan 2023 19:18:32 +0600 Subject: [PATCH 54/57] Use parent clip rect of the adorned visual --- .../Server/ServerCompositionVisual.cs | 19 ++++- .../Controls/AdornerTests.cs | 73 ++++++++++++++++++ tests/Avalonia.RenderTests/TestBase.cs | 9 ++- ...s_Adorner_Is_Properly_Clipped.expected.png | Bin 0 -> 673 bytes ...s_Adorner_Is_Properly_Clipped.expected.png | Bin 0 -> 673 bytes 5 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 tests/Avalonia.RenderTests/Controls/AdornerTests.cs create mode 100644 tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png create mode 100644 tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index 0e15cbd54b..0b2df6d2b3 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -42,11 +42,18 @@ namespace Avalonia.Rendering.Composition.Server Root!.RenderedVisuals++; + if (Opacity != 1) + canvas.PushOpacity(Opacity); + if (AdornedVisual != null) + { + canvas.PostTransform = Matrix.Identity; + canvas.Transform = Matrix.Identity; + canvas.PushClip(AdornedVisual._combinedTransformedClipBounds); + } var transform = GlobalTransformMatrix; canvas.PostTransform = MatrixUtils.ToMatrix(transform); canvas.Transform = Matrix.Identity; - if (Opacity != 1) - canvas.PushOpacity(Opacity); + var boundsRect = new Rect(new Size(Size.X, Size.Y)); if (ClipToBounds && !HandlesClipToBounds) canvas.PushClip(Root!.SnapToDevicePixels(boundsRect)); @@ -67,6 +74,8 @@ namespace Avalonia.Rendering.Composition.Server canvas.PopGeometryClip(); if (ClipToBounds && !HandlesClipToBounds) canvas.PopClip(); + if (AdornedVisual != null) + canvas.PopClip(); if(Opacity != 1) canvas.PopOpacity(); } @@ -155,8 +164,12 @@ namespace Avalonia.Rendering.Composition.Server _clipSizeDirty = false; } + + _combinedTransformedClipBounds = + AdornedVisual?._combinedTransformedClipBounds + ?? Parent?._combinedTransformedClipBounds + ?? new Rect(Root!.Size); - _combinedTransformedClipBounds = Parent?._combinedTransformedClipBounds ?? new Rect(Root!.Size); if (_transformedClipBounds != null) _combinedTransformedClipBounds = _combinedTransformedClipBounds.Intersect(_transformedClipBounds.Value); diff --git a/tests/Avalonia.RenderTests/Controls/AdornerTests.cs b/tests/Avalonia.RenderTests/Controls/AdornerTests.cs new file mode 100644 index 0000000000..c833017212 --- /dev/null +++ b/tests/Avalonia.RenderTests/Controls/AdornerTests.cs @@ -0,0 +1,73 @@ +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Layout; +using Avalonia.Media; +using Xunit; + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests; +#else +namespace Avalonia.Direct2D1.RenderTests.Controls; +#endif + +public class AdornerTests : TestBase +{ + public AdornerTests() + : base(@"Controls\Adorner") + { + } + + [Fact] + public async Task Focus_Adorner_Is_Properly_Clipped() + { + Border adorned; + var tree = new Decorator + { + Child = new VisualLayerManager + { + Child = new Border + { + Background = Brushes.Red, + Padding = new Thickness(10, 50, 10,10), + Child = new Border() + { + Background = Brushes.White, + ClipToBounds = true, + Padding = new Thickness(0, -30, 0, 0), + Child = adorned = new Border + { + Background = Brushes.Green, + VerticalAlignment = VerticalAlignment.Top, + Height = 100, + Width = 50 + } + } + } + }, + Width = 200, + Height = 200 + }; + var adorner = new Border + { + BorderThickness = new Thickness(2), + BorderBrush = Brushes.Black + }; + + var size = new Size(tree.Width, tree.Height); + tree.Measure(size); + tree.Arrange(new Rect(size)); + + + adorned.AttachedToVisualTree += delegate + { + AdornerLayer.SetAdornedElement(adorner, adorned); + AdornerLayer.GetAdornerLayer(adorned)!.Children.Add(adorner); + }; + tree.Measure(size); + tree.Arrange(new Rect(size)); + + await RenderToFile(tree); + CompareImages(skipImmediate: true, skipDeferred: true); + } +} \ No newline at end of file diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 8a127897d7..313281d6c6 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -156,7 +156,8 @@ namespace Avalonia.Direct2D1.RenderTests public ILockedFramebuffer Lock() => _bitmap.Lock(); } - protected void CompareImages([CallerMemberName] string testName = "") + protected void CompareImages([CallerMemberName] string testName = "", + bool skipImmediate = false, bool skipDeferred = false, bool skipCompositor = false) { var expectedPath = Path.Combine(OutputPath, testName + ".expected.png"); var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png"); @@ -172,17 +173,17 @@ namespace Avalonia.Direct2D1.RenderTests var deferredError = CompareImages(deferred, expected); var compositedError = CompareImages(composited, expected); - if (immediateError > 0.022) + if (immediateError > 0.022 && !skipImmediate) { Assert.True(false, immediatePath + ": Error = " + immediateError); } - if (deferredError > 0.022) + if (deferredError > 0.022 && !skipDeferred) { Assert.True(false, deferredPath + ": Error = " + deferredError); } - if (compositedError > 0.022) + if (compositedError > 0.022 && !skipCompositor) { Assert.True(false, compositedPath + ": Error = " + compositedError); } diff --git a/tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png b/tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..6a67087d41828128492aa2cb44f7d53f067db4f1 GIT binary patch literal 673 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yu@pObhHwBu4M$1`0|Qfpr;B4q z#hkZSH+nfc3OHPhJ=y;J{BfllC%-D5QmgWtEE3Jb^Zo3+t!a$&tE_DqY>XQn1UOje zPqf^AI^B4`#j)%g)%O4H^!I(N{eJxXji*uayZ)-!D`Iz4IT|2zRSq{DZ!m$3x^zZ)*9ThlOnjD4(@g_3ckDu}UDb_{>0S+c~;sf9P X|Dv4+8exvWG{@lS>gTe~DWM4f2)3Ck literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png b/tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..6a67087d41828128492aa2cb44f7d53f067db4f1 GIT binary patch literal 673 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yu@pObhHwBu4M$1`0|Qfpr;B4q z#hkZSH+nfc3OHPhJ=y;J{BfllC%-D5QmgWtEE3Jb^Zo3+t!a$&tE_DqY>XQn1UOje zPqf^AI^B4`#j)%g)%O4H^!I(N{eJxXji*uayZ)-!D`Iz4IT|2zRSq{DZ!m$3x^zZ)*9ThlOnjD4(@g_3ckDu}UDb_{>0S+c~;sf9P X|Dv4+8exvWG{@lS>gTe~DWM4f2)3Ck literal 0 HcmV?d00001 From 78b4c6a96e80b8ac6f9a103c71154bd3d8caea20 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 1 Jan 2023 20:25:26 +0600 Subject: [PATCH 55/57] Ignore Opacity for hit-testing purposes --- .../Server/ServerCompositionVisual.cs | 13 ++++++-- .../Rendering/CompositorHitTestingTests.cs | 33 +++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index 0e15cbd54b..c4f0ebcd3b 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -162,8 +162,14 @@ namespace Avalonia.Rendering.Composition.Server EffectiveOpacity = Opacity * (Parent?.EffectiveOpacity ?? 1); - IsVisibleInFrame = _parent?.IsVisibleInFrame != false && Visible && EffectiveOpacity > 0.04 && !_isBackface && - !_combinedTransformedClipBounds.IsDefault; + IsHitTestVisibleInFrame = _parent?.IsHitTestVisibleInFrame != false + && Visible + && !_isBackface + && !_combinedTransformedClipBounds.IsDefault; + + IsVisibleInFrame = IsHitTestVisibleInFrame + && _parent?.IsVisibleInFrame != false + && EffectiveOpacity > 0.04; if (wasVisible != IsVisibleInFrame || positionChanged) { @@ -187,7 +193,7 @@ namespace Avalonia.Rendering.Composition.Server readback.Revision = root.Revision; readback.Matrix = GlobalTransformMatrix; readback.TargetId = Root.Id; - readback.Visible = IsVisibleInFrame; + readback.Visible = IsHitTestVisibleInFrame; } void AddDirtyRect(Rect rc) @@ -248,6 +254,7 @@ namespace Avalonia.Rendering.Composition.Server } public bool IsVisibleInFrame { get; set; } + public bool IsHitTestVisibleInFrame { get; set; } public double EffectiveOpacity { get; set; } public Rect TransformedOwnContentBounds { get; set; } public virtual Rect OwnContentBounds => new Rect(0, 0, Size.X, Size.Y); diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs index 27faf1e13c..3d8369faeb 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs @@ -82,6 +82,39 @@ public class CompositorHitTestingTests : CompositorTestsBase } } + [Theory, + InlineData(false, false), + InlineData(true, false), + InlineData(false, true), + InlineData(true, true), + ] + public void HitTest_Should_Find_Zero_Opacity_Controls_At_Point(bool parent, bool child) + { + + using (var s = new CompositorServices(new Size(200, 200))) + { + Border visible, border; + s.TopLevel.Content = border = new Border + { + Width = 100, + Height = 100, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + Opacity = parent ? 0 : 1, + Child = visible = new Border + { + Opacity = child ? 0 : 1, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Stretch, + VerticalAlignment = VerticalAlignment.Stretch, + } + }; + + s.AssertHitTest(new Point(100, 100), null, visible, border); + } + } + [Fact] public void HitTest_Should_Not_Find_Control_Outside_Point() { From 11b8bc2322ed4da16e115a28e3580e35a5eb4ab5 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 2 Jan 2023 15:01:14 +0000 Subject: [PATCH 56/57] keep app builder instance --- .../Avalonia.Android/AvaloniaSplashActivity.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs index ec26ee5599..38038ef26c 100644 --- a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs @@ -1,6 +1,5 @@ using Android.OS; using AndroidX.AppCompat.App; -using Avalonia.Controls; namespace Avalonia.Android { @@ -8,15 +7,22 @@ namespace Avalonia.Android { protected abstract AppBuilder CreateAppBuilder(); + private static AppBuilder s_appBuilder; + protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); - var builder = CreateAppBuilder(); + if (s_appBuilder == null) + { + var builder = CreateAppBuilder(); + + var lifetime = new SingleViewLifetime(); - var lifetime = new SingleViewLifetime(); + builder.SetupWithLifetime(lifetime); - builder.SetupWithLifetime(lifetime); + s_appBuilder = builder; + } } } From 5ff712109ba30fc82f4116e11691086d7cb52c98 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 3 Jan 2023 15:22:12 +1100 Subject: [PATCH 57/57] avoid nuget refs on net6 System.ValueTuple, Runtime.CompilerServices.Unsafe, and System.Memory should not be required no net6 and up --- build/Base.props | 2 +- build/System.Memory.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/Base.props b/build/Base.props index 100c9088cd..9ec1c3c2d3 100644 --- a/build/Base.props +++ b/build/Base.props @@ -1,5 +1,5 @@  - + diff --git a/build/System.Memory.props b/build/System.Memory.props index b36998a780..a413e18927 100644 --- a/build/System.Memory.props +++ b/build/System.Memory.props @@ -1,5 +1,5 @@ - +