Browse Source

Merge pull request #8577 from AvaloniaUI/cleanup-pointer-members

Remove all obsolete members from Input namespace
pull/8580/head
Max Katz 4 years ago
committed by GitHub
parent
commit
d2a67d15ae
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      src/Avalonia.Base/Input/Cursor.cs
  2. 18
      src/Avalonia.Base/Input/DragEventArgs.cs
  3. 11
      src/Avalonia.Base/Input/GotFocusEventArgs.cs
  4. 5
      src/Avalonia.Base/Input/IInputRoot.cs
  5. 13
      src/Avalonia.Base/Input/IKeyboardDevice.cs
  6. 12
      src/Avalonia.Base/Input/IMouseDevice.cs
  7. 14
      src/Avalonia.Base/Input/IPointerDevice.cs
  8. 2
      src/Avalonia.Base/Input/KeyEventArgs.cs
  9. 12
      src/Avalonia.Base/Input/KeyGesture.cs
  10. 54
      src/Avalonia.Base/Input/MouseDevice.cs
  11. 19
      src/Avalonia.Base/Input/PenDevice.cs
  12. 62
      src/Avalonia.Base/Input/PointerEventArgs.cs
  13. 2
      src/Avalonia.Base/Input/PointerOverPreProcessor.cs
  14. 5
      src/Avalonia.Base/Input/Raw/RawDragEvent.cs
  15. 3
      src/Avalonia.Base/Input/Raw/RawTouchEventArgs.cs
  16. 11
      src/Avalonia.Base/Input/TouchDevice.cs
  17. 9
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  18. 11
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  19. 1
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  20. 5
      src/Avalonia.Controls/TopLevel.cs
  21. 11
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  22. 1
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  23. 21
      src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs
  24. 7
      src/Windows/Avalonia.Win32/WindowImpl.cs
  25. 93
      tests/Avalonia.Base.UnitTests/Input/MouseDeviceTests.cs
  26. 88
      tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
  27. 90
      tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs
  28. 8
      tests/Avalonia.UnitTests/MockWindowingPlatform.cs
  29. 4
      tests/Avalonia.UnitTests/TestRoot.cs

5
src/Avalonia.Base/Input/Cursor.cs

@ -32,10 +32,7 @@ namespace Avalonia.Input
DragCopy,
DragLink,
None,
[Obsolete("Use BottomSide")]
BottomSize = BottomSide
// Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/
// We might enable them later, preferably, by loading pixmax directly from theme with fallback image
// SizeNorthWestSouthEast,

18
src/Avalonia.Base/Input/DragEventArgs.cs

@ -13,9 +13,6 @@ namespace Avalonia.Input
public IDataObject Data { get; private set; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers { get; private set; }
public KeyModifiers KeyModifiers { get; private set; }
public Point GetPosition(IVisual relativeTo)
@ -35,17 +32,6 @@ namespace Avalonia.Input
return point;
}
[Obsolete("Use constructor taking KeyModifiers")]
public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data, Interactive target, Point targetLocation, InputModifiers modifiers)
: base(routedEvent)
{
Data = data;
_target = target;
_targetLocation = targetLocation;
Modifiers = modifiers;
KeyModifiers = (KeyModifiers)(((int)modifiers) & 0xF);
}
public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers)
: base(routedEvent)
{
@ -53,10 +39,6 @@ namespace Avalonia.Input
_target = target;
_targetLocation = targetLocation;
KeyModifiers = keyModifiers;
#pragma warning disable CS0618 // Type or member is obsolete
Modifiers = (InputModifiers)keyModifiers;
#pragma warning restore CS0618 // Type or member is obsolete
}
}
}

11
src/Avalonia.Base/Input/GotFocusEventArgs.cs

@ -1,4 +1,3 @@
using System;
using Avalonia.Interactivity;
namespace Avalonia.Input
@ -13,16 +12,6 @@ namespace Avalonia.Input
/// </summary>
public NavigationMethod NavigationMethod { get; set; }
/// <summary>
/// Gets or sets any input modifiers active at the time of focus.
/// </summary>
[Obsolete("Use KeyModifiers")]
public InputModifiers InputModifiers
{
get => (InputModifiers)KeyModifiers;
set => KeyModifiers = (KeyModifiers)((int)value & 0xF);
}
/// <summary>
/// Gets or sets any key modifiers active at the time of focus.
/// </summary>

5
src/Avalonia.Base/Input/IInputRoot.cs

@ -27,10 +27,5 @@ namespace Avalonia.Input
/// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary>
bool ShowAccessKeys { get; set; }
/// <summary>
/// Gets associated mouse device
/// </summary>
IMouseDevice? MouseDevice { get; }
}
}

13
src/Avalonia.Base/Input/IKeyboardDevice.cs

@ -4,19 +4,6 @@ using Avalonia.Metadata;
namespace Avalonia.Input
{
[Flags, Obsolete("Use KeyModifiers and PointerPointProperties")]
public enum InputModifiers
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
Windows = 8,
LeftMouseButton = 16,
RightMouseButton = 32,
MiddleMouseButton = 64
}
[Flags]
public enum KeyModifiers
{

12
src/Avalonia.Base/Input/IMouseDevice.cs

@ -1,4 +1,3 @@
using System;
using Avalonia.Metadata;
namespace Avalonia.Input
@ -9,16 +8,5 @@ namespace Avalonia.Input
[NotClientImplementable]
public interface IMouseDevice : IPointerDevice
{
/// <summary>
/// Gets the mouse position, in screen coordinates.
/// </summary>
[Obsolete("Use PointerEventArgs.GetPosition")]
PixelPoint Position { get; }
[Obsolete]
void TopLevelClosed(IInputRoot root);
[Obsolete]
void SceneInvalidated(IInputRoot root, Rect rect);
}
}

14
src/Avalonia.Base/Input/IPointerDevice.cs

@ -1,5 +1,3 @@
using System;
using Avalonia.VisualTree;
using Avalonia.Input.Raw;
using Avalonia.Metadata;
@ -8,18 +6,6 @@ namespace Avalonia.Input
[NotClientImplementable]
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>

2
src/Avalonia.Base/Input/KeyEventArgs.cs

@ -9,8 +9,6 @@ namespace Avalonia.Input
public Key Key { get; set; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers => (InputModifiers)KeyModifiers;
public KeyModifiers KeyModifiers { get; set; }
}
}

12
src/Avalonia.Base/Input/KeyGesture.cs

@ -15,13 +15,6 @@ namespace Avalonia.Input
{ "+", Key.OemPlus }, { "-", Key.OemMinus }, { ".", Key.OemPeriod }, { ",", Key.OemComma }
};
[Obsolete("Use constructor taking KeyModifiers")]
public KeyGesture(Key key, InputModifiers modifiers)
{
Key = key;
KeyModifiers = (KeyModifiers)(((int)modifiers) & 0xf);
}
public KeyGesture(Key key, KeyModifiers modifiers = KeyModifiers.None)
{
Key = key;
@ -63,10 +56,7 @@ namespace Avalonia.Input
}
public Key Key { get; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers => (InputModifiers)KeyModifiers;
public KeyModifiers KeyModifiers { get; }
public static KeyGesture Parse(string gesture)

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

@ -21,7 +21,6 @@ namespace Avalonia.Input
private readonly Pointer _pointer;
private bool _disposed;
private PixelPoint? _position;
private MouseButton _lastMouseDownButton;
public MouseDevice(Pointer? pointer = null)
@ -29,43 +28,6 @@ namespace Avalonia.Input
_pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
}
[Obsolete("Use IPointer instead")]
public IInputElement? Captured => _pointer.Captured;
[Obsolete("Use events instead")]
public PixelPoint Position
{
get => _position ?? new PixelPoint(-1, -1);
protected set => _position = value;
}
[Obsolete("Use IPointer instead")]
public void Capture(IInputElement? control)
{
_pointer.Capture(control);
}
/// <summary>
/// Gets the mouse position relative to a control.
/// </summary>
/// <param name="relativeTo">The control.</param>
/// <returns>The mouse position in the control's coordinates.</returns>
public Point GetPosition(IVisual relativeTo)
{
relativeTo = relativeTo ?? throw new ArgumentNullException(nameof(relativeTo));
if (relativeTo.VisualRoot == null)
{
throw new InvalidOperationException("Control is not attached to visual tree.");
}
#pragma warning disable CS0618 // Type or member is obsolete
var rootPoint = relativeTo.VisualRoot.PointToClient(Position);
#pragma warning restore CS0618 // Type or member is obsolete
var transform = relativeTo.VisualRoot.TransformToVisual(relativeTo);
return rootPoint * transform!.Value;
}
public void ProcessRawEvent(RawInputEventArgs e)
{
if (!e.Handled && e is RawPointerEventArgs margs)
@ -96,7 +58,6 @@ namespace Avalonia.Input
if(mouse._disposed)
return;
_position = e.Root.PointToScreen(e.Position);
var props = CreateProperties(e);
var keyModifiers = e.InputModifiers.ToKeyModifiers();
switch (e.Type)
@ -145,7 +106,6 @@ namespace Avalonia.Input
private void LeaveWindow()
{
_position = null;
}
PointerPointProperties CreateProperties(RawPointerEventArgs args)
@ -324,19 +284,7 @@ namespace Avalonia.Input
_disposed = true;
_pointer?.Dispose();
}
[Obsolete]
public void TopLevelClosed(IInputRoot root)
{
// no-op
}
[Obsolete]
public void SceneInvalidated(IInputRoot root, Rect rect)
{
// no-op
}
public IPointer? TryGetPointer(RawPointerEventArgs ev)
{
return _pointer;

19
src/Avalonia.Base/Input/PenDevice.cs

@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Input
{
@ -14,7 +12,6 @@ namespace Avalonia.Input
public class PenDevice : IPenDevice, IDisposable
{
private readonly Dictionary<long, Pointer> _pointers = new();
private readonly Dictionary<long, PixelPoint> _lastPositions = new();
private int _clickCount;
private Rect _lastClickRect;
private ulong _lastClickTime;
@ -41,9 +38,7 @@ namespace Avalonia.Input
_pointers[e.RawPointerId] = pointer = new Pointer(Pointer.GetNextFreeId(),
PointerType.Pen, _pointers.Count == 0);
}
_lastPositions[e.RawPointerId] = e.Root.PointToScreen(e.Position);
var props = new PointerPointProperties(e.InputModifiers, e.Type.ToUpdateKind(),
e.Point.Twist, e.Point.Pressure, e.Point.XTilt, e.Point.YTilt);
var keyModifiers = e.InputModifiers.ToKeyModifiers();
@ -69,7 +64,6 @@ namespace Avalonia.Input
{
pointer.Dispose();
_pointers.Remove(e.RawPointerId);
_lastPositions.Remove(e.RawPointerId);
}
}
@ -153,17 +147,6 @@ namespace Avalonia.Input
p.Dispose();
}
[Obsolete]
IInputElement? IPointerDevice.Captured => _pointers.Values
.FirstOrDefault(p => p.IsPrimary)?.Captured;
[Obsolete]
void IPointerDevice.Capture(IInputElement? control) => _pointers.Values
.FirstOrDefault(p => p.IsPrimary)?.Capture(control);
[Obsolete]
Point IPointerDevice.GetPosition(IVisual relativeTo) => new Point(-1, -1);
public IPointer? TryGetPointer(RawPointerEventArgs ev)
{
return _pointers.TryGetValue(ev.RawPointerId, out var pointer)

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

@ -11,7 +11,7 @@ namespace Avalonia.Input
private readonly IVisual? _rootVisual;
private readonly Point _rootVisualPosition;
private readonly PointerPointProperties _properties;
private Lazy<IReadOnlyList<RawPointerPoint>?>? _previousPoints;
private readonly Lazy<IReadOnlyList<RawPointerPoint>?>? _previousPoints;
public PointerEventArgs(RoutedEvent routedEvent,
IInteractive? source,
@ -43,29 +43,6 @@ namespace Avalonia.Input
{
_previousPoints = previousPoints;
}
class EmulatedDevice : IPointerDevice
{
private readonly PointerEventArgs _ev;
public EmulatedDevice(PointerEventArgs ev)
{
_ev = ev;
}
public void ProcessRawEvent(RawInputEventArgs ev) => throw new NotSupportedException();
public IInputElement? Captured => _ev.Pointer.Captured;
public void Capture(IInputElement? control)
{
_ev.Pointer.Capture(control);
}
public Point GetPosition(IVisual relativeTo) => _ev.GetPosition(relativeTo);
public IPointer? TryGetPointer(RawPointerEventArgs ev) => _ev.Pointer;
}
/// <summary>
/// Gets specific pointer generated by input device.
@ -77,28 +54,6 @@ namespace Avalonia.Input
/// </summary>
public ulong Timestamp { get; }
private IPointerDevice? _device;
[Obsolete("Use Pointer to get pointer-specific information")]
public IPointerDevice Device => _device ?? (_device = new EmulatedDevice(this));
[Obsolete("Use KeyModifiers and PointerPointProperties")]
public InputModifiers InputModifiers
{
get
{
var mods = (InputModifiers)KeyModifiers;
if (_properties.IsLeftButtonPressed)
mods |= InputModifiers.LeftMouseButton;
if (_properties.IsMiddleButtonPressed)
mods |= InputModifiers.MiddleMouseButton;
if (_properties.IsRightButtonPressed)
mods |= InputModifiers.RightMouseButton;
return mods;
}
}
/// <summary>
/// Gets a value that indicates which key modifiers were active at the time that the pointer event was initiated.
/// </summary>
@ -120,9 +75,6 @@ namespace Avalonia.Input
/// <returns>The pointer position in the control's coordinates.</returns>
public Point GetPosition(IVisual? relativeTo) => GetPosition(_rootVisualPosition, relativeTo);
[Obsolete("Use GetCurrentPoint")]
public PointerPoint GetPointerPoint(IVisual? relativeTo) => GetCurrentPoint(relativeTo);
/// <summary>
/// Returns the PointerPoint associated with the current event
/// </summary>
@ -171,8 +123,6 @@ namespace Avalonia.Input
public class PointerPressedEventArgs : PointerEventArgs
{
private readonly int _clickCount;
public PointerPressedEventArgs(
IInteractive source,
IPointer pointer,
@ -184,13 +134,10 @@ namespace Avalonia.Input
: base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition,
timestamp, properties, modifiers)
{
_clickCount = clickCount;
ClickCount = clickCount;
}
public int ClickCount => _clickCount;
[Obsolete("Use PointerPressedEventArgs.GetCurrentPoint(this).Properties")]
public MouseButton MouseButton => Properties.PointerUpdateKind.GetMouseButton();
public int ClickCount { get; }
}
public class PointerReleasedEventArgs : PointerEventArgs
@ -210,9 +157,6 @@ namespace Avalonia.Input
/// Gets the mouse button that triggered the corresponding PointerPressed event
/// </summary>
public MouseButton InitialPressMouseButton { get; }
[Obsolete("Use InitialPressMouseButton")]
public MouseButton MouseButton => InitialPressMouseButton;
}
public class PointerCaptureLostEventArgs : RoutedEventArgs

2
src/Avalonia.Base/Input/PointerOverPreProcessor.cs

@ -15,6 +15,8 @@ namespace Avalonia.Input
_inputRoot = inputRoot ?? throw new ArgumentNullException(nameof(inputRoot));
}
public PixelPoint? LastPosition => _lastPointer?.position;
public void OnCompleted()
{
ClearPointerOver();

5
src/Avalonia.Base/Input/Raw/RawDragEvent.cs

@ -8,8 +8,6 @@ namespace Avalonia.Input.Raw
public IDataObject Data { get; }
public DragDropEffects Effects { get; set; }
public RawDragEventType Type { get; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers { get; }
public KeyModifiers KeyModifiers { get; }
public RawDragEvent(IDragDropDevice inputDevice, RawDragEventType type,
@ -21,9 +19,6 @@ namespace Avalonia.Input.Raw
Data = data;
Effects = effects;
KeyModifiers = modifiers.ToKeyModifiers();
#pragma warning disable CS0618 // Type or member is obsolete
Modifiers = (InputModifiers)modifiers;
#pragma warning restore CS0618 // Type or member is obsolete
}
}
}

3
src/Avalonia.Base/Input/Raw/RawTouchEventArgs.cs

@ -19,8 +19,5 @@ namespace Avalonia.Input.Raw
{
RawPointerId = rawPointerId;
}
[Obsolete("Use RawPointerId")]
public long TouchPointId { get => RawPointerId; set => RawPointerId = value; }
}
}

11
src/Avalonia.Base/Input/TouchDevice.cs

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Input
{
@ -20,9 +19,6 @@ namespace Avalonia.Input
private int _clickCount;
private Rect _lastClickRect;
private ulong _lastClickTime;
private Pointer? _lastPointer;
IInputElement? IPointerDevice.Captured => _lastPointer?.Captured;
RawInputModifiers GetModifiers(RawInputModifiers modifiers, bool isLeftButtonDown)
{
@ -32,10 +28,6 @@ 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)
@ -51,7 +43,6 @@ namespace Avalonia.Input
PointerType.Touch, _pointers.Count == 0);
pointer.Capture(hit);
}
_lastPointer = pointer;
var target = pointer.Captured ?? args.Root;
var updateKind = args.Type.ToUpdateKind();
@ -96,7 +87,6 @@ namespace Avalonia.Input
new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind),
keyModifier, MouseButton.Left));
}
_lastPointer = null;
}
if (args.Type == RawPointerEventType.TouchCancel)
@ -104,7 +94,6 @@ namespace Avalonia.Input
_pointers.Remove(args.RawPointerId);
using (pointer)
pointer.Capture(null);
_lastPointer = null;
}
if (args.Type == RawPointerEventType.TouchUpdate)

9
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -6,7 +6,6 @@ using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Metadata;
using Avalonia.Rendering;
using JetBrains.Annotations;
namespace Avalonia.Platform
{
@ -151,13 +150,7 @@ namespace Avalonia.Platform
/// Gets or sets a method called when the input focus is lost.
/// </summary>
Action? LostFocus { get; set; }
/// <summary>
/// Gets a mouse device associated with toplevel
/// </summary>
[CanBeNull]
IMouseDevice MouseDevice { get; }
IPopupImpl? CreatePopup();
/// <summary>

11
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@ -45,6 +45,7 @@ Copyright © 2019 Nikita Tsukanov
*/
using System;
using Avalonia.Input;
using Avalonia.Metadata;
using Avalonia.VisualTree;
using Avalonia.Media;
@ -452,16 +453,14 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
PopupPositionerConstraintAdjustment constraintAdjustment, Rect? rect,
FlowDirection flowDirection)
{
// We need a better way for tracking the last pointer position
#pragma warning disable CS0618 // Type or member is obsolete
var pointer = topLevel.PointToClient(topLevel.PlatformImpl?.MouseDevice.Position ?? default);
#pragma warning restore CS0618 // Type or member is obsolete
positionerParameters.Offset = offset;
positionerParameters.ConstraintAdjustment = constraintAdjustment;
if (placement == PlacementMode.Pointer)
{
positionerParameters.AnchorRectangle = new Rect(pointer, new Size(1, 1));
// We need a better way for tracking the last pointer position
var position = topLevel.PointToClient(topLevel.LastPointerPosition ?? default);
positionerParameters.AnchorRectangle = new Rect(position, new Size(1, 1));
positionerParameters.Anchor = PopupAnchor.TopLeft;
positionerParameters.Gravity = PopupGravity.BottomRight;
}

1
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@ -12,7 +12,6 @@ using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Input;
using Avalonia.Remote.Protocol.Viewport;
using Avalonia.Threading;
using InputModifiers = Avalonia.Input.InputModifiers;
using Key = Avalonia.Input.Key;
using PixelFormat = Avalonia.Platform.PixelFormat;
using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat;

5
src/Avalonia.Controls/TopLevel.cs

@ -285,6 +285,8 @@ namespace Avalonia.Controls
/// </summary>
public IRenderer Renderer { get; private set; }
internal PixelPoint? LastPointerPosition => _pointerOverPreProcessor?.LastPosition;
/// <summary>
/// Gets the access key handler for the window.
/// </summary>
@ -302,9 +304,6 @@ namespace Avalonia.Controls
set { SetValue(PointerOverElementProperty, value); }
}
/// <inheritdoc/>
IMouseDevice? IInputRoot.MouseDevice => PlatformImpl?.MouseDevice;
/// <summary>
/// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary>

11
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@ -19,8 +19,10 @@ namespace Avalonia.Diagnostics.Views
internal class MainWindow : Window, IStyleHost
{
private readonly IDisposable? _keySubscription;
private readonly IDisposable? _pointerSubscription;
private readonly Dictionary<Popup, IDisposable> _frozenPopupStates;
private AvaloniaObject? _root;
private PixelPoint _lastPointerPosition;
public MainWindow()
{
@ -30,6 +32,10 @@ namespace Avalonia.Diagnostics.Views
.OfType<RawKeyEventArgs>()
.Where(x => x.Type == RawKeyEventType.KeyDown)
.Subscribe(RawKeyDown);
_pointerSubscription = InputManager.Instance?.Process
.OfType<RawPointerEventArgs>()
.Subscribe(x => _lastPointerPosition = x.Root.PointToScreen(x.Position));
_frozenPopupStates = new Dictionary<Popup, IDisposable>();
@ -84,6 +90,7 @@ namespace Avalonia.Diagnostics.Views
{
base.OnClosed(e);
_keySubscription?.Dispose();
_pointerSubscription?.Dispose();
foreach (var state in _frozenPopupStates)
{
@ -108,9 +115,7 @@ namespace Avalonia.Diagnostics.Views
private IControl? GetHoveredControl(TopLevel topLevel)
{
#pragma warning disable CS0618 // Type or member is obsolete
var point = (topLevel as IInputRoot)?.MouseDevice?.GetPosition(topLevel) ?? default;
#pragma warning restore CS0618 // Type or member is obsolete
var point = topLevel.PointToClient(_lastPointerPosition);
return (IControl?)topLevel.GetVisualsAt(point, x =>
{

1
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@ -101,7 +101,6 @@ namespace Avalonia.Win32.Interop.Wpf
Size ITopLevelImpl.ClientSize => _finalSize;
Size? ITopLevelImpl.FrameSize => null;
IMouseDevice ITopLevelImpl.MouseDevice => _mouse;
double ITopLevelImpl.RenderScaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1;

21
src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs

@ -8,15 +8,28 @@ namespace Avalonia.Win32.Input
{
class WindowsMouseDevice : MouseDevice
{
public WindowsMouseDevice() : base(new WindowsMousePointer())
private readonly IPointer _pointer;
public WindowsMouseDevice() : base(WindowsMousePointer.CreatePointer(out var pointer))
{
_pointer = pointer;
}
// Normally user should use IPointer.Capture instead of MouseDevice.Capture,
// But on Windows we need to handle WM_MOUSE capture manually without having access to the Pointer.
internal void Capture(IInputElement control)
{
_pointer.Capture(control);
}
class WindowsMousePointer : Pointer
internal class WindowsMousePointer : Pointer
{
public WindowsMousePointer() : base(Pointer.GetNextFreeId(),PointerType.Mouse, true)
private WindowsMousePointer() : base(Pointer.GetNextFreeId(),PointerType.Mouse, true)
{
}
public static WindowsMousePointer CreatePointer(out WindowsMousePointer pointer)
{
return pointer = new WindowsMousePointer();
}
protected override void PlatformCapture(IInputElement element)

7
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -72,7 +72,7 @@ namespace Avalonia.Win32
private const WindowStyles WindowStateMask = (WindowStyles.WS_MAXIMIZE | WindowStyles.WS_MINIMIZE);
private readonly TouchDevice _touchDevice;
private readonly MouseDevice _mouseDevice;
private readonly WindowsMouseDevice _mouseDevice;
private readonly PenDevice _penDevice;
private readonly ManagedDeferredRendererLock _rendererLock;
private readonly FramebufferManager _framebuffer;
@ -689,10 +689,9 @@ namespace Avalonia.Win32
public void BeginMoveDrag(PointerPressedEventArgs e)
{
_mouseDevice.Capture(null);
e.Pointer.Capture(null);
DefWindowProc(_hwnd, (int)WindowsMessage.WM_NCLBUTTONDOWN,
new IntPtr((int)HitTestValues.HTCAPTION), IntPtr.Zero);
e.Pointer.Capture(null);
}
public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e)
@ -702,7 +701,7 @@ namespace Avalonia.Win32
#if USE_MANAGED_DRAG
_managedDrag.BeginResizeDrag(edge, ScreenToClient(MouseDevice.Position.ToPoint(_scaling)));
#else
_mouseDevice.Capture(null);
e.Pointer.Capture(null);
DefWindowProc(_hwnd, (int)WindowsMessage.WM_NCLBUTTONDOWN,
new IntPtr((int)s_edgeLookup[edge]), IntPtr.Zero);
#endif

93
tests/Avalonia.Base.UnitTests/Input/MouseDeviceTests.cs

@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Moq;
@ -11,71 +14,79 @@ using Xunit;
namespace Avalonia.Base.UnitTests.Input
{
public class MouseDeviceTests
public class MouseDeviceTests : PointerTestsBase
{
#pragma warning disable CS0618 // Type or member is obsolete
[Fact]
public void Capture_Is_Transferred_To_Parent_When_Control_Removed()
{
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
var renderer = new Mock<IRenderer>();
var device = new MouseDevice();
var impl = CreateTopLevelImplMock(renderer.Object);
Canvas control;
var root = new TestRoot
Panel rootChild;
var root = CreateInputRoot(impl.Object, rootChild = new Panel
{
Children =
{
(control = new Canvas())
}
});
// Synthesize event to receive a pointer.
IPointer result = null;
root.PointerMoved += (_, a) =>
{
Child = control = new Canvas(),
result = a.Pointer;
};
var target = new MouseDevice();
SetHit(renderer, control);
impl.Object.Input!(CreateRawPointerMovedArgs(device, root));
target.Capture(control);
Assert.Same(control, target.Captured);
Assert.NotNull(result);
result.Capture(control);
Assert.Same(control, result.Captured);
root.Child = null;
rootChild.Children.Clear();
Assert.Same(root, target.Captured);
Assert.Same(rootChild, result.Captured);
}
#pragma warning restore CS0618 // Type or member is obsolete
[Fact]
public void GetPosition_Should_Respect_Control_RenderTransform()
{
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
var renderer = new Mock<IRenderer>();
var device = new MouseDevice();
var impl = CreateTopLevelImplMock(renderer.Object);
using (TestApplication(renderer.Object))
Border border;
var root = CreateInputRoot(impl.Object, new Panel
{
var inputManager = InputManager.Instance;
var root = new TestRoot
Children =
{
MouseDevice = new MouseDevice(),
Child = new Border
(border = new Border
{
Background = Brushes.Black,
RenderTransform = new TranslateTransform(10, 0),
}
};
SendMouseMove(inputManager, root, new Point(11, 11));
#pragma warning disable CS0618 // Type or member is obsolete
var result = root.MouseDevice.GetPosition(root.Child);
#pragma warning restore CS0618 // Type or member is obsolete
Assert.Equal(new Point(1, 11), result);
}
}
})
}
});
Point? result = null;
root.PointerMoved += (_, a) =>
{
result = a.GetPosition(border);
};
private void SendMouseMove(IInputManager inputManager, TestRoot root, Point p = new Point())
{
inputManager.ProcessInput(new RawPointerEventArgs(
root.MouseDevice,
0,
root,
RawPointerEventType.Move,
p,
RawInputModifiers.None));
}
SetHit(renderer, border);
impl.Object.Input!(CreateRawPointerMovedArgs(device, root, new Point(11, 11)));
private IDisposable TestApplication(IRenderer renderer)
{
return UnitTestApplication.Start(
new TestServices(inputManager: new InputManager()));
Assert.Equal(new Point(1, 11), result);
}
}
}

88
tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs

@ -3,14 +3,10 @@ using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
@ -18,7 +14,7 @@ using Xunit;
namespace Avalonia.Base.UnitTests.Input
{
public class PointerOverTests
public class PointerOverTests : PointerTestsBase
{
// https://github.com/AvaloniaUI/Avalonia/issues/2821
[Fact]
@ -448,87 +444,5 @@ namespace Avalonia.Base.UnitTests.Input
c.PointerMoved += handler;
}
}
private static void SetHit(Mock<IRenderer> renderer, IControl? hit)
{
renderer.Setup(x => x.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns(hit is null ? Array.Empty<IControl>() : new[] { hit });
renderer.Setup(x => x.HitTestFirst(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns(hit);
}
private static void SetMove(Mock<IPointerDevice> deviceMock, IInputRoot root, IInputElement element)
{
deviceMock.Setup(d => d.ProcessRawEvent(It.IsAny<RawPointerEventArgs>()))
.Callback(() => element.RaiseEvent(CreatePointerMovedArgs(root, element)));
}
private static Mock<IWindowImpl> CreateTopLevelImplMock(IRenderer renderer)
{
var impl = new Mock<IWindowImpl>();
impl.DefaultValue = DefaultValue.Mock;
impl.SetupAllProperties();
impl.SetupGet(r => r.RenderScaling).Returns(1);
impl.Setup(r => r.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer);
impl.Setup(r => r.PointToScreen(It.IsAny<Point>())).Returns<Point>(p => new PixelPoint((int)p.X, (int)p.Y));
impl.Setup(r => r.PointToClient(It.IsAny<PixelPoint>())).Returns<PixelPoint>(p => new Point(p.X, p.Y));
return impl;
}
private static IInputRoot CreateInputRoot(IWindowImpl impl, IControl child)
{
var root = new Window(impl)
{
Width = 100,
Height = 100,
Content = child,
Template = new FuncControlTemplate<Window>((w, _) => new ContentPresenter
{
Content = w.Content
})
};
root.Show();
return root;
}
private static IInputRoot CreateInputRoot(IRenderer renderer, IControl child)
{
return CreateInputRoot(CreateTopLevelImplMock(renderer).Object, child);
}
private static RawPointerEventArgs CreateRawPointerMovedArgs(
IPointerDevice pointerDevice,
IInputRoot root,
Point? positition = null)
{
return new RawPointerEventArgs(pointerDevice, 0, root, RawPointerEventType.Move,
positition ?? default, default);
}
private static PointerEventArgs CreatePointerMovedArgs(
IInputRoot root, IInputElement? source, Point? positition = null)
{
return new PointerEventArgs(InputElement.PointerMovedEvent, source, new Mock<IPointer>().Object, root,
positition ?? default, default, PointerPointProperties.None, KeyModifiers.None);
}
private static Mock<IPointerDevice> CreatePointerDeviceMock(
IPointer? pointer = null,
PointerType pointerType = PointerType.Mouse)
{
if (pointer is null)
{
var pointerMock = new Mock<IPointer>();
pointerMock.SetupGet(p => p.Type).Returns(pointerType);
pointer = pointerMock.Object;
}
var pointerDevice = new Mock<IPointerDevice>();
pointerDevice.Setup(d => d.TryGetPointer(It.IsAny<RawPointerEventArgs>()))
.Returns(pointer);
return pointerDevice;
}
}
}

90
tests/Avalonia.Base.UnitTests/Input/PointerTestsBase.cs

@ -0,0 +1,90 @@
#nullable enable
using System;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.VisualTree;
using Moq;
namespace Avalonia.Base.UnitTests.Input;
public abstract class PointerTestsBase
{
protected static void SetHit(Mock<IRenderer> renderer, IControl? hit)
{
renderer.Setup(x => x.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns(hit is null ? Array.Empty<IControl>() : new[] { hit });
renderer.Setup(x => x.HitTestFirst(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns(hit);
}
protected static void SetMove(Mock<IPointerDevice> deviceMock, IInputRoot root, IInputElement element)
{
deviceMock.Setup(d => d.ProcessRawEvent(It.IsAny<RawPointerEventArgs>()))
.Callback(() => element.RaiseEvent(CreatePointerMovedArgs(root, element)));
}
protected static Mock<IWindowImpl> CreateTopLevelImplMock(IRenderer renderer)
{
var impl = new Mock<IWindowImpl>();
impl.DefaultValue = DefaultValue.Mock;
impl.SetupAllProperties();
impl.SetupGet(r => r.RenderScaling).Returns(1);
impl.Setup(r => r.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer);
impl.Setup(r => r.PointToScreen(It.IsAny<Point>())).Returns<Point>(p => new PixelPoint((int)p.X, (int)p.Y));
impl.Setup(r => r.PointToClient(It.IsAny<PixelPoint>())).Returns<PixelPoint>(p => new Point(p.X, p.Y));
return impl;
}
protected static IInputRoot CreateInputRoot(IWindowImpl impl, IControl child)
{
var root = new Window(impl)
{
Width = 100,
Height = 100,
Content = child,
Template = new FuncControlTemplate<Window>((w, _) => new ContentPresenter { Content = w.Content })
};
root.Show();
return root;
}
protected static RawPointerEventArgs CreateRawPointerMovedArgs(
IPointerDevice pointerDevice,
IInputRoot root,
Point? positition = null)
{
return new RawPointerEventArgs(pointerDevice, 0, root, RawPointerEventType.Move,
positition ?? default, default);
}
protected static PointerEventArgs CreatePointerMovedArgs(
IInputRoot root, IInputElement? source, Point? positition = null)
{
return new PointerEventArgs(InputElement.PointerMovedEvent, source, new Mock<IPointer>().Object, root,
positition ?? default, default, PointerPointProperties.None, KeyModifiers.None);
}
protected static Mock<IPointerDevice> CreatePointerDeviceMock(
IPointer? pointer = null,
PointerType pointerType = PointerType.Mouse)
{
if (pointer is null)
{
var pointerMock = new Mock<IPointer>();
pointerMock.SetupGet(p => p.Type).Returns(pointerType);
pointer = pointerMock.Object;
}
var pointerDevice = new Mock<IPointerDevice>();
pointerDevice.Setup(d => d.TryGetPointer(It.IsAny<RawPointerEventArgs>()))
.Returns(pointer);
return pointerDevice;
}
}

8
tests/Avalonia.UnitTests/MockWindowingPlatform.cs

@ -34,7 +34,6 @@ namespace Avalonia.UnitTests
windowImpl.Setup(x => x.RenderScaling).Returns(1);
windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object);
windowImpl.Setup(x => x.Position).Returns(() => position);
SetupToplevel(windowImpl);
windowImpl.Setup(x => x.CreatePopup()).Returns(() =>
{
@ -100,8 +99,6 @@ namespace Avalonia.UnitTests
{
popupImpl.Object.Closed?.Invoke();
});
SetupToplevel(popupImpl);
return popupImpl;
}
@ -144,10 +141,5 @@ namespace Avalonia.UnitTests
{
return null;
}
private static void SetupToplevel<T>(Mock<T> mock) where T : class, ITopLevelImpl
{
mock.SetupGet(x => x.MouseDevice).Returns(new MouseDevice());
}
}
}

4
tests/Avalonia.UnitTests/TestRoot.cs

@ -58,9 +58,7 @@ namespace Avalonia.UnitTests
public IKeyboardNavigationHandler KeyboardNavigationHandler => null;
public IInputElement PointerOverElement { get; set; }
public IMouseDevice MouseDevice { get; set; }
public bool ShowAccessKeys { get; set; }
public IStyleHost StylingParent { get; set; }

Loading…
Cancel
Save