Browse Source

Internal PointerCaptureChanging event (#19833)

* PointerCaptureChanging event

* PR feedback

* Announce implicit capture to platform

---------

Co-authored-by: Jan Kučera <miloush@users.noreply.github.com>
Co-authored-by: Max Katz <maxkatz6@outlook.com>
pull/19875/head
Jan Kučera 4 months ago
committed by Jan Kučera
parent
commit
dba0388af7
  1. 32
      src/Avalonia.Base/Input/InputElement.cs
  2. 4
      src/Avalonia.Base/Input/MouseDevice.cs
  3. 53
      src/Avalonia.Base/Input/Pointer.cs
  4. 17
      src/Avalonia.Base/Input/PointerEventArgs.cs

32
src/Avalonia.Base/Input/InputElement.cs

@ -166,6 +166,14 @@ namespace Avalonia.Input
nameof(PointerReleased), nameof(PointerReleased),
RoutingStrategies.Tunnel | RoutingStrategies.Bubble); RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="PointerCaptureChanging"/> routed event.
/// </summary>
internal static readonly RoutedEvent<PointerCaptureChangingEventArgs> PointerCaptureChangingEvent =
RoutedEvent.Register<InputElement, PointerCaptureChangingEventArgs>(
nameof(PointerCaptureChanging),
RoutingStrategies.Direct);
/// <summary> /// <summary>
/// Defines the <see cref="PointerCaptureLost"/> routed event. /// Defines the <see cref="PointerCaptureLost"/> routed event.
/// </summary> /// </summary>
@ -221,6 +229,7 @@ namespace Avalonia.Input
PointerMovedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerMoved(e)); PointerMovedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerMoved(e));
PointerPressedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerPressed(e)); PointerPressedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerPressed(e));
PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e)); PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e));
PointerCaptureChangingEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureChanging(e));
PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e)); PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e));
PointerWheelChangedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerWheelChanged(e)); PointerWheelChangedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerWheelChanged(e));
@ -339,9 +348,19 @@ namespace Avalonia.Input
remove { RemoveHandler(PointerReleasedEvent, value); } remove { RemoveHandler(PointerReleasedEvent, value); }
} }
/// <summary>
/// Occurs when the control or its child control is about to lose capture,
/// event will not be triggered for a parent control if capture was transferred to another child of that parent control.
/// </summary>
internal event EventHandler<PointerCaptureChangingEventArgs>? PointerCaptureChanging
{
add => AddHandler(PointerCaptureChangingEvent, value);
remove => RemoveHandler(PointerCaptureChangingEvent, value);
}
/// <summary> /// <summary>
/// Occurs when the control or its child control loses the pointer capture for any reason, /// Occurs when the control or its child control loses the pointer capture for any reason,
/// event will not be triggered for a parent control if capture was transferred to another child of that parent control /// event will not be triggered for a parent control if capture was transferred to another child of that parent control.
/// </summary> /// </summary>
public event EventHandler<PointerCaptureLostEventArgs>? PointerCaptureLost public event EventHandler<PointerCaptureLostEventArgs>? PointerCaptureLost
{ {
@ -671,6 +690,17 @@ namespace Avalonia.Input
} }
} }
/// <summary>
/// Invoked when an unhandled <see cref="PointerCaptureChangingEvent"/> reaches an element in its
/// route that is derived from this class. Implement this method to add class handling
/// for this event.
/// </summary>
/// <param name="e">Data about the event.</param>
internal virtual void OnPointerCaptureChanging(PointerCaptureChangingEventArgs e)
{
}
/// <summary> /// <summary>
/// Invoked when an unhandled <see cref="PointerCaptureLostEvent"/> reaches an element in its /// Invoked when an unhandled <see cref="PointerCaptureLostEvent"/> reaches an element in its
/// route that is derived from this class. Implement this method to add class handling /// route that is derived from this class. Implement this method to add class handling

4
src/Avalonia.Base/Input/MouseDevice.cs

@ -131,7 +131,7 @@ namespace Avalonia.Input
if (source != null) if (source != null)
{ {
_pointer.Capture(source); _pointer.Capture(source, CaptureSource.Implicit);
var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings; var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings;
if (settings is not null) if (settings is not null)
@ -206,7 +206,7 @@ namespace Avalonia.Input
} }
finally finally
{ {
_pointer.Capture(null); _pointer.Capture(null, CaptureSource.Implicit);
_pointer.CaptureGestureRecognizer(null); _pointer.CaptureGestureRecognizer(null);
_pointer.IsGestureRecognitionSkipped = false; _pointer.IsGestureRecognitionSkipped = false;
_lastMouseDownButton = default; _lastMouseDownButton = default;

53
src/Avalonia.Base/Input/Pointer.cs

@ -6,6 +6,13 @@ using Avalonia.VisualTree;
namespace Avalonia.Input namespace Avalonia.Input
{ {
internal enum CaptureSource
{
Explicit,
Implicit,
Platform
}
public class Pointer : IPointer, IDisposable public class Pointer : IPointer, IDisposable
{ {
private static int s_NextFreePointerId = 1000; private static int s_NextFreePointerId = 1000;
@ -30,46 +37,60 @@ namespace Avalonia.Input
protected virtual void PlatformCapture(IInputElement? element) protected virtual void PlatformCapture(IInputElement? element)
{ {
} }
internal void PlatformCaptureLost() internal void PlatformCaptureLost()
{ {
if (Captured != null) if (Captured != null)
Capture(null, platformInitiated: true); Capture(null, CaptureSource.Platform);
} }
public void Capture(IInputElement? control) public void Capture(IInputElement? control)
{ {
Capture(control, platformInitiated: false); Capture(control, CaptureSource.Explicit);
} }
private void Capture(IInputElement? control, bool platformInitiated) internal void Capture(IInputElement? control, CaptureSource source)
{ {
var oldCapture = Captured; var oldCapture = Captured;
if (oldCapture == control) if (oldCapture == control)
return; return;
if (oldCapture is Visual v1) var oldVisual = oldCapture as Visual;
v1.DetachedFromVisualTree -= OnCaptureDetached;
IInputElement? commonParent = null;
if (oldVisual != null)
{
commonParent = FindCommonParent(control, oldCapture);
foreach (var notifyTarget in oldVisual.GetSelfAndVisualAncestors().OfType<IInputElement>())
{
if (notifyTarget == commonParent)
break;
var args = new PointerCaptureChangingEventArgs(notifyTarget, this, control, source);
notifyTarget.RaiseEvent(args);
if (args.Handled)
return;
}
}
if (oldVisual != null)
oldVisual.DetachedFromVisualTree -= OnCaptureDetached;
Captured = control; Captured = control;
if (!platformInitiated) if (source != CaptureSource.Platform)
PlatformCapture(control); PlatformCapture(control);
if (oldCapture is Visual v2) if (oldVisual != null)
{ foreach (var notifyTarget in oldVisual.GetSelfAndVisualAncestors().OfType<IInputElement>())
var commonParent = FindCommonParent(control, oldCapture);
foreach (var notifyTarget in v2.GetSelfAndVisualAncestors().OfType<IInputElement>())
{ {
if (notifyTarget == commonParent) if (notifyTarget == commonParent)
break; break;
notifyTarget.RaiseEvent(new PointerCaptureLostEventArgs(notifyTarget, this)); notifyTarget.RaiseEvent(new PointerCaptureLostEventArgs(notifyTarget, this));
} }
}
if (Captured is Visual v3) if (Captured is Visual newVisual)
v3.DetachedFromVisualTree += OnCaptureDetached; newVisual.DetachedFromVisualTree += OnCaptureDetached;
if (Captured != null) if (Captured != null)
CaptureGestureRecognizer(null); CaptureGestureRecognizer(null);
@ -92,7 +113,7 @@ namespace Avalonia.Input
public IInputElement? Captured { get; private set; } public IInputElement? Captured { get; private set; }
public PointerType Type { get; } public PointerType Type { get; }
public bool IsPrimary { get; } public bool IsPrimary { get; }

17
src/Avalonia.Base/Input/PointerEventArgs.cs

@ -202,11 +202,26 @@ namespace Avalonia.Input
{ {
public IPointer Pointer { get; } public IPointer Pointer { get; }
[Unstable("This constructor might be removed in 12.0. If you need to remove capture, use stable methods on the IPointer instance.,")] [Unstable("This constructor might be removed in 12.0. If you need to remove capture, use stable methods on the IPointer instance.")]
public PointerCaptureLostEventArgs(object source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent) public PointerCaptureLostEventArgs(object source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent)
{ {
Pointer = pointer; Pointer = pointer;
Source = source; Source = source;
} }
} }
internal class PointerCaptureChangingEventArgs : RoutedEventArgs
{
public IPointer Pointer { get; }
public CaptureSource CaptureSource { get; }
public IInputElement? NewValue { get; }
internal PointerCaptureChangingEventArgs(object source, IPointer pointer, IInputElement? newValue, CaptureSource captureSource) : base(InputElement.PointerCaptureChangingEvent)
{
Pointer = pointer;
Source = source;
NewValue = newValue;
CaptureSource = captureSource;
}
}
} }

Loading…
Cancel
Save