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);
}