diff --git a/dirs.proj b/dirs.proj
index 4939a158bb..e56320e73f 100644
--- a/dirs.proj
+++ b/dirs.proj
@@ -8,10 +8,11 @@
-
-
+
+
+
diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs
index 93f5611ec4..c35b4a7919 100644
--- a/samples/ControlCatalog.NetCore/Program.cs
+++ b/samples/ControlCatalog.NetCore/Program.cs
@@ -46,6 +46,8 @@ namespace ControlCatalog.NetCore
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure()
.UsePlatformDetect()
+ .With(new X11PlatformOptions {EnableMultiTouch = true})
+ .With(new Win32PlatformOptions {EnableMultitouch = true})
.UseSkia()
.UseReactiveUI();
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 9f1899acc5..1cddb9d295 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -31,6 +31,7 @@
+
diff --git a/samples/ControlCatalog/Pages/PointersPage.cs b/samples/ControlCatalog/Pages/PointersPage.cs
new file mode 100644
index 0000000000..a1359519e6
--- /dev/null
+++ b/samples/ControlCatalog/Pages/PointersPage.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Media;
+using Avalonia.Media.Immutable;
+
+namespace ControlCatalog.Pages
+{
+ public class PointersPage : Control
+ {
+ class PointerInfo
+ {
+ public Point Point { get; set; }
+ public Color Color { get; set; }
+ }
+
+ private static Color[] AllColors = new[]
+ {
+ Colors.Aqua,
+ Colors.Beige,
+ Colors.Chartreuse,
+ Colors.Coral,
+ Colors.Fuchsia,
+ Colors.Crimson,
+ Colors.Lavender,
+ Colors.Orange,
+ Colors.Orchid,
+ Colors.ForestGreen,
+ Colors.SteelBlue,
+ Colors.PapayaWhip,
+ Colors.PaleVioletRed,
+ Colors.Goldenrod,
+ Colors.Maroon,
+ Colors.Moccasin,
+ Colors.Navy,
+ Colors.Wheat,
+ Colors.Violet,
+ Colors.Sienna,
+ Colors.Indigo,
+ Colors.Honeydew
+ };
+
+ private Dictionary _pointers = new Dictionary();
+
+ public PointersPage()
+ {
+ ClipToBounds = true;
+ }
+
+ void UpdatePointer(PointerEventArgs e)
+ {
+ if (!_pointers.TryGetValue(e.Pointer, out var info))
+ {
+ if (e.RoutedEvent == PointerMovedEvent)
+ return;
+ var colors = AllColors.Except(_pointers.Values.Select(c => c.Color)).ToArray();
+ var color = colors[new Random().Next(0, colors.Length - 1)];
+ _pointers[e.Pointer] = info = new PointerInfo {Color = color};
+ }
+
+ info.Point = e.GetPosition(this);
+ InvalidateVisual();
+ }
+
+ protected override void OnPointerPressed(PointerPressedEventArgs e)
+ {
+ UpdatePointer(e);
+ e.Pointer.Capture(this);
+ base.OnPointerPressed(e);
+ }
+
+ protected override void OnPointerMoved(PointerEventArgs e)
+ {
+ UpdatePointer(e);
+ base.OnPointerMoved(e);
+ }
+
+ protected override void OnPointerReleased(PointerReleasedEventArgs e)
+ {
+ _pointers.Remove(e.Pointer);
+ InvalidateVisual();
+ }
+
+ public override void Render(DrawingContext context)
+ {
+ context.FillRectangle(Brushes.Transparent, new Rect(default, Bounds.Size));
+ foreach (var pt in _pointers.Values)
+ {
+ var brush = new ImmutableSolidColorBrush(pt.Color);
+ context.DrawGeometry(brush, null, new EllipseGeometry(new Rect(pt.Point.X - 75, pt.Point.Y - 75,
+ 150, 150)));
+ }
+
+ }
+ }
+}
diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
index 112925ab0f..463d499aad 100644
--- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
+++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
@@ -33,7 +33,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
return null;
}
- RawMouseEventType? mouseEventType = null;
+ RawPointerEventType? mouseEventType = null;
var eventTime = DateTime.Now;
//Basic touch support
switch (e.Action)
@@ -42,17 +42,17 @@ namespace Avalonia.Android.Platform.Specific.Helpers
//may be bot flood the evnt system with too many event especially on not so powerfull mobile devices
if ((eventTime - _lastTouchMoveEventTime).TotalMilliseconds > 10)
{
- mouseEventType = RawMouseEventType.Move;
+ mouseEventType = RawPointerEventType.Move;
}
break;
case MotionEventActions.Down:
- mouseEventType = RawMouseEventType.LeftButtonDown;
+ mouseEventType = RawPointerEventType.LeftButtonDown;
break;
case MotionEventActions.Up:
- mouseEventType = RawMouseEventType.LeftButtonUp;
+ mouseEventType = RawPointerEventType.LeftButtonUp;
break;
}
@@ -75,14 +75,14 @@ namespace Avalonia.Android.Platform.Specific.Helpers
//we need to generate mouse move before first mouse down event
//as this is the way buttons are working every time
//otherwise there is a problem sometimes
- if (mouseEventType == RawMouseEventType.LeftButtonDown)
+ if (mouseEventType == RawPointerEventType.LeftButtonDown)
{
- var me = new RawMouseEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
- RawMouseEventType.Move, _point, InputModifiers.None);
+ var me = new RawPointerEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
+ RawPointerEventType.Move, _point, InputModifiers.None);
_view.Input(me);
}
- var mouseEvent = new RawMouseEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
+ var mouseEvent = new RawPointerEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
mouseEventType.Value, _point, InputModifiers.LeftMouseButton);
_view.Input(mouseEvent);
diff --git a/src/Avalonia.Controls/Calendar/CalendarButton.cs b/src/Avalonia.Controls/Calendar/CalendarButton.cs
index 224d9b782b..53852defb3 100644
--- a/src/Avalonia.Controls/Calendar/CalendarButton.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarButton.cs
@@ -176,18 +176,5 @@ namespace Avalonia.Controls.Primitives
if (e.MouseButton == MouseButton.Left)
CalendarLeftMouseButtonUp?.Invoke(this, e);
}
-
- ///
- /// We need to simulate the MouseLeftButtonUp event for the
- /// CalendarButton that stays in Pressed state after MouseCapture is
- /// released since there is no actual MouseLeftButtonUp event for the
- /// release.
- ///
- /// Event arguments.
- internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e)
- {
- e.Handled = false;
- base.OnPointerReleased(e);
- }
}
}
diff --git a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs
index 1b36f92fd3..cb2a98e5ca 100644
--- a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs
@@ -234,18 +234,5 @@ namespace Avalonia.Controls.Primitives
if (e.MouseButton == MouseButton.Left)
CalendarDayButtonMouseUp?.Invoke(this, e);
}
-
- ///
- /// We need to simulate the MouseLeftButtonUp event for the
- /// CalendarDayButton that stays in Pressed state after MouseCapture is
- /// released since there is no actual MouseLeftButtonUp event for the
- /// release.
- ///
- /// Event arguments.
- internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e)
- {
- e.Handled = false;
- base.OnPointerReleased(e);
- }
}
}
diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs
index fb6dacaf81..8232697c18 100644
--- a/src/Avalonia.Controls/Calendar/CalendarItem.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs
@@ -934,22 +934,6 @@ namespace Avalonia.Controls.Primitives
// The button is in Pressed state. Change the state to normal.
if (e.Device.Captured == b)
e.Device.Capture(null);
- // null check is added for unit tests
- if (_downEventArg != null)
- {
- var arg =
- new PointerReleasedEventArgs()
- {
- Device = _downEventArg.Device,
- MouseButton = _downEventArg.MouseButton,
- Handled = _downEventArg.Handled,
- InputModifiers = _downEventArg.InputModifiers,
- Route = _downEventArg.Route,
- Source = _downEventArg.Source
- };
-
- b.SendMouseLeftButtonUp(arg);
- }
_lastCalendarDayButton = b;
}
}
@@ -1221,21 +1205,7 @@ namespace Avalonia.Controls.Primitives
if (e.Device.Captured == b)
e.Device.Capture(null);
//b.ReleaseMouseCapture();
- if (_downEventArgYearView != null)
- {
- var args =
- new PointerReleasedEventArgs()
- {
- Device = _downEventArgYearView.Device,
- MouseButton = _downEventArgYearView.MouseButton,
- Handled = _downEventArgYearView.Handled,
- InputModifiers = _downEventArgYearView.InputModifiers,
- Route = _downEventArgYearView.Route,
- Source = _downEventArgYearView.Source
- };
-
- b.SendMouseLeftButtonUp(args);
- }
+
_lastCalendarButton = b;
}
}
diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs
index d8473dc613..d0ab0a0c8b 100644
--- a/src/Avalonia.Controls/MenuItem.cs
+++ b/src/Avalonia.Controls/MenuItem.cs
@@ -337,12 +337,9 @@ namespace Avalonia.Controls
{
base.OnPointerEnter(e);
- RaiseEvent(new PointerEventArgs
- {
- Device = e.Device,
- RoutedEvent = PointerEnterItemEvent,
- Source = this,
- });
+ var point = e.GetPointerPoint(null);
+ RaiseEvent(new PointerEventArgs(PointerEnterItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
+ point.Properties, e.InputModifiers));
}
///
@@ -350,12 +347,9 @@ namespace Avalonia.Controls
{
base.OnPointerLeave(e);
- RaiseEvent(new PointerEventArgs
- {
- Device = e.Device,
- RoutedEvent = PointerLeaveItemEvent,
- Source = this,
- });
+ var point = e.GetPointerPoint(null);
+ RaiseEvent(new PointerEventArgs(PointerLeaveItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
+ point.Properties, e.InputModifiers));
}
///
diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
index 73854b9f60..5f63a44717 100644
--- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
+++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
@@ -373,9 +373,9 @@ namespace Avalonia.Controls.Platform
protected internal virtual void RawInput(RawInputEventArgs e)
{
- var mouse = e as RawMouseEventArgs;
+ var mouse = e as RawPointerEventArgs;
- if (mouse?.Type == RawMouseEventType.NonClientLeftButtonDown)
+ if (mouse?.Type == RawPointerEventType.NonClientLeftButtonDown)
{
Menu.Close();
}
diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs
index 0918da1a90..76f17332bf 100644
--- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs
+++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs
@@ -43,7 +43,7 @@ namespace Avalonia.Platform
_lastPosition = default(Point);
_allowedEffects = allowedEffects;
- using (_inputManager.PreProcess.OfType().Subscribe(ProcessMouseEvents))
+ using (_inputManager.PreProcess.OfType().Subscribe(ProcessMouseEvents))
{
using (_inputManager.PreProcess.OfType().Subscribe(ProcessKeyEvents))
{
@@ -153,7 +153,7 @@ namespace Avalonia.Platform
}
}
- private void ProcessMouseEvents(RawMouseEventArgs e)
+ private void ProcessMouseEvents(RawPointerEventArgs e)
{
if (!_initialInputModifiers.HasValue)
_initialInputModifiers = e.InputModifiers & MOUSE_INPUTMODIFIERS;
@@ -174,22 +174,22 @@ namespace Avalonia.Platform
switch (e.Type)
{
- case RawMouseEventType.LeftButtonDown:
- case RawMouseEventType.RightButtonDown:
- case RawMouseEventType.MiddleButtonDown:
- case RawMouseEventType.NonClientLeftButtonDown:
+ case RawPointerEventType.LeftButtonDown:
+ case RawPointerEventType.RightButtonDown:
+ case RawPointerEventType.MiddleButtonDown:
+ case RawPointerEventType.NonClientLeftButtonDown:
CancelDragging();
e.Handled = true;
return;
- case RawMouseEventType.LeaveWindow:
+ case RawPointerEventType.LeaveWindow:
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, e.Root, e.Position, e.InputModifiers); break;
- case RawMouseEventType.LeftButtonUp:
+ case RawPointerEventType.LeftButtonUp:
CheckDraggingAccepted(InputModifiers.LeftMouseButton); break;
- case RawMouseEventType.MiddleButtonUp:
+ case RawPointerEventType.MiddleButtonUp:
CheckDraggingAccepted(InputModifiers.MiddleMouseButton); break;
- case RawMouseEventType.RightButtonUp:
+ case RawPointerEventType.RightButtonUp:
CheckDraggingAccepted(InputModifiers.RightMouseButton); break;
- case RawMouseEventType.Move:
+ case RawPointerEventType.Move:
var mods = e.InputModifiers & MOUSE_INPUTMODIFIERS;
if (_initialInputModifiers.Value != mods)
{
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index f349bcf059..e02d46c1df 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -421,9 +421,9 @@ namespace Avalonia.Controls.Primitives
private void ListenForNonClientClick(RawInputEventArgs e)
{
- var mouse = e as RawMouseEventArgs;
+ var mouse = e as RawPointerEventArgs;
- if (!StaysOpen && mouse?.Type == RawMouseEventType.NonClientLeftButtonDown)
+ if (!StaysOpen && mouse?.Type == RawPointerEventType.NonClientLeftButtonDown)
{
Close();
}
diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
index 6293cbfbfd..1ef03b49ce 100644
--- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
@@ -39,21 +39,21 @@ namespace Avalonia.Controls.Remote.Server
KeyboardDevice = AvaloniaLocator.Current.GetService();
}
- private static RawMouseEventType GetAvaloniaEventType (Avalonia.Remote.Protocol.Input.MouseButton button, bool pressed)
+ private static RawPointerEventType GetAvaloniaEventType (Avalonia.Remote.Protocol.Input.MouseButton button, bool pressed)
{
switch (button)
{
case Avalonia.Remote.Protocol.Input.MouseButton.Left:
- return pressed ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp;
+ return pressed ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp;
case Avalonia.Remote.Protocol.Input.MouseButton.Middle:
- return pressed ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp;
+ return pressed ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp;
case Avalonia.Remote.Protocol.Input.MouseButton.Right:
- return pressed ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp;
+ return pressed ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp;
default:
- return RawMouseEventType.Move;
+ return RawPointerEventType.Move;
}
}
@@ -166,11 +166,11 @@ namespace Avalonia.Controls.Remote.Server
{
Dispatcher.UIThread.Post(() =>
{
- Input?.Invoke(new RawMouseEventArgs(
+ Input?.Invoke(new RawPointerEventArgs(
MouseDevice,
0,
InputRoot,
- RawMouseEventType.Move,
+ RawPointerEventType.Move,
new Point(pointer.X, pointer.Y),
GetAvaloniaInputModifiers(pointer.Modifiers)));
}, DispatcherPriority.Input);
@@ -179,7 +179,7 @@ namespace Avalonia.Controls.Remote.Server
{
Dispatcher.UIThread.Post(() =>
{
- Input?.Invoke(new RawMouseEventArgs(
+ Input?.Invoke(new RawPointerEventArgs(
MouseDevice,
0,
InputRoot,
@@ -192,7 +192,7 @@ namespace Avalonia.Controls.Remote.Server
{
Dispatcher.UIThread.Post(() =>
{
- Input?.Invoke(new RawMouseEventArgs(
+ Input?.Invoke(new RawPointerEventArgs(
MouseDevice,
0,
InputRoot,
diff --git a/src/Avalonia.Input/IMouseDevice.cs b/src/Avalonia.Input/IMouseDevice.cs
index a1d1bb3eb8..7e6bf657ae 100644
--- a/src/Avalonia.Input/IMouseDevice.cs
+++ b/src/Avalonia.Input/IMouseDevice.cs
@@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
+
namespace Avalonia.Input
{
///
@@ -11,6 +13,9 @@ namespace Avalonia.Input
///
/// Gets the mouse position, in screen coordinates.
///
+ [Obsolete("Use PointerEventArgs.GetPosition")]
PixelPoint Position { get; }
+
+ void SceneInvalidated(IInputRoot root, Rect rect);
}
}
diff --git a/src/Avalonia.Input/IPointer.cs b/src/Avalonia.Input/IPointer.cs
new file mode 100644
index 0000000000..a3f051ce7f
--- /dev/null
+++ b/src/Avalonia.Input/IPointer.cs
@@ -0,0 +1,18 @@
+namespace Avalonia.Input
+{
+ public interface IPointer
+ {
+ int Id { get; }
+ void Capture(IInputElement control);
+ IInputElement Captured { get; }
+ PointerType Type { get; }
+ bool IsPrimary { get; }
+
+ }
+
+ public enum PointerType
+ {
+ Mouse,
+ Touch
+ }
+}
diff --git a/src/Avalonia.Input/IPointerDevice.cs b/src/Avalonia.Input/IPointerDevice.cs
index 932cbc989f..de775d90f2 100644
--- a/src/Avalonia.Input/IPointerDevice.cs
+++ b/src/Avalonia.Input/IPointerDevice.cs
@@ -1,18 +1,20 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
using Avalonia.VisualTree;
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);
-
- void SceneInvalidated(IInputRoot root, Rect rect);
}
}
diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs
index c195209305..90d9c37bd4 100644
--- a/src/Avalonia.Input/MouseDevice.cs
+++ b/src/Avalonia.Input/MouseDevice.cs
@@ -14,14 +14,18 @@ namespace Avalonia.Input
///
/// Represents a mouse device.
///
- public class MouseDevice : IMouseDevice
+ public class MouseDevice : IMouseDevice, IPointer
{
private int _clickCount;
private Rect _lastClickRect;
private ulong _lastClickTime;
private IInputElement _captured;
private IDisposable _capturedSubscription;
-
+
+ PointerType IPointer.Type => PointerType.Mouse;
+ bool IPointer.IsPrimary => true;
+ int IPointer.Id { get; } = Pointer.GetNextFreeId();
+
///
/// Gets the control that is currently capturing by the mouse, if any.
///
@@ -96,7 +100,7 @@ namespace Avalonia.Input
public void ProcessRawEvent(RawInputEventArgs e)
{
- if (!e.Handled && e is RawMouseEventArgs margs)
+ if (!e.Handled && e is RawPointerEventArgs margs)
ProcessRawEvent(margs);
}
@@ -117,42 +121,53 @@ namespace Avalonia.Input
}
}
- private void ProcessRawEvent(RawMouseEventArgs e)
+ int ButtonCount(PointerPointProperties props)
+ {
+ var rv = 0;
+ if (props.IsLeftButtonPressed)
+ rv++;
+ if (props.IsMiddleButtonPressed)
+ rv++;
+ if (props.IsRightButtonPressed)
+ rv++;
+ return rv;
+ }
+
+ private void ProcessRawEvent(RawPointerEventArgs e)
{
Contract.Requires(e != null);
var mouse = (IMouseDevice)e.Device;
Position = e.Root.PointToScreen(e.Position);
-
+ var props = CreateProperties(e);
switch (e.Type)
{
- case RawMouseEventType.LeaveWindow:
+ case RawPointerEventType.LeaveWindow:
LeaveWindow(mouse, e.Root, e.InputModifiers);
break;
- case RawMouseEventType.LeftButtonDown:
- case RawMouseEventType.RightButtonDown:
- case RawMouseEventType.MiddleButtonDown:
- e.Handled = MouseDown(mouse, e.Timestamp, e.Root, e.Position,
- e.Type == RawMouseEventType.LeftButtonDown
- ? MouseButton.Left
- : e.Type == RawMouseEventType.RightButtonDown ? MouseButton.Right : MouseButton.Middle,
- e.InputModifiers);
+ case RawPointerEventType.LeftButtonDown:
+ case RawPointerEventType.RightButtonDown:
+ case RawPointerEventType.MiddleButtonDown:
+ if (ButtonCount(props) > 1)
+ e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers);
+ else
+ e.Handled = MouseDown(mouse, e.Timestamp, e.Root, e.Position,
+ props, e.InputModifiers);
break;
- case RawMouseEventType.LeftButtonUp:
- case RawMouseEventType.RightButtonUp:
- case RawMouseEventType.MiddleButtonUp:
- e.Handled = MouseUp(mouse, e.Root, e.Position,
- e.Type == RawMouseEventType.LeftButtonUp
- ? MouseButton.Left
- : e.Type == RawMouseEventType.RightButtonUp ? MouseButton.Right : MouseButton.Middle,
- e.InputModifiers);
+ case RawPointerEventType.LeftButtonUp:
+ case RawPointerEventType.RightButtonUp:
+ case RawPointerEventType.MiddleButtonUp:
+ if (ButtonCount(props) != 0)
+ e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers);
+ else
+ e.Handled = MouseUp(mouse, e.Root, e.Position, props, e.InputModifiers);
break;
- case RawMouseEventType.Move:
- e.Handled = MouseMove(mouse, e.Root, e.Position, e.InputModifiers);
+ case RawPointerEventType.Move:
+ e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers);
break;
- case RawMouseEventType.Wheel:
- e.Handled = MouseWheel(mouse, e.Root, e.Position, ((RawMouseWheelEventArgs)e).Delta, e.InputModifiers);
+ case RawPointerEventType.Wheel:
+ e.Handled = MouseWheel(mouse, e.Root, e.Position, props, ((RawMouseWheelEventArgs)e).Delta, e.InputModifiers);
break;
}
}
@@ -165,7 +180,30 @@ namespace Avalonia.Input
ClearPointerOver(this, root, inputModifiers);
}
- private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers)
+
+ PointerPointProperties CreateProperties(RawPointerEventArgs args)
+ {
+ var rv = new PointerPointProperties(args.InputModifiers);
+
+ if (args.Type == RawPointerEventType.LeftButtonDown)
+ rv.IsLeftButtonPressed = true;
+ if (args.Type == RawPointerEventType.MiddleButtonDown)
+ rv.IsMiddleButtonPressed = true;
+ if (args.Type == RawPointerEventType.RightButtonDown)
+ rv.IsRightButtonPressed = true;
+ if (args.Type == RawPointerEventType.LeftButtonUp)
+ rv.IsLeftButtonPressed = false;
+ if (args.Type == RawPointerEventType.MiddleButtonUp)
+ rv.IsMiddleButtonPressed = false;
+ if (args.Type == RawPointerEventType.RightButtonDown)
+ rv.IsRightButtonPressed = false;
+ return rv;
+ }
+
+ private MouseButton _lastMouseDownButton;
+ private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p,
+ PointerPointProperties properties,
+ InputModifiers inputModifiers)
{
Contract.Requires(device != null);
Contract.Requires(root != null);
@@ -190,16 +228,8 @@ namespace Avalonia.Input
_lastClickTime = timestamp;
_lastClickRect = new Rect(p, new Size())
.Inflate(new Thickness(settings.DoubleClickSize.Width / 2, settings.DoubleClickSize.Height / 2));
-
- var e = new PointerPressedEventArgs
- {
- Device = this,
- RoutedEvent = InputElement.PointerPressedEvent,
- Source = source,
- ClickCount = _clickCount,
- MouseButton = button,
- InputModifiers = inputModifiers
- };
+ _lastMouseDownButton = properties.GetObsoleteMouseButton();
+ var e = new PointerPressedEventArgs(source, this, root, p, properties, inputModifiers, _clickCount);
source.RaiseEvent(e);
return e.Handled;
@@ -209,7 +239,8 @@ namespace Avalonia.Input
return false;
}
- private bool MouseMove(IMouseDevice device, IInputRoot root, Point p, InputModifiers inputModifiers)
+ private bool MouseMove(IMouseDevice device, IInputRoot root, Point p, PointerPointProperties properties,
+ InputModifiers inputModifiers)
{
Contract.Requires(device != null);
Contract.Requires(root != null);
@@ -226,19 +257,15 @@ namespace Avalonia.Input
source = Captured;
}
- var e = new PointerEventArgs
- {
- Device = this,
- RoutedEvent = InputElement.PointerMovedEvent,
- Source = source,
- InputModifiers = inputModifiers
- };
+ var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, this, root,
+ p, properties, inputModifiers);
source?.RaiseEvent(e);
return e.Handled;
}
- private bool MouseUp(IMouseDevice device, IInputRoot root, Point p, MouseButton button, InputModifiers inputModifiers)
+ private bool MouseUp(IMouseDevice device, IInputRoot root, Point p, PointerPointProperties props,
+ InputModifiers inputModifiers)
{
Contract.Requires(device != null);
Contract.Requires(root != null);
@@ -248,14 +275,7 @@ namespace Avalonia.Input
if (hit != null)
{
var source = GetSource(hit);
- var e = new PointerReleasedEventArgs
- {
- Device = this,
- RoutedEvent = InputElement.PointerReleasedEvent,
- Source = source,
- MouseButton = button,
- InputModifiers = inputModifiers
- };
+ var e = new PointerReleasedEventArgs(source, this, root, p, props, inputModifiers, _lastMouseDownButton);
source?.RaiseEvent(e);
return e.Handled;
@@ -264,7 +284,9 @@ namespace Avalonia.Input
return false;
}
- private bool MouseWheel(IMouseDevice device, IInputRoot root, Point p, Vector delta, InputModifiers inputModifiers)
+ private bool MouseWheel(IMouseDevice device, IInputRoot root, Point p,
+ PointerPointProperties props,
+ Vector delta, InputModifiers inputModifiers)
{
Contract.Requires(device != null);
Contract.Requires(root != null);
@@ -274,14 +296,7 @@ namespace Avalonia.Input
if (hit != null)
{
var source = GetSource(hit);
- var e = new PointerWheelEventArgs
- {
- Device = this,
- RoutedEvent = InputElement.PointerWheelChangedEvent,
- Source = source,
- Delta = delta,
- InputModifiers = inputModifiers
- };
+ var e = new PointerWheelEventArgs(source, this, root, p, props, inputModifiers, delta);
source?.RaiseEvent(e);
return e.Handled;
@@ -306,18 +321,19 @@ namespace Avalonia.Input
return Captured ?? root.InputHitTest(p);
}
+ PointerEventArgs CreateSimpleEvent(RoutedEvent ev, IInteractive source, InputModifiers inputModifiers)
+ {
+ return new PointerEventArgs(ev, source, this, null, default,
+ new PointerPointProperties(inputModifiers), inputModifiers);
+ }
+
private void ClearPointerOver(IPointerDevice device, IInputRoot root, InputModifiers inputModifiers)
{
Contract.Requires(device != null);
Contract.Requires(root != null);
var element = root.PointerOverElement;
- var e = new PointerEventArgs
- {
- RoutedEvent = InputElement.PointerLeaveEvent,
- Device = device,
- InputModifiers = inputModifiers
- };
+ var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, element, inputModifiers);
if (element!=null && !element.IsAttachedToVisualTree)
{
@@ -384,7 +400,6 @@ namespace Avalonia.Input
IInputElement branch = null;
- var e = new PointerEventArgs { Device = device, InputModifiers = inputModifiers };
var el = element;
while (el != null)
@@ -398,8 +413,8 @@ namespace Avalonia.Input
}
el = root.PointerOverElement;
- e.RoutedEvent = InputElement.PointerLeaveEvent;
+ var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, el, inputModifiers);
if (el!=null && branch!=null && !el.IsAttachedToVisualTree)
{
ClearChildrenPointerOver(e,branch,false);
diff --git a/src/Avalonia.Input/Pointer.cs b/src/Avalonia.Input/Pointer.cs
new file mode 100644
index 0000000000..bdf2501b32
--- /dev/null
+++ b/src/Avalonia.Input/Pointer.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Linq;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Input
+{
+ public class Pointer : IPointer, IDisposable
+ {
+ private static int s_NextFreePointerId = 1000;
+ public static int GetNextFreeId() => s_NextFreePointerId++;
+
+ public Pointer(int id, PointerType type, bool isPrimary, IInputElement implicitlyCaptured)
+ {
+ Id = id;
+ Type = type;
+ IsPrimary = isPrimary;
+ ImplicitlyCaptured = implicitlyCaptured;
+ if (ImplicitlyCaptured != null)
+ ImplicitlyCaptured.DetachedFromVisualTree += OnImplicitCaptureDetached;
+ }
+
+ public int Id { get; }
+
+ public void Capture(IInputElement control)
+ {
+ if (Captured != null)
+ Captured.DetachedFromVisualTree -= OnCaptureDetached;
+ Captured = control;
+ if (Captured != null)
+ Captured.DetachedFromVisualTree += OnCaptureDetached;
+ }
+
+ IInputElement GetNextCapture(IVisual parent) =>
+ parent as IInputElement ?? parent.GetVisualAncestors().OfType().FirstOrDefault();
+
+ private void OnCaptureDetached(object sender, VisualTreeAttachmentEventArgs e)
+ {
+ Capture(GetNextCapture(e.Parent));
+ }
+
+ private void OnImplicitCaptureDetached(object sender, VisualTreeAttachmentEventArgs e)
+ {
+ ImplicitlyCaptured.DetachedFromVisualTree -= OnImplicitCaptureDetached;
+ ImplicitlyCaptured = GetNextCapture(e.Parent);
+ if (ImplicitlyCaptured != null)
+ ImplicitlyCaptured.DetachedFromVisualTree += OnImplicitCaptureDetached;
+ }
+
+ public IInputElement Captured { get; private set; }
+ public IInputElement ImplicitlyCaptured { get; private set; }
+ public IInputElement GetEffectiveCapture() => Captured ?? ImplicitlyCaptured;
+
+ public PointerType Type { get; }
+ public bool IsPrimary { get; }
+ public void Dispose()
+ {
+ if (ImplicitlyCaptured != null)
+ ImplicitlyCaptured.DetachedFromVisualTree -= OnImplicitCaptureDetached;
+ if (Captured != null)
+ Captured.DetachedFromVisualTree -= OnCaptureDetached;
+ }
+ }
+}
diff --git a/src/Avalonia.Input/PointerEventArgs.cs b/src/Avalonia.Input/PointerEventArgs.cs
index 7b2497c460..1d07190a81 100644
--- a/src/Avalonia.Input/PointerEventArgs.cs
+++ b/src/Avalonia.Input/PointerEventArgs.cs
@@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
+using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
@@ -8,25 +10,65 @@ namespace Avalonia.Input
{
public class PointerEventArgs : RoutedEventArgs
{
- public PointerEventArgs()
- {
+ private readonly IVisual _rootVisual;
+ private readonly Point _rootVisualPosition;
+ private readonly PointerPointProperties _properties;
+ public PointerEventArgs(RoutedEvent routedEvent,
+ IInteractive source,
+ IPointer pointer,
+ IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties,
+ InputModifiers modifiers)
+ : base(routedEvent)
+ {
+ Source = source;
+ _rootVisual = rootVisual;
+ _rootVisualPosition = rootVisualPosition;
+ _properties = properties;
+ Pointer = pointer;
+ InputModifiers = modifiers;
}
- public PointerEventArgs(RoutedEvent routedEvent)
- : base(routedEvent)
+ 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 IPointerDevice Device { get; set; }
+ public IPointer Pointer { get; }
+
+ private IPointerDevice _device;
+
+ [Obsolete("Use Pointer to get pointer-specific information")]
+ public IPointerDevice Device => _device ?? (_device = new EmulatedDevice(this));
- public InputModifiers InputModifiers { get; set; }
+ public InputModifiers InputModifiers { get; }
public Point GetPosition(IVisual relativeTo)
{
- return Device.GetPosition(relativeTo);
+ if (_rootVisual == null)
+ return default;
+ if (relativeTo == null)
+ return _rootVisualPosition;
+ return _rootVisualPosition * _rootVisual.TransformToVisual(relativeTo) ?? default;
}
+
+ public PointerPoint GetPointerPoint(IVisual relativeTo)
+ => new PointerPoint(Pointer, GetPosition(relativeTo), _properties);
}
public enum MouseButton
@@ -39,32 +81,39 @@ namespace Avalonia.Input
public class PointerPressedEventArgs : PointerEventArgs
{
- public PointerPressedEventArgs()
- : base(InputElement.PointerPressedEvent)
- {
- }
+ private readonly int _obsoleteClickCount;
- public PointerPressedEventArgs(RoutedEvent routedEvent)
- : base(routedEvent)
+ public PointerPressedEventArgs(
+ IInteractive source,
+ IPointer pointer,
+ IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties,
+ InputModifiers modifiers,
+ int obsoleteClickCount = 1)
+ : base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition, properties,
+ modifiers)
{
+ _obsoleteClickCount = obsoleteClickCount;
}
- public int ClickCount { get; set; }
- public MouseButton MouseButton { get; set; }
+ [Obsolete("Use DoubleTapped or DoubleRightTapped event instead")]
+ public int ClickCount => _obsoleteClickCount;
+
+ [Obsolete] public MouseButton MouseButton => GetPointerPoint(null).Properties.GetObsoleteMouseButton();
}
public class PointerReleasedEventArgs : PointerEventArgs
{
- public PointerReleasedEventArgs()
- : base(InputElement.PointerReleasedEvent)
- {
- }
-
- public PointerReleasedEventArgs(RoutedEvent routedEvent)
- : base(routedEvent)
+ public PointerReleasedEventArgs(
+ IInteractive source, IPointer pointer,
+ IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties, InputModifiers modifiers,
+ MouseButton obsoleteMouseButton)
+ : base(InputElement.PointerReleasedEvent, source, pointer, rootVisual, rootVisualPosition,
+ properties, modifiers)
{
+ MouseButton = obsoleteMouseButton;
}
- public MouseButton MouseButton { get; set; }
+ [Obsolete()]
+ public MouseButton MouseButton { get; private set; }
}
}
diff --git a/src/Avalonia.Input/PointerPoint.cs b/src/Avalonia.Input/PointerPoint.cs
new file mode 100644
index 0000000000..7117b5709c
--- /dev/null
+++ b/src/Avalonia.Input/PointerPoint.cs
@@ -0,0 +1,45 @@
+namespace Avalonia.Input
+{
+ public sealed class PointerPoint
+ {
+ public PointerPoint(IPointer pointer, Point position, PointerPointProperties properties)
+ {
+ Pointer = pointer;
+ Position = position;
+ Properties = properties;
+ }
+ public IPointer Pointer { get; }
+ public PointerPointProperties Properties { get; }
+ public Point Position { get; }
+ }
+
+ public sealed class PointerPointProperties
+ {
+ public bool IsLeftButtonPressed { get; set; }
+ public bool IsMiddleButtonPressed { get; set; }
+ public bool IsRightButtonPressed { get; set; }
+
+ public PointerPointProperties()
+ {
+
+ }
+
+ public PointerPointProperties(InputModifiers modifiers)
+ {
+ IsLeftButtonPressed = modifiers.HasFlag(InputModifiers.LeftMouseButton);
+ IsMiddleButtonPressed = modifiers.HasFlag(InputModifiers.MiddleMouseButton);
+ IsRightButtonPressed = modifiers.HasFlag(InputModifiers.RightMouseButton);
+ }
+
+ public MouseButton GetObsoleteMouseButton()
+ {
+ if (IsLeftButtonPressed)
+ return MouseButton.Left;
+ if (IsMiddleButtonPressed)
+ return MouseButton.Middle;
+ if (IsRightButtonPressed)
+ return MouseButton.Right;
+ return MouseButton.None;
+ }
+ }
+}
diff --git a/src/Avalonia.Input/PointerWheelEventArgs.cs b/src/Avalonia.Input/PointerWheelEventArgs.cs
index 401d1d847c..b409cc81bd 100644
--- a/src/Avalonia.Input/PointerWheelEventArgs.cs
+++ b/src/Avalonia.Input/PointerWheelEventArgs.cs
@@ -1,10 +1,21 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using Avalonia.Interactivity;
+using Avalonia.VisualTree;
+
namespace Avalonia.Input
{
public class PointerWheelEventArgs : PointerEventArgs
{
public Vector Delta { get; set; }
+
+ public PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual,
+ Point rootVisualPosition,
+ PointerPointProperties properties, InputModifiers modifiers, Vector delta)
+ : base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition, properties, modifiers)
+ {
+ Delta = delta;
+ }
}
}
diff --git a/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs b/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs
index d2e5faab6c..186ad99efc 100644
--- a/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs
+++ b/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs
@@ -4,7 +4,7 @@
namespace Avalonia.Input.Raw
{
- public class RawMouseWheelEventArgs : RawMouseEventArgs
+ public class RawMouseWheelEventArgs : RawPointerEventArgs
{
public RawMouseWheelEventArgs(
IInputDevice device,
@@ -12,7 +12,7 @@ namespace Avalonia.Input.Raw
IInputRoot root,
Point position,
Vector delta, InputModifiers inputModifiers)
- : base(device, timestamp, root, RawMouseEventType.Wheel, position, inputModifiers)
+ : base(device, timestamp, root, RawPointerEventType.Wheel, position, inputModifiers)
{
Delta = delta;
}
diff --git a/src/Avalonia.Input/Raw/RawMouseEventArgs.cs b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs
similarity index 84%
rename from src/Avalonia.Input/Raw/RawMouseEventArgs.cs
rename to src/Avalonia.Input/Raw/RawPointerEventArgs.cs
index c5637d66cc..b728844e97 100644
--- a/src/Avalonia.Input/Raw/RawMouseEventArgs.cs
+++ b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs
@@ -5,7 +5,7 @@ using System;
namespace Avalonia.Input.Raw
{
- public enum RawMouseEventType
+ public enum RawPointerEventType
{
LeaveWindow,
LeftButtonDown,
@@ -17,15 +17,18 @@ namespace Avalonia.Input.Raw
Move,
Wheel,
NonClientLeftButtonDown,
+ TouchBegin,
+ TouchUpdate,
+ TouchEnd
}
///
/// A raw mouse event.
///
- public class RawMouseEventArgs : RawInputEventArgs
+ public class RawPointerEventArgs : RawInputEventArgs
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The associated device.
/// The event timestamp.
@@ -33,11 +36,11 @@ namespace Avalonia.Input.Raw
/// The type of the event.
/// The mouse position, in client DIPs.
/// The input modifiers.
- public RawMouseEventArgs(
+ public RawPointerEventArgs(
IInputDevice device,
ulong timestamp,
IInputRoot root,
- RawMouseEventType type,
+ RawPointerEventType type,
Point position,
InputModifiers inputModifiers)
: base(device, timestamp)
@@ -64,7 +67,7 @@ namespace Avalonia.Input.Raw
///
/// Gets the type of the event.
///
- public RawMouseEventType Type { get; private set; }
+ public RawPointerEventType Type { get; private set; }
///
/// Gets the input modifiers.
diff --git a/src/Avalonia.Input/Raw/RawTouchEventArgs.cs b/src/Avalonia.Input/Raw/RawTouchEventArgs.cs
new file mode 100644
index 0000000000..5299633b26
--- /dev/null
+++ b/src/Avalonia.Input/Raw/RawTouchEventArgs.cs
@@ -0,0 +1,15 @@
+namespace Avalonia.Input.Raw
+{
+ public class RawTouchEventArgs : RawPointerEventArgs
+ {
+ public RawTouchEventArgs(IInputDevice device, ulong timestamp, IInputRoot root,
+ RawPointerEventType type, Point position, InputModifiers inputModifiers,
+ long touchPointId)
+ : base(device, timestamp, root, type, position, inputModifiers)
+ {
+ TouchPointId = touchPointId;
+ }
+
+ public long TouchPointId { get; set; }
+ }
+}
diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs
new file mode 100644
index 0000000000..80d8a5e780
--- /dev/null
+++ b/src/Avalonia.Input/TouchDevice.cs
@@ -0,0 +1,72 @@
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Input.Raw;
+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
+ {
+ Dictionary _pointers = new Dictionary();
+
+ static InputModifiers GetModifiers(InputModifiers modifiers, bool left)
+ {
+ var mask = (InputModifiers)0x7fffffff ^ InputModifiers.LeftMouseButton ^ InputModifiers.MiddleMouseButton ^
+ InputModifiers.RightMouseButton;
+ modifiers &= mask;
+ if (left)
+ modifiers |= InputModifiers.LeftMouseButton;
+ return modifiers;
+ }
+
+ public void ProcessRawEvent(RawInputEventArgs ev)
+ {
+ var args = (RawTouchEventArgs)ev;
+ if (!_pointers.TryGetValue(args.TouchPointId, out var pointer))
+ {
+ if (args.Type == RawPointerEventType.TouchEnd)
+ return;
+ var hit = args.Root.InputHitTest(args.Position);
+
+ _pointers[args.TouchPointId] = pointer = new Pointer(Pointer.GetNextFreeId(),
+ PointerType.Touch, _pointers.Count == 0, hit);
+ }
+
+
+ var target = pointer.GetEffectiveCapture() ?? args.Root;
+ if (args.Type == RawPointerEventType.TouchBegin)
+ {
+ var modifiers = GetModifiers(args.InputModifiers, pointer.IsPrimary);
+ target.RaiseEvent(new PointerPressedEventArgs(target, pointer,
+ args.Root, args.Position, new PointerPointProperties(modifiers),
+ modifiers));
+ }
+
+ if (args.Type == RawPointerEventType.TouchEnd)
+ {
+ _pointers.Remove(args.TouchPointId);
+ var modifiers = GetModifiers(args.InputModifiers, false);
+ using (pointer)
+ {
+ target.RaiseEvent(new PointerReleasedEventArgs(target, pointer,
+ args.Root, args.Position, new PointerPointProperties(modifiers),
+ modifiers, pointer.IsPrimary ? MouseButton.Left : MouseButton.None));
+ }
+ }
+
+ if (args.Type == RawPointerEventType.TouchUpdate)
+ {
+ var modifiers = GetModifiers(args.InputModifiers, pointer.IsPrimary);
+ target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, args.Root,
+ args.Position, new PointerPointProperties(modifiers), modifiers));
+ }
+ }
+
+ }
+}
diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs
index 73b8834375..638879ba14 100644
--- a/src/Avalonia.Native/WindowImplBase.cs
+++ b/src/Avalonia.Native/WindowImplBase.cs
@@ -226,7 +226,7 @@ namespace Avalonia.Native
break;
default:
- Input?.Invoke(new RawMouseEventArgs(_mouse, timeStamp, _inputRoot, (RawMouseEventType)type, point.ToAvaloniaPoint(), (InputModifiers)modifiers));
+ Input?.Invoke(new RawPointerEventArgs(_mouse, timeStamp, _inputRoot, (RawPointerEventType)type, point.ToAvaloniaPoint(), (InputModifiers)modifiers));
break;
}
}
diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs
index ce03113169..cf5902eff7 100644
--- a/src/Avalonia.X11/X11Platform.cs
+++ b/src/Avalonia.X11/X11Platform.cs
@@ -28,6 +28,7 @@ namespace Avalonia.X11
public X11PlatformOptions Options { get; private set; }
public void Initialize(X11PlatformOptions options)
{
+ Options = options;
XInitThreads();
Display = XOpenDisplay(IntPtr.Zero);
DeferredDisplay = XOpenDisplay(IntPtr.Zero);
@@ -66,7 +67,7 @@ namespace Avalonia.X11
GlxGlPlatformFeature.TryInitialize(Info);
}
- Options = options;
+
}
public IntPtr DeferredDisplay { get; set; }
@@ -96,6 +97,7 @@ namespace Avalonia
public bool UseEGL { get; set; }
public bool UseGpu { get; set; } = true;
public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name ?? "AvaloniaApplication";
+ public bool? EnableMultiTouch { get; set; }
}
public static class AvaloniaX11PlatformExtensions
{
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index c5e77fe352..01beebfff1 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -314,9 +314,9 @@ namespace Avalonia.X11
else if (ev.type == XEventName.FocusOut)
Deactivated?.Invoke();
else if (ev.type == XEventName.MotionNotify)
- MouseEvent(RawMouseEventType.Move, ref ev, ev.MotionEvent.state);
+ MouseEvent(RawPointerEventType.Move, ref ev, ev.MotionEvent.state);
else if (ev.type == XEventName.LeaveNotify)
- MouseEvent(RawMouseEventType.LeaveWindow, ref ev, ev.CrossingEvent.state);
+ MouseEvent(RawPointerEventType.LeaveWindow, ref ev, ev.CrossingEvent.state);
else if (ev.type == XEventName.PropertyNotify)
{
OnPropertyChange(ev.PropertyEvent.atom, ev.PropertyEvent.state == 0);
@@ -326,9 +326,9 @@ namespace Avalonia.X11
if (ActivateTransientChildIfNeeded())
return;
if (ev.ButtonEvent.button < 4)
- MouseEvent(ev.ButtonEvent.button == 1 ? RawMouseEventType.LeftButtonDown
- : ev.ButtonEvent.button == 2 ? RawMouseEventType.MiddleButtonDown
- : RawMouseEventType.RightButtonDown, ref ev, ev.ButtonEvent.state);
+ MouseEvent(ev.ButtonEvent.button == 1 ? RawPointerEventType.LeftButtonDown
+ : ev.ButtonEvent.button == 2 ? RawPointerEventType.MiddleButtonDown
+ : RawPointerEventType.RightButtonDown, ref ev, ev.ButtonEvent.state);
else
{
var delta = ev.ButtonEvent.button == 4
@@ -347,9 +347,9 @@ namespace Avalonia.X11
else if (ev.type == XEventName.ButtonRelease)
{
if (ev.ButtonEvent.button < 4)
- MouseEvent(ev.ButtonEvent.button == 1 ? RawMouseEventType.LeftButtonUp
- : ev.ButtonEvent.button == 2 ? RawMouseEventType.MiddleButtonUp
- : RawMouseEventType.RightButtonUp, ref ev, ev.ButtonEvent.state);
+ MouseEvent(ev.ButtonEvent.button == 1 ? RawPointerEventType.LeftButtonUp
+ : ev.ButtonEvent.button == 2 ? RawPointerEventType.MiddleButtonUp
+ : RawPointerEventType.RightButtonUp, ref ev, ev.ButtonEvent.state);
}
else if (ev.type == XEventName.ConfigureNotify)
{
@@ -577,7 +577,7 @@ namespace Avalonia.X11
public void ScheduleInput(RawInputEventArgs args)
{
- if (args is RawMouseEventArgs mouse)
+ if (args is RawPointerEventArgs mouse)
mouse.Position = mouse.Position / Scaling;
if (args is RawDragEvent drag)
drag.Location = drag.Location / Scaling;
@@ -598,13 +598,13 @@ namespace Avalonia.X11
}
}
- void MouseEvent(RawMouseEventType type, ref XEvent ev, XModifierMask mods)
+ void MouseEvent(RawPointerEventType type, ref XEvent ev, XModifierMask mods)
{
- var mev = new RawMouseEventArgs(
+ var mev = new RawPointerEventArgs(
_mouse, (ulong)ev.ButtonEvent.time.ToInt64(), _inputRoot,
type, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), TranslateModifiers(mods));
- if(type == RawMouseEventType.Move && _inputQueue.Count>0 && _lastEvent.Event is RawMouseEventArgs ma)
- if (ma.Type == RawMouseEventType.Move)
+ if(type == RawPointerEventType.Move && _inputQueue.Count>0 && _lastEvent.Event is RawPointerEventArgs ma)
+ if (ma.Type == RawPointerEventType.Move)
{
_lastEvent.Event = mev;
return;
diff --git a/src/Avalonia.X11/XI2Manager.cs b/src/Avalonia.X11/XI2Manager.cs
index ee73ccc907..0a78c0dfd9 100644
--- a/src/Avalonia.X11/XI2Manager.cs
+++ b/src/Avalonia.X11/XI2Manager.cs
@@ -11,6 +11,7 @@ namespace Avalonia.X11
unsafe class XI2Manager
{
private X11Info _x11;
+ private bool _multitouch;
private Dictionary _clients = new Dictionary();
class DeviceInfo
{
@@ -77,11 +78,14 @@ namespace Avalonia.X11
private PointerDeviceInfo _pointerDevice;
private AvaloniaX11Platform _platform;
+ private readonly TouchDevice _touchDevice = new TouchDevice();
+
public bool Init(AvaloniaX11Platform platform)
{
_platform = platform;
_x11 = platform.Info;
+ _multitouch = platform.Options?.EnableMultiTouch ?? false;
var devices =(XIDeviceInfo*) XIQueryDevice(_x11.Display,
(int)XiPredefinedDeviceId.XIAllMasterDevices, out int num);
for (var c = 0; c < num; c++)
@@ -121,16 +125,23 @@ namespace Avalonia.X11
public XEventMask AddWindow(IntPtr xid, IXI2Client window)
{
_clients[xid] = window;
-
- XiSelectEvents(_x11.Display, xid, new Dictionary>
+ var events = new List
{
- [_pointerDevice.Id] = new List()
+ XiEventType.XI_Motion,
+ XiEventType.XI_ButtonPress,
+ XiEventType.XI_ButtonRelease
+ };
+
+ if (_multitouch)
+ events.AddRange(new[]
{
- XiEventType.XI_Motion,
- XiEventType.XI_ButtonPress,
- XiEventType.XI_ButtonRelease,
- }
- });
+ XiEventType.XI_TouchBegin,
+ XiEventType.XI_TouchUpdate,
+ XiEventType.XI_TouchEnd
+ });
+
+ XiSelectEvents(_x11.Display, xid,
+ new Dictionary> {[_pointerDevice.Id] = events});
// We are taking over mouse input handling from here
return XEventMask.PointerMotionMask
@@ -154,8 +165,9 @@ namespace Avalonia.X11
_pointerDevice.Update(changed->Classes, changed->NumClasses);
}
- //TODO: this should only be used for non-touch devices
- if (xev->evtype >= XiEventType.XI_ButtonPress && xev->evtype <= XiEventType.XI_Motion)
+
+ if ((xev->evtype >= XiEventType.XI_ButtonPress && xev->evtype <= XiEventType.XI_Motion)
+ || (xev->evtype>=XiEventType.XI_TouchBegin&&xev->evtype<=XiEventType.XI_TouchEnd))
{
var dev = (XIDeviceEvent*)xev;
if (_clients.TryGetValue(dev->EventWindow, out var client))
@@ -165,6 +177,23 @@ namespace Avalonia.X11
void OnDeviceEvent(IXI2Client client, ParsedDeviceEvent ev)
{
+ if (ev.Type == XiEventType.XI_TouchBegin
+ || ev.Type == XiEventType.XI_TouchUpdate
+ || ev.Type == XiEventType.XI_TouchEnd)
+ {
+ var type = ev.Type == XiEventType.XI_TouchBegin ?
+ RawPointerEventType.TouchBegin :
+ (ev.Type == XiEventType.XI_TouchUpdate ?
+ RawPointerEventType.TouchUpdate :
+ RawPointerEventType.TouchEnd);
+ client.ScheduleInput(new RawTouchEventArgs(_touchDevice,
+ ev.Timestamp, client.InputRoot, type, ev.Position, ev.Modifiers, ev.Detail));
+ return;
+ }
+
+ if (_multitouch && ev.Emulated)
+ return;
+
if (ev.Type == XiEventType.XI_Motion)
{
Vector scrollDelta = default;
@@ -194,23 +223,23 @@ namespace Avalonia.X11
client.ScheduleInput(new RawMouseWheelEventArgs(_platform.MouseDevice, ev.Timestamp,
client.InputRoot, ev.Position, scrollDelta, ev.Modifiers));
if (_pointerDevice.HasMotion(ev))
- client.ScheduleInput(new RawMouseEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot,
- RawMouseEventType.Move, ev.Position, ev.Modifiers));
+ client.ScheduleInput(new RawPointerEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot,
+ RawPointerEventType.Move, ev.Position, ev.Modifiers));
}
if (ev.Type == XiEventType.XI_ButtonPress || ev.Type == XiEventType.XI_ButtonRelease)
{
var down = ev.Type == XiEventType.XI_ButtonPress;
var type =
- ev.Button == 1 ? (down ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp)
- : ev.Button == 2 ? (down ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp)
- : ev.Button == 3 ? (down ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp)
- : (RawMouseEventType?)null;
+ ev.Button == 1 ? (down ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp)
+ : ev.Button == 2 ? (down ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp)
+ : ev.Button == 3 ? (down ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp)
+ : (RawPointerEventType?)null;
if (type.HasValue)
- client.ScheduleInput(new RawMouseEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot,
+ client.ScheduleInput(new RawPointerEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot,
type.Value, ev.Position, ev.Modifiers));
}
-
+
_pointerDevice.UpdateValuators(ev.Valuators);
}
}
@@ -222,6 +251,8 @@ namespace Avalonia.X11
public ulong Timestamp { get; }
public Point Position { get; }
public int Button { get; set; }
+ public int Detail { get; set; }
+ public bool Emulated { get; set; }
public Dictionary Valuators { get; }
public ParsedDeviceEvent(XIDeviceEvent* ev)
{
@@ -258,6 +289,8 @@ namespace Avalonia.X11
Valuators[c] = *values++;
if (Type == XiEventType.XI_ButtonPress || Type == XiEventType.XI_ButtonRelease)
Button = ev->detail;
+ Detail = ev->detail;
+ Emulated = ev->flags.HasFlag(XiDeviceEventFlags.XIPointerEmulated);
}
}
diff --git a/src/Avalonia.X11/XIStructs.cs b/src/Avalonia.X11/XIStructs.cs
index ef49a72c43..4675ef47f2 100644
--- a/src/Avalonia.X11/XIStructs.cs
+++ b/src/Avalonia.X11/XIStructs.cs
@@ -230,13 +230,20 @@ namespace Avalonia.X11
public double root_y;
public double event_x;
public double event_y;
- public int flags;
+ public XiDeviceEventFlags flags;
public XIButtonState buttons;
public XIValuatorState valuators;
public XIModifierState mods;
public XIModifierState group;
}
+ [Flags]
+ public enum XiDeviceEventFlags : int
+ {
+ None = 0,
+ XIPointerEmulated = (1 << 16)
+ }
+
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIEvent
{
diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
index c4697462d2..bff50a979d 100644
--- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
+++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
@@ -145,17 +145,17 @@ namespace Avalonia.Gtk3
private unsafe bool OnButton(IntPtr w, IntPtr ev, IntPtr userdata)
{
var evnt = (GdkEventButton*)ev;
- var e = new RawMouseEventArgs(
+ var e = new RawPointerEventArgs(
Gtk3Platform.Mouse,
evnt->time,
_inputRoot,
evnt->type == GdkEventType.ButtonRelease
? evnt->button == 1
- ? RawMouseEventType.LeftButtonUp
- : evnt->button == 3 ? RawMouseEventType.RightButtonUp : RawMouseEventType.MiddleButtonUp
+ ? RawPointerEventType.LeftButtonUp
+ : evnt->button == 3 ? RawPointerEventType.RightButtonUp : RawPointerEventType.MiddleButtonUp
: evnt->button == 1
- ? RawMouseEventType.LeftButtonDown
- : evnt->button == 3 ? RawMouseEventType.RightButtonDown : RawMouseEventType.MiddleButtonDown,
+ ? RawPointerEventType.LeftButtonDown
+ : evnt->button == 3 ? RawPointerEventType.RightButtonDown : RawPointerEventType.MiddleButtonDown,
new Point(evnt->x, evnt->y), GetModifierKeys(evnt->state));
OnInput(e);
return true;
@@ -179,11 +179,11 @@ namespace Avalonia.Gtk3
var evnt = (GdkEventMotion*)ev;
var position = new Point(evnt->x, evnt->y);
Native.GdkEventRequestMotions(ev);
- var e = new RawMouseEventArgs(
+ var e = new RawPointerEventArgs(
Gtk3Platform.Mouse,
evnt->time,
_inputRoot,
- RawMouseEventType.Move,
+ RawPointerEventType.Move,
position, GetModifierKeys(evnt->state));
OnInput(e);
@@ -237,10 +237,10 @@ namespace Avalonia.Gtk3
{
var evnt = (GdkEventCrossing*) pev;
var position = new Point(evnt->x, evnt->y);
- OnInput(new RawMouseEventArgs(Gtk3Platform.Mouse,
+ OnInput(new RawPointerEventArgs(Gtk3Platform.Mouse,
evnt->time,
_inputRoot,
- RawMouseEventType.Move,
+ RawPointerEventType.Move,
position, GetModifierKeys(evnt->state)));
return true;
}
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs b/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs
index 4fa57dbf00..b982b98d38 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs
@@ -76,9 +76,9 @@ namespace Avalonia.LinuxFramebuffer
_y = Math.Min(_height, Math.Max(0, _y + ev.value));
else
return;
- Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice,
+ Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice,
LinuxFramebufferPlatform.Timestamp,
- LinuxFramebufferPlatform.TopLevel.InputRoot, RawMouseEventType.Move, new Point(_x, _y),
+ LinuxFramebufferPlatform.TopLevel.InputRoot, RawPointerEventType.Move, new Point(_x, _y),
InputModifiers.None));
}
if (ev.type ==(int) EvType.EV_ABS)
@@ -89,24 +89,24 @@ namespace Avalonia.LinuxFramebuffer
_y = TranslateAxis(device.AbsY.Value, ev.value, _height);
else
return;
- Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice,
+ Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice,
LinuxFramebufferPlatform.Timestamp,
- LinuxFramebufferPlatform.TopLevel.InputRoot, RawMouseEventType.Move, new Point(_x, _y),
+ LinuxFramebufferPlatform.TopLevel.InputRoot, RawPointerEventType.Move, new Point(_x, _y),
InputModifiers.None));
}
if (ev.type == (short) EvType.EV_KEY)
{
- RawMouseEventType? type = null;
+ RawPointerEventType? type = null;
if (ev.code == (ushort) EvKey.BTN_LEFT)
- type = ev.value == 1 ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp;
+ type = ev.value == 1 ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp;
if (ev.code == (ushort)EvKey.BTN_RIGHT)
- type = ev.value == 1 ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp;
+ type = ev.value == 1 ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp;
if (ev.code == (ushort) EvKey.BTN_MIDDLE)
- type = ev.value == 1 ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp;
+ type = ev.value == 1 ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp;
if (!type.HasValue)
return;
- Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice,
+ Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice,
LinuxFramebufferPlatform.Timestamp,
LinuxFramebufferPlatform.TopLevel.InputRoot, type.Value, new Point(_x, _y), default(InputModifiers)));
}
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
index 7005459487..c89d0a15cf 100644
--- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
@@ -160,19 +160,19 @@ namespace Avalonia.Win32.Interop.Wpf
return rv;
}
- void MouseEvent(RawMouseEventType type, MouseEventArgs e)
- => _ttl.Input?.Invoke(new RawMouseEventArgs(_mouse, (uint)e.Timestamp, _inputRoot, type,
+ void MouseEvent(RawPointerEventType type, MouseEventArgs e)
+ => _ttl.Input?.Invoke(new RawPointerEventArgs(_mouse, (uint)e.Timestamp, _inputRoot, type,
e.GetPosition(this).ToAvaloniaPoint(), GetModifiers()));
protected override void OnMouseDown(MouseButtonEventArgs e)
{
- RawMouseEventType type;
+ RawPointerEventType type;
if(e.ChangedButton == MouseButton.Left)
- type = RawMouseEventType.LeftButtonDown;
+ type = RawPointerEventType.LeftButtonDown;
else if (e.ChangedButton == MouseButton.Middle)
- type = RawMouseEventType.MiddleButtonDown;
+ type = RawPointerEventType.MiddleButtonDown;
else if (e.ChangedButton == MouseButton.Right)
- type = RawMouseEventType.RightButtonDown;
+ type = RawPointerEventType.RightButtonDown;
else
return;
MouseEvent(type, e);
@@ -181,13 +181,13 @@ namespace Avalonia.Win32.Interop.Wpf
protected override void OnMouseUp(MouseButtonEventArgs e)
{
- RawMouseEventType type;
+ RawPointerEventType type;
if (e.ChangedButton == MouseButton.Left)
- type = RawMouseEventType.LeftButtonUp;
+ type = RawPointerEventType.LeftButtonUp;
else if (e.ChangedButton == MouseButton.Middle)
- type = RawMouseEventType.MiddleButtonUp;
+ type = RawPointerEventType.MiddleButtonUp;
else if (e.ChangedButton == MouseButton.Right)
- type = RawMouseEventType.RightButtonUp;
+ type = RawPointerEventType.RightButtonUp;
else
return;
MouseEvent(type, e);
@@ -196,14 +196,14 @@ namespace Avalonia.Win32.Interop.Wpf
protected override void OnMouseMove(MouseEventArgs e)
{
- MouseEvent(RawMouseEventType.Move, e);
+ MouseEvent(RawPointerEventType.Move, e);
}
protected override void OnMouseWheel(MouseWheelEventArgs e) =>
_ttl.Input?.Invoke(new RawMouseWheelEventArgs(_mouse, (uint) e.Timestamp, _inputRoot,
e.GetPosition(this).ToAvaloniaPoint(), new Vector(0, e.Delta), GetModifiers()));
- protected override void OnMouseLeave(MouseEventArgs e) => MouseEvent(RawMouseEventType.LeaveWindow, e);
+ protected override void OnMouseLeave(MouseEventArgs e) => MouseEvent(RawPointerEventType.LeaveWindow, e);
protected override void OnKeyDown(KeyEventArgs e)
=> _ttl.Input?.Invoke(new RawKeyEventArgs(_keyboard, (uint) e.Timestamp, RawKeyEventType.KeyDown,
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index 7d6e8fc8ce..bc7fc1c9fa 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
@@ -574,6 +574,7 @@ namespace Avalonia.Win32.Interop
WM_AFXLAST = 0x037F,
WM_PENWINFIRST = 0x0380,
WM_PENWINLAST = 0x038F,
+ WM_TOUCH = 0x0240,
WM_APP = 0x8000,
WM_USER = 0x0400,
@@ -836,10 +837,16 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
+
+ [DllImport("user32")]
+ public static extern IntPtr GetMessageExtraInfo();
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "RegisterClassExW")]
public static extern ushort RegisterClassEx(ref WNDCLASSEX lpwcx);
+ [DllImport("user32.dll")]
+ public static extern void RegisterTouchWindow(IntPtr hWnd, int flags);
+
[DllImport("user32.dll")]
public static extern bool ReleaseCapture();
@@ -1035,6 +1042,17 @@ namespace Avalonia.Win32.Interop
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMonitorInfo([In] IntPtr hMonitor, ref MONITORINFO lpmi);
+ [DllImport("user32")]
+ public static extern bool GetTouchInputInfo(
+ IntPtr hTouchInput,
+ uint cInputs,
+ [Out]TOUCHINPUT[] pInputs,
+ int cbSize
+ );
+
+ [DllImport("user32")]
+ public static extern bool CloseTouchInputHandle(IntPtr hTouchInput);
+
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "PostMessageW")]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
@@ -1309,6 +1327,60 @@ namespace Avalonia.Win32.Interop
public IntPtr hIconSm;
}
+ [StructLayout(LayoutKind.Sequential)]
+ public struct TOUCHINPUT
+ {
+ public int X;
+ public int Y;
+ public IntPtr Source;
+ public uint Id;
+ public TouchInputFlags Flags;
+ public int Mask;
+ public uint Time;
+ public IntPtr ExtraInfo;
+ public int CxContact;
+ public int CyContact;
+ }
+
+ [Flags]
+ public enum TouchInputFlags
+ {
+ ///
+ /// Movement has occurred. Cannot be combined with TOUCHEVENTF_DOWN.
+ ///
+ TOUCHEVENTF_MOVE = 0x0001,
+
+ ///
+ /// The corresponding touch point was established through a new contact. Cannot be combined with TOUCHEVENTF_MOVE or TOUCHEVENTF_UP.
+ ///
+ TOUCHEVENTF_DOWN = 0x0002,
+
+ ///
+ /// A touch point was removed.
+ ///
+ TOUCHEVENTF_UP = 0x0004,
+
+ ///
+ /// A touch point is in range. This flag is used to enable touch hover support on compatible hardware. Applications that do not want support for hover can ignore this flag.
+ ///
+ TOUCHEVENTF_INRANGE = 0x0008,
+
+ ///
+ /// Indicates that this TOUCHINPUT structure corresponds to a primary contact point. See the following text for more information on primary touch points.
+ ///
+ TOUCHEVENTF_PRIMARY = 0x0010,
+
+ ///
+ /// When received using GetTouchInputInfo, this input was not coalesced.
+ ///
+ TOUCHEVENTF_NOCOALESCE = 0x0020,
+
+ ///
+ /// The touch event came from the user's palm.
+ ///
+ TOUCHEVENTF_PALM = 0x0080
+ }
+
[Flags]
public enum OpenFileNameFlags
{
diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs
index f679c2410e..c45bf6389e 100644
--- a/src/Windows/Avalonia.Win32/Win32Platform.cs
+++ b/src/Windows/Avalonia.Win32/Win32Platform.cs
@@ -40,6 +40,7 @@ namespace Avalonia
{
public bool UseDeferredRendering { get; set; } = true;
public bool AllowEglInitialization { get; set; }
+ public bool? EnableMultitouch { get; set; }
}
}
@@ -59,7 +60,8 @@ namespace Avalonia.Win32
CreateMessageWindow();
}
- public static bool UseDeferredRendering { get; set; }
+ public static bool UseDeferredRendering => Options.UseDeferredRendering;
+ public static Win32PlatformOptions Options { get; private set; }
public Size DoubleClickSize => new Size(
UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXDOUBLECLK),
@@ -74,6 +76,7 @@ namespace Avalonia.Win32
public static void Initialize(Win32PlatformOptions options)
{
+ Options = options;
AvaloniaLocator.CurrentMutable
.Bind().ToSingleton()
.Bind().ToConstant(CursorFactory.Instance)
@@ -88,7 +91,7 @@ namespace Avalonia.Win32
.Bind().ToConstant(s_instance);
if (options.AllowEglInitialization)
Win32GlManager.Initialize();
- UseDeferredRendering = options.UseDeferredRendering;
+
_uiThread = Thread.CurrentThread;
if (OleContext.Current != null)
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 081a713e95..7f07f36de8 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -30,6 +30,8 @@ namespace Avalonia.Win32
private UnmanagedMethods.WndProc _wndProcDelegate;
private string _className;
private IntPtr _hwnd;
+ private bool _multitouch;
+ private TouchDevice _touchDevice = new TouchDevice();
private IInputRoot _owner;
private bool _trackingMouse;
private bool _decorated = true;
@@ -414,6 +416,15 @@ namespace Avalonia.Win32
IntPtr.Zero);
}
+ bool ShouldIgnoreTouchEmulatedMessage()
+ {
+ if (!_multitouch)
+ return false;
+ var marker = 0xFF515700L;
+ var info = GetMessageExtraInfo().ToInt64();
+ return (info & marker) == marker;
+ }
+
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]
protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
@@ -519,34 +530,40 @@ namespace Avalonia.Win32
case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_RBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_MBUTTONDOWN:
- e = new RawMouseEventArgs(
+ if(ShouldIgnoreTouchEmulatedMessage())
+ break;
+ e = new RawPointerEventArgs(
WindowsMouseDevice.Instance,
timestamp,
_owner,
msg == (int)UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN
- ? RawMouseEventType.LeftButtonDown
+ ? RawPointerEventType.LeftButtonDown
: msg == (int)UnmanagedMethods.WindowsMessage.WM_RBUTTONDOWN
- ? RawMouseEventType.RightButtonDown
- : RawMouseEventType.MiddleButtonDown,
+ ? RawPointerEventType.RightButtonDown
+ : RawPointerEventType.MiddleButtonDown,
DipFromLParam(lParam), GetMouseModifiers(wParam));
break;
case UnmanagedMethods.WindowsMessage.WM_LBUTTONUP:
case UnmanagedMethods.WindowsMessage.WM_RBUTTONUP:
case UnmanagedMethods.WindowsMessage.WM_MBUTTONUP:
- e = new RawMouseEventArgs(
+ if(ShouldIgnoreTouchEmulatedMessage())
+ break;
+ e = new RawPointerEventArgs(
WindowsMouseDevice.Instance,
timestamp,
_owner,
msg == (int)UnmanagedMethods.WindowsMessage.WM_LBUTTONUP
- ? RawMouseEventType.LeftButtonUp
+ ? RawPointerEventType.LeftButtonUp
: msg == (int)UnmanagedMethods.WindowsMessage.WM_RBUTTONUP
- ? RawMouseEventType.RightButtonUp
- : RawMouseEventType.MiddleButtonUp,
+ ? RawPointerEventType.RightButtonUp
+ : RawPointerEventType.MiddleButtonUp,
DipFromLParam(lParam), GetMouseModifiers(wParam));
break;
case UnmanagedMethods.WindowsMessage.WM_MOUSEMOVE:
+ if(ShouldIgnoreTouchEmulatedMessage())
+ break;
if (!_trackingMouse)
{
var tm = new UnmanagedMethods.TRACKMOUSEEVENT
@@ -560,11 +577,11 @@ namespace Avalonia.Win32
UnmanagedMethods.TrackMouseEvent(ref tm);
}
- e = new RawMouseEventArgs(
+ e = new RawPointerEventArgs(
WindowsMouseDevice.Instance,
timestamp,
_owner,
- RawMouseEventType.Move,
+ RawPointerEventType.Move,
DipFromLParam(lParam), GetMouseModifiers(wParam));
break;
@@ -589,29 +606,52 @@ namespace Avalonia.Win32
case UnmanagedMethods.WindowsMessage.WM_MOUSELEAVE:
_trackingMouse = false;
- e = new RawMouseEventArgs(
+ e = new RawPointerEventArgs(
WindowsMouseDevice.Instance,
timestamp,
_owner,
- RawMouseEventType.LeaveWindow,
+ RawPointerEventType.LeaveWindow,
new Point(), WindowsKeyboardDevice.Instance.Modifiers);
break;
case UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_NCMBUTTONDOWN:
- e = new RawMouseEventArgs(
+ e = new RawPointerEventArgs(
WindowsMouseDevice.Instance,
timestamp,
_owner,
msg == (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN
- ? RawMouseEventType.NonClientLeftButtonDown
+ ? RawPointerEventType.NonClientLeftButtonDown
: msg == (int)UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN
- ? RawMouseEventType.RightButtonDown
- : RawMouseEventType.MiddleButtonDown,
+ ? RawPointerEventType.RightButtonDown
+ : RawPointerEventType.MiddleButtonDown,
new Point(0, 0), GetMouseModifiers(wParam));
break;
-
+ case WindowsMessage.WM_TOUCH:
+ var touchInputs = new TOUCHINPUT[wParam.ToInt32()];
+ if (GetTouchInputInfo(lParam, (uint)wParam.ToInt32(), touchInputs, Marshal.SizeOf()))
+ {
+ foreach (var touchInput in touchInputs)
+ {
+ var pt = new POINT {X = touchInput.X / 100, Y = touchInput.Y / 100};
+ UnmanagedMethods.ScreenToClient(_hwnd, ref pt);
+ Input?.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time,
+ _owner,
+ touchInput.Flags.HasFlag(TouchInputFlags.TOUCHEVENTF_UP) ?
+ RawPointerEventType.TouchEnd :
+ touchInput.Flags.HasFlag(TouchInputFlags.TOUCHEVENTF_DOWN) ?
+ RawPointerEventType.TouchBegin :
+ RawPointerEventType.TouchUpdate,
+ new Point(pt.X, pt.Y),
+ WindowsKeyboardDevice.Instance.Modifiers,
+ touchInput.Id));
+ }
+ CloseTouchInputHandle(lParam);
+ return IntPtr.Zero;
+ }
+
+ break;
case WindowsMessage.WM_NCPAINT:
if (!_decorated)
{
@@ -754,6 +794,10 @@ namespace Avalonia.Win32
Handle = new PlatformHandle(_hwnd, PlatformConstants.WindowHandleType);
+ _multitouch = Win32Platform.Options.EnableMultitouch ?? false;
+ if (_multitouch)
+ RegisterTouchWindow(_hwnd, 0);
+
if (UnmanagedMethods.ShCoreAvailable)
{
uint dpix, dpiy;
diff --git a/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj b/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj
index 683c256b7b..e57fcc643f 100644
--- a/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj
+++ b/src/iOS/Avalonia.iOS/Avalonia.iOS.csproj
@@ -6,6 +6,7 @@
+
diff --git a/src/iOS/Avalonia.iOS/TopLevelImpl.cs b/src/iOS/Avalonia.iOS/TopLevelImpl.cs
index 83d10b8b44..15e8b35056 100644
--- a/src/iOS/Avalonia.iOS/TopLevelImpl.cs
+++ b/src/iOS/Avalonia.iOS/TopLevelImpl.cs
@@ -86,11 +86,11 @@ namespace Avalonia.iOS
{
var location = touch.LocationInView(this).ToAvalonia();
- Input?.Invoke(new RawMouseEventArgs(
+ Input?.Invoke(new RawPointerEventArgs(
iOSPlatform.MouseDevice,
(uint)touch.Timestamp,
_inputRoot,
- RawMouseEventType.LeftButtonUp,
+ RawPointerEventType.LeftButtonUp,
location,
InputModifiers.None));
}
@@ -104,11 +104,11 @@ namespace Avalonia.iOS
{
var location = touch.LocationInView(this).ToAvalonia();
_touchLastPoint = location;
- Input?.Invoke(new RawMouseEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
- RawMouseEventType.Move, location, InputModifiers.None));
+ Input?.Invoke(new RawPointerEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
+ RawPointerEventType.Move, location, InputModifiers.None));
- Input?.Invoke(new RawMouseEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
- RawMouseEventType.LeftButtonDown, location, InputModifiers.None));
+ Input?.Invoke(new RawPointerEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
+ RawPointerEventType.LeftButtonDown, location, InputModifiers.None));
}
}
@@ -119,8 +119,8 @@ namespace Avalonia.iOS
{
var location = touch.LocationInView(this).ToAvalonia();
if (iOSPlatform.MouseDevice.Captured != null)
- Input?.Invoke(new RawMouseEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
- RawMouseEventType.Move, location, InputModifiers.LeftMouseButton));
+ Input?.Invoke(new RawPointerEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
+ RawPointerEventType.Move, location, InputModifiers.LeftMouseButton));
else
{
//magic number based on test - correction of 0.02 is working perfect
diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
index 9a751d4953..20b2ac5d9d 100644
--- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs
@@ -14,6 +14,8 @@ namespace Avalonia.Controls.UnitTests
{
public class ButtonTests
{
+ private MouseTestHelper _helper = new MouseTestHelper();
+
[Fact]
public void Button_Is_Disabled_When_Command_Is_Disabled()
{
@@ -102,12 +104,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Button_Raises_Click()
{
- var mouse = Mock.Of();
var renderer = Mock.Of();
- IInputElement captured = null;
- Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny())).Returns(new Point(50, 50));
- Mock.Get(mouse).Setup(m => m.Capture(It.IsAny())).Callback(v => captured = v);
- Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured);
+ var pt = new Point(50, 50);
Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>()))
.Returns>((p, r, f) =>
r.Bounds.Contains(p) ? new IVisual[] { r } : new IVisual[0]);
@@ -122,15 +120,15 @@ namespace Avalonia.Controls.UnitTests
target.Click += (s, e) => clicked = true;
- RaisePointerEnter(target, mouse);
- RaisePointerMove(target, mouse);
- RaisePointerPressed(target, mouse, 1, MouseButton.Left);
+ RaisePointerEnter(target);
+ RaisePointerMove(target, pt);
+ RaisePointerPressed(target, 1, MouseButton.Left, pt);
- Assert.Equal(captured, target);
+ Assert.Equal(_helper.Captured, target);
- RaisePointerReleased(target, mouse, MouseButton.Left);
+ RaisePointerReleased(target, MouseButton.Left, pt);
- Assert.Equal(captured, null);
+ Assert.Equal(_helper.Captured, null);
Assert.True(clicked);
}
@@ -138,12 +136,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside()
{
- var mouse = Mock.Of();
var renderer = Mock.Of();
- IInputElement captured = null;
- Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny())).Returns(new Point(200, 50));
- Mock.Get(mouse).Setup(m => m.Capture(It.IsAny())).Callback(v => captured = v);
- Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured);
+
Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>()))
.Returns>((p, r, f) =>
r.Bounds.Contains(p) ? new IVisual[] { r } : new IVisual[0]);
@@ -158,16 +152,16 @@ namespace Avalonia.Controls.UnitTests
target.Click += (s, e) => clicked = true;
- RaisePointerEnter(target, mouse);
- RaisePointerMove(target, mouse);
- RaisePointerPressed(target, mouse, 1, MouseButton.Left);
- RaisePointerLeave(target, mouse);
+ RaisePointerEnter(target);
+ RaisePointerMove(target, new Point(50,50));
+ RaisePointerPressed(target, 1, MouseButton.Left, new Point(50, 50));
+ RaisePointerLeave(target);
- Assert.Equal(captured, target);
+ Assert.Equal(_helper.Captured, target);
- RaisePointerReleased(target, mouse, MouseButton.Left);
+ RaisePointerReleased(target, MouseButton.Left, new Point(200, 50));
- Assert.Equal(captured, null);
+ Assert.Equal(_helper.Captured, null);
Assert.False(clicked);
}
@@ -175,12 +169,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Button_With_RenderTransform_Raises_Click()
{
- var mouse = Mock.Of();
var renderer = Mock.Of();
- IInputElement captured = null;
- Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny())).Returns(new Point(150, 50));
- Mock.Get(mouse).Setup(m => m.Capture(It.IsAny())).Callback(v => captured = v);
- Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured);
+ var pt = new Point(150, 50);
Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>()))
.Returns>((p, r, f) =>
r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ?
@@ -204,15 +194,15 @@ namespace Avalonia.Controls.UnitTests
target.Click += (s, e) => clicked = true;
- RaisePointerEnter(target, mouse);
- RaisePointerMove(target, mouse);
- RaisePointerPressed(target, mouse, 1, MouseButton.Left);
+ RaisePointerEnter(target);
+ RaisePointerMove(target, pt);
+ RaisePointerPressed(target, 1, MouseButton.Left, pt);
- Assert.Equal(captured, target);
+ Assert.Equal(_helper.Captured, target);
- RaisePointerReleased(target, mouse, MouseButton.Left);
+ RaisePointerReleased(target, MouseButton.Left, pt);
- Assert.Equal(captured, null);
+ Assert.Equal(_helper.Captured, null);
Assert.True(clicked);
}
@@ -278,57 +268,29 @@ namespace Avalonia.Controls.UnitTests
public PixelPoint PointToScreen(Point p) => throw new NotImplementedException();
}
- private void RaisePointerPressed(Button button, IMouseDevice device, int clickCount, MouseButton mouseButton)
+ private void RaisePointerPressed(Button button, int clickCount, MouseButton mouseButton, Point position)
{
- button.RaiseEvent(new PointerPressedEventArgs
- {
- RoutedEvent = InputElement.PointerPressedEvent,
- Source = button,
- MouseButton = mouseButton,
- ClickCount = clickCount,
- Device = device,
- });
+ _helper.Down(button, mouseButton, position, clickCount: clickCount);
}
- private void RaisePointerReleased(Button button, IMouseDevice device, MouseButton mouseButton)
+ private void RaisePointerReleased(Button button, MouseButton mouseButton, Point pt)
{
- button.RaiseEvent(new PointerReleasedEventArgs
- {
- RoutedEvent = InputElement.PointerReleasedEvent,
- Source = button,
- MouseButton = mouseButton,
- Device = device,
- });
+ _helper.Up(button, mouseButton, pt);
}
- private void RaisePointerEnter(Button button, IMouseDevice device)
+ private void RaisePointerEnter(Button button)
{
- button.RaiseEvent(new PointerEventArgs
- {
- RoutedEvent = InputElement.PointerEnterEvent,
- Source = button,
- Device = device,
- });
+ _helper.Enter(button);
}
- private void RaisePointerLeave(Button button, IMouseDevice device)
+ private void RaisePointerLeave(Button button)
{
- button.RaiseEvent(new PointerEventArgs
- {
- RoutedEvent = InputElement.PointerLeaveEvent,
- Source = button,
- Device = device,
- });
+ _helper.Leave(button);
}
- private void RaisePointerMove(Button button, IMouseDevice device)
+ private void RaisePointerMove(Button button, Point pos)
{
- button.RaiseEvent(new PointerEventArgs
- {
- RoutedEvent = InputElement.PointerMovedEvent,
- Source = button,
- Device = device,
- });
+ _helper.Move(button, pos);
}
private class TestCommand : ICommand
diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
index fdbca70350..70ec6c1408 100644
--- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
@@ -15,6 +15,8 @@ namespace Avalonia.Controls.UnitTests
{
public class ComboBoxTests
{
+ MouseTestHelper _helper = new MouseTestHelper();
+
[Fact]
public void Clicking_On_Control_Toggles_IsDropDownOpen()
{
@@ -23,17 +25,11 @@ namespace Avalonia.Controls.UnitTests
Items = new[] { "Foo", "Bar" },
};
- target.RaiseEvent(new PointerPressedEventArgs
- {
- RoutedEvent = InputElement.PointerPressedEvent,
- });
-
+ _helper.Down(target);
+ _helper.Up(target);
Assert.True(target.IsDropDownOpen);
- target.RaiseEvent(new PointerPressedEventArgs
- {
- RoutedEvent = InputElement.PointerPressedEvent,
- });
+ _helper.Down(target);
Assert.False(target.IsDropDownOpen);
}
diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
index 834c49ba6b..067c66969f 100644
--- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
@@ -14,6 +14,7 @@ namespace Avalonia.Controls.UnitTests
public class ContextMenuTests
{
private Mock popupImpl;
+ private MouseTestHelper _mouse = new MouseTestHelper();
[Fact]
public void Clicking_On_Control_Toggles_ContextMenu()
@@ -31,19 +32,11 @@ namespace Avalonia.Controls.UnitTests
new Window { Content = target };
- target.RaiseEvent(new PointerReleasedEventArgs
- {
- RoutedEvent = InputElement.PointerReleasedEvent,
- MouseButton = MouseButton.Right
- });
+ _mouse.Click(target, MouseButton.Right);
Assert.True(sut.IsOpen);
- target.RaiseEvent(new PointerReleasedEventArgs
- {
- RoutedEvent = InputElement.PointerReleasedEvent,
- MouseButton = MouseButton.None
- });
+ _mouse.Click(target);
Assert.False(sut.IsOpen);
popupImpl.Verify(x => x.Show(), Times.Once);
@@ -69,19 +62,11 @@ namespace Avalonia.Controls.UnitTests
Avalonia.Application.Current.MainWindow = window;
- target.RaiseEvent(new PointerReleasedEventArgs
- {
- RoutedEvent = InputElement.PointerReleasedEvent,
- MouseButton = MouseButton.Right
- });
+ _mouse.Click(target, MouseButton.Right);
Assert.True(sut.IsOpen);
- target.RaiseEvent(new PointerReleasedEventArgs
- {
- RoutedEvent = InputElement.PointerReleasedEvent,
- MouseButton = MouseButton.Right
- });
+ _mouse.Click(target, MouseButton.Right);
Assert.True(sut.IsOpen);
popupImpl.Verify(x => x.Hide(), Times.Once);
@@ -106,11 +91,7 @@ namespace Avalonia.Controls.UnitTests
sut.ContextMenuOpening += (c, e) => { eventCalled = true; e.Cancel = true; };
- target.RaiseEvent(new PointerReleasedEventArgs
- {
- RoutedEvent = InputElement.PointerReleasedEvent,
- MouseButton = MouseButton.Right
- });
+ _mouse.Click(target, MouseButton.Right);
Assert.True(eventCalled);
Assert.False(sut.IsOpen);
@@ -136,19 +117,11 @@ namespace Avalonia.Controls.UnitTests
sut.ContextMenuClosing += (c, e) => { eventCalled = true; e.Cancel = true; };
- target.RaiseEvent(new PointerReleasedEventArgs
- {
- RoutedEvent = InputElement.PointerReleasedEvent,
- MouseButton = MouseButton.Right
- });
+ _mouse.Click(target, MouseButton.Right);
Assert.True(sut.IsOpen);
- target.RaiseEvent(new PointerReleasedEventArgs
- {
- RoutedEvent = InputElement.PointerReleasedEvent,
- MouseButton = MouseButton.None
- });
+ _mouse.Click(target, MouseButton.Right);
Assert.True(eventCalled);
Assert.True(sut.IsOpen);
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
index 343d8d41f3..238e214a5d 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
@@ -16,6 +16,8 @@ namespace Avalonia.Controls.UnitTests
{
public class ListBoxTests
{
+ private MouseTestHelper _mouse = new MouseTestHelper();
+
[Fact]
public void Should_Use_ItemTemplate_To_Create_Item_Content()
{
@@ -225,12 +227,7 @@ namespace Avalonia.Controls.UnitTests
private void RaisePressedEvent(ListBox listBox, ListBoxItem item, MouseButton mouseButton)
{
- listBox.RaiseEvent(new PointerPressedEventArgs
- {
- Source = item,
- RoutedEvent = InputElement.PointerPressedEvent,
- MouseButton = mouseButton
- });
+ _mouse.Click(listBox, item, mouseButton);
}
[Fact]
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
index 70d59e82c8..de34558ad1 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
@@ -18,6 +18,8 @@ namespace Avalonia.Controls.UnitTests
{
public class ListBoxTests_Single
{
+ MouseTestHelper _mouse = new MouseTestHelper();
+
[Fact]
public void Focusing_Item_With_Tab_Should_Not_Select_It()
{
@@ -68,12 +70,7 @@ namespace Avalonia.Controls.UnitTests
};
ApplyTemplate(target);
-
- target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
- {
- RoutedEvent = InputElement.PointerPressedEvent,
- MouseButton = MouseButton.Left,
- });
+ _mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
@@ -90,11 +87,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
target.SelectedIndex = 0;
- target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
- {
- RoutedEvent = InputElement.PointerPressedEvent,
- MouseButton = MouseButton.Left,
- });
+ _mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
@@ -111,11 +104,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
- target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
- {
- RoutedEvent = InputElement.PointerPressedEvent,
- MouseButton = MouseButton.Left,
- });
+ _mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
@@ -133,11 +122,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
target.SelectedIndex = 0;
- target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
- {
- RoutedEvent = InputElement.PointerPressedEvent,
- MouseButton = MouseButton.Left,
- });
+ _mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(-1, target.SelectedIndex);
}
@@ -155,11 +140,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
target.SelectedIndex = 0;
- target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
- {
- RoutedEvent = InputElement.PointerPressedEvent,
- MouseButton = MouseButton.Left,
- });
+ _mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
@@ -177,11 +158,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
target.SelectedIndex = 1;
- target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
- {
- RoutedEvent = InputElement.PointerPressedEvent,
- MouseButton = MouseButton.Left,
- });
+ _mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
@@ -306,4 +283,4 @@ namespace Avalonia.Controls.UnitTests
target.Presenter.ApplyTemplate();
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs b/tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs
new file mode 100644
index 0000000000..d6542d23f0
--- /dev/null
+++ b/tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs
@@ -0,0 +1,116 @@
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls.UnitTests
+{
+ public class MouseTestHelper
+ {
+
+ class TestPointer : IPointer
+ {
+ public int Id { get; } = Pointer.GetNextFreeId();
+
+ public void Capture(IInputElement control)
+ {
+ Captured = control;
+ }
+
+ public IInputElement Captured { get; set; }
+ public PointerType Type => PointerType.Mouse;
+ public bool IsPrimary => true;
+ }
+
+ TestPointer _pointer = new TestPointer();
+
+ private InputModifiers _pressedButtons;
+ public IInputElement Captured => _pointer.Captured;
+
+ InputModifiers Convert(MouseButton mouseButton)
+ => (mouseButton == MouseButton.Left ? InputModifiers.LeftMouseButton
+ : mouseButton == MouseButton.Middle ? InputModifiers.MiddleMouseButton
+ : mouseButton == MouseButton.Right ? InputModifiers.RightMouseButton : InputModifiers.None);
+
+ int ButtonCount(PointerPointProperties props)
+ {
+ var rv = 0;
+ if (props.IsLeftButtonPressed)
+ rv++;
+ if (props.IsMiddleButtonPressed)
+ rv++;
+ if (props.IsRightButtonPressed)
+ rv++;
+ return rv;
+ }
+
+ private MouseButton _pressedButton;
+
+ InputModifiers GetModifiers(InputModifiers modifiers) => modifiers | _pressedButtons;
+
+ public void Down(IInteractive target, MouseButton mouseButton = MouseButton.Left, Point position = default,
+ InputModifiers modifiers = default, int clickCount = 1)
+ => Down(target, target, mouseButton, position, modifiers, clickCount);
+
+ public void Down(IInteractive target, IInteractive source, MouseButton mouseButton = MouseButton.Left,
+ Point position = default, InputModifiers modifiers = default, int clickCount = 1)
+ {
+ _pressedButtons |= Convert(mouseButton);
+ var props = new PointerPointProperties(_pressedButtons);
+ if (ButtonCount(props) > 1)
+ Move(target, source, position);
+ else
+ {
+ _pressedButton = mouseButton;
+ target.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (IVisual)source, position, props,
+ GetModifiers(modifiers), clickCount));
+ }
+ }
+
+ public void Move(IInteractive target, in Point position, InputModifiers modifiers = default) => Move(target, target, position, modifiers);
+ public void Move(IInteractive target, IInteractive source, in Point position, InputModifiers modifiers = default)
+ {
+ target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (IVisual)target, position,
+ new PointerPointProperties(_pressedButtons), GetModifiers(modifiers)));
+ }
+
+ public void Up(IInteractive target, MouseButton mouseButton = MouseButton.Left, Point position = default,
+ InputModifiers modifiers = default)
+ => Up(target, target, mouseButton, position, modifiers);
+
+ public void Up(IInteractive target, IInteractive source, MouseButton mouseButton = MouseButton.Left,
+ Point position = default, InputModifiers modifiers = default)
+ {
+ var conv = Convert(mouseButton);
+ _pressedButtons = (_pressedButtons | conv) ^ conv;
+ var props = new PointerPointProperties(_pressedButtons);
+ if (ButtonCount(props) == 0)
+ target.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (IVisual)target, position, props,
+ GetModifiers(modifiers), _pressedButton));
+ else
+ Move(target, source, position);
+ }
+
+ public void Click(IInteractive target, MouseButton button = MouseButton.Left, Point position = default,
+ InputModifiers modifiers = default)
+ => Click(target, target, button, position, modifiers);
+ public void Click(IInteractive target, IInteractive source, MouseButton button = MouseButton.Left,
+ Point position = default, InputModifiers modifiers = default)
+ {
+ Down(target, source, button, position, modifiers);
+ Up(target, source, button, position, modifiers);
+ }
+
+ public void Enter(IInteractive target)
+ {
+ target.RaiseEvent(new PointerEventArgs(InputElement.PointerEnterEvent, target, _pointer, (IVisual)target, default,
+ new PointerPointProperties(_pressedButtons), _pressedButtons));
+ }
+
+ public void Leave(IInteractive target)
+ {
+ target.RaiseEvent(new PointerEventArgs(InputElement.PointerLeaveEvent, target, _pointer, (IVisual)target, default,
+ new PointerPointProperties(_pressedButtons), _pressedButtons));
+ }
+
+ }
+}
diff --git a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
index df1846c617..fb3a5bfefb 100644
--- a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
@@ -2,6 +2,7 @@
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Interactivity;
+using Avalonia.VisualTree;
using Moq;
using Xunit;
@@ -9,6 +10,16 @@ namespace Avalonia.Controls.UnitTests.Platform
{
public class DefaultMenuInteractionHandlerTests
{
+ static PointerEventArgs CreateArgs(RoutedEvent ev, IInteractive source)
+ => new PointerEventArgs(ev, source, new FakePointer(), (IVisual)source, default, new PointerPointProperties(), default);
+
+ static PointerPressedEventArgs CreatePressed(IInteractive source) => new PointerPressedEventArgs(source,
+ new FakePointer(), (IVisual)source, default, new PointerPointProperties {IsLeftButtonPressed = true},
+ default);
+
+ static PointerReleasedEventArgs CreateReleased(IInteractive source) => new PointerReleasedEventArgs(source,
+ new FakePointer(), (IVisual)source, default, new PointerPointProperties(), default, MouseButton.Left);
+
public class TopLevel
{
[Fact]
@@ -121,7 +132,8 @@ namespace Avalonia.Controls.UnitTests.Platform
x.IsTopLevel == true &&
x.HasSubMenu == true &&
x.Parent == menu);
- var e = new PointerPressedEventArgs { MouseButton = MouseButton.Left, Source = item };
+
+ var e = CreatePressed(item);
target.PointerPressed(item, e);
Mock.Get(menu).Verify(x => x.Close());
@@ -141,7 +153,7 @@ namespace Avalonia.Controls.UnitTests.Platform
x.IsTopLevel == true &&
x.HasSubMenu == true &&
x.Parent == menu.Object);
- var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = nextItem };
+ var e = CreateArgs(MenuItem.PointerEnterItemEvent, nextItem);
menu.SetupGet(x => x.SelectedItem).Returns(item);
@@ -161,7 +173,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var target = new DefaultMenuInteractionHandler(false);
var menu = new Mock();
var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu.Object);
- var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+ var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
menu.SetupGet(x => x.SelectedItem).Returns(item);
target.PointerLeave(item, e);
@@ -176,7 +188,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var target = new DefaultMenuInteractionHandler(false);
var menu = new Mock();
var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu.Object);
- var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+ var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
menu.SetupGet(x => x.IsOpen).Returns(true);
menu.SetupGet(x => x.SelectedItem).Returns(item);
@@ -330,7 +342,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of();
var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of(x => x.Parent == parentItem);
- var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
+ var e = CreateArgs(MenuItem.PointerEnterItemEvent, item);
target.PointerEnter(item, e);
@@ -346,7 +358,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of();
var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true);
- var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
+ var e = CreateArgs(MenuItem.PointerEnterItemEvent, item);
target.PointerEnter(item, e);
Mock.Get(item).Verify(x => x.Open(), Times.Never);
@@ -366,7 +378,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of(x => x.Parent == parentItem);
var sibling = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsSubMenuOpen == true);
- var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
+ var e = CreateArgs(MenuItem.PointerEnterItemEvent, item);
Mock.Get(parentItem).SetupGet(x => x.SubItems).Returns(new[] { item, sibling });
@@ -386,7 +398,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of();
var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of(x => x.Parent == parentItem);
- var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+ var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(item);
target.PointerLeave(item, e);
@@ -403,7 +415,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of(x => x.Parent == parentItem);
var sibling = Mock.Of(x => x.Parent == parentItem);
- var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+ var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(sibling);
target.PointerLeave(item, e);
@@ -419,7 +431,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of();
var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsPointerOverSubMenu == true);
- var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+ var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
target.PointerLeave(item, e);
@@ -434,7 +446,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of();
var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of(x => x.Parent == parentItem);
- var e = new PointerReleasedEventArgs { MouseButton = MouseButton.Left, Source = item };
+ var e = CreateReleased(item);
target.PointerReleased(item, e);
@@ -452,8 +464,8 @@ namespace Avalonia.Controls.UnitTests.Platform
var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true);
var childItem = Mock.Of(x => x.Parent == item);
- var enter = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
- var leave = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
+ var enter = CreateArgs(MenuItem.PointerEnterItemEvent, item);
+ var leave = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
// Pointer enters item; item is selected.
target.PointerEnter(item, enter);
@@ -488,7 +500,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of();
var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true);
- var e = new PointerPressedEventArgs { MouseButton = MouseButton.Left, Source = item };
+ var e = CreatePressed(item);
target.PointerPressed(item, e);
@@ -537,5 +549,19 @@ namespace Avalonia.Controls.UnitTests.Platform
_action = action;
}
}
+
+ class FakePointer : IPointer
+ {
+ public int Id { get; } = Pointer.GetNextFreeId();
+
+ public void Capture(IInputElement control)
+ {
+ Captured = control;
+ }
+
+ public IInputElement Captured { get; set; }
+ public PointerType Type { get; }
+ public bool IsPrimary { get; } = true;
+ }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
index 037c16e231..3b5aa53d56 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
@@ -22,6 +22,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
public class SelectingItemsControlTests
{
+ private MouseTestHelper _helper = new MouseTestHelper();
+
[Fact]
public void SelectedIndex_Should_Initially_Be_Minus_1()
{
@@ -675,12 +677,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
-
- target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs
- {
- RoutedEvent = InputElement.PointerPressedEvent,
- MouseButton = MouseButton.Left,
- });
+ _helper.Down((Interactive)target.Presenter.Panel.Children[1]);
var panel = target.Presenter.Panel;
@@ -703,11 +700,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
- target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs
- {
- RoutedEvent = InputElement.PointerPressedEvent,
- MouseButton = MouseButton.Left,
- });
+ _helper.Down(target.Presenter.Panel.Children[1]);
items.RemoveAt(1);
diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
index 15081b184c..b66d6ed11c 100644
--- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
@@ -19,6 +19,8 @@ namespace Avalonia.Controls.UnitTests
{
public class TreeViewTests
{
+ MouseTestHelper _mouse = new MouseTestHelper();
+
[Fact]
public void Items_Should_Be_Created()
{
@@ -130,11 +132,7 @@ namespace Avalonia.Controls.UnitTests
Assert.NotNull(container);
- container.RaiseEvent(new PointerPressedEventArgs
- {
- RoutedEvent = InputElement.PointerPressedEvent,
- MouseButton = MouseButton.Left,
- });
+ _mouse.Click(container);
Assert.Equal(item, target.SelectedItem);
Assert.True(container.IsSelected);
@@ -165,12 +163,7 @@ namespace Avalonia.Controls.UnitTests
Assert.True(container.IsSelected);
- container.RaiseEvent(new PointerPressedEventArgs
- {
- RoutedEvent = InputElement.PointerPressedEvent,
- MouseButton = MouseButton.Left,
- InputModifiers = InputModifiers.Control
- });
+ _mouse.Click(container, modifiers: InputModifiers.Control);
Assert.Null(target.SelectedItem);
Assert.False(container.IsSelected);
@@ -205,13 +198,8 @@ namespace Avalonia.Controls.UnitTests
Assert.True(container1.IsSelected);
- container2.RaiseEvent(new PointerPressedEventArgs
- {
- RoutedEvent = InputElement.PointerPressedEvent,
- MouseButton = MouseButton.Left,
- InputModifiers = InputModifiers.Control
- });
-
+ _mouse.Click(container2, modifiers: InputModifiers.Control);
+
Assert.Equal(item2, target.SelectedItem);
Assert.False(container1.IsSelected);
Assert.True(container2.IsSelected);
@@ -242,15 +230,15 @@ namespace Avalonia.Controls.UnitTests
var item1Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1);
var item2Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2);
- TreeTestHelper.ClickContainer(item1Container, InputModifiers.Control);
+ ClickContainer(item1Container, InputModifiers.Control);
Assert.True(item1Container.IsSelected);
- TreeTestHelper.ClickContainer(item2Container, InputModifiers.Control);
+ ClickContainer(item2Container, InputModifiers.Control);
Assert.True(item2Container.IsSelected);
Assert.Equal(new[] {item1, item2}, target.SelectedItems.OfType());
- TreeTestHelper.ClickContainer(item1Container, InputModifiers.Control);
+ ClickContainer(item1Container, InputModifiers.Control);
Assert.False(item1Container.IsSelected);
Assert.DoesNotContain(item1, target.SelectedItems.OfType());
@@ -281,12 +269,12 @@ namespace Avalonia.Controls.UnitTests
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
- TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
+ ClickContainer(fromContainer, InputModifiers.None);
Assert.True(fromContainer.IsSelected);
- TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
- TreeTestHelper.AssertChildrenSelected(target, rootNode);
+ ClickContainer(toContainer, InputModifiers.Shift);
+ AssertChildrenSelected(target, rootNode);
}
[Fact]
@@ -314,12 +302,12 @@ namespace Avalonia.Controls.UnitTests
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
- TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
+ ClickContainer(fromContainer, InputModifiers.None);
Assert.True(fromContainer.IsSelected);
- TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
- TreeTestHelper.AssertChildrenSelected(target, rootNode);
+ ClickContainer(toContainer, InputModifiers.Shift);
+ AssertChildrenSelected(target, rootNode);
}
[Fact]
@@ -347,12 +335,12 @@ namespace Avalonia.Controls.UnitTests
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
- TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
+ ClickContainer(fromContainer, InputModifiers.None);
- TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
- TreeTestHelper.AssertChildrenSelected(target, rootNode);
+ ClickContainer(toContainer, InputModifiers.Shift);
+ AssertChildrenSelected(target, rootNode);
- TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
+ ClickContainer(fromContainer, InputModifiers.None);
Assert.True(fromContainer.IsSelected);
@@ -656,7 +644,7 @@ namespace Avalonia.Controls.UnitTests
target.RaiseEvent(keyEvent);
- TreeTestHelper.AssertChildrenSelected(target, rootNode);
+ AssertChildrenSelected(target, rootNode);
}
}
@@ -687,8 +675,8 @@ namespace Avalonia.Controls.UnitTests
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
- TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
- TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
+ ClickContainer(fromContainer, InputModifiers.None);
+ ClickContainer(toContainer, InputModifiers.Shift);
var keymap = AvaloniaLocator.Current.GetService();
var selectAllGesture = keymap.SelectAll.First();
@@ -702,7 +690,7 @@ namespace Avalonia.Controls.UnitTests
target.RaiseEvent(keyEvent);
- TreeTestHelper.AssertChildrenSelected(target, rootNode);
+ AssertChildrenSelected(target, rootNode);
}
}
@@ -733,8 +721,8 @@ namespace Avalonia.Controls.UnitTests
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
- TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
- TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
+ ClickContainer(fromContainer, InputModifiers.None);
+ ClickContainer(toContainer, InputModifiers.Shift);
var keymap = AvaloniaLocator.Current.GetService();
var selectAllGesture = keymap.SelectAll.First();
@@ -748,7 +736,7 @@ namespace Avalonia.Controls.UnitTests
target.RaiseEvent(keyEvent);
- TreeTestHelper.AssertChildrenSelected(target, rootNode);
+ AssertChildrenSelected(target, rootNode);
}
}
@@ -871,29 +859,22 @@ namespace Avalonia.Controls.UnitTests
}
}
- private static class TreeTestHelper
+ void ClickContainer(IControl container, InputModifiers modifiers)
{
- public static void ClickContainer(IControl container, InputModifiers modifiers)
- {
- container.RaiseEvent(new PointerPressedEventArgs
- {
- RoutedEvent = InputElement.PointerPressedEvent,
- MouseButton = MouseButton.Left,
- InputModifiers = modifiers
- });
- }
+ _mouse.Click(container, modifiers: modifiers);
+ }
- public static void AssertChildrenSelected(TreeView treeView, Node rootNode)
+ void AssertChildrenSelected(TreeView treeView, Node rootNode)
+ {
+ foreach (var child in rootNode.Children)
{
- foreach (var child in rootNode.Children)
- {
- var container = (TreeViewItem)treeView.ItemContainerGenerator.Index.ContainerFromItem(child);
+ var container = (TreeViewItem)treeView.ItemContainerGenerator.Index.ContainerFromItem(child);
- Assert.True(container.IsSelected);
- }
+ Assert.True(container.IsSelected);
}
}
+
private class Node : NotifyingBase
{
private IAvaloniaList _children;
diff --git a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs b/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs
index af034f541b..8f1c071695 100644
--- a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs
+++ b/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs
@@ -225,11 +225,11 @@ namespace Avalonia.Input.UnitTests
private void SendMouseMove(IInputManager inputManager, TestRoot root, Point p = new Point())
{
- inputManager.ProcessInput(new RawMouseEventArgs(
+ inputManager.ProcessInput(new RawPointerEventArgs(
root.MouseDevice,
0,
root,
- RawMouseEventType.Move,
+ RawPointerEventType.Move,
p,
InputModifiers.None));
}
diff --git a/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj b/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj
index 6b19d81034..7316b1de3d 100644
--- a/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj
+++ b/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj
@@ -4,6 +4,7 @@
netcoreapp2.0;net47
Library
true
+ latest
@@ -19,6 +20,7 @@
+
diff --git a/tests/Avalonia.Interactivity.UnitTests/GestureTests.cs b/tests/Avalonia.Interactivity.UnitTests/GestureTests.cs
index bebf7f9bc9..69bdf58f9d 100644
--- a/tests/Avalonia.Interactivity.UnitTests/GestureTests.cs
+++ b/tests/Avalonia.Interactivity.UnitTests/GestureTests.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using Avalonia.Controls;
+using Avalonia.Controls.UnitTests;
using Avalonia.Input;
using Xunit;
@@ -10,6 +11,8 @@ namespace Avalonia.Interactivity.UnitTests
{
public class GestureTests
{
+ private MouseTestHelper _mouse = new MouseTestHelper();
+
[Fact]
public void Tapped_Should_Follow_Pointer_Pressed_Released()
{
@@ -27,8 +30,7 @@ namespace Avalonia.Interactivity.UnitTests
border.AddHandler(Border.PointerReleasedEvent, (s, e) => result.Add("br"));
border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
- border.RaiseEvent(new PointerPressedEventArgs());
- border.RaiseEvent(new PointerReleasedEventArgs());
+ _mouse.Click(border);
Assert.Equal(new[] { "bp", "dp", "br", "dr", "bt", "dt" }, result);
}
@@ -47,8 +49,7 @@ namespace Avalonia.Interactivity.UnitTests
decorator.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("dt"));
border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
- border.RaiseEvent(new PointerPressedEventArgs());
- border.RaiseEvent(new PointerReleasedEventArgs());
+ _mouse.Click(border);
Assert.Equal(new[] { "bt", "dt" }, result);
}
@@ -72,9 +73,8 @@ namespace Avalonia.Interactivity.UnitTests
border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
border.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("bdt"));
- border.RaiseEvent(new PointerPressedEventArgs());
- border.RaiseEvent(new PointerReleasedEventArgs());
- border.RaiseEvent(new PointerPressedEventArgs { ClickCount = 2 });
+ _mouse.Click(border);
+ _mouse.Down(border, clickCount: 2);
Assert.Equal(new[] { "bp", "dp", "br", "dr", "bt", "dt", "bp", "dp", "bdt", "ddt" }, result);
}
@@ -103,9 +103,8 @@ namespace Avalonia.Interactivity.UnitTests
border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
border.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("bdt"));
- border.RaiseEvent(new PointerPressedEventArgs());
- border.RaiseEvent(new PointerReleasedEventArgs());
- border.RaiseEvent(new PointerPressedEventArgs { ClickCount = 2 });
+ _mouse.Click(border);
+ _mouse.Down(border, clickCount: 2);
Assert.Equal(new[] { "bp", "dp", "br", "dr", "bt", "dt", "bp", "dp" }, result);
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
index 795aba153a..3511919e39 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
@@ -137,8 +137,9 @@ namespace Avalonia.Markup.Xaml.UnitTests
.GetVisualChildren().First()
.GetVisualChildren().First()
.GetVisualChildren().First();
- ((Control)item).RaiseEvent(new PointerPressedEventArgs {ClickCount = 20});
- Assert.Equal(20, w.Args.ClickCount);
+
+ ((Control)item).DataContext = "test";
+ Assert.Equal("test", w.SavedContext);
}
}
@@ -161,10 +162,10 @@ namespace Avalonia.Markup.Xaml.UnitTests
public class XamlIlBugTestsEventHandlerCodeBehind : Window
{
- public PointerPressedEventArgs Args;
- public void HandlePointerPressed(object sender, PointerPressedEventArgs args)
+ public object SavedContext;
+ public void HandleDataContextChanged(object sender, EventArgs args)
{
- Args = args;
+ SavedContext = ((Control)sender).DataContext;
}
public XamlIlBugTestsEventHandlerCodeBehind()
@@ -178,7 +179,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
-
+