diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 55202dd20d..75a34659a2 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -15,7 +15,6 @@ using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.Utilities;
using Avalonia.VisualTree;
-using JetBrains.Annotations;
namespace Avalonia.Controls
{
@@ -87,6 +86,8 @@ namespace Avalonia.Controls
private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler;
private readonly IPlatformRenderInterface? _renderInterface;
private readonly IGlobalStyles? _globalStyles;
+ private readonly PointerOverPreProcessor? _pointerOverPreProcessor;
+ private readonly IDisposable? _pointerOverPreProcessorSubscription;
private Size _clientSize;
private Size? _frameSize;
private WindowTransparencyLevel _actualTransparencyLevel;
@@ -195,6 +196,9 @@ namespace Avalonia.Controls
}
impl.LostFocus += PlatformImpl_LostFocus;
+
+ _pointerOverPreProcessor = new PointerOverPreProcessor(this);
+ _pointerOverPreProcessorSubscription = _inputManager?.PreProcess.Subscribe(_pointerOverPreProcessor);
}
///
@@ -283,9 +287,7 @@ namespace Avalonia.Controls
///
IKeyboardNavigationHandler IInputRoot.KeyboardNavigationHandler => _keyboardNavigationHandler!;
- ///
- /// Gets or sets the input element that the pointer is currently over.
- ///
+ ///
IInputElement? IInputRoot.PointerOverElement
{
get { return GetValue(PointerOverElementProperty); }
@@ -378,10 +380,12 @@ namespace Avalonia.Controls
Renderer?.Dispose();
Renderer = null!;
-
- (this as IInputRoot).MouseDevice?.TopLevelClosed(this);
+
+ _pointerOverPreProcessor?.OnCompleted();
+ _pointerOverPreProcessorSubscription?.Dispose();
+
PlatformImpl = null;
-
+
var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null);
((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);
@@ -515,12 +519,17 @@ namespace Avalonia.Controls
/// The event args.
private void HandleInput(RawInputEventArgs e)
{
+ if (e is RawPointerEventArgs pointerArgs)
+ {
+ pointerArgs.InputHitTestResult = this.InputHitTest(pointerArgs.Position);
+ }
+
_inputManager?.ProcessInput(e);
}
private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e)
{
- (this as IInputRoot).MouseDevice?.SceneInvalidated(this, e.DirtyRect);
+ _pointerOverPreProcessor?.SceneInvalidated(e.DirtyRect);
}
void PlatformImpl_LostFocus()
diff --git a/src/Avalonia.Input/IInputRoot.cs b/src/Avalonia.Input/IInputRoot.cs
index 3e2b8cc477..98e8699573 100644
--- a/src/Avalonia.Input/IInputRoot.cs
+++ b/src/Avalonia.Input/IInputRoot.cs
@@ -1,5 +1,3 @@
-using JetBrains.Annotations;
-
namespace Avalonia.Input
{
///
@@ -30,7 +28,6 @@ namespace Avalonia.Input
///
/// Gets associated mouse device
///
- [CanBeNull]
IMouseDevice? MouseDevice { get; }
}
}
diff --git a/src/Avalonia.Input/IKeyboardDevice.cs b/src/Avalonia.Input/IKeyboardDevice.cs
index 9506dc36fb..d0e84e5ad0 100644
--- a/src/Avalonia.Input/IKeyboardDevice.cs
+++ b/src/Avalonia.Input/IKeyboardDevice.cs
@@ -50,12 +50,6 @@ namespace Avalonia.Input
KeyboardMask = Alt | Control | Shift | Meta
}
- internal static class KeyModifiersUtils
- {
- public static KeyModifiers ConvertToKey(RawInputModifiers modifiers) =>
- (KeyModifiers)(modifiers & RawInputModifiers.KeyboardMask);
- }
-
public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged
{
IInputElement? FocusedElement { get; }
diff --git a/src/Avalonia.Input/IMouseDevice.cs b/src/Avalonia.Input/IMouseDevice.cs
index 272d1eb8d7..6b7f0e76e5 100644
--- a/src/Avalonia.Input/IMouseDevice.cs
+++ b/src/Avalonia.Input/IMouseDevice.cs
@@ -13,8 +13,10 @@ namespace Avalonia.Input
[Obsolete("Use PointerEventArgs.GetPosition")]
PixelPoint Position { get; }
+ [Obsolete]
void TopLevelClosed(IInputRoot root);
+ [Obsolete]
void SceneInvalidated(IInputRoot root, Rect rect);
}
}
diff --git a/src/Avalonia.Input/IPointerDevice.cs b/src/Avalonia.Input/IPointerDevice.cs
index 1f82cb1ed7..0096bb77bf 100644
--- a/src/Avalonia.Input/IPointerDevice.cs
+++ b/src/Avalonia.Input/IPointerDevice.cs
@@ -1,17 +1,31 @@
using System;
using Avalonia.VisualTree;
+using Avalonia.Input.Raw;
namespace Avalonia.Input
{
public interface IPointerDevice : IInputDevice
{
+ ///
[Obsolete("Use IPointer")]
IInputElement? Captured { get; }
-
+
+ ///
[Obsolete("Use IPointer")]
void Capture(IInputElement? control);
+ ///
[Obsolete("Use PointerEventArgs.GetPosition")]
Point GetPosition(IVisual relativeTo);
+
+ ///
+ /// Gets a pointer for specific event args.
+ ///
+ ///
+ /// If pointer doesn't exist or wasn't yet created this method will return null.
+ ///
+ /// Raw pointer event args associated with the pointer.
+ /// The pointer.
+ IPointer? TryGetPointer(RawPointerEventArgs ev);
}
}
diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs
index 3df717b8c4..0600b54618 100644
--- a/src/Avalonia.Input/KeyboardDevice.cs
+++ b/src/Avalonia.Input/KeyboardDevice.cs
@@ -188,7 +188,7 @@ namespace Avalonia.Input
RoutedEvent = routedEvent,
Device = this,
Key = keyInput.Key,
- KeyModifiers = KeyModifiersUtils.ConvertToKey(keyInput.Modifiers),
+ KeyModifiers = keyInput.Modifiers.ToKeyModifiers(),
Source = element,
};
diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs
index a5d54bb047..5f8ab24b79 100644
--- a/src/Avalonia.Input/MouseDevice.cs
+++ b/src/Avalonia.Input/MouseDevice.cs
@@ -21,27 +21,17 @@ namespace Avalonia.Input
private readonly Pointer _pointer;
private bool _disposed;
- private PixelPoint? _position;
+ private PixelPoint? _position;
+ private MouseButton _lastMouseDownButton;
public MouseDevice(Pointer? pointer = null)
{
_pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
}
-
- ///
- /// Gets the control that is currently capturing by the mouse, if any.
- ///
- ///
- /// When an element captures the mouse, it receives mouse input whether the cursor is
- /// within the control's bounds or not. To set the mouse capture, call the
- /// method.
- ///
+
[Obsolete("Use IPointer instead")]
public IInputElement? Captured => _pointer.Captured;
- ///
- /// Gets the mouse position, in screen coordinates.
- ///
[Obsolete("Use events instead")]
public PixelPoint Position
{
@@ -49,15 +39,7 @@ namespace Avalonia.Input
protected set => _position = value;
}
- ///
- /// Captures mouse input to the specified control.
- ///
- /// The control.
- ///
- /// When an element captures the mouse, it receives mouse input whether the cursor is
- /// within the control's bounds or not. The current mouse capture control is exposed
- /// by the property.
- ///
+ [Obsolete("Use IPointer instead")]
public void Capture(IInputElement? control)
{
_pointer.Capture(control);
@@ -90,39 +72,6 @@ namespace Avalonia.Input
ProcessRawEvent(margs);
}
- public void TopLevelClosed(IInputRoot root)
- {
- ClearPointerOver(this, 0, root, PointerPointProperties.None, KeyModifiers.None);
- }
-
- public void SceneInvalidated(IInputRoot root, Rect rect)
- {
- // Pointer is outside of the target area
- if (_position == null )
- {
- if (root.PointerOverElement != null)
- ClearPointerOver(this, 0, root, PointerPointProperties.None, KeyModifiers.None);
- return;
- }
-
-
- var clientPoint = root.PointToClient(_position.Value);
-
- if (rect.Contains(clientPoint))
- {
- if (_pointer.Captured == null)
- {
- SetPointerOver(this, 0 /* TODO: proper timestamp */, root, clientPoint,
- PointerPointProperties.None, KeyModifiers.None);
- }
- else
- {
- SetPointerOver(this, 0 /* TODO: proper timestamp */, root, _pointer.Captured,
- PointerPointProperties.None, KeyModifiers.None);
- }
- }
- }
-
int ButtonCount(PointerPointProperties props)
{
var rv = 0;
@@ -138,7 +87,7 @@ namespace Avalonia.Input
rv++;
return rv;
}
-
+
private void ProcessRawEvent(RawPointerEventArgs e)
{
e = e ?? throw new ArgumentNullException(nameof(e));
@@ -147,15 +96,14 @@ namespace Avalonia.Input
if(mouse._disposed)
return;
- if (e.Type == RawPointerEventType.NonClientLeftButtonDown) return;
-
_position = e.Root.PointToScreen(e.Position);
var props = CreateProperties(e);
- var keyModifiers = KeyModifiersUtils.ConvertToKey(e.InputModifiers);
+ var keyModifiers = e.InputModifiers.ToKeyModifiers();
switch (e.Type)
{
case RawPointerEventType.LeaveWindow:
- LeaveWindow(mouse, e.Timestamp, e.Root, props, keyModifiers);
+ case RawPointerEventType.NonClientLeftButtonDown:
+ LeaveWindow();
break;
case RawPointerEventType.LeftButtonDown:
case RawPointerEventType.RightButtonDown:
@@ -163,10 +111,9 @@ namespace Avalonia.Input
case RawPointerEventType.XButton1Down:
case RawPointerEventType.XButton2Down:
if (ButtonCount(props) > 1)
- e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers, e.IntermediatePoints);
+ e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers, e.IntermediatePoints, e.InputHitTestResult);
else
- e.Handled = MouseDown(mouse, e.Timestamp, e.Root, e.Position,
- props, keyModifiers);
+ e.Handled = MouseDown(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers, e.InputHitTestResult);
break;
case RawPointerEventType.LeftButtonUp:
case RawPointerEventType.RightButtonUp:
@@ -174,82 +121,50 @@ namespace Avalonia.Input
case RawPointerEventType.XButton1Up:
case RawPointerEventType.XButton2Up:
if (ButtonCount(props) != 0)
- e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers, e.IntermediatePoints);
+ e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers, e.IntermediatePoints, e.InputHitTestResult);
else
- e.Handled = MouseUp(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers);
+ e.Handled = MouseUp(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers, e.InputHitTestResult);
break;
case RawPointerEventType.Move:
- e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers, e.IntermediatePoints);
+ e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers, e.IntermediatePoints, e.InputHitTestResult);
break;
case RawPointerEventType.Wheel:
- e.Handled = MouseWheel(mouse, e.Timestamp, e.Root, e.Position, props, ((RawMouseWheelEventArgs)e).Delta, keyModifiers);
+ e.Handled = MouseWheel(mouse, e.Timestamp, e.Root, e.Position, props, ((RawMouseWheelEventArgs)e).Delta, keyModifiers, e.InputHitTestResult);
break;
case RawPointerEventType.Magnify:
- e.Handled = GestureMagnify(mouse, e.Timestamp, e.Root, e.Position, props, ((RawPointerGestureEventArgs)e).Delta, keyModifiers);
+ e.Handled = GestureMagnify(mouse, e.Timestamp, e.Root, e.Position, props, ((RawPointerGestureEventArgs)e).Delta, keyModifiers, e.InputHitTestResult);
break;
case RawPointerEventType.Rotate:
- e.Handled = GestureRotate(mouse, e.Timestamp, e.Root, e.Position, props, ((RawPointerGestureEventArgs)e).Delta, keyModifiers);
+ e.Handled = GestureRotate(mouse, e.Timestamp, e.Root, e.Position, props, ((RawPointerGestureEventArgs)e).Delta, keyModifiers, e.InputHitTestResult);
break;
case RawPointerEventType.Swipe:
- e.Handled = GestureSwipe(mouse, e.Timestamp, e.Root, e.Position, props, ((RawPointerGestureEventArgs)e).Delta, keyModifiers);
+ e.Handled = GestureSwipe(mouse, e.Timestamp, e.Root, e.Position, props, ((RawPointerGestureEventArgs)e).Delta, keyModifiers, e.InputHitTestResult);
break;
}
}
- private void LeaveWindow(IMouseDevice device, ulong timestamp, IInputRoot root, PointerPointProperties properties,
- KeyModifiers inputModifiers)
+ private void LeaveWindow()
{
- device = device ?? throw new ArgumentNullException(nameof(device));
- root = root ?? throw new ArgumentNullException(nameof(root));
-
_position = null;
- ClearPointerOver(this, timestamp, root, properties, inputModifiers);
}
-
PointerPointProperties CreateProperties(RawPointerEventArgs args)
{
-
- var kind = PointerUpdateKind.Other;
-
- if (args.Type == RawPointerEventType.LeftButtonDown)
- kind = PointerUpdateKind.LeftButtonPressed;
- if (args.Type == RawPointerEventType.MiddleButtonDown)
- kind = PointerUpdateKind.MiddleButtonPressed;
- if (args.Type == RawPointerEventType.RightButtonDown)
- kind = PointerUpdateKind.RightButtonPressed;
- if (args.Type == RawPointerEventType.XButton1Down)
- kind = PointerUpdateKind.XButton1Pressed;
- if (args.Type == RawPointerEventType.XButton2Down)
- kind = PointerUpdateKind.XButton2Pressed;
- if (args.Type == RawPointerEventType.LeftButtonUp)
- kind = PointerUpdateKind.LeftButtonReleased;
- if (args.Type == RawPointerEventType.MiddleButtonUp)
- kind = PointerUpdateKind.MiddleButtonReleased;
- if (args.Type == RawPointerEventType.RightButtonUp)
- kind = PointerUpdateKind.RightButtonReleased;
- if (args.Type == RawPointerEventType.XButton1Up)
- kind = PointerUpdateKind.XButton1Released;
- if (args.Type == RawPointerEventType.XButton2Up)
- kind = PointerUpdateKind.XButton2Released;
-
- return new PointerPointProperties(args.InputModifiers, kind);
+ return new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind());
}
- private MouseButton _lastMouseDownButton;
private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p,
PointerPointProperties properties,
- KeyModifiers inputModifiers)
+ KeyModifiers inputModifiers, IInputElement? hitTest)
{
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
- var hit = HitTest(root, p);
+ var source = _pointer.Captured ?? root.InputHitTest(p);
- if (hit != null)
+ if (source != null)
{
- _pointer.Capture(hit);
- var source = GetSource(hit);
+ _pointer.Capture(source);
if (source != null)
{
var settings = AvaloniaLocator.Current.GetService();
@@ -275,23 +190,14 @@ namespace Avalonia.Input
return false;
}
- private bool MouseMove(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties properties,
- KeyModifiers inputModifiers, Lazy?>? intermediatePoints)
+ private bool MouseMove(IMouseDevice device, ulong timestamp, IInputRoot root, Point p,
+ PointerPointProperties properties, KeyModifiers inputModifiers, Lazy?>? intermediatePoints,
+ IInputElement? hitTest)
{
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
- IInputElement? source;
-
- if (_pointer.Captured == null)
- {
- source = SetPointerOver(this, timestamp, root, p, properties, inputModifiers);
- }
- else
- {
- SetPointerOver(this, timestamp, root, _pointer.Captured, properties, inputModifiers);
- source = _pointer.Captured;
- }
+ var source = _pointer.Captured ?? hitTest;
if (source is object)
{
@@ -306,13 +212,12 @@ namespace Avalonia.Input
}
private bool MouseUp(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties props,
- KeyModifiers inputModifiers)
+ KeyModifiers inputModifiers, IInputElement? hitTest)
{
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
- var hit = HitTest(root, p);
- var source = GetSource(hit);
+ var source = _pointer.Captured ?? hitTest;
if (source is not null)
{
@@ -329,13 +234,12 @@ namespace Avalonia.Input
private bool MouseWheel(IMouseDevice device, ulong timestamp, IInputRoot root, Point p,
PointerPointProperties props,
- Vector delta, KeyModifiers inputModifiers)
+ Vector delta, KeyModifiers inputModifiers, IInputElement? hitTest)
{
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
- var hit = HitTest(root, p);
- var source = GetSource(hit);
+ var source = _pointer.Captured ?? hitTest;
// KeyModifiers.Shift should scroll in horizontal direction. This does not work on every platform.
// If Shift-Key is pressed and X is close to 0 we swap the Vector.
@@ -356,16 +260,15 @@ namespace Avalonia.Input
}
private bool GestureMagnify(IMouseDevice device, ulong timestamp, IInputRoot root, Point p,
- PointerPointProperties props, Vector delta, KeyModifiers inputModifiers)
+ PointerPointProperties props, Vector delta, KeyModifiers inputModifiers, IInputElement? hitTest)
{
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
- var hit = HitTest(root, p);
+ var source = _pointer.Captured ?? hitTest;
- if (hit != null)
+ if (source != null)
{
- var source = GetSource(hit);
var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureMagnifyEvent, source,
_pointer, root, p, timestamp, props, inputModifiers, delta);
@@ -377,16 +280,15 @@ namespace Avalonia.Input
}
private bool GestureRotate(IMouseDevice device, ulong timestamp, IInputRoot root, Point p,
- PointerPointProperties props, Vector delta, KeyModifiers inputModifiers)
+ PointerPointProperties props, Vector delta, KeyModifiers inputModifiers, IInputElement? hitTest)
{
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
- var hit = HitTest(root, p);
+ var source = _pointer.Captured ?? hitTest;
- if (hit != null)
+ if (source != null)
{
- var source = GetSource(hit);
var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureRotateEvent, source,
_pointer, root, p, timestamp, props, inputModifiers, delta);
@@ -398,16 +300,15 @@ namespace Avalonia.Input
}
private bool GestureSwipe(IMouseDevice device, ulong timestamp, IInputRoot root, Point p,
- PointerPointProperties props, Vector delta, KeyModifiers inputModifiers)
+ PointerPointProperties props, Vector delta, KeyModifiers inputModifiers, IInputElement? hitTest)
{
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
- var hit = HitTest(root, p);
+ var source = _pointer.Captured ?? hitTest;
- if (hit != null)
+ if (source != null)
{
- var source = GetSource(hit);
var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureSwipeEvent, source,
_pointer, root, p, timestamp, props, inputModifiers, delta);
@@ -418,154 +319,27 @@ namespace Avalonia.Input
return false;
}
- private IInteractive? GetSource(IVisual? hit)
- {
- if (hit is null)
- return null;
-
- return _pointer.Captured ??
- (hit as IInteractive) ??
- hit.GetSelfAndVisualAncestors().OfType().FirstOrDefault();
- }
-
- private IInputElement? HitTest(IInputElement root, Point p)
- {
- root = root ?? throw new ArgumentNullException(nameof(root));
-
- return _pointer.Captured ?? root.InputHitTest(p);
- }
-
- PointerEventArgs CreateSimpleEvent(RoutedEvent ev, ulong timestamp, IInteractive? source,
- PointerPointProperties properties,
- KeyModifiers inputModifiers)
- {
- return new PointerEventArgs(ev, source, _pointer, null, default,
- timestamp, properties, inputModifiers);
- }
-
- private void ClearPointerOver(IPointerDevice device, ulong timestamp, IInputRoot root,
- PointerPointProperties properties,
- KeyModifiers inputModifiers)
- {
- device = device ?? throw new ArgumentNullException(nameof(device));
- root = root ?? throw new ArgumentNullException(nameof(root));
-
- var element = root.PointerOverElement;
- var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, timestamp, element, properties, inputModifiers);
-
- if (element!=null && !element.IsAttachedToVisualTree)
- {
- // element has been removed from visual tree so do top down cleanup
- if (root.IsPointerOver)
- ClearChildrenPointerOver(e, root,true);
- }
- while (element != null)
- {
- e.Source = element;
- e.Handled = false;
- element.RaiseEvent(e);
- element = (IInputElement?)element.VisualParent;
- }
-
- root.PointerOverElement = null;
- }
-
- private void ClearChildrenPointerOver(PointerEventArgs e, IInputElement element,bool clearRoot)
+ public void Dispose()
{
- foreach (IInputElement el in element.VisualChildren)
- {
- if (el.IsPointerOver)
- {
- ClearChildrenPointerOver(e, el, true);
- break;
- }
- }
- if(clearRoot)
- {
- e.Source = element;
- e.Handled = false;
- element.RaiseEvent(e);
- }
+ _disposed = true;
+ _pointer?.Dispose();
}
- private IInputElement? SetPointerOver(IPointerDevice device, ulong timestamp, IInputRoot root, Point p,
- PointerPointProperties properties,
- KeyModifiers inputModifiers)
+ [Obsolete]
+ public void TopLevelClosed(IInputRoot root)
{
- device = device ?? throw new ArgumentNullException(nameof(device));
- root = root ?? throw new ArgumentNullException(nameof(root));
-
- var element = root.InputHitTest(p);
-
- if (element != root.PointerOverElement)
- {
- if (element != null)
- {
- SetPointerOver(device, timestamp, root, element, properties, inputModifiers);
- }
- else
- {
- ClearPointerOver(device, timestamp, root, properties, inputModifiers);
- }
- }
-
- return element;
+ // no-op
}
- private void SetPointerOver(IPointerDevice device, ulong timestamp, IInputRoot root, IInputElement element,
- PointerPointProperties properties,
- KeyModifiers inputModifiers)
+ [Obsolete]
+ public void SceneInvalidated(IInputRoot root, Rect rect)
{
- device = device ?? throw new ArgumentNullException(nameof(device));
- root = root ?? throw new ArgumentNullException(nameof(root));
- element = element ?? throw new ArgumentNullException(nameof(element));
-
- IInputElement? branch = null;
-
- IInputElement? el = element;
-
- while (el != null)
- {
- if (el.IsPointerOver)
- {
- branch = el;
- break;
- }
- el = (IInputElement?)el.VisualParent;
- }
-
- el = root.PointerOverElement;
-
- var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, timestamp, el, properties, inputModifiers);
- if (el!=null && branch!=null && !el.IsAttachedToVisualTree)
- {
- ClearChildrenPointerOver(e,branch,false);
- }
-
- while (el != null && el != branch)
- {
- e.Source = el;
- e.Handled = false;
- el.RaiseEvent(e);
- el = (IInputElement?)el.VisualParent;
- }
-
- el = root.PointerOverElement = element;
- e.RoutedEvent = InputElement.PointerEnterEvent;
-
- while (el != null && el != branch)
- {
- e.Source = el;
- e.Handled = false;
- el.RaiseEvent(e);
- el = (IInputElement?)el.VisualParent;
- }
+ // no-op
}
- public void Dispose()
+ public IPointer? TryGetPointer(RawPointerEventArgs ev)
{
- _disposed = true;
- _pointer?.Dispose();
+ return _pointer;
}
}
}
diff --git a/src/Avalonia.Input/PointerEventArgs.cs b/src/Avalonia.Input/PointerEventArgs.cs
index 0604d09dc4..5495802920 100644
--- a/src/Avalonia.Input/PointerEventArgs.cs
+++ b/src/Avalonia.Input/PointerEventArgs.cs
@@ -63,6 +63,8 @@ namespace Avalonia.Input
}
public Point GetPosition(IVisual relativeTo) => _ev.GetPosition(relativeTo);
+
+ public IPointer? TryGetPointer(RawPointerEventArgs ev) => _ev.Pointer;
}
public IPointer Pointer { get; }
diff --git a/src/Avalonia.Input/PointerOverPreProcessor.cs b/src/Avalonia.Input/PointerOverPreProcessor.cs
new file mode 100644
index 0000000000..a38364197d
--- /dev/null
+++ b/src/Avalonia.Input/PointerOverPreProcessor.cs
@@ -0,0 +1,210 @@
+using System;
+using Avalonia.Input.Raw;
+
+namespace Avalonia.Input
+{
+ internal class PointerOverPreProcessor : IObserver
+ {
+ private IPointerDevice? _lastActivePointerDevice;
+ private (IPointer pointer, PixelPoint position)? _lastPointer;
+
+ private readonly IInputRoot _inputRoot;
+
+ public PointerOverPreProcessor(IInputRoot inputRoot)
+ {
+ _inputRoot = inputRoot ?? throw new ArgumentNullException(nameof(inputRoot));
+ }
+
+ public void OnCompleted()
+ {
+ ClearPointerOver();
+ }
+
+ public void OnError(Exception error)
+ {
+ }
+
+ public void OnNext(RawInputEventArgs value)
+ {
+ if (value is RawPointerEventArgs args
+ && args.Root == _inputRoot
+ && value.Device is IPointerDevice pointerDevice)
+ {
+ if (pointerDevice != _lastActivePointerDevice)
+ {
+ ClearPointerOver();
+
+ // Set last active device before processing input, because ClearPointerOver might be called and clear last device.
+ _lastActivePointerDevice = pointerDevice;
+ }
+
+ if (args.Type is RawPointerEventType.LeaveWindow or RawPointerEventType.NonClientLeftButtonDown
+ && _lastPointer is (var lastPointer, var lastPosition))
+ {
+ _lastPointer = null;
+ ClearPointerOver(lastPointer, args.Root, 0, args.Root.PointToClient(lastPosition),
+ new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind()),
+ args.InputModifiers.ToKeyModifiers());
+ }
+ else if (pointerDevice.TryGetPointer(args) is IPointer pointer
+ && pointer.Type != PointerType.Touch)
+ {
+ var element = pointer.Captured ?? args.InputHitTestResult;
+
+ SetPointerOver(pointer, args.Root, element, args.Timestamp, args.Position,
+ new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind()),
+ args.InputModifiers.ToKeyModifiers());
+ }
+ }
+ }
+
+ public void SceneInvalidated(Rect dirtyRect)
+ {
+ // Pointer is outside of the target area
+ if (_lastPointer is (var pointer, var position))
+ {
+ var clientPoint = _inputRoot.PointToClient(position);
+
+ if (dirtyRect.Contains(clientPoint))
+ {
+ SetPointerOver(pointer, _inputRoot, _inputRoot.InputHitTest(clientPoint), 0, clientPoint, PointerPointProperties.None, KeyModifiers.None);
+ }
+ else if (!_inputRoot.Bounds.Contains(clientPoint))
+ {
+ ClearPointerOver(pointer, _inputRoot, 0, new Point(-1, -1), PointerPointProperties.None, KeyModifiers.None);
+ }
+ }
+ }
+
+ private void ClearPointerOver()
+ {
+ if (_lastPointer is (var pointer, var _))
+ {
+ ClearPointerOver(pointer, _inputRoot, 0, new Point(-1, -1), PointerPointProperties.None, KeyModifiers.None);
+ }
+ _lastPointer = null;
+ _lastActivePointerDevice = null;
+ }
+
+ private void ClearPointerOver(IPointer pointer, IInputRoot root,
+ ulong timestamp, Point position, PointerPointProperties properties, KeyModifiers inputModifiers)
+ {
+ var element = root.PointerOverElement;
+ if (element is null)
+ {
+ return;
+ }
+
+ // Do not pass rootVisual, when we have unknown (negative) position,
+ // so GetPosition won't return invalid values.
+ var hasPosition = position.X >= 0 && position.Y >= 0;
+ var e = new PointerEventArgs(InputElement.PointerLeaveEvent, element, pointer,
+ hasPosition ? root : null, hasPosition ? position : default,
+ timestamp, properties, inputModifiers);
+
+ if (element != null && !element.IsAttachedToVisualTree)
+ {
+ // element has been removed from visual tree so do top down cleanup
+ if (root.IsPointerOver)
+ {
+ ClearChildrenPointerOver(e, root, true);
+ }
+ }
+ while (element != null)
+ {
+ e.Source = element;
+ e.Handled = false;
+ element.RaiseEvent(e);
+ element = (IInputElement?)element.VisualParent;
+ }
+
+ root.PointerOverElement = null;
+ _lastActivePointerDevice = null;
+ _lastPointer = null;
+ }
+
+ private void ClearChildrenPointerOver(PointerEventArgs e, IInputElement element, bool clearRoot)
+ {
+ foreach (IInputElement el in element.VisualChildren)
+ {
+ if (el.IsPointerOver)
+ {
+ ClearChildrenPointerOver(e, el, true);
+ break;
+ }
+ }
+ if (clearRoot)
+ {
+ e.Source = element;
+ e.Handled = false;
+ element.RaiseEvent(e);
+ }
+ }
+
+ private void SetPointerOver(IPointer pointer, IInputRoot root, IInputElement? element,
+ ulong timestamp, Point position, PointerPointProperties properties, KeyModifiers inputModifiers)
+ {
+ var pointerOverElement = root.PointerOverElement;
+
+ if (element != pointerOverElement)
+ {
+ if (element != null)
+ {
+ SetPointerOverToElement(pointer, root, element, timestamp, position, properties, inputModifiers);
+ }
+ else
+ {
+ ClearPointerOver(pointer, root, timestamp, position, properties, inputModifiers);
+ }
+ }
+ }
+
+ private void SetPointerOverToElement(IPointer pointer, IInputRoot root, IInputElement element,
+ ulong timestamp, Point position, PointerPointProperties properties, KeyModifiers inputModifiers)
+ {
+ IInputElement? branch = null;
+
+ IInputElement? el = element;
+
+ while (el != null)
+ {
+ if (el.IsPointerOver)
+ {
+ branch = el;
+ break;
+ }
+ el = (IInputElement?)el.VisualParent;
+ }
+
+ el = root.PointerOverElement;
+
+ var e = new PointerEventArgs(InputElement.PointerLeaveEvent, el, pointer, root, position,
+ timestamp, properties, inputModifiers);
+ if (el != null && branch != null && !el.IsAttachedToVisualTree)
+ {
+ ClearChildrenPointerOver(e, branch, false);
+ }
+
+ while (el != null && el != branch)
+ {
+ e.Source = el;
+ e.Handled = false;
+ el.RaiseEvent(e);
+ el = (IInputElement?)el.VisualParent;
+ }
+
+ el = root.PointerOverElement = element;
+ _lastPointer = (pointer, root.PointToScreen(position));
+
+ e.RoutedEvent = InputElement.PointerEnterEvent;
+
+ while (el != null && el != branch)
+ {
+ e.Source = el;
+ e.Handled = false;
+ el.RaiseEvent(e);
+ el = (IInputElement?)el.VisualParent;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Input/Raw/RawDragEvent.cs b/src/Avalonia.Input/Raw/RawDragEvent.cs
index 6e9ce20ff1..652bad7115 100644
--- a/src/Avalonia.Input/Raw/RawDragEvent.cs
+++ b/src/Avalonia.Input/Raw/RawDragEvent.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Input.Raw
Location = location;
Data = data;
Effects = effects;
- KeyModifiers = KeyModifiersUtils.ConvertToKey(modifiers);
+ KeyModifiers = modifiers.ToKeyModifiers();
#pragma warning disable CS0618 // Type or member is obsolete
Modifiers = (InputModifiers)modifiers;
#pragma warning restore CS0618 // Type or member is obsolete
diff --git a/src/Avalonia.Input/Raw/RawInputHelpers.cs b/src/Avalonia.Input/Raw/RawInputHelpers.cs
new file mode 100644
index 0000000000..9d329bae59
--- /dev/null
+++ b/src/Avalonia.Input/Raw/RawInputHelpers.cs
@@ -0,0 +1,27 @@
+using Avalonia.Input.Raw;
+
+namespace Avalonia.Input
+{
+ internal static class RawInputHelpers
+ {
+ public static KeyModifiers ToKeyModifiers(this RawInputModifiers modifiers) =>
+ (KeyModifiers)(modifiers & RawInputModifiers.KeyboardMask);
+
+ public static PointerUpdateKind ToUpdateKind(this RawPointerEventType type) => type switch
+ {
+ RawPointerEventType.LeftButtonDown => PointerUpdateKind.LeftButtonPressed,
+ RawPointerEventType.LeftButtonUp => PointerUpdateKind.LeftButtonReleased,
+ RawPointerEventType.RightButtonDown => PointerUpdateKind.RightButtonPressed,
+ RawPointerEventType.RightButtonUp => PointerUpdateKind.RightButtonReleased,
+ RawPointerEventType.MiddleButtonDown => PointerUpdateKind.MiddleButtonPressed,
+ RawPointerEventType.MiddleButtonUp => PointerUpdateKind.MiddleButtonReleased,
+ RawPointerEventType.XButton1Down => PointerUpdateKind.XButton1Pressed,
+ RawPointerEventType.XButton1Up => PointerUpdateKind.XButton1Released,
+ RawPointerEventType.XButton2Down => PointerUpdateKind.XButton2Pressed,
+ RawPointerEventType.XButton2Up => PointerUpdateKind.XButton2Released,
+ RawPointerEventType.TouchBegin => PointerUpdateKind.LeftButtonPressed,
+ RawPointerEventType.TouchEnd => PointerUpdateKind.LeftButtonReleased,
+ _ => PointerUpdateKind.Other
+ };
+ }
+}
diff --git a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs
index c157fa059c..8b9d7c161d 100644
--- a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs
+++ b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs
@@ -120,6 +120,8 @@ namespace Avalonia.Input.Raw
/// only valid for Move and TouchUpdate
///
public Lazy?>? IntermediatePoints { get; set; }
+
+ internal IInputElement? InputHitTestResult { get; set; }
}
public struct RawPointerPoint
diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs
index 20cafb9e8e..54dcc4051e 100644
--- a/src/Avalonia.Input/TouchDevice.cs
+++ b/src/Avalonia.Input/TouchDevice.cs
@@ -3,24 +3,26 @@ using System.Collections.Generic;
using System.Linq;
using Avalonia.Input.Raw;
using Avalonia.Platform;
+using Avalonia.VisualTree;
namespace Avalonia.Input
{
///
/// Handles raw touch events
+ ///
///
/// This class is supposed to be used on per-toplevel basis, don't use a shared one
///
- ///
- public class TouchDevice : IInputDevice, IDisposable
+ public class TouchDevice : IPointerDevice, IDisposable
{
private readonly Dictionary _pointers = new Dictionary();
private bool _disposed;
private int _clickCount;
private Rect _lastClickRect;
private ulong _lastClickTime;
- KeyModifiers GetKeyModifiers(RawInputModifiers modifiers) =>
- (KeyModifiers)(modifiers & RawInputModifiers.KeyboardMask);
+ private Pointer? _lastPointer;
+
+ IInputElement? IPointerDevice.Captured => _lastPointer?.Captured;
RawInputModifiers GetModifiers(RawInputModifiers modifiers, bool isLeftButtonDown)
{
@@ -30,6 +32,10 @@ namespace Avalonia.Input
return rv;
}
+ void IPointerDevice.Capture(IInputElement? control) => _lastPointer?.Capture(control);
+
+ Point IPointerDevice.GetPosition(IVisual relativeTo) => default;
+
public void ProcessRawEvent(RawInputEventArgs ev)
{
if (ev.Handled || _disposed)
@@ -39,15 +45,18 @@ namespace Avalonia.Input
{
if (args.Type == RawPointerEventType.TouchEnd)
return;
- var hit = args.Root.InputHitTest(args.Position);
+ var hit = args.InputHitTestResult;
_pointers[args.TouchPointId] = pointer = new Pointer(Pointer.GetNextFreeId(),
PointerType.Touch, _pointers.Count == 0);
pointer.Capture(hit);
}
-
+ _lastPointer = pointer;
var target = pointer.Captured ?? args.Root;
+ var updateKind = args.Type.ToUpdateKind();
+ var keyModifier = args.InputModifiers.ToKeyModifiers();
+
if (args.Type == RawPointerEventType.TouchBegin)
{
if (_pointers.Count > 1)
@@ -73,9 +82,8 @@ namespace Avalonia.Input
target.RaiseEvent(new PointerPressedEventArgs(target, pointer,
args.Root, args.Position, ev.Timestamp,
- new PointerPointProperties(GetModifiers(args.InputModifiers, true),
- PointerUpdateKind.LeftButtonPressed),
- GetKeyModifiers(args.InputModifiers), _clickCount));
+ new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind),
+ keyModifier, _clickCount));
}
if (args.Type == RawPointerEventType.TouchEnd)
@@ -85,10 +93,10 @@ namespace Avalonia.Input
{
target.RaiseEvent(new PointerReleasedEventArgs(target, pointer,
args.Root, args.Position, ev.Timestamp,
- new PointerPointProperties(GetModifiers(args.InputModifiers, false),
- PointerUpdateKind.LeftButtonReleased),
- GetKeyModifiers(args.InputModifiers), MouseButton.Left));
+ new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind),
+ keyModifier, MouseButton.Left));
}
+ _lastPointer = null;
}
if (args.Type == RawPointerEventType.TouchCancel)
@@ -96,18 +104,16 @@ namespace Avalonia.Input
_pointers.Remove(args.TouchPointId);
using (pointer)
pointer.Capture(null);
+ _lastPointer = null;
}
if (args.Type == RawPointerEventType.TouchUpdate)
{
- var modifiers = GetModifiers(args.InputModifiers, pointer.IsPrimary);
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, args.Root,
args.Position, ev.Timestamp,
- new PointerPointProperties(GetModifiers(args.InputModifiers, true), PointerUpdateKind.Other),
- GetKeyModifiers(args.InputModifiers), args.IntermediatePoints));
+ new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind),
+ keyModifier, args.IntermediatePoints));
}
-
-
}
public void Dispose()
@@ -121,5 +127,12 @@ namespace Avalonia.Input
p.Dispose();
}
+ public IPointer? TryGetPointer(RawPointerEventArgs ev)
+ {
+ return ev is RawTouchEventArgs args
+ && _pointers.TryGetValue(args.TouchPointId, out var pointer)
+ ? pointer
+ : null;
+ }
}
}