diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index e9d23632b1..c8195a18eb 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -163,7 +163,7 @@ namespace Avalonia.Input { if(s_isHolding && ev.Source is Interactive i) { - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer.Type)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer.Type, e)); } s_holdCancellationToken?.Cancel(); s_holdCancellationToken?.Dispose(); @@ -191,7 +191,7 @@ namespace Avalonia.Input 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)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type, e)); } }, settings.HoldWaitDuration); } @@ -231,7 +231,7 @@ namespace Avalonia.Input if(s_isHolding) { s_isHolding = false; - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed, s_lastPressPoint, s_lastPointer!.Type)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed, s_lastPressPoint, s_lastPointer!.Type, e)); } else if (e.InitialPressMouseButton == MouseButton.Right) { @@ -264,18 +264,18 @@ namespace Avalonia.Input { var point = e.GetCurrentPoint((Visual)target); var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings; - var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4); - var tapRect = new Rect(s_lastPressPoint, new Size()) - .Inflate(new Thickness(tapSize.Width, tapSize.Height)); + var holdSize = new Size(4, 4); + var holdRect = new Rect(s_lastPressPoint, new Size()) + .Inflate(new Thickness(holdSize.Width, holdSize.Height)); - if (tapRect.ContainsExclusive(point.Position)) + if (holdRect.ContainsExclusive(point.Position)) { return; } if (s_isHolding) { - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type, e)); } } } diff --git a/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs index b9a877b2ed..efb0c01599 100644 --- a/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs +++ b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs @@ -20,6 +20,8 @@ namespace Avalonia.Input /// public PointerType PointerType { get; } + internal PointerEventArgs? PointerEventArgs { get; } + /// /// Initializes a new instance of the class. /// @@ -29,6 +31,14 @@ namespace Avalonia.Input Position = position; PointerType = pointerType; } + + /// + /// Initializes a new instance of the class. + /// + internal HoldingRoutedEventArgs(HoldingState holdingState, Point position, PointerType pointerType, PointerEventArgs pointerEventArgs) : this(holdingState, position, pointerType) + { + PointerEventArgs = pointerEventArgs; + } } public enum HoldingState diff --git a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs index 0afdb8e080..7134a42666 100644 --- a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs +++ b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs @@ -41,7 +41,8 @@ namespace Avalonia.Input _lastActivePointerDevice = pointerDevice; } - if (args.Type is RawPointerEventType.LeaveWindow or RawPointerEventType.NonClientLeftButtonDown + if (args.Type is RawPointerEventType.LeaveWindow or RawPointerEventType.NonClientLeftButtonDown + or RawPointerEventType.TouchCancel or RawPointerEventType.TouchEnd && _currentPointer is var (lastPointer, lastPosition)) { _currentPointer = null; @@ -49,8 +50,20 @@ namespace Avalonia.Input new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind()), args.InputModifiers.ToKeyModifiers()); } - else if (pointerDevice.TryGetPointer(args) is { } pointer - && pointer.Type != PointerType.Touch) + else if (args.Type is RawPointerEventType.TouchBegin or RawPointerEventType.TouchUpdate && args.Root is Visual visual) + { + _lastKnownPosition = visual.PointToScreen(args.Position); + } + else if (args.Type is RawPointerEventType.TouchCancel or RawPointerEventType.TouchEnd + && pointerDevice.TryGetPointer(args) is { } p) + { + _currentPointer = null; + ClearPointerOver(p, args.Root, 0, args.Position, + new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind()), + args.InputModifiers.ToKeyModifiers()); + } + else if (pointerDevice.TryGetPointer(args) is { } pointer && + pointer.Type != PointerType.Touch) { var element = pointer.Captured ?? args.InputHitTestResult; diff --git a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs index a4d14b0084..a63682a0b9 100644 --- a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs +++ b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs @@ -21,6 +21,7 @@ namespace Avalonia.Platform _ => new(4, 4), }; } + public virtual Size GetDoubleTapSize(PointerType type) { return type switch @@ -29,6 +30,7 @@ namespace Avalonia.Platform _ => new(4, 4), }; } + public virtual TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(500); public virtual TimeSpan HoldWaitDuration => TimeSpan.FromMilliseconds(300); diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 0851c14178..2e1bfe100d 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -212,6 +212,14 @@ namespace Avalonia.Controls /// bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; + static Control() + { + Gestures.HoldingEvent.AddClassHandler((control, e) => + { + control.OnHoldEvent(control, e); + }); + } + /// void ISetterValue.Initialize(SetterBase setter) { @@ -371,8 +379,6 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTreeCore(e); - AddHandler(Gestures.HoldingEvent, OnHoldEvent); - InitializeIfNeeded(); ScheduleOnLoadedCore(); @@ -380,10 +386,10 @@ namespace Avalonia.Controls private void OnHoldEvent(object? sender, HoldingRoutedEventArgs e) { - if(e.HoldingState == HoldingState.Started) + if (!e.Handled && e.HoldingState == HoldingState.Started) { // Trigger ContentRequest when hold has started - RaiseEvent(new ContextRequestedEventArgs()); + RaiseEvent(e.PointerEventArgs is { } ev ? new ContextRequestedEventArgs(ev) : new ContextRequestedEventArgs()); } } @@ -392,8 +398,6 @@ namespace Avalonia.Controls { base.OnDetachedFromVisualTreeCore(e); - RemoveHandler(Gestures.HoldingEvent, OnHoldEvent); - OnUnloadedCore(); } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 8a5ac4e495..e246d48831 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -316,8 +316,13 @@ namespace Avalonia.Controls private bool _canRedo; private int _wordSelectionStart = -1; + private bool _touchDragStarted; + private bool _inTouchDrag; + private Point _touchDragStartPoint; private int _selectedTextChangesMadeSinceLastUndoSnapshot; private bool _hasDoneSnapshotOnce; + private static bool _isHolding; + private int _currentClickCount; private const int _maxCharsBeforeUndoSnapshot = 7; static TextBox() @@ -821,6 +826,16 @@ namespace Avalonia.Controls _presenter.PropertyChanged += PresenterPropertyChanged; } + + AddHandler(Gestures.HoldingEvent, OnHolding); + } + + private void OnHolding(object? sender, HoldingRoutedEventArgs e) + { + if (e.Handled || e.HoldingState != HoldingState.Started) + return; + + _isHolding = true; } protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) @@ -835,6 +850,8 @@ namespace Avalonia.Controls } _imClient.SetPresenter(null, null); + + RemoveHandler(Gestures.HoldingEvent, OnHolding); } private void PresenterPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) @@ -1456,8 +1473,21 @@ namespace Avalonia.Controls if (text != null && clickInfo.Properties.IsLeftButtonPressed && !(clickInfo.Pointer?.Captured is Border)) { + var isTouch = e.Pointer.Type == PointerType.Touch; + + _currentClickCount = e.ClickCount; var point = e.GetPosition(_presenter); + if(isTouch && e.ClickCount == 1) + { + _wordSelectionStart = -1; + _touchDragStarted = true; + _touchDragStartPoint = point; + e.Pointer.Capture(_presenter); + e.Handled = true; + return; + } + _presenter.MoveCaretToPoint(point); var caretIndex = _presenter.CaretIndex; @@ -1491,7 +1521,6 @@ namespace Avalonia.Controls break; case 2: - if (!StringUtils.IsStartOfWord(text, caretIndex)) { selectionStart = StringUtils.PreviousWord(text, caretIndex); @@ -1525,7 +1554,7 @@ namespace Avalonia.Controls protected override void OnPointerMoved(PointerEventArgs e) { - if (_presenter == null) + if (_presenter == null || _isHolding) { return; } @@ -1539,6 +1568,16 @@ namespace Avalonia.Controls MathUtilities.Clamp(point.X, 0, Math.Max(_presenter.Bounds.Width - 1, 0)), MathUtilities.Clamp(point.Y, 0, Math.Max(_presenter.Bounds.Height - 1, 0))); + if(_touchDragStarted) + { + _touchDragStarted = false; + _inTouchDrag = true; + + _presenter.MoveCaretToPoint(_touchDragStartPoint); + _touchDragStartPoint = default; + SetCurrentValue(SelectionStartProperty, _presenter.CaretIndex); + } + _presenter.MoveCaretToPoint(point); var caretIndex = _presenter.CaretIndex; @@ -1593,12 +1632,23 @@ namespace Avalonia.Controls return; } + _touchDragStarted = false; + _touchDragStartPoint = default; + + var isInTouchDrag = _inTouchDrag; + _inTouchDrag = false; + if (e.Pointer.Captured != _presenter) { return; } - if (e.InitialPressMouseButton == MouseButton.Right) + // Don't update selection if the pointer was held + if (_isHolding) + { + _isHolding = false; + } + else if (e.InitialPressMouseButton == MouseButton.Right) { var point = e.GetPosition(_presenter); @@ -1619,6 +1669,24 @@ namespace Avalonia.Controls SetCurrentValue(SelectionStartProperty, caretIndex); } } + else if (e.Pointer.Type == PointerType.Touch) + { + if (_currentClickCount == 1 && !isInTouchDrag) + { + var point = e.GetPosition(_presenter); + + _presenter.MoveCaretToPoint(point); + + var caretIndex = _presenter.CaretIndex; + SetCurrentValue(SelectionStartProperty, caretIndex); + SetCurrentValue(SelectionEndProperty, caretIndex); + } + + if(SelectionStart != SelectionEnd) + { + RaiseEvent(new ContextRequestedEventArgs(e)); + } + } e.Pointer.Capture(null); }