Browse Source

Hold Gesture size and event improvements (#12879)

* add hold size to platform settings, reduce hold size

* use class handler for control hold event

* improve holding interaction with textbox

* remove GetHoldSize method

* inprove touch input pointer over detection

* improve hold support in textbox

* fix tests

* remove selection updates on hold

* make pointer event args for holding event internal

---------

Co-authored-by: Max Katz <maxkatz6@outlook.com>
pull/13019/head
Emmanuel Hansen 3 years ago
committed by GitHub
parent
commit
347162050e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      src/Avalonia.Base/Input/Gestures.cs
  2. 10
      src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs
  3. 19
      src/Avalonia.Base/Input/PointerOverPreProcessor.cs
  4. 2
      src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
  5. 16
      src/Avalonia.Controls/Control.cs
  6. 74
      src/Avalonia.Controls/TextBox.cs

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

10
src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs

@ -20,6 +20,8 @@ namespace Avalonia.Input
/// </summary>
public PointerType PointerType { get; }
internal PointerEventArgs? PointerEventArgs { get; }
/// <summary>
/// Initializes a new instance of the <see cref="HoldingRoutedEventArgs"/> class.
/// </summary>
@ -29,6 +31,14 @@ namespace Avalonia.Input
Position = position;
PointerType = pointerType;
}
/// <summary>
/// Initializes a new instance of the <see cref="HoldingRoutedEventArgs"/> class.
/// </summary>
internal HoldingRoutedEventArgs(HoldingState holdingState, Point position, PointerType pointerType, PointerEventArgs pointerEventArgs) : this(holdingState, position, pointerType)
{
PointerEventArgs = pointerEventArgs;
}
}
public enum HoldingState

19
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;

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

16
src/Avalonia.Controls/Control.cs

@ -212,6 +212,14 @@ namespace Avalonia.Controls
/// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
static Control()
{
Gestures.HoldingEvent.AddClassHandler<Control>((control, e) =>
{
control.OnHoldEvent(control, e);
});
}
/// <inheritdoc/>
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();
}

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

Loading…
Cancel
Save