diff --git a/src/Common.WinForms/Controls/GestureEventArgs.cs b/src/Common.WinForms/Controls/GestureEventArgs.cs
new file mode 100644
index 000000000..af7f49983
--- /dev/null
+++ b/src/Common.WinForms/Controls/GestureEventArgs.cs
@@ -0,0 +1,100 @@
+// Copyright Bastian Eicher
+// Licensed under the MIT License
+
+using System;
+
+namespace NanoByte.Common.Controls;
+
+///
+/// Flags for gesture information.
+///
+[Flags]
+public enum GestureFlags
+{
+ /// Marks the beginning of a gesture.
+ Begin = 0x00000001,
+
+ /// Indicates that the gesture is in inertia mode.
+ Inertia = 0x00000002,
+
+ /// Marks the end of a gesture.
+ End = 0x00000004
+}
+
+///
+/// Base class for gesture event arguments.
+///
+public abstract class GestureEventArgs : EventArgs
+{
+ ///
+ /// The X coordinate of the gesture in client coordinates.
+ ///
+ public int LocationX { get; set; }
+
+ ///
+ /// The Y coordinate of the gesture in client coordinates.
+ ///
+ public int LocationY { get; set; }
+
+ ///
+ /// Flags indicating the state of the gesture.
+ ///
+ public GestureFlags Flags { get; set; }
+
+ ///
+ /// Unique identifier for this gesture sequence.
+ ///
+ public int SequenceId { get; set; }
+}
+
+///
+/// Event arguments for pan gesture.
+///
+public class PanGestureEventArgs : GestureEventArgs
+{
+ ///
+ /// The horizontal distance panned.
+ ///
+ public int PanDistanceX { get; set; }
+
+ ///
+ /// The vertical distance panned.
+ ///
+ public int PanDistanceY { get; set; }
+}
+
+///
+/// Event arguments for zoom gesture.
+///
+public class ZoomGestureEventArgs : GestureEventArgs
+{
+ ///
+ /// The distance between the two fingers.
+ ///
+ public long Distance { get; set; }
+}
+
+///
+/// Event arguments for rotate gesture.
+///
+public class RotateGestureEventArgs : GestureEventArgs
+{
+ ///
+ /// The angle of rotation in radians.
+ ///
+ public double Angle { get; set; }
+}
+
+///
+/// Event arguments for tap gesture (two-finger tap).
+///
+public class TapGestureEventArgs : GestureEventArgs
+{
+}
+
+///
+/// Event arguments for press and tap gesture.
+///
+public class PressAndTapGestureEventArgs : GestureEventArgs
+{
+}
diff --git a/src/Common.WinForms/Controls/ITouchControl.cs b/src/Common.WinForms/Controls/ITouchControl.cs
index 34f462f6f..cbbe45e8c 100644
--- a/src/Common.WinForms/Controls/ITouchControl.cs
+++ b/src/Common.WinForms/Controls/ITouchControl.cs
@@ -4,22 +4,32 @@
namespace NanoByte.Common.Controls;
///
-/// A control that can raise touch events.
+/// A control that can raise touch gesture events.
///
public interface ITouchControl
{
///
- /// Raised when the user begins touching the screen.
+ /// Raised when the user performs a pan gesture.
///
- event EventHandler TouchDown;
+ event EventHandler Pan;
///
- /// Raised when the user stops touching the screen.
+ /// Raised when the user performs a zoom gesture.
///
- event EventHandler TouchUp;
+ event EventHandler Zoom;
///
- /// Raised when the user moves fingers while touching the screen.
+ /// Raised when the user performs a rotate gesture.
///
- event EventHandler TouchMove;
+ event EventHandler Rotate;
+
+ ///
+ /// Raised when the user performs a two-finger tap gesture.
+ ///
+ event EventHandler Tap;
+
+ ///
+ /// Raised when the user performs a press and tap gesture.
+ ///
+ event EventHandler PressAndTap;
}
diff --git a/src/Common.WinForms/Controls/TouchEventArgs.cs b/src/Common.WinForms/Controls/TouchEventArgs.cs
deleted file mode 100644
index de5a5eb9b..000000000
--- a/src/Common.WinForms/Controls/TouchEventArgs.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright Bastian Eicher
-// Licensed under the MIT License
-
-namespace NanoByte.Common.Controls;
-
-// ReSharper disable CommentTypo
-///
-/// Mask indicating which fields in are valid.
-///
-///
-[SuppressMessage("Microsoft.Naming", "CA1714:FlagsEnumsShouldHavePluralNames", Justification = "The keyword mask implies flag usage without the need for a plural form")]
-[Flags]
-public enum TouchEventMask
-{
- /// TOUCHINPUTMASKF_TIMEFROMSYSTEM
- Time = 0x0001,
-
- /// TOUCHINPUTMASKF_EXTRAINFO
- ExtraInfo = 0x0002,
-
- /// TOUCHINPUTMASKF_CONTACTAREA
- ContactArea = 0x0004
-}
-
-///
-/// Event information about a touch event.
-///
-public class TouchEventArgs : EventArgs
-{
- ///
- /// Touch X client coordinate in pixels.
- ///
- public int LocationX { get; set; }
-
- ///
- /// Touch Y client coordinate in pixels.
- ///
- public int LocationY { get; set; }
-
- ///
- /// X size of the contact area in pixels.
- ///
- public int ContactX { get; set; }
-
- ///
- /// X size of the contact area in pixels.
- ///
- public int ContactY { get; set; }
-
- ///
- /// Contact ID.
- ///
- public int ID { get; set; }
-
- ///
- /// Mask indicating which fields in the structure are valid.
- ///
- public TouchEventMask Mask { get; set; }
-
- ///
- /// Touch event time.
- ///
- public int Time { get; set; }
-
- ///
- /// Indicates that this structure corresponds to a primary contact point.
- ///
- public bool Primary;
-
- ///
- /// The touch event came from the user's palm.
- ///
- public bool Palm;
-
- ///
- /// The user is hovering above the touch screen.
- ///
- public bool InRange;
-
- ///
- /// This input was not coalesced.
- ///
- public bool NoCoalesce;
-}
diff --git a/src/Common.WinForms/Controls/TouchForm.cs b/src/Common.WinForms/Controls/TouchForm.cs
index 0ed4edec2..759041028 100644
--- a/src/Common.WinForms/Controls/TouchForm.cs
+++ b/src/Common.WinForms/Controls/TouchForm.cs
@@ -10,18 +10,24 @@
namespace NanoByte.Common.Controls;
///
-/// Represents a window that reacts to touch input on Windows 7 or newer.
+/// Represents a window that reacts to touch gestures on Windows 7 or newer.
///
public class TouchForm : Form, ITouchControl
{
///
- public event EventHandler? TouchDown;
+ public event EventHandler? Pan;
///
- public event EventHandler? TouchUp;
+ public event EventHandler? Zoom;
///
- public event EventHandler? TouchMove;
+ public event EventHandler? Rotate;
+
+ ///
+ public event EventHandler? Tap;
+
+ ///
+ public event EventHandler? PressAndTap;
protected override CreateParams CreateParams
{
@@ -39,7 +45,7 @@ protected override CreateParams CreateParams
protected override void CreateHandle()
{
base.CreateHandle();
- WinFormsUtils.RegisterTouchWindow(this);
+ WinFormsUtils.RegisterGestureWindow(this);
}
#if NETFRAMEWORK
@@ -47,7 +53,9 @@ protected override void CreateHandle()
#endif
protected override void WndProc(ref Message m)
{
- WinFormsUtils.HandleTouchMessage(ref m, this, TouchDown, TouchMove, TouchUp);
+ bool handled = WinFormsUtils.HandleGestureMessage(ref m, this, Pan, Zoom, Rotate, Tap, PressAndTap);
base.WndProc(ref m);
+ if (handled)
+ m.Result = new IntPtr(1);
}
}
diff --git a/src/Common.WinForms/Controls/TouchPanel.cs b/src/Common.WinForms/Controls/TouchPanel.cs
index dbdbf93a2..28d812070 100644
--- a/src/Common.WinForms/Controls/TouchPanel.cs
+++ b/src/Common.WinForms/Controls/TouchPanel.cs
@@ -10,18 +10,24 @@
namespace NanoByte.Common.Controls;
///
-/// Represents a panel that reacts to touch input on Windows 7 or newer.
+/// Represents a panel that reacts to touch gestures on Windows 7 or newer.
///
public class TouchPanel : Panel, ITouchControl
{
///
- public event EventHandler? TouchDown;
+ public event EventHandler? Pan;
///
- public event EventHandler? TouchUp;
+ public event EventHandler? Zoom;
///
- public event EventHandler? TouchMove;
+ public event EventHandler? Rotate;
+
+ ///
+ public event EventHandler? Tap;
+
+ ///
+ public event EventHandler? PressAndTap;
protected override CreateParams CreateParams
{
@@ -39,7 +45,7 @@ protected override CreateParams CreateParams
protected override void CreateHandle()
{
base.CreateHandle();
- WinFormsUtils.RegisterTouchWindow(this);
+ WinFormsUtils.RegisterGestureWindow(this);
}
#if NETFRAMEWORK
@@ -47,7 +53,9 @@ protected override void CreateHandle()
#endif
protected override void WndProc(ref Message m)
{
- WinFormsUtils.HandleTouchMessage(ref m, this, TouchDown, TouchMove, TouchUp);
+ bool handled = WinFormsUtils.HandleGestureMessage(ref m, this, Pan, Zoom, Rotate, Tap, PressAndTap);
base.WndProc(ref m);
+ if (handled)
+ m.Result = new IntPtr(1);
}
}
diff --git a/src/Common.WinForms/Native/WinFormsUtils.NativeMethods.cs b/src/Common.WinForms/Native/WinFormsUtils.NativeMethods.cs
index 244c9a4c8..e1dc20c3f 100644
--- a/src/Common.WinForms/Native/WinFormsUtils.NativeMethods.cs
+++ b/src/Common.WinForms/Native/WinFormsUtils.NativeMethods.cs
@@ -55,50 +55,65 @@ public struct WinMessage
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ReleaseCapture();
- [DllImport("user32")]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool RegisterTouchWindow(IntPtr hWnd, uint ulFlags);
-
- [DllImport("user32")]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool GetTouchInputInfo(IntPtr hTouchInput, int cInputs, [In, Out] TouchInput[] pInputs, int cbSize);
+ // Gesture-related constants
+ public const int GestureConfigAll = 0x00000001; // GC_ALLGESTURES
+
+ // Gesture IDs
+ public const int GestureIdBegin = 1;
+ public const int GestureIdEnd = 2;
+ public const int GestureIdZoom = 3;
+ public const int GestureIdPan = 4;
+ public const int GestureIdRotate = 5;
+ public const int GestureIdTwoFingerTap = 6;
+ public const int GestureIdPressAndTap = 7;
+
+ // Gesture flags
+ public const int GestureFlagBegin = 0x00000001;
+ public const int GestureFlagInertia = 0x00000002;
+ public const int GestureFlagEnd = 0x00000004;
- [Flags]
- public enum TouchEvents
+ [StructLayout(LayoutKind.Sequential)]
+ [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
+ [SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
+ public struct GestureConfig
{
- Move = 0x0001, // TOUCHEVENTF_MOVE
- Down = 0x0002, // TOUCHEVENTF_DOWN
- Up = 0x0004, // TOUCHEVENTF_UP
- InRange = 0x0008, // TOUCHEVENTF_INRANGE
- Primary = 0x0010, // TOUCHEVENTF_PRIMARY
- NoCoalesce = 0x0020, // TOUCHEVENTF_NOCOALESCE
- Palm = 0x0080 // TOUCHEVENTF_PALM
+ public int dwID; // gesture ID
+ public int dwWant; // settings related to gesture ID that are to be turned on
+ public int dwBlock; // settings related to gesture ID that are to be turned off
}
[StructLayout(LayoutKind.Sequential)]
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
- public struct TouchInput
+ [SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
+ public struct Points
{
- public int x, y;
- public IntPtr hSource;
- public int dwID;
- public TouchEvents dwFlags;
- public TouchEventMask dwMask;
- public int dwTime;
- public IntPtr dwExtraInfo;
- public int cxContact;
- public int cyContact;
+ public short x;
+ public short y;
}
[StructLayout(LayoutKind.Sequential)]
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
- private struct Points
+ [SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
+ public struct GestureInfo
{
- public short x, y;
+ public int cbSize; // size, in bytes, of this structure
+ public int dwFlags; // see GF_* flags
+ public int dwID; // gesture ID, see GID_* defines
+ public IntPtr hwndTarget; // handle to window targeted by this gesture
+ [MarshalAs(UnmanagedType.Struct)]
+ public Points ptsLocation; // current location of this gesture
+ public int dwInstanceID; // internally used
+ public int dwSequenceID; // internally used
+ public long ullArguments; // arguments for gestures whose arguments fit in 8 BYTES
+ public int cbExtraArgs; // size, in bytes, of extra arguments, if any, that accompany this gesture
}
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
- public static extern void CloseTouchInputHandle(IntPtr lParam);
+ public static extern bool SetGestureConfig(IntPtr hWnd, int dwReserved, int cIDs, ref GestureConfig pGestureConfig, int cbSize);
+
+ [DllImport("user32")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool GetGestureInfo(IntPtr hGestureInfo, ref GestureInfo pGestureInfo);
}
}
diff --git a/src/Common.WinForms/Native/WinFormsUtils.Touch.cs b/src/Common.WinForms/Native/WinFormsUtils.Touch.cs
index 7d2dd21f8..f6a20ceb0 100644
--- a/src/Common.WinForms/Native/WinFormsUtils.Touch.cs
+++ b/src/Common.WinForms/Native/WinFormsUtils.Touch.cs
@@ -7,94 +7,186 @@ namespace NanoByte.Common.Native;
partial class WinFormsUtils
{
- // Note: The following code is based on Windows API Code Pack for Microsoft .NET Framework 1.0.1
+ // Note: The following code is based on Windows 7 Touch Gesture API
+ // See: https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Win7Samples/Touch/MTGestures/CS/MTGestures.cs
+
+ private static readonly int _gestureConfigSize = Marshal.SizeOf(typeof(NativeMethods.GestureConfig));
///
- /// Registers a control as a receiver for touch events.
+ /// Registers a control to receive gesture events.
+ /// This method is kept for API compatibility but gesture configuration is done automatically.
///
/// The control to register.
- public static void RegisterTouchWindow(Control control)
+ public static void RegisterGestureWindow(Control control)
{
#region Sanity checks
if (control == null) throw new ArgumentNullException(nameof(control));
#endregion
- if (WindowsUtils.IsWindows7) NativeMethods.RegisterTouchWindow(control.Handle, 0);
+ // Gesture configuration is done in response to WM_GESTURENOTIFY
+ // No registration call is needed upfront
}
- private static readonly int _touchInputSize = Marshal.SizeOf(new NativeMethods.TouchInput());
+ ///
+ /// Configures which gestures are enabled for a window.
+ ///
+ /// Handle to the window.
+ public static void ConfigureGestures(IntPtr hWnd)
+ {
+ if (!WindowsUtils.IsWindows7) return;
+
+ var gc = new NativeMethods.GestureConfig
+ {
+ dwID = 0, // gesture ID
+ dwWant = NativeMethods.GestureConfigAll, // enable all gestures
+ dwBlock = 0 // block no gestures
+ };
+
+ NativeMethods.SetGestureConfig(
+ hWnd,
+ 0,
+ 1,
+ ref gc,
+ _gestureConfigSize);
+ }
///
- /// Handles touch-related s.
+ /// Handles gesture-related s.
///
/// The message to handle.
/// The object to send possible events from.
- /// The event handler to call for touch down events; can be null.
- /// The event handler to call for touch move events; can be null.
- /// The event handler to call for touch up events; can be null.
+ /// The event handler to call for pan gestures; can be null.
+ /// The event handler to call for zoom gestures; can be null.
+ /// The event handler to call for rotate gestures; can be null.
+ /// The event handler to call for two-finger tap gestures; can be null.
+ /// The event handler to call for press and tap gestures; can be null.
+ /// true if the message was handled; otherwise false.
[SuppressMessage("ReSharper", "InconsistentNaming")]
- public static void HandleTouchMessage(ref Message m, object? sender, EventHandler? onTouchDown, EventHandler? onTouchMove, EventHandler? onTouchUp)
+ public static bool HandleGestureMessage(
+ ref Message m,
+ object? sender,
+ EventHandler? onPan,
+ EventHandler? onZoom,
+ EventHandler? onRotate,
+ EventHandler? onTap,
+ EventHandler? onPressAndTap)
{
- const int WM_TOUCHMOVE = 0x0240, WM_TOUCHDOWN = 0x0241, WM_TOUCHUP = 0x0242;
+ const int WM_GESTURENOTIFY = 0x011A;
+ const int WM_GESTURE = 0x0119;
- if (!WindowsUtils.IsWindows7) return;
- if (m.Msg != WM_TOUCHDOWN && m.Msg != WM_TOUCHMOVE && m.Msg != WM_TOUCHUP) return;
-
- // More than one touchinput may be associated with a touch message,
- // so an array is needed to get all event information.
- short inputCount = (short)(m.WParam.ToInt32() & 0xffff); // Number of touch inputs, actual per-contact messages
-
- if (inputCount < 0) return;
- var inputs = new NativeMethods.TouchInput[inputCount];
-
- // Unpack message parameters into the array of TOUCHINPUT structures, each
- // representing a message for one single contact.
- //Exercise2-Task1-Step3
- if (!NativeMethods.GetTouchInputInfo(m.LParam, inputCount, inputs, _touchInputSize))
- return;
-
- // For each contact, dispatch the message to the appropriate message
- // handler.
- // Note that for WM_TOUCHDOWN you can get down & move notifications
- // and for WM_TOUCHUP you can get up & move notifications
- // WM_TOUCHMOVE will only contain move notifications
- // and up & down notifications will never come in the same message
- for (int i = 0; i < inputCount; i++)
+ if (!WindowsUtils.IsWindows7) return false;
+
+ if (m.Msg == WM_GESTURENOTIFY)
+ {
+ ConfigureGestures(m.HWnd);
+ return true;
+ }
+
+ if (m.Msg != WM_GESTURE) return false;
+
+ var gi = new NativeMethods.GestureInfo { cbSize = Marshal.SizeOf(typeof(NativeMethods.GestureInfo)) };
+
+ if (!NativeMethods.GetGestureInfo(m.LParam, ref gi))
+ return false;
+
+ // Convert screen coordinates to client coordinates
+ var location = Control.FromHandle(m.HWnd)?.PointToClient(new Point(gi.ptsLocation.x, gi.ptsLocation.y)) ?? new Point(gi.ptsLocation.x, gi.ptsLocation.y);
+
+ var flags = (GestureFlags)gi.dwFlags;
+
+ switch (gi.dwID)
{
- NativeMethods.TouchInput ti = inputs[i];
-
- // Assign a handler to this message.
- EventHandler? handler = null; // Touch event handler
- if (ti.dwFlags.HasFlag(NativeMethods.TouchEvents.Down)) handler = onTouchDown;
- else if (ti.dwFlags.HasFlag(NativeMethods.TouchEvents.Up)) handler = onTouchUp;
- else if (ti.dwFlags.HasFlag(NativeMethods.TouchEvents.Move)) handler = onTouchMove;
-
- // Convert message parameters into touch event arguments and handle the event.
- if (handler != null)
- {
- // TOUCHINFO point coordinates and contact size is in 1/100 of a pixel; convert it to pixels.
- // Also convert screen to client coordinates.
- var te = new TouchEventArgs
+ case NativeMethods.GestureIdBegin:
+ case NativeMethods.GestureIdEnd:
+ // These are informational only
+ break;
+
+ case NativeMethods.GestureIdPan:
+ if (onPan != null)
+ {
+ // ullArguments contains the total pan distance as two 32-bit signed integers:
+ // Lower 32 bits = X distance (horizontal pan)
+ // Upper 32 bits = Y distance (vertical pan)
+ var args = new PanGestureEventArgs
+ {
+ LocationX = location.X,
+ LocationY = location.Y,
+ Flags = flags,
+ SequenceId = gi.dwSequenceID,
+ PanDistanceX = (int)(gi.ullArguments & 0xFFFFFFFF),
+ PanDistanceY = (int)((gi.ullArguments >> 32) & 0xFFFFFFFF)
+ };
+ onPan(sender, args);
+ }
+ break;
+
+ case NativeMethods.GestureIdZoom:
+ if (onZoom != null)
+ {
+ var args = new ZoomGestureEventArgs
+ {
+ LocationX = location.X,
+ LocationY = location.Y,
+ Flags = flags,
+ SequenceId = gi.dwSequenceID,
+ Distance = gi.ullArguments
+ };
+ onZoom(sender, args);
+ }
+ break;
+
+ case NativeMethods.GestureIdRotate:
+ if (onRotate != null)
{
- ContactY = ti.cyContact / 100,
- ContactX = ti.cxContact / 100,
- ID = ti.dwID,
- LocationX = ti.x / 100,
- LocationY = ti.y / 100,
- Time = ti.dwTime,
- Mask = ti.dwMask,
- InRange = ti.dwFlags.HasFlag(NativeMethods.TouchEvents.InRange),
- Primary = ti.dwFlags.HasFlag(NativeMethods.TouchEvents.Primary),
- NoCoalesce = ti.dwFlags.HasFlag(NativeMethods.TouchEvents.NoCoalesce),
- Palm = ti.dwFlags.HasFlag(NativeMethods.TouchEvents.Palm)
- };
-
- handler(sender, te);
-
- m.Result = new IntPtr(1); // Indicate to Windows that the message was handled
- }
+ var args = new RotateGestureEventArgs
+ {
+ LocationX = location.X,
+ LocationY = location.Y,
+ Flags = flags,
+ SequenceId = gi.dwSequenceID,
+ Angle = ArgToRadians(gi.ullArguments)
+ };
+ onRotate(sender, args);
+ }
+ break;
+
+ case NativeMethods.GestureIdTwoFingerTap:
+ if (onTap != null)
+ {
+ var args = new TapGestureEventArgs
+ {
+ LocationX = location.X,
+ LocationY = location.Y,
+ Flags = flags,
+ SequenceId = gi.dwSequenceID
+ };
+ onTap(sender, args);
+ }
+ break;
+
+ case NativeMethods.GestureIdPressAndTap:
+ if (onPressAndTap != null)
+ {
+ var args = new PressAndTapGestureEventArgs
+ {
+ LocationX = location.X,
+ LocationY = location.Y,
+ Flags = flags,
+ SequenceId = gi.dwSequenceID
+ };
+ onPressAndTap(sender, args);
+ }
+ break;
}
- NativeMethods.CloseTouchInputHandle(m.LParam);
+ return true;
+ }
+
+ ///
+ /// Converts from "binary radians" to traditional radians.
+ ///
+ private static double ArgToRadians(long arg)
+ {
+ return ((arg / 65535.0) * 4.0 * Math.PI) - 2.0 * Math.PI;
}
}