Skip to content
27 changes: 27 additions & 0 deletions sources/engine/Stride.Games/Desktop/GameForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using static Stride.Games.TouchUtils;

namespace Stride.Games
{
Expand Down Expand Up @@ -169,6 +170,11 @@ public GameForm()

internal bool IsFullScreen { get; set; }

internal delegate void TouchFingerDelegate(POINTER_TOUCH_INFO e);
internal event TouchFingerDelegate FingerMoveActions;
internal event TouchFingerDelegate FingerPressActions;
internal event TouchFingerDelegate FingerReleaseActions;

/// <summary>
/// Raises the <see cref="E:System.Windows.Forms.Form.ResizeBegin"/> event.
/// </summary>
Expand Down Expand Up @@ -430,6 +436,27 @@ protected override void WndProc(ref Message m)
isSwitchingFullScreen = false;
}
break;
// https://devblogs.microsoft.com/oldnewthing/20210728-00/?p=105487
case TouchUtils.WM_POINTERDOWN:
case TouchUtils.WM_POINTERUPDATE:
case TouchUtils.WM_POINTERUP:
var pointerId = TouchUtils.GET_POINTERID_WPARAM(unchecked((ulong)wparam));
if (TouchUtils.GetPointerType(pointerId, out var type) && type == TouchUtils.PointerInputType.PT_TOUCH)
{
TouchUtils.GetPointerTouchInfo(pointerId, out var touchInfo);

if (m.Msg == TouchUtils.WM_POINTERDOWN)
FingerPressActions?.Invoke(touchInfo);
else if (m.Msg == TouchUtils.WM_POINTERUP)
FingerReleaseActions?.Invoke(touchInfo);
else
FingerMoveActions?.Invoke(touchInfo);

// Per WM_POINTER documentation, return 0 to indicate the message was handled
m.Result = IntPtr.Zero;
return;
}
break;
}
base.WndProc(ref m);
}
Expand Down
129 changes: 129 additions & 0 deletions sources/engine/Stride.Games/Desktop/Win32Native.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,135 @@ public static extern sbyte GetMessage(out NativeMessage lpMsg, IntPtr hWnd, uint

public const int PM_REMOVE = 0x0001;
}

internal static class TouchUtils
{
public const int WM_POINTERDOWN = 0x0246;
public const int WM_POINTERUP = 0x0247;
public const int WM_POINTERUPDATE = 0x0245;
public const int WM_POINTERWHEEL = 0x024E;
public const int WM_POINTERHWHEEL = 0x024F;
public const int WM_POINTERCAPTURECHANGED = 0x024C;

[DllImport("user32.dll", SetLastError = true)]
public static extern bool GetPointerType(uint pointerId, out PointerInputType pointerType);

[DllImport("user32.dll", SetLastError = true)]
public static extern bool GetPointerTouchInfo(uint pointerId, out POINTER_TOUCH_INFO touchInfo);

[Flags]
public enum TouchFlags
{
TOUCH_FLAG_NONE = 0x00000000
}

[Flags]
public enum TouchMask
{
TOUCH_MASK_NONE = 0x00000000,
TOUCH_MASK_CONTACTAREA = 0x00000001,
TOUCH_MASK_ORIENTATION = 0x00000002,
TOUCH_MASK_PRESSURE = 0x00000004,
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct POINTER_TOUCH_INFO
{
public POINTER_INFO pointerInfo;
public TouchFlags touchFlags;
public TouchMask touchMask;
public int rcContactLeft;
public int rcContactTop;
public int rcContactRight;
public int rcContactBottom;
public int rcContactRawLeft;
public int rcContactRawTop;
public int rcContactRawRight;
public int rcContactRawBottom;
public uint orientation;
public uint pressure;
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct POINTER_INFO
{
public PointerInputType pointerType;
public uint pointerId;
public uint frameId;
public PointerFlags pointerFlags;
public IntPtr sourceDevice;
public IntPtr hwndTarget;
public int ptPixelLocationX;
public int ptPixelLocationY;
public int ptHimetricLocationX;
public int ptHimetricLocationY;
public int ptPixelLocationRawX;
public int ptPixelLocationRawY;
public int ptHimetricLocationRawX;
public int ptHimetricLocationRawY;
public uint dwTime;
public uint historyCount;
public int inputData;
public uint dwKeyStates;
public ulong PerformanceCount;
public PointerButtonChangeType ButtonChangeType;
}

public enum PointerInputType
{
PT_NONE = 0x00000000,
PT_POINTER = 0x00000001,
PT_TOUCH = 0x00000002,
PT_PEN = 0x00000003,
PT_MOUSE = 0x00000004,
PT_TOUCHPAD = 0x00000005
}

[Flags]
public enum PointerFlags
{
POINTER_FLAG_NONE = 0x00000000,
POINTER_FLAG_NEW = 0x00000001,
POINTER_FLAG_INRANGE = 0x00000002,
POINTER_FLAG_INCONTACT = 0x00000004,
POINTER_FLAG_FIRSTBUTTON = 0x00000010,
POINTER_FLAG_SECONDBUTTON = 0x00000020,
POINTER_FLAG_THIRDBUTTON = 0x00000040,
POINTER_FLAG_FOURTHBUTTON = 0x00000080,
POINTER_FLAG_FIFTHBUTTON = 0x00000100,
POINTER_FLAG_PRIMARY = 0x00002000,
POINTER_FLAG_CONFIDENCE = 0x00000400,
POINTER_FLAG_CANCELED = 0x00000800,
POINTER_FLAG_DOWN = 0x00010000,
POINTER_FLAG_UPDATE = 0x00020000,
POINTER_FLAG_UP = 0x00040000,
POINTER_FLAG_WHEEL = 0x00080000,
POINTER_FLAG_HWHEEL = 0x00100000,
POINTER_FLAG_CAPTURECHANGED = 0x00200000,
POINTER_FLAG_HASTRANSFORM = 0x00400000
}

public enum PointerButtonChangeType : ulong
{
POINTER_CHANGE_NONE,
POINTER_CHANGE_FIRSTBUTTON_DOWN,
POINTER_CHANGE_FIRSTBUTTON_UP,
POINTER_CHANGE_SECONDBUTTON_DOWN,
POINTER_CHANGE_SECONDBUTTON_UP,
POINTER_CHANGE_THIRDBUTTON_DOWN,
POINTER_CHANGE_THIRDBUTTON_UP,
POINTER_CHANGE_FOURTHBUTTON_DOWN,
POINTER_CHANGE_FOURTHBUTTON_UP,
POINTER_CHANGE_FIFTHBUTTON_DOWN,
POINTER_CHANGE_FIFTHBUTTON_UP
}

public static ushort LOWORD(ulong l) { return (ushort)(l & 0xFFFF); }
public static ushort HIWORD(ulong l) { return (ushort)((l >> 16) & 0xFFFF); }
public static ushort GET_POINTERID_WPARAM(ulong wParam) { return LOWORD(wParam); }
public static ushort GET_X_LPARAM(ulong lp) { return LOWORD(lp); }
public static ushort GET_Y_LPARAM(ulong lp) { return HIWORD(lp); }
}
}

#endif
35 changes: 35 additions & 0 deletions sources/engine/Stride.Input/PointerDeviceBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using Stride.Core.Collections;
using Stride.Core.Extensions;
using Stride.Core.Mathematics;

namespace Stride.Input
Expand All @@ -13,6 +14,9 @@ namespace Stride.Input
/// </summary>
public abstract class PointerDeviceBase : IPointerDevice
{
private readonly Dictionary<(long touchId, long fingerId), int> touchFingerIndexMap = new Dictionary<(long touchId, long fingerId), int>();
private int touchCounter;

protected PointerDeviceState PointerState;

protected PointerDeviceBase()
Expand Down Expand Up @@ -52,5 +56,36 @@ protected Vector2 Normalize(Vector2 position)
{
return position * PointerState.InverseSurfaceSize;
}

protected int GetFingerId(long touchId, long fingerId, PointerEventType type)
{
// Assign finger index (starting at 0) to touch ID
int touchFingerIndex = 0;
var key = (touchId, fingerId);
if (type == PointerEventType.Pressed)
{
touchFingerIndex = touchCounter++;
touchFingerIndexMap[key] = touchFingerIndex;
}
else
{
touchFingerIndexMap.TryGetValue(key, out touchFingerIndex);
}

// Remove index
if (type == PointerEventType.Released && touchFingerIndexMap.Remove(key))
{
touchCounter = 0; // Reset touch counter

// Recalculate next finger index
if (touchFingerIndexMap.Count > 0)
{
touchFingerIndexMap.ForEach(pair => touchCounter = Math.Max(touchCounter, pair.Value));
touchCounter++; // next
}
}

return touchFingerIndex;
}
}
}
33 changes: 0 additions & 33 deletions sources/engine/Stride.Input/SDL/PointerSDL.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ internal class PointerSDL : PointerDeviceBase, IDisposable
private static Sdl SDL = Window.SDL;

private readonly Window uiControl;
private readonly Dictionary<(long touchId, long fingerId), int> touchFingerIndexMap = new Dictionary<(long touchId, long fingerId), int>();
private int touchCounter;

public PointerSDL(InputSourceSDL source, Window uiControl)
{
Expand Down Expand Up @@ -62,37 +60,6 @@ private void OnSizeChanged(WindowEvent eventArgs)
SetSurfaceSize(new Vector2(uiControl.ClientSize.Width, uiControl.ClientSize.Height));
}

private int GetFingerId(long touchId, long fingerId, PointerEventType type)
{
// Assign finger index (starting at 0) to touch ID
int touchFingerIndex = 0;
var key = (touchId, fingerId);
if (type == PointerEventType.Pressed)
{
touchFingerIndex = touchCounter++;
touchFingerIndexMap[key] = touchFingerIndex;
}
else
{
touchFingerIndexMap.TryGetValue(key, out touchFingerIndex);
}

// Remove index
if (type == PointerEventType.Released && touchFingerIndexMap.Remove(key))
{
touchCounter = 0; // Reset touch counter

// Recalculate next finger index
if (touchFingerIndexMap.Count > 0)
{
touchFingerIndexMap.ForEach(pair => touchCounter = Math.Max(touchCounter, pair.Value));
touchCounter++; // next
}
}

return touchFingerIndex;
}

private void HandleFingerEvent(TouchFingerEvent e, PointerEventType type)
{
var newPosition = new Vector2(e.X, e.Y);
Expand Down
4 changes: 4 additions & 0 deletions sources/engine/Stride.Input/Windows/InputSourceWinforms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal class InputSourceWinforms : InputSourceBase

private KeyboardWinforms keyboard;
private MouseWinforms mouse;
private PointerWinforms pointer;

private IntPtr defaultWndProc;
private Win32Native.WndProc inputWndProc;
Expand Down Expand Up @@ -60,6 +61,9 @@ public override void Initialize(InputManager inputManager)

mouse = new MouseWinforms(this, uiControl);
RegisterDevice(mouse);

pointer = new PointerWinforms(this, uiControl);
RegisterDevice(pointer);
}

public override void Dispose()
Expand Down
84 changes: 84 additions & 0 deletions sources/engine/Stride.Input/Windows/PointerWinforms.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.

#if STRIDE_UI_WINFORMS
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Stride.Core.Extensions;
using Stride.Core.Mathematics;
using Stride.Games;
using static Stride.Games.TouchUtils;

namespace Stride.Input
{
/// <summary>
/// Class handling finger touch inputs using the Winforms backend
/// </summary>
internal class PointerWinforms : PointerDeviceBase, IDisposable
{
private readonly GameForm uiControl;

public PointerWinforms(InputSourceWinforms source, Control uiControl)
{
Source = source;
this.uiControl = uiControl as GameForm;

this.uiControl.FingerMoveActions += OnFingerMoveEvent;
this.uiControl.FingerPressActions += OnFingerPressEvent;
this.uiControl.FingerReleaseActions += OnFingerReleaseEvent;

uiControl.Resize += OnSizeChanged;
OnSizeChanged(uiControl, EventArgs.Empty);

Id = InputDeviceUtils.DeviceNameToGuid(uiControl.Handle.ToString() + Name);
}

public override string Name => "Winforms Pointer";

public override Guid Id { get; }

public override IInputSource Source { get; }

public void Dispose()
{
uiControl.FingerMoveActions -= OnFingerMoveEvent;
uiControl.FingerPressActions -= OnFingerPressEvent;
uiControl.FingerReleaseActions -= OnFingerReleaseEvent;

uiControl.Resize -= OnSizeChanged;
}

private void OnSizeChanged(object sender, EventArgs eventArgs)
{
SetSurfaceSize(new Vector2(uiControl.ClientSize.Width, uiControl.ClientSize.Height));
}

private void HandleFingerEvent(POINTER_TOUCH_INFO e, PointerEventType type)
{
var pointerInfo = e.pointerInfo;
var point = uiControl.PointToClient(new System.Drawing.Point(pointerInfo.ptPixelLocationX, pointerInfo.ptPixelLocationY));
var pixel = new Vector2(point.X, point.Y);
var newPosition = Normalize(pixel);
var id = GetFingerId(pointerInfo.sourceDevice.ToInt64(), pointerInfo.pointerId, type);

PointerState.PointerInputEvents.Add(new PointerDeviceState.InputEvent { Type = type, Position = newPosition, Id = id });
}

private void OnFingerMoveEvent(POINTER_TOUCH_INFO e)
{
HandleFingerEvent(e, PointerEventType.Moved);
}

private void OnFingerPressEvent(POINTER_TOUCH_INFO e)
{
HandleFingerEvent(e, PointerEventType.Pressed);
}

private void OnFingerReleaseEvent(POINTER_TOUCH_INFO e)
{
HandleFingerEvent(e, PointerEventType.Released);
}
}
}
#endif
Loading