Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion src/Avalonia.Base/Input/InputElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,14 @@ public class InputElement : Interactive, IInputElement
nameof(PointerReleased),
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>
/// Defines the <see cref="PointerCaptureLost"/> routed event.
/// </summary>
Expand Down Expand Up @@ -240,6 +248,7 @@ static InputElement()
PointerMovedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerMoved(e));
PointerPressedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerPressed(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));
PointerWheelChangedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerWheelChanged(e));

Expand Down Expand Up @@ -381,9 +390,19 @@ public event EventHandler<PointerReleasedEventArgs>? PointerReleased
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>
/// 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>
public event EventHandler<PointerCaptureLostEventArgs>? PointerCaptureLost
{
Expand Down Expand Up @@ -770,6 +789,17 @@ private void OnGesturePointerMoved(PointerEventArgs e)
/// <returns>Last focusable element if available/>.</returns>
protected internal virtual InputElement? GetLastFocusableElementOverride() => null;

/// <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>
/// 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
Expand Down
4 changes: 2 additions & 2 deletions src/Avalonia.Base/Input/MouseDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root,

if (source != null)
{
_pointer.Capture(source);
_pointer.Capture(source, CaptureSource.Implicit);

var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings;
if (settings is not null)
Expand Down Expand Up @@ -206,7 +206,7 @@ private bool MouseUp(IMouseDevice device, ulong timestamp, IInputRoot root, Poin
}
finally
{
_pointer.Capture(null);
_pointer.Capture(null, CaptureSource.Implicit);
_pointer.CaptureGestureRecognizer(null);
_pointer.IsGestureRecognitionSkipped = false;
_lastMouseDownButton = default;
Expand Down
53 changes: 37 additions & 16 deletions src/Avalonia.Base/Input/Pointer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@

namespace Avalonia.Input
{
internal enum CaptureSource
{
Explicit,
Implicit,
Platform
}

public class Pointer : IPointer, IDisposable
{
private static int s_NextFreePointerId = 1000;
Expand All @@ -30,46 +37,60 @@ public Pointer(int id, PointerType type, bool isPrimary)

protected virtual void PlatformCapture(IInputElement? element)
{

}

internal void PlatformCaptureLost()
{
if (Captured != null)
Capture(null, platformInitiated: true);
Capture(null, CaptureSource.Platform);
}

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;
if (oldCapture == control)
return;

if (oldCapture is Visual v1)
v1.DetachedFromVisualTree -= OnCaptureDetached;
var oldVisual = oldCapture as Visual;

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;
if (!platformInitiated)

if (source != CaptureSource.Platform)
PlatformCapture(control);

if (oldCapture is Visual v2)
{
var commonParent = FindCommonParent(control, oldCapture);
foreach (var notifyTarget in v2.GetSelfAndVisualAncestors().OfType<IInputElement>())
if (oldVisual != null)
foreach (var notifyTarget in oldVisual.GetSelfAndVisualAncestors().OfType<IInputElement>())
{
if (notifyTarget == commonParent)
break;
notifyTarget.RaiseEvent(new PointerCaptureLostEventArgs(notifyTarget, this));
}
}

if (Captured is Visual v3)
v3.DetachedFromVisualTree += OnCaptureDetached;
if (Captured is Visual newVisual)
newVisual.DetachedFromVisualTree += OnCaptureDetached;

if (Captured != null)
CaptureGestureRecognizer(null);
Expand All @@ -92,7 +113,7 @@ private void OnCaptureDetached(object? sender, VisualTreeAttachmentEventArgs e)


public IInputElement? Captured { get; private set; }

public PointerType Type { get; }
public bool IsPrimary { get; }

Expand Down
17 changes: 16 additions & 1 deletion src/Avalonia.Base/Input/PointerEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,26 @@ public class PointerCaptureLostEventArgs : RoutedEventArgs
{
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)
{
Pointer = pointer;
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;
}
}
}