13 changed files with 358 additions and 314 deletions
@ -1,17 +1,31 @@ |
|||
using System; |
|||
using Avalonia.VisualTree; |
|||
using Avalonia.Input.Raw; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
public interface IPointerDevice : IInputDevice |
|||
{ |
|||
/// <inheritdoc cref="IPointer.Captured" />
|
|||
[Obsolete("Use IPointer")] |
|||
IInputElement? Captured { get; } |
|||
|
|||
|
|||
/// <inheritdoc cref="IPointer.Capture(IInputElement?)" />
|
|||
[Obsolete("Use IPointer")] |
|||
void Capture(IInputElement? control); |
|||
|
|||
/// <inheritdoc cref="PointerEventArgs.GetPosition(IVisual?)" />
|
|||
[Obsolete("Use PointerEventArgs.GetPosition")] |
|||
Point GetPosition(IVisual relativeTo); |
|||
|
|||
/// <summary>
|
|||
/// Gets a pointer for specific event args.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If pointer doesn't exist or wasn't yet created this method will return null.
|
|||
/// </remarks>
|
|||
/// <param name="ev">Raw pointer event args associated with the pointer.</param>
|
|||
/// <returns>The pointer.</returns>
|
|||
IPointer? TryGetPointer(RawPointerEventArgs ev); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,210 @@ |
|||
using System; |
|||
using Avalonia.Input.Raw; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
internal class PointerOverPreProcessor : IObserver<RawInputEventArgs> |
|||
{ |
|||
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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
}; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue