Use an InteractionContext to interpret touch gestures

This commit is contained in:
David Capello 2017-11-02 21:19:28 -03:00
parent 8c9b8910c1
commit 6cb0c2c315
7 changed files with 261 additions and 55 deletions

View File

@ -86,7 +86,6 @@ namespace she {
// We suppose that if we are receiving precise scrolling deltas,
// it means that the user is using a touch-like surface (trackpad,
// magic mouse scrolling, touch wacom tablet, etc.)
// TODO change this with the new PointerType::Multitouch
bool preciseWheel() const { return m_preciseWheel; }
PointerType pointerType() const { return m_pointerType; }

View File

@ -454,7 +454,7 @@ using namespace she;
scale = [(OSXWindow*)self.window scale];
if (event.hasPreciseScrollingDeltas) {
ev.setPointerType(she::PointerType::Multitouch);
ev.setPointerType(she::PointerType::Touchpad);
ev.setWheelDelta(gfx::Point(-event.scrollingDeltaX / scale,
-event.scrollingDeltaY / scale));
ev.setPreciseWheel(true);
@ -485,7 +485,7 @@ using namespace she;
ev.setMagnification(event.magnification);
ev.setPosition(get_local_mouse_pos(self, event));
ev.setModifiers(get_modifiers_from_nsevent(event));
ev.setPointerType(she::PointerType::Multitouch);
ev.setPointerType(she::PointerType::Touchpad);
queue_event(ev);
}

View File

@ -1,5 +1,5 @@
// SHE library
// Copyright (C) 2016 David Capello
// Copyright (C) 2016-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -14,7 +14,8 @@ namespace she {
enum class PointerType {
Unknown,
Mouse, // A regular mouse
Multitouch, // Trackpad/multitouch surface
Touchpad, // Touchpad/trackpad
Touch, // Touch screen
Pen, // Stylus pen
Cursor, // Puck like device
Eraser // Eraser end of a stylus pen

View File

@ -20,15 +20,36 @@ WinAPI::WinAPI()
, IsMouseInPointerEnabled(nullptr)
, GetPointerInfo(nullptr)
, GetPointerPenInfo(nullptr)
, CreateInteractionContext(nullptr)
, DestroyInteractionContext(nullptr)
, StopInteractionContext(nullptr)
, RegisterOutputCallbackInteractionContext(nullptr)
, AddPointerInteractionContext(nullptr)
, RemovePointerInteractionContext(nullptr)
, SetInteractionConfigurationInteractionContext(nullptr)
, ProcessPointerFramesInteractionContext(nullptr)
, m_user32(nullptr)
, m_ninput(nullptr)
{
m_user32 = base::load_dll("user32.dll");
m_ninput = base::load_dll("ninput.dll");
if (m_user32) {
GET_PROC(m_user32, EnableMouseInPointer);
GET_PROC(m_user32, IsMouseInPointerEnabled);
GET_PROC(m_user32, GetPointerInfo);
GET_PROC(m_user32, GetPointerPenInfo);
}
if (m_ninput) {
GET_PROC(m_ninput, CreateInteractionContext);
GET_PROC(m_ninput, DestroyInteractionContext);
GET_PROC(m_ninput, StopInteractionContext);
GET_PROC(m_ninput, RegisterOutputCallbackInteractionContext);
GET_PROC(m_ninput, AddPointerInteractionContext);
GET_PROC(m_ninput, RemovePointerInteractionContext);
GET_PROC(m_ninput, SetInteractionConfigurationInteractionContext);
GET_PROC(m_ninput, SetPropertyInteractionContext);
GET_PROC(m_ninput, ProcessPointerFramesInteractionContext);
}
}
WinAPI::~WinAPI()
@ -37,6 +58,10 @@ WinAPI::~WinAPI()
base::unload_dll(m_user32);
m_user32 = nullptr;
}
if (m_ninput) {
base::unload_dll(m_ninput);
m_ninput = nullptr;
}
}
} // namespace she

View File

@ -11,6 +11,7 @@
#include "base/dll.h"
#include <windows.h>
#include <interactioncontext.h>
namespace she {
@ -19,6 +20,33 @@ namespace she {
typedef BOOL (WINAPI* GetPointerInfo_Func)(UINT32 pointerId, POINTER_INFO* pointerInfo);
typedef BOOL (WINAPI* GetPointerPenInfo_Func)(UINT32 pointerId, POINTER_PEN_INFO* penInfo);
typedef HRESULT (WINAPI* CreateInteractionContext_Func)(HINTERACTIONCONTEXT* interactionContext);
typedef HRESULT (WINAPI* DestroyInteractionContext_Func)(HINTERACTIONCONTEXT interactionContext);
typedef HRESULT (WINAPI* StopInteractionContext_Func)(HINTERACTIONCONTEXT interactionContext);
typedef HRESULT (WINAPI* RegisterOutputCallbackInteractionContext_Func)(
HINTERACTIONCONTEXT interactionContext,
INTERACTION_CONTEXT_OUTPUT_CALLBACK outputCallback,
void* clientData);
typedef HRESULT (WINAPI* AddPointerInteractionContext_Func)(
HINTERACTIONCONTEXT interactionContext,
UINT32 pointerId);
typedef HRESULT (WINAPI* RemovePointerInteractionContext_Func)(
HINTERACTIONCONTEXT interactionContext,
UINT32 pointerId);
typedef HRESULT (WINAPI* SetInteractionConfigurationInteractionContext_Func)(
HINTERACTIONCONTEXT interactionContext,
UINT32 configurationCount,
const INTERACTION_CONTEXT_CONFIGURATION* configuration);
typedef HRESULT (WINAPI* SetPropertyInteractionContext_Func)(
HINTERACTIONCONTEXT interactionContext,
INTERACTION_CONTEXT_PROPERTY contextProperty,
UINT32 value);
typedef HRESULT (WINAPI* ProcessPointerFramesInteractionContext_Func)(
HINTERACTIONCONTEXT interactionContext,
UINT32 entriesCount,
UINT32 pointerCount,
const POINTER_INFO* pointerInfo);
class WinAPI {
public:
WinAPI();
@ -30,8 +58,20 @@ namespace she {
GetPointerInfo_Func GetPointerInfo;
GetPointerPenInfo_Func GetPointerPenInfo;
// InteractionContext introduced on Windows 8
CreateInteractionContext_Func CreateInteractionContext;
DestroyInteractionContext_Func DestroyInteractionContext;
StopInteractionContext_Func StopInteractionContext;
RegisterOutputCallbackInteractionContext_Func RegisterOutputCallbackInteractionContext;
AddPointerInteractionContext_Func AddPointerInteractionContext;
RemovePointerInteractionContext_Func RemovePointerInteractionContext;
SetInteractionConfigurationInteractionContext_Func SetInteractionConfigurationInteractionContext;
SetPropertyInteractionContext_Func SetPropertyInteractionContext;
ProcessPointerFramesInteractionContext_Func ProcessPointerFramesInteractionContext;
private:
base::dll m_user32;
base::dll m_ninput;
};
} // namespace she

View File

@ -40,6 +40,10 @@
// messages.
#define USE_EnableMouseInPointer 0
#ifndef INTERACTION_CONTEXT_PROPERTY_MEASUREMENT_UNITS_SCREEN
#define INTERACTION_CONTEXT_PROPERTY_MEASUREMENT_UNITS_SCREEN 1
#endif
namespace she {
WinWindow::WinWindow(int width, int height, int scale)
@ -56,7 +60,7 @@ WinWindow::WinWindow(int width, int height, int scale)
, m_usePointerApi(false)
, m_ignoreMouseMessages(false)
, m_lastPointerId(0)
, m_capturePointerId(0)
, m_ictx(nullptr)
, m_hpenctx(nullptr)
, m_pointerType(PointerType::Unknown)
, m_pressure(0.0)
@ -74,6 +78,49 @@ WinWindow::WinWindow(int width, int height, int scale)
m_ignoreMouseMessages = (winApi.IsMouseInPointerEnabled() ? true: false);
}
#endif
// Initialize a Interaction Context to convert WM_POINTER messages
// into gestures processed by handleInteractionContextOutput().
if (winApi.CreateInteractionContext &&
winApi.RegisterOutputCallbackInteractionContext &&
winApi.SetInteractionConfigurationInteractionContext) {
HRESULT hr = winApi.CreateInteractionContext(&m_ictx);
if (SUCCEEDED(hr)) {
hr = winApi.RegisterOutputCallbackInteractionContext(
m_ictx, &WinWindow::staticInteractionContextCallback, this);
}
if (SUCCEEDED(hr)) {
INTERACTION_CONTEXT_CONFIGURATION cfg[] = {
{ INTERACTION_ID_MANIPULATION,
INTERACTION_CONFIGURATION_FLAG_MANIPULATION |
INTERACTION_CONFIGURATION_FLAG_MANIPULATION_TRANSLATION_X |
INTERACTION_CONFIGURATION_FLAG_MANIPULATION_TRANSLATION_Y |
INTERACTION_CONFIGURATION_FLAG_MANIPULATION_SCALING |
INTERACTION_CONFIGURATION_FLAG_MANIPULATION_TRANSLATION_INERTIA |
INTERACTION_CONFIGURATION_FLAG_MANIPULATION_SCALING_INERTIA },
{ INTERACTION_ID_TAP,
INTERACTION_CONFIGURATION_FLAG_TAP |
INTERACTION_CONFIGURATION_FLAG_TAP_DOUBLE },
{ INTERACTION_ID_SECONDARY_TAP,
INTERACTION_CONFIGURATION_FLAG_SECONDARY_TAP },
{ INTERACTION_ID_HOLD,
INTERACTION_CONFIGURATION_FLAG_NONE },
{ INTERACTION_ID_DRAG,
INTERACTION_CONFIGURATION_FLAG_NONE },
{ INTERACTION_ID_CROSS_SLIDE,
INTERACTION_CONFIGURATION_FLAG_NONE }
};
hr = winApi.SetInteractionConfigurationInteractionContext(
m_ictx, sizeof(cfg) / sizeof(INTERACTION_CONTEXT_CONFIGURATION), cfg);
}
if (SUCCEEDED(hr)) {
hr = winApi.SetPropertyInteractionContext(
m_ictx,
INTERACTION_CONTEXT_PROPERTY_MEASUREMENT_UNITS,
INTERACTION_CONTEXT_PROPERTY_MEASUREMENT_UNITS_SCREEN);
}
}
m_usePointerApi = true;
}
@ -96,6 +143,10 @@ WinWindow::WinWindow(int width, int height, int scale)
WinWindow::~WinWindow()
{
auto& winApi = system()->winApi();
if (m_ictx && winApi.DestroyInteractionContext)
winApi.DestroyInteractionContext(m_ictx);
if (m_hwnd)
DestroyWindow(m_hwnd);
}
@ -155,7 +206,6 @@ void WinWindow::setTitle(const std::string& title)
void WinWindow::captureMouse()
{
m_captureMouse = true;
m_capturePointerId = m_lastPointerId;
if (GetCapture() != m_hwnd) {
MOUSE_TRACE("SetCapture\n");
@ -166,7 +216,6 @@ void WinWindow::captureMouse()
void WinWindow::releaseMouse()
{
m_captureMouse = false;
m_capturePointerId = 0;
if (GetCapture() == m_hwnd) {
MOUSE_TRACE("ReleaseCapture\n");
@ -706,22 +755,16 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam)
case WM_POINTERCAPTURECHANGED: {
MOUSE_TRACE("POINTERCAPTURECHANGED\n");
m_capturePointerId = 0;
releaseMouse();
break;
}
case WM_POINTERENTER: {
POINTER_INFO pi;
Event ev;
if (!pointerEvent(wparam, lparam, ev, pi))
if (!pointerEvent(wparam, ev, pi))
break;
// Ignore this message because we have captured other pointerId.
if (m_capturePointerId &&
m_capturePointerId != pi.pointerId) {
return 0;
}
MOUSE_TRACE("POINTERENTER id=%d xy=%d,%d\n",
pi.pointerId, ev.position().x, ev.position().y);
@ -732,6 +775,12 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam)
m_ignoreMouseMessages = true;
#endif
if (pi.pointerType == PT_TOUCH) {
auto& winApi = system()->winApi();
if (m_ictx && winApi.AddPointerInteractionContext)
winApi.AddPointerInteractionContext(m_ictx, pi.pointerId);
}
if (!m_hasMouse) {
m_hasMouse = true;
@ -744,44 +793,51 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam)
}
case WM_POINTERLEAVE: {
UINT32 pointerId = GET_POINTERID_WPARAM(wparam);
POINTER_INFO pi;
Event ev;
if (!pointerEvent(wparam, ev, pi))
break;
// Ignore this message because we have captured other pointerId.
if (m_capturePointerId &&
m_capturePointerId != pointerId) {
return 0;
}
MOUSE_TRACE("POINTERLEAVE id=%d\n", pointerId);
MOUSE_TRACE("POINTERLEAVE id=%d\n", pi.pointerId);
#if USE_EnableMouseInPointer == 0
m_ignoreMouseMessages = false;
#endif
if (pi.pointerType == PT_TOUCH) {
auto& winApi = system()->winApi();
if (m_ictx && winApi.RemovePointerInteractionContext)
winApi.RemovePointerInteractionContext(m_ictx, pi.pointerId);
}
#if 0 // Don't generate MouseLeave from pen/touch messages
// TODO we should generate this message, but after this touch
// messages don't work anymore, so we have to fix that problem.
if (m_hasMouse) {
m_hasMouse = false;
Event ev;
ev.setType(Event::MouseLeave);
ev.setModifiers(get_modifiers_from_last_win32_message());
queueEvent(ev);
MOUSE_TRACE("-> Event::MouseLeave\n");
return 0;
}
#endif
break;
}
case WM_POINTERDOWN: {
POINTER_INFO pi;
Event ev;
if (!pointerEvent(wparam, lparam, ev, pi))
if (!pointerEvent(wparam, ev, pi))
break;
// Ignore this message because we have captured other pointerId.
if (m_capturePointerId &&
m_capturePointerId != pi.pointerId) {
return 0;
if (pi.pointerType == PT_TOUCH) {
auto& winApi = system()->winApi();
if (m_ictx && winApi.ProcessPointerFramesInteractionContext) {
winApi.ProcessPointerFramesInteractionContext(m_ictx, 1, 1, &pi);
return 0;
}
}
ev.setType(Event::MouseDown);
@ -803,13 +859,15 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam)
case WM_POINTERUP: {
POINTER_INFO pi;
Event ev;
if (!pointerEvent(wparam, lparam, ev, pi))
if (!pointerEvent(wparam, ev, pi))
break;
// Ignore this message because we have captured other pointerId.
if (m_capturePointerId &&
m_capturePointerId != pi.pointerId) {
return 0;
if (pi.pointerType == PT_TOUCH) {
auto& winApi = system()->winApi();
if (m_ictx && winApi.ProcessPointerFramesInteractionContext) {
winApi.ProcessPointerFramesInteractionContext(m_ictx, 1, 1, &pi);
return 0;
}
}
ev.setType(Event::MouseUp);
@ -831,13 +889,15 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam)
case WM_POINTERUPDATE: {
POINTER_INFO pi;
Event ev;
if (!pointerEvent(wparam, lparam, ev, pi))
if (!pointerEvent(wparam, ev, pi))
break;
// Ignore this message because we have captured other pointerId.
if (m_capturePointerId &&
m_capturePointerId != pi.pointerId) {
return 0;
if (pi.pointerType == PT_TOUCH) {
auto& winApi = system()->winApi();
if (m_ictx && winApi.ProcessPointerFramesInteractionContext) {
winApi.ProcessPointerFramesInteractionContext(m_ictx, 1, 1, &pi);
return 0;
}
}
if (!m_hasMouse) {
@ -861,15 +921,9 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam)
case WM_POINTERHWHEEL: {
POINTER_INFO pi;
Event ev;
if (!pointerEvent(wparam, lparam, ev, pi))
if (!pointerEvent(wparam, ev, pi))
break;
// Ignore this message because we have captured other pointerId.
if (m_capturePointerId &&
m_capturePointerId != pi.pointerId) {
return 0;
}
ev.setType(Event::MouseWheel);
int z = GET_WHEEL_DELTA_WPARAM(wparam);
@ -1090,8 +1144,7 @@ void WinWindow::mouseEvent(LPARAM lparam, Event& ev)
GET_Y_LPARAM(lparam) / m_scale));
}
bool WinWindow::pointerEvent(WPARAM wparam, LPARAM lparam,
Event& ev, POINTER_INFO& pi)
bool WinWindow::pointerEvent(WPARAM wparam, Event& ev, POINTER_INFO& pi)
{
if (!m_usePointerApi)
return false;
@ -1111,9 +1164,12 @@ bool WinWindow::pointerEvent(WPARAM wparam, LPARAM lparam,
ev.setPointerType(PointerType::Mouse);
break;
}
case PT_TOUCH:
case PT_TOUCH: {
ev.setPointerType(PointerType::Touch);
break;
}
case PT_TOUCHPAD: {
ev.setPointerType(PointerType::Multitouch);
ev.setPointerType(PointerType::Touchpad);
break;
}
case PT_PEN: {
@ -1132,6 +1188,75 @@ bool WinWindow::pointerEvent(WPARAM wparam, LPARAM lparam,
return true;
}
void WinWindow::handleInteractionContextOutput(
const INTERACTION_CONTEXT_OUTPUT* output)
{
MOUSE_TRACE("%s (%d) xy=%.16g %.16g flags=%d type=%d\n",
output->interactionId == INTERACTION_ID_MANIPULATION ? "INTERACTION_ID_MANIPULATION":
output->interactionId == INTERACTION_ID_TAP ? "INTERACTION_ID_TAP":
output->interactionId == INTERACTION_ID_SECONDARY_TAP ? "INTERACTION_ID_SECONDARY_TAP":
output->interactionId == INTERACTION_ID_HOLD ? "INTERACTION_ID_HOLD": "INTERACTION_ID_???",
output->interactionId,
output->x, output->y,
output->interactionFlags,
output->inputType);
// We use the InteractionContext to interpret touch gestures only.
if (output->inputType == PT_TOUCH) {
ABS_CLIENT_RC(rc);
gfx::Point pos(int((output->x - rc.left) / m_scale),
int((output->y - rc.top) / m_scale));
Event ev;
ev.setModifiers(get_modifiers_from_last_win32_message());
ev.setPosition(pos);
switch (output->interactionId) {
case INTERACTION_ID_MANIPULATION: {
MOUSE_TRACE(" - delta xy=%.16g %.16g scale=%.16g expansion=%.16g rotation=%.16g\n",
output->arguments.manipulation.delta.translationX,
output->arguments.manipulation.delta.translationY,
output->arguments.manipulation.delta.scale,
output->arguments.manipulation.delta.expansion,
output->arguments.manipulation.delta.rotation);
gfx::Point delta(-int(output->arguments.manipulation.delta.translationX) / m_scale,
-int(output->arguments.manipulation.delta.translationY) / m_scale);
ev.setType(Event::MouseWheel);
ev.setWheelDelta(delta);
ev.setPreciseWheel(true);
queueEvent(ev);
ev.setType(Event::TouchMagnify);
ev.setMagnification(output->arguments.manipulation.delta.scale - 1.0);
queueEvent(ev);
break;
}
case INTERACTION_ID_TAP:
MOUSE_TRACE(" - count=%d\n", output->arguments.tap.count);
ev.setButton(Event::LeftButton);
if (output->arguments.tap.count == 2) {
ev.setType(Event::MouseDoubleClick); queueEvent(ev);
}
else {
ev.setType(Event::MouseDown); queueEvent(ev);
ev.setType(Event::MouseUp); queueEvent(ev);
}
break;
case INTERACTION_ID_SECONDARY_TAP:
case INTERACTION_ID_HOLD:
ev.setButton(Event::RightButton);
ev.setType(Event::MouseDown); queueEvent(ev);
ev.setType(Event::MouseUp); queueEvent(ev);
break;
}
}
}
//static
void WinWindow::registerClass()
{
@ -1233,6 +1358,15 @@ LRESULT CALLBACK WinWindow::staticWndProc(HWND hwnd, UINT msg, WPARAM wparam, LP
}
}
//static
void CALLBACK WinWindow::staticInteractionContextCallback(
void* clientData,
const INTERACTION_CONTEXT_OUTPUT* output)
{
WinWindow* self = reinterpret_cast<WinWindow*>(clientData);
self->handleInteractionContextOutput(output);
}
// static
WindowSystem* WinWindow::system()
{

View File

@ -15,6 +15,7 @@
#include <string>
#include <windows.h>
#include <interactioncontext.h>
namespace she {
class Event;
@ -55,8 +56,9 @@ namespace she {
bool setCursor(HCURSOR hcursor, bool custom);
LRESULT wndProc(UINT msg, WPARAM wparam, LPARAM lparam);
void mouseEvent(LPARAM lparam, Event& ev);
bool pointerEvent(WPARAM wparam, LPARAM lparam,
Event& ev, POINTER_INFO& pi);
bool pointerEvent(WPARAM wparam, Event& ev, POINTER_INFO& pi);
void handleInteractionContextOutput(
const INTERACTION_CONTEXT_OUTPUT* output);
virtual void onQueueEvent(Event& ev) { }
virtual void onResize(const gfx::Size& sz) { }
@ -64,7 +66,11 @@ namespace she {
static void registerClass();
static HWND createHwnd(WinWindow* self, int width, int height);
static LRESULT CALLBACK staticWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
static LRESULT CALLBACK staticWndProc(
HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
static void CALLBACK staticInteractionContextCallback(
void* clientData,
const INTERACTION_CONTEXT_OUTPUT* output);
static WindowSystem* system();
@ -84,6 +90,7 @@ namespace she {
bool m_ignoreMouseMessages;
UINT32 m_lastPointerId;
UINT32 m_capturePointerId;
HINTERACTIONCONTEXT m_ictx;
// Wintab API data
HCTX m_hpenctx;