Browse Source

feat: Support ContactRect in X11 and Windows platform (#16498)

* feat: Support ContactRect in X11 platform

* Provide compat value for ContactRect property

* Using touch position to get the screen.

* Try get the correct screen

* Remove the code of log.

* Improve performance avoid the GC stress.

* Add a second overload in PointerPointProperties to avoid break API change

* Support touch size in WM_Touch

* Support get ContactRect in WM_POINTER

* Fix the coordinate of touch contact area

* Fix the calcuate of WM_Touch contact area

* Fix the X11 area contact

* Fix the touch major and minor default value is not null.

The XIValuatorClassInfo is struct, so the FirstOrDefault will return the default struct when not found.
xclass-generator
lindexi 2 years ago
committed by GitHub
parent
commit
35328892c7
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      src/Avalonia.Base/Input/PenDevice.cs
  2. 16
      src/Avalonia.Base/Input/PointerPoint.cs
  3. 8
      src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs
  4. 4
      src/Avalonia.X11/X11Platform.cs
  5. 86
      src/Avalonia.X11/XI2Manager.cs
  6. 60
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

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

@ -52,7 +52,7 @@ namespace Avalonia.Input
}
var props = new PointerPointProperties(e.InputModifiers, e.Type.ToUpdateKind(),
e.Point.Twist, e.Point.Pressure, e.Point.XTilt, e.Point.YTilt);
e.Point.Twist, e.Point.Pressure, e.Point.XTilt, e.Point.YTilt, e.Point.ContactRect);
var keyModifiers = e.InputModifiers.ToKeyModifiers();
bool shouldReleasePointer = false;

16
src/Avalonia.Base/Input/PointerPoint.cs

@ -35,6 +35,11 @@ namespace Avalonia.Input
/// </summary>
public record struct PointerPointProperties
{
/// <summary>
/// Gets the bounding rectangle of the contact area (typically from touch input).
/// </summary>
public Rect ContactRect { get; }
/// <summary>
/// Gets a value that indicates whether the pointer input was triggered by the primary action mode of an input device.
/// </summary>
@ -155,17 +160,22 @@ namespace Avalonia.Input
}
public PointerPointProperties(RawInputModifiers modifiers, PointerUpdateKind kind,
float twist, float pressure, float xTilt, float yTilt
) : this (modifiers, kind)
float twist, float pressure, float xTilt, float yTilt) : this(modifiers, kind, twist, pressure, xTilt, yTilt, default)
{
}
public PointerPointProperties(RawInputModifiers modifiers, PointerUpdateKind kind,
float twist, float pressure, float xTilt, float yTilt, Rect contactRect) : this(modifiers, kind)
{
Twist = twist;
Pressure = pressure;
XTilt = xTilt;
YTilt = yTilt;
ContactRect = contactRect;
}
internal PointerPointProperties(RawInputModifiers modifiers, PointerUpdateKind kind, RawPointerPoint rawPoint)
: this(modifiers, kind, rawPoint.Twist, rawPoint.Pressure, rawPoint.XTilt, rawPoint.YTilt)
: this(modifiers, kind, rawPoint.Twist, rawPoint.Pressure, rawPoint.XTilt, rawPoint.YTilt, rawPoint.ContactRect)
{
}

8
src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs

@ -143,6 +143,14 @@ namespace Avalonia.Input.Raw
/// <inheritdoc cref="PointerPointProperties.YTilt" />
public float YTilt { get; set; }
/// <inheritdoc cref="PointerPointProperties.ContactRect" />
public Rect ContactRect
{
get => _contactRect ?? new Rect(Position, new Size());
set => _contactRect = value;
}
private Rect? _contactRect;
public RawPointerPoint()
{

4
src/Avalonia.X11/X11Platform.cs

@ -26,9 +26,9 @@ namespace Avalonia.X11
{
private Lazy<KeyboardDevice> _keyboardDevice = new Lazy<KeyboardDevice>(() => new KeyboardDevice());
public KeyboardDevice KeyboardDevice => _keyboardDevice.Value;
public Dictionary<IntPtr, X11PlatformThreading.EventHandler> Windows =
public Dictionary<IntPtr, X11PlatformThreading.EventHandler> Windows { get; } =
new Dictionary<IntPtr, X11PlatformThreading.EventHandler>();
public XI2Manager XI2;
public XI2Manager XI2 { get; private set; }
public X11Info Info { get; private set; }
public X11Screens X11Screens { get; private set; }
public Compositor Compositor { get; private set; }

86
src/Avalonia.X11/XI2Manager.cs

@ -1,8 +1,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
@ -97,13 +101,15 @@ namespace Avalonia.X11
private AvaloniaX11Platform _platform;
private XIValuatorClassInfo? _pressureXIValuatorClassInfo;
private XIValuatorClassInfo? _touchMajorXIValuatorClassInfo;
private XIValuatorClassInfo? _touchMinorXIValuatorClassInfo;
public bool Init(AvaloniaX11Platform platform)
{
_platform = platform;
_x11 = platform.Info;
_multitouch = platform.Options?.EnableMultiTouch ?? true;
var devices =(XIDeviceInfo*) XIQueryDevice(_x11.Display,
var devices = (XIDeviceInfo*) XIQueryDevice(_x11.Display,
(int)XiPredefinedDeviceId.XIAllMasterDevices, out int num);
for (var c = 0; c < num; c++)
{
@ -118,8 +124,31 @@ namespace Avalonia.X11
if (_multitouch)
{
// ABS_MT_TOUCH_MAJOR ABS_MT_TOUCH_MINOR
// https://www.kernel.org/doc/html/latest/input/multi-touch-protocol.html
var touchMajorAtom = XInternAtom(_x11.Display, "Abs MT Touch Major", false);
var touchMinorAtom = XInternAtom(_x11.Display, "Abs MT Touch Minor", false);
var pressureAtom = XInternAtom(_x11.Display, "Abs MT Pressure", false);
_pressureXIValuatorClassInfo = _pointerDevice.Valuators.FirstOrDefault(t => t.Label == pressureAtom);
var pressureXIValuatorClassInfo = _pointerDevice.Valuators.FirstOrDefault(t => t.Label == pressureAtom);
if (pressureXIValuatorClassInfo.Label == pressureAtom)
{
// Why check twice? The XIValuatorClassInfo is struct, so the FirstOrDefault will return the default struct when not found.
_pressureXIValuatorClassInfo = pressureXIValuatorClassInfo;
}
var touchMajorXIValuatorClassInfo = _pointerDevice.Valuators.FirstOrDefault(t => t.Label == touchMajorAtom);
if (touchMajorXIValuatorClassInfo.Label == touchMajorAtom)
{
_touchMajorXIValuatorClassInfo = touchMajorXIValuatorClassInfo;
}
var touchMinorXIValuatorClassInfo = _pointerDevice.Valuators.FirstOrDefault(t => t.Label == touchMinorAtom);
if (touchMinorXIValuatorClassInfo.Label == touchMinorAtom)
{
_touchMinorXIValuatorClassInfo = touchMinorXIValuatorClassInfo;
}
}
/*
@ -256,6 +285,57 @@ namespace Avalonia.X11
}
}
if(_touchMajorXIValuatorClassInfo is {} touchMajorXIValuatorClassInfo)
{
double? touchMajor = null;
double? touchMinor = null;
PixelRect screenBounds = default;
if (ev.Valuators.TryGetValue(touchMajorXIValuatorClassInfo.Number, out var touchMajorValue))
{
var pixelPoint = new PixelPoint((int)ev.RootPosition.X, (int)ev.RootPosition.Y);
var screen = _platform.Screens.ScreenFromPoint(pixelPoint);
var screenBoundsFromPoint = screen?.Bounds;
Debug.Assert(screenBoundsFromPoint != null);
if (screenBoundsFromPoint != null)
{
screenBounds = screenBoundsFromPoint.Value;
// As https://www.kernel.org/doc/html/latest/input/multi-touch-protocol.html says, using `screenBounds.Width` is not accurate enough.
touchMajor = (touchMajorValue - touchMajorXIValuatorClassInfo.Min) /
(touchMajorXIValuatorClassInfo.Max - touchMajorXIValuatorClassInfo.Min) * screenBounds.Width;
}
}
if (touchMajor != null)
{
if(_touchMinorXIValuatorClassInfo is {} touchMinorXIValuatorClassInfo)
{
if (ev.Valuators.TryGetValue(touchMinorXIValuatorClassInfo.Number, out var touchMinorValue))
{
touchMinor = (touchMinorValue - touchMinorXIValuatorClassInfo.Min) /
(touchMinorXIValuatorClassInfo.Max - touchMinorXIValuatorClassInfo.Min) * screenBounds.Height;
}
}
if (touchMinor == null)
{
touchMinor = touchMajor;
}
var center = ev.Position;
var leftX = center.X - touchMajor.Value / 2;
var topY = center.Y - touchMinor.Value / 2;
rawPointerPoint.ContactRect = new Rect
(
leftX,
topY,
touchMajor.Value,
touchMinor.Value
);
}
}
client.ScheduleXI2Input(new RawTouchEventArgs(client.TouchDevice,
ev.Timestamp, client.InputRoot, type, rawPointerPoint, ev.Modifiers, ev.Detail));
return;
@ -340,6 +420,7 @@ namespace Avalonia.X11
public RawInputModifiers Modifiers { get; }
public ulong Timestamp { get; }
public Point Position { get; }
public Point RootPosition { get; }
public int Button { get; set; }
public int Detail { get; set; }
public bool Emulated { get; set; }
@ -385,6 +466,7 @@ namespace Avalonia.X11
Valuators = new Dictionary<int, double>();
Position = new Point(ev->event_x, ev->event_y);
RootPosition = new Point(ev->root_x, ev->root_y);
var values = ev->valuators.Values;
if(ev->valuators.Mask != null)
for (var c = 0; c < ev->valuators.MaskLen * 8; c++)

60
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -436,6 +436,38 @@ namespace Avalonia.Win32
{
foreach (var touchInput in touchInputs)
{
var position = PointToClient(new PixelPoint(touchInput.X / 100, touchInput.Y / 100));
var rawPointerPoint = new RawPointerPoint()
{
Position = position,
};
// Try to get the touch width and height.
// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-touchinput
// > The width of the touch contact area in hundredths of a pixel in physical screen coordinates. This value is only valid if the dwMask member has the TOUCHEVENTFMASK_CONTACTAREA flag set.
const int TOUCHEVENTFMASK_CONTACTAREA = 0x0004; // Known as TOUCHINPUTMASKF_CONTACTAREA in the docs.
if ((touchInput.Mask & TOUCHEVENTFMASK_CONTACTAREA) != 0)
{
var centerX = touchInput.X / 100.0;
var centerY = touchInput.Y / 100.0;
var rightX = centerX + touchInput.CxContact / 100.0 /
2 /*The center X add the half width is the right X*/;
var bottomY = centerY + touchInput.CyContact / 100.0 /
2 /*The center Y add the half height is the bottom Y*/;
var bottomRightPixelPoint =
new PixelPoint((int)rightX, (int)bottomY);
var bottomRightPosition = PointToClient(bottomRightPixelPoint);
var centerPosition = position;
var halfWidth = bottomRightPosition.X - centerPosition.X;
var halfHeight = bottomRightPosition.Y - centerPosition.Y;
var leftTopPosition = new Point(centerPosition.X - halfWidth, centerPosition.Y - halfHeight);
rawPointerPoint.ContactRect = new Rect(leftTopPosition, bottomRightPosition);
}
input.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time,
Owner,
touchInput.Flags.HasAllFlags(TouchInputFlags.TOUCHEVENTF_UP) ?
@ -443,7 +475,7 @@ namespace Avalonia.Win32
touchInput.Flags.HasAllFlags(TouchInputFlags.TOUCHEVENTF_DOWN) ?
RawPointerEventType.TouchBegin :
RawPointerEventType.TouchUpdate,
PointToClient(new PixelPoint(touchInput.X / 100, touchInput.Y / 100)),
rawPointerPoint,
WindowsKeyboardDevice.Instance.Modifiers,
touchInput.Id));
}
@ -1053,13 +1085,35 @@ namespace Avalonia.Win32
{
var pointerInfo = info.pointerInfo;
var point = PointToClient(new PixelPoint(pointerInfo.ptPixelLocationX, pointerInfo.ptPixelLocationY));
return new RawPointerPoint
var pointerPoint = new RawPointerPoint
{
Position = point,
// POINTER_PEN_INFO.pressure is normalized to a range between 0 and 1024, with 512 as a default.
// But in our API we use range from 0.0 to 1.0.
Pressure = info.pressure / 1024f
Pressure = info.pressure / 1024f,
};
// See https://learn.microsoft.com/en-us/windows/win32/inputmsg/touch-mask-constants
// > TOUCH_MASK_CONTACTAREA: rcContact of the POINTER_TOUCH_INFO structure is valid.
if ((info.touchMask & TouchMask.TOUCH_MASK_CONTACTAREA) != 0)
{
// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-pointer_touch_info
// > The predicted screen coordinates of the contact area, in pixels. By default, if the device does not report a contact area, this field defaults to a 0-by-0 rectangle centered around the pointer location.
var leftTopPixelPoint =
new PixelPoint(info.rcContactLeft, info.rcContactTop);
var leftTopPosition = PointToClient(leftTopPixelPoint);
var bottomRightPixelPoint =
new PixelPoint(info.rcContactRight, info.rcContactBottom);
var bottomRightPosition = PointToClient(bottomRightPixelPoint);
// Why not use ptPixelLocationX and ptPixelLocationY to as leftTopPosition?
// Because ptPixelLocationX and ptPixelLocationY will be the center of the contact area.
pointerPoint.ContactRect = new Rect(leftTopPosition, bottomRightPosition);
}
return pointerPoint;
}
private RawPointerPoint CreateRawPointerPoint(POINTER_PEN_INFO info)
{

Loading…
Cancel
Save