From d74bd5d7e3463883f9c5f870625e091327c4b8ac Mon Sep 17 00:00:00 2001 From: Zeljko Predjeskovic Date: Wed, 2 Sep 2026 14:06:19 +0200 Subject: [PATCH] Fixed action sheet returning the wrong item on touch and selecting while scrolling --- .../Views/ActionSheetDialogContent.axaml | 6 +- .../Views/ActionSheetDialogContent.axaml.cs | 99 ++++++++++++++----- .../Svg.Editor.Avalon.Forms.csproj | 4 +- .../Svg.Editor.Avalon.Views.csproj | 4 +- Svg.Editor.Core/Svg.Editor.Core.csproj | 4 +- Svg/Svg.csproj | 4 +- 6 files changed, 91 insertions(+), 30 deletions(-) diff --git a/Svg.Editor.Avalonia.Forms/Dialog/Views/ActionSheetDialogContent.axaml b/Svg.Editor.Avalonia.Forms/Dialog/Views/ActionSheetDialogContent.axaml index c69bcc391..5e27b488a 100644 --- a/Svg.Editor.Avalonia.Forms/Dialog/Views/ActionSheetDialogContent.axaml +++ b/Svg.Editor.Avalonia.Forms/Dialog/Views/ActionSheetDialogContent.axaml @@ -24,8 +24,10 @@ - diff --git a/Svg.Editor.Avalonia.Forms/Dialog/Views/ActionSheetDialogContent.axaml.cs b/Svg.Editor.Avalonia.Forms/Dialog/Views/ActionSheetDialogContent.axaml.cs index b04070c41..0e2dd0e75 100644 --- a/Svg.Editor.Avalonia.Forms/Dialog/Views/ActionSheetDialogContent.axaml.cs +++ b/Svg.Editor.Avalonia.Forms/Dialog/Views/ActionSheetDialogContent.axaml.cs @@ -3,6 +3,7 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Threading; +using Avalonia.VisualTree; namespace Svg.Editor.Avalon.Forms.Dialog.Views; @@ -15,42 +16,92 @@ public ActionSheetDialogContent() private ActionSheetDialogResultViewModel ResultViewModel => (ActionSheetDialogResultViewModel)DataContext!; - private void SelectingItemsControl_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) - { - if (ResultViewModel is null) - throw new InvalidOperationException("DataContext was null"); - - ResultViewModel.AcceptPrimary(); - } - #region HandleClickingItem - private object? _previouslyClickedOnItem = null; + /// Used when the control is not (yet) attached to a visual root. + private static readonly Size FallbackTapSize = new(10, 10); + + private IPointer? _pressedPointer; + private Control? _pressedItemVisual; + private Point _pressRootPoint; + private bool _tapCancelled; - private void InputElement_OnPointerPressed(object? sender, PointerPressedEventArgs e) + private void Item_OnPointerPressed(object? sender, PointerPressedEventArgs e) { - _previouslyClickedOnItem = sender; + // Reset first: a press that we do not track must not leave state from a previous + // gesture behind, otherwise a later release could be matched against a stale press. + _pressedPointer = null; + _pressedItemVisual = null; + _tapCancelled = false; + + if (!e.GetCurrentPoint(null).Properties.IsLeftButtonPressed) + return; + + _pressedPointer = e.Pointer; + _pressedItemVisual = sender as Control; + // Root (TopLevel) coordinates on purpose: measuring relative to the item would be + // useless, because while the ListBox scrolls the item travels with the finger and + // the relative delta stays at ~zero. + _pressRootPoint = e.GetPosition(null); } - private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e) + private void Item_OnPointerMoved(object? sender, PointerEventArgs e) { - if (sender == _previouslyClickedOnItem) - { - ResultViewModel.AcceptPrimary(); - _previouslyClickedOnItem = null; - } + if (_tapCancelled || _pressedPointer is null || !ReferenceEquals(e.Pointer, _pressedPointer)) + return; + + if (ExceedsTapSlop(e.GetPosition(null), e.Pointer.Type)) + _tapCancelled = true; } - #endregion + /// + /// Raised when a gesture recognizer (ScrollGestureRecognizer) steals the pointer, + /// i.e. the user started scrolling rather than tapping. + /// + private void Item_OnPointerCaptureLost(object? sender, PointerCaptureLostEventArgs e) + { + if (_pressedPointer is not null && ReferenceEquals(e.Pointer, _pressedPointer)) + _tapCancelled = true; + } - #region HandlePressingEnterr on item + private void Item_OnPointerReleased(object? sender, PointerReleasedEventArgs e) + { + var cancelled = _tapCancelled; + var pressedPointer = _pressedPointer; + var pressedItemVisual = _pressedItemVisual; + + _tapCancelled = false; + _pressedPointer = null; + _pressedItemVisual = null; + + if (cancelled + || pressedPointer is null + || !ReferenceEquals(e.Pointer, pressedPointer) + || !ReferenceEquals(sender, pressedItemVisual) + || e.InitialPressMouseButton != MouseButton.Left) + return; + + if (ExceedsTapSlop(e.GetPosition(null), e.Pointer.Type)) + return; + + if (sender is not Control { DataContext: ActionSheetItem item }) + return; + + // Resolve the tapped item ourselves instead of relying on ListBox.SelectedItem: + // for touch and pen the ListBox only updates the selection *later* in the same + // bubbling route (it selects on PointerReleased, not PointerPressed), so reading + // SelectedItem here could yield the previously selected item. + ResultViewModel.SelectedItem = item; + ResultViewModel.AcceptPrimary(); + } - private void InputElement_OnKeyUp(object? sender, KeyEventArgs e) + private bool ExceedsTapSlop(Point currentRootPoint, PointerType pointerType) { - if (e.Key == Key.Enter) - { - ResultViewModel.AcceptPrimary(); - } + // TopLevel.PlatformSettings is private in Avalonia 12; VisualExtensions is the public way in. + var slop = this.GetPlatformSettings()?.GetTapSize(pointerType) ?? FallbackTapSize; + + return Math.Abs(currentRootPoint.X - _pressRootPoint.X) > slop.Width + || Math.Abs(currentRootPoint.Y - _pressRootPoint.Y) > slop.Height; } #endregion diff --git a/Svg.Editor.Avalonia.Forms/Svg.Editor.Avalon.Forms.csproj b/Svg.Editor.Avalonia.Forms/Svg.Editor.Avalon.Forms.csproj index e17e0c896..93881cfdc 100644 --- a/Svg.Editor.Avalonia.Forms/Svg.Editor.Avalon.Forms.csproj +++ b/Svg.Editor.Avalonia.Forms/Svg.Editor.Avalon.Forms.csproj @@ -3,8 +3,10 @@ net10.0 enable latest - 3.2.0-optiq11 + 3.2.0-optiq12 + #3.2.0-optiq12 + Fixed action sheet returning the wrong item on touch and selecting while scrolling #3.2.0-optiq11 Fixed huge paragraph spacing in multi-line text (LineHeight 12f -> 1.25f) #3.2.0-optiq09 diff --git a/Svg.Editor.Avalonia.Views/Svg.Editor.Avalon.Views.csproj b/Svg.Editor.Avalonia.Views/Svg.Editor.Avalon.Views.csproj index 0701aff35..a7067d58a 100644 --- a/Svg.Editor.Avalonia.Views/Svg.Editor.Avalon.Views.csproj +++ b/Svg.Editor.Avalonia.Views/Svg.Editor.Avalon.Views.csproj @@ -3,8 +3,10 @@ net10.0 enable latest - 3.2.0-optiq11 + 3.2.0-optiq12 + #3.2.0-optiq12 + Fixed action sheet returning the wrong item on touch and selecting while scrolling #3.2.0-optiq11 Fixed huge paragraph spacing in multi-line text (LineHeight 12f -> 1.25f) #3.2.0-optiq09 diff --git a/Svg.Editor.Core/Svg.Editor.Core.csproj b/Svg.Editor.Core/Svg.Editor.Core.csproj index 09ff7d256..abbcbbc31 100644 --- a/Svg.Editor.Core/Svg.Editor.Core.csproj +++ b/Svg.Editor.Core/Svg.Editor.Core.csproj @@ -4,9 +4,11 @@ Svg.Editor en-US net10.0 - 3.2.0-optiq11 + 3.2.0-optiq12 latest + #3.2.0-optiq12 + Fixed action sheet returning the wrong item on touch and selecting while scrolling #3.2.0-optiq11 Fixed huge paragraph spacing in multi-line text (LineHeight 12f -> 1.25f) #3.2.0-optiq09 diff --git a/Svg/Svg.csproj b/Svg/Svg.csproj index 4fad3772c..5fd1e118e 100644 --- a/Svg/Svg.csproj +++ b/Svg/Svg.csproj @@ -3,10 +3,12 @@ netstandard2.0;net48;net10.0 PackageReference - 3.2.0-optiq11 + 3.2.0-optiq12 gentledpp,zepr Opti-Q GmbH + #3.2.0-optiq12 + Fixed action sheet returning the wrong item on touch and selecting while scrolling #3.2.0-optiq11 Fixed huge paragraph spacing in multi-line text (LineHeight 12f -> 1.25f) #3.2.0-optiq10