Fix T55589, T60967: Windows pen pressure issues at start/end of the stroke.
The new implementation uses WM_POINTERDOWN, WM_POINTERUP and WM_POINTERUPDATE and the pointer API to process stylus events. This avoids the delays that comes with the WM_MOUSE and WM_xBUTTON events. The implementation should work on Windows 8, and Windows 10 with both legacy and new pen interaction. It also changes how the pressure is reset when the Windows Ink implementation is enabled. The previous version reset the pressure to full when the pen left the screen, however for some hardware implementations this allowed a small window where Blender may process the final move event and read the pressure as full leaving a dot on the last event. Differential Revision: https://developer.blender.org/D4314
This commit is contained in:
parent
6a9a2b5133
commit
2933fd6c7c
|
@ -116,6 +116,9 @@
|
|||
#define WM_POINTERUPDATE 0x0245
|
||||
#endif // WM_POINTERUPDATE
|
||||
|
||||
#define WM_POINTERDOWN 0x0246
|
||||
#define WM_POINTERUP 0x0247
|
||||
|
||||
/* Workaround for some laptop touchpads, some of which seems to
|
||||
* have driver issues which makes it so window function receives
|
||||
* the message, but PeekMessage doesn't pick those messages for
|
||||
|
@ -791,9 +794,55 @@ GHOST_EventButton *GHOST_SystemWin32::processButtonEvent(
|
|||
GHOST_WindowWin32 *window,
|
||||
GHOST_TButtonMask mask)
|
||||
{
|
||||
return new GHOST_EventButton(getSystem()->getMilliSeconds(), type, window, mask);
|
||||
GHOST_SystemWin32 * system = (GHOST_SystemWin32 *)getSystem();
|
||||
if (window->useTabletAPI(GHOST_kTabletNative)) {
|
||||
window->setTabletData(NULL);
|
||||
}
|
||||
return new GHOST_EventButton(system->getMilliSeconds(), type, window, mask);
|
||||
}
|
||||
|
||||
GHOST_Event *GHOST_SystemWin32::processPointerEvent(
|
||||
GHOST_TEventType type,
|
||||
GHOST_WindowWin32 *window,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
bool& eventHandled)
|
||||
{
|
||||
GHOST_PointerInfoWin32 pointerInfo;
|
||||
GHOST_SystemWin32 * system = (GHOST_SystemWin32 *)getSystem();
|
||||
|
||||
if (!window->useTabletAPI(GHOST_kTabletNative)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (window->getPointerInfo(&pointerInfo, wParam, lParam) != GHOST_kSuccess) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!pointerInfo.isPrimary) {
|
||||
eventHandled = true;
|
||||
return NULL; // For multi-touch displays we ignore these events
|
||||
}
|
||||
|
||||
system->setCursorPosition(pointerInfo.pixelLocation.x, pointerInfo.pixelLocation.y);
|
||||
|
||||
switch (type) {
|
||||
case GHOST_kEventButtonDown:
|
||||
window->setTabletData(&pointerInfo.tabletData);
|
||||
eventHandled = true;
|
||||
return new GHOST_EventButton(system->getMilliSeconds(), GHOST_kEventButtonDown, window, pointerInfo.buttonMask);
|
||||
case GHOST_kEventButtonUp:
|
||||
eventHandled = true;
|
||||
return new GHOST_EventButton(system->getMilliSeconds(), GHOST_kEventButtonUp, window, pointerInfo.buttonMask);
|
||||
case GHOST_kEventCursorMove:
|
||||
window->setTabletData(&pointerInfo.tabletData);
|
||||
eventHandled = true;
|
||||
return new GHOST_EventCursor(system->getMilliSeconds(), GHOST_kEventCursorMove, window,
|
||||
pointerInfo.pixelLocation.x, pointerInfo.pixelLocation.y);
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_TEventType type, GHOST_WindowWin32 *window)
|
||||
{
|
||||
|
@ -1220,8 +1269,23 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
|||
case WT_PROXIMITY:
|
||||
window->processWin32TabletInitEvent();
|
||||
break;
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Pointer events, processed
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
case WM_POINTERDOWN:
|
||||
event = processPointerEvent(GHOST_kEventButtonDown, window, wParam, lParam, eventHandled);
|
||||
if (event && eventHandled) {
|
||||
window->registerMouseClickEvent(0);
|
||||
}
|
||||
break;
|
||||
case WM_POINTERUP:
|
||||
event = processPointerEvent(GHOST_kEventButtonUp, window, wParam, lParam, eventHandled);
|
||||
if (event && eventHandled) {
|
||||
window->registerMouseClickEvent(1);
|
||||
}
|
||||
break;
|
||||
case WM_POINTERUPDATE:
|
||||
window->processWin32PointerEvent(wParam);
|
||||
event = processPointerEvent(GHOST_kEventCursorMove, window, wParam, lParam, eventHandled);
|
||||
break;
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Mouse events, processed
|
||||
|
|
|
@ -260,6 +260,17 @@ protected:
|
|||
*/
|
||||
static GHOST_EventButton *processButtonEvent(GHOST_TEventType type, GHOST_WindowWin32 *window, GHOST_TButtonMask mask);
|
||||
|
||||
/**
|
||||
* Creates pointer event.
|
||||
* \param type The type of event to create.
|
||||
* \param window The window receiving the event (the active window).
|
||||
* \param wParam The wParam from the wndproc
|
||||
* \param lParam The lParam from the wndproc
|
||||
* \param eventhandled true if the method handled the event
|
||||
* \return The event created.
|
||||
*/
|
||||
static GHOST_Event *processPointerEvent(GHOST_TEventType type, GHOST_WindowWin32 *window, WPARAM wParam, LPARAM lParam, bool & eventhandled);
|
||||
|
||||
/**
|
||||
* Creates cursor event.
|
||||
* \param type The type of event to create.
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include <Dwmapi.h>
|
||||
#endif
|
||||
|
||||
#include <windowsx.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
@ -82,9 +83,10 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
|
|||
m_customCursor(0),
|
||||
m_wantAlphaBackground(alphaBackground),
|
||||
m_normal_state(GHOST_kWindowStateNormal),
|
||||
m_user32(NULL),
|
||||
m_user32(NULL),
|
||||
m_fpGetPointerInfo(NULL),
|
||||
m_fpGetPointerPenInfo(NULL),
|
||||
m_fpGetPointerTouchInfo(NULL),
|
||||
m_parentWindowHwnd(parentwindowhwnd),
|
||||
m_debug_context(is_debug)
|
||||
{
|
||||
|
@ -284,6 +286,7 @@ GHOST_WindowWin32::GHOST_WindowWin32(GHOST_SystemWin32 *system,
|
|||
if (m_user32) {
|
||||
m_fpGetPointerInfo = (GHOST_WIN32_GetPointerInfo) ::GetProcAddress(m_user32, "GetPointerInfo");
|
||||
m_fpGetPointerPenInfo = (GHOST_WIN32_GetPointerPenInfo) ::GetProcAddress(m_user32, "GetPointerPenInfo");
|
||||
m_fpGetPointerTouchInfo = (GHOST_WIN32_GetPointerTouchInfo) ::GetProcAddress(m_user32, "GetPointerTouchInfo");
|
||||
}
|
||||
|
||||
// Initialize Wintab
|
||||
|
@ -372,6 +375,7 @@ GHOST_WindowWin32::~GHOST_WindowWin32()
|
|||
m_user32 = NULL;
|
||||
m_fpGetPointerInfo = NULL;
|
||||
m_fpGetPointerPenInfo = NULL;
|
||||
m_fpGetPointerTouchInfo = NULL;
|
||||
}
|
||||
|
||||
if (m_customCursor) {
|
||||
|
@ -396,11 +400,6 @@ GHOST_WindowWin32::~GHOST_WindowWin32()
|
|||
::DestroyWindow(m_hWnd);
|
||||
m_hWnd = 0;
|
||||
}
|
||||
|
||||
if (m_user32) {
|
||||
FreeLibrary(m_user32);
|
||||
m_user32 = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool GHOST_WindowWin32::getValid() const
|
||||
|
@ -878,54 +877,93 @@ GHOST_TSuccess GHOST_WindowWin32::setWindowCursorShape(GHOST_TStandardCursor cur
|
|||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
void GHOST_WindowWin32::processWin32PointerEvent(WPARAM wParam)
|
||||
GHOST_TSuccess GHOST_WindowWin32::getPointerInfo(GHOST_PointerInfoWin32 *pointerInfo, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (!useTabletAPI(GHOST_kTabletNative)) {
|
||||
return; // Other tablet API specified by user
|
||||
ZeroMemory(pointerInfo, sizeof(GHOST_PointerInfoWin32));
|
||||
|
||||
// Obtain the basic information from the event
|
||||
pointerInfo->pointerId = GET_POINTERID_WPARAM(wParam);
|
||||
pointerInfo->isInContact = IS_POINTER_INCONTACT_WPARAM(wParam);
|
||||
pointerInfo->isPrimary = IS_POINTER_PRIMARY_WPARAM(wParam);
|
||||
|
||||
// Obtain more accurate and predicted information from the Pointer API
|
||||
POINTER_INFO pointerApiInfo;
|
||||
if (!(m_fpGetPointerInfo && m_fpGetPointerInfo(pointerInfo->pointerId, &pointerApiInfo))) {
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
if (!m_fpGetPointerInfo || !m_fpGetPointerPenInfo) {
|
||||
return; // OS version does not support pointer API
|
||||
pointerInfo->hasButtonMask = GHOST_kSuccess;
|
||||
switch (pointerApiInfo.ButtonChangeType) {
|
||||
case POINTER_CHANGE_FIRSTBUTTON_DOWN:
|
||||
case POINTER_CHANGE_FIRSTBUTTON_UP:
|
||||
pointerInfo->buttonMask = GHOST_kButtonMaskLeft;
|
||||
break;
|
||||
case POINTER_CHANGE_SECONDBUTTON_DOWN:
|
||||
case POINTER_CHANGE_SECONDBUTTON_UP:
|
||||
pointerInfo->buttonMask = GHOST_kButtonMaskRight;
|
||||
break;
|
||||
case POINTER_CHANGE_THIRDBUTTON_DOWN:
|
||||
case POINTER_CHANGE_THIRDBUTTON_UP:
|
||||
pointerInfo->buttonMask = GHOST_kButtonMaskMiddle;
|
||||
break;
|
||||
case POINTER_CHANGE_FOURTHBUTTON_DOWN:
|
||||
case POINTER_CHANGE_FOURTHBUTTON_UP:
|
||||
pointerInfo->buttonMask = GHOST_kButtonMaskButton4;
|
||||
break;
|
||||
case POINTER_CHANGE_FIFTHBUTTON_DOWN:
|
||||
case POINTER_CHANGE_FIFTHBUTTON_UP:
|
||||
pointerInfo->buttonMask = GHOST_kButtonMaskButton5;
|
||||
break;
|
||||
default:
|
||||
pointerInfo->hasButtonMask = GHOST_kFailure;
|
||||
break;
|
||||
}
|
||||
|
||||
UINT32 pointerId = GET_POINTERID_WPARAM(wParam);
|
||||
POINTER_INFO pointerInfo;
|
||||
if (!m_fpGetPointerInfo(pointerId, &pointerInfo)) {
|
||||
return; // Invalid pointer info
|
||||
pointerInfo->pixelLocation = pointerApiInfo.ptPixelLocation;
|
||||
pointerInfo->tabletData.Active = GHOST_kTabletModeNone;
|
||||
pointerInfo->tabletData.Pressure = 1.0f;
|
||||
pointerInfo->tabletData.Xtilt = 0.0f;
|
||||
pointerInfo->tabletData.Ytilt = 0.0f;
|
||||
|
||||
if (pointerApiInfo.pointerType != PT_PEN) {
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
m_tabletData.Active = GHOST_kTabletModeNone;
|
||||
m_tabletData.Pressure = 1.0f;
|
||||
m_tabletData.Xtilt = 0.0f;
|
||||
m_tabletData.Ytilt = 0.0f;
|
||||
POINTER_PEN_INFO pointerPenInfo;
|
||||
if (m_fpGetPointerPenInfo && m_fpGetPointerPenInfo(pointerInfo->pointerId, &pointerPenInfo)) {
|
||||
pointerInfo->tabletData.Active = GHOST_kTabletModeStylus;
|
||||
|
||||
if (pointerInfo.pointerType & PT_POINTER) {
|
||||
POINTER_PEN_INFO pointerPenInfo;
|
||||
if (!m_fpGetPointerPenInfo(pointerId, &pointerPenInfo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// With the Microsoft Surface Pen if you hover the within 1cm of the screen the WM_POINTERUPDATE
|
||||
// event will fire with PEN_MASK_PRESSURE mask set and zero pressure. In this case we disable
|
||||
// tablet mode until the pen is physically touching. This enables the user to switch to the
|
||||
// mouse and draw at full pressure.
|
||||
if (pointerPenInfo.penMask & PEN_MASK_PRESSURE && pointerPenInfo.pressure > 0) {
|
||||
m_tabletData.Active = GHOST_kTabletModeStylus;
|
||||
m_tabletData.Pressure = pointerPenInfo.pressure / 1024.0f;
|
||||
if (pointerPenInfo.penMask & PEN_MASK_PRESSURE) {
|
||||
pointerInfo->tabletData.Pressure = pointerPenInfo.pressure / 1024.0f;
|
||||
}
|
||||
|
||||
if (pointerPenInfo.penFlags & PEN_FLAG_ERASER) {
|
||||
m_tabletData.Active = GHOST_kTabletModeEraser;
|
||||
pointerInfo->tabletData.Active = GHOST_kTabletModeEraser;
|
||||
}
|
||||
|
||||
if (pointerPenInfo.penFlags & PEN_MASK_TILT_X) {
|
||||
m_tabletData.Xtilt = fmin(fabs(pointerPenInfo.tiltX / 90), 1.0f);
|
||||
pointerInfo->tabletData.Xtilt = fmin(fabs(pointerPenInfo.tiltX / 90), 1.0f);
|
||||
}
|
||||
|
||||
if (pointerPenInfo.penFlags & PEN_MASK_TILT_Y) {
|
||||
m_tabletData.Ytilt = fmin(fabs(pointerPenInfo.tiltY / 90), 1.0f);
|
||||
pointerInfo->tabletData.Ytilt = fmin(fabs(pointerPenInfo.tiltY / 90), 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
|
||||
void GHOST_WindowWin32::setTabletData(GHOST_TabletData * pTabletData)
|
||||
{
|
||||
if (pTabletData) {
|
||||
m_tabletData = *pTabletData;
|
||||
}
|
||||
else {
|
||||
m_tabletData.Active = GHOST_kTabletModeNone;
|
||||
m_tabletData.Pressure = 1.0f;
|
||||
m_tabletData.Xtilt = 0.0f;
|
||||
m_tabletData.Ytilt = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void GHOST_WindowWin32::processWin32TabletActivateEvent(WORD state)
|
||||
|
|
|
@ -130,8 +130,70 @@ typedef struct tagPOINTER_PEN_INFO {
|
|||
INT32 tiltY;
|
||||
} POINTER_PEN_INFO;
|
||||
|
||||
/*
|
||||
* Flags that appear in pointer input message parameters
|
||||
*/
|
||||
#define POINTER_MESSAGE_FLAG_NEW 0x00000001 // New pointer
|
||||
#define POINTER_MESSAGE_FLAG_INRANGE 0x00000002 // Pointer has not departed
|
||||
#define POINTER_MESSAGE_FLAG_INCONTACT 0x00000004 // Pointer is in contact
|
||||
#define POINTER_MESSAGE_FLAG_FIRSTBUTTON 0x00000010 // Primary action
|
||||
#define POINTER_MESSAGE_FLAG_SECONDBUTTON 0x00000020 // Secondary action
|
||||
#define POINTER_MESSAGE_FLAG_THIRDBUTTON 0x00000040 // Third button
|
||||
#define POINTER_MESSAGE_FLAG_FOURTHBUTTON 0x00000080 // Fourth button
|
||||
#define POINTER_MESSAGE_FLAG_FIFTHBUTTON 0x00000100 // Fifth button
|
||||
#define POINTER_MESSAGE_FLAG_PRIMARY 0x00002000 // Pointer is primary
|
||||
#define POINTER_MESSAGE_FLAG_CONFIDENCE 0x00004000 // Pointer is considered unlikely to be accidental
|
||||
#define POINTER_MESSAGE_FLAG_CANCELED 0x00008000 // Pointer is departing in an abnormal manner
|
||||
|
||||
typedef UINT32 TOUCH_FLAGS;
|
||||
#define TOUCH_FLAG_NONE 0x00000000 // Default
|
||||
|
||||
typedef UINT32 TOUCH_MASK;
|
||||
#define TOUCH_MASK_NONE 0x00000000 // Default - none of the optional fields are valid
|
||||
#define TOUCH_MASK_CONTACTAREA 0x00000001 // The rcContact field is valid
|
||||
#define TOUCH_MASK_ORIENTATION 0x00000002 // The orientation field is valid
|
||||
#define TOUCH_MASK_PRESSURE 0x00000004 // The pressure field is valid
|
||||
|
||||
typedef struct tagPOINTER_TOUCH_INFO {
|
||||
POINTER_INFO pointerInfo;
|
||||
TOUCH_FLAGS touchFlags;
|
||||
TOUCH_MASK touchMask;
|
||||
RECT rcContact;
|
||||
RECT rcContactRaw;
|
||||
UINT32 orientation;
|
||||
UINT32 pressure;
|
||||
} POINTER_TOUCH_INFO;
|
||||
|
||||
/*
|
||||
* Macros to retrieve information from pointer input message parameters
|
||||
*/
|
||||
#define GET_POINTERID_WPARAM(wParam) (LOWORD(wParam))
|
||||
#define IS_POINTER_FLAG_SET_WPARAM(wParam, flag) (((DWORD)HIWORD(wParam) & (flag)) == (flag))
|
||||
#define IS_POINTER_NEW_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_NEW)
|
||||
#define IS_POINTER_INRANGE_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_INRANGE)
|
||||
#define IS_POINTER_INCONTACT_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_INCONTACT)
|
||||
#define IS_POINTER_FIRSTBUTTON_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIRSTBUTTON)
|
||||
#define IS_POINTER_SECONDBUTTON_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_SECONDBUTTON)
|
||||
#define IS_POINTER_THIRDBUTTON_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_THIRDBUTTON)
|
||||
#define IS_POINTER_FOURTHBUTTON_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FOURTHBUTTON)
|
||||
#define IS_POINTER_FIFTHBUTTON_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_FIFTHBUTTON)
|
||||
#define IS_POINTER_PRIMARY_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_PRIMARY)
|
||||
#define HAS_POINTER_CONFIDENCE_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_CONFIDENCE)
|
||||
#define IS_POINTER_CANCELED_WPARAM(wParam) IS_POINTER_FLAG_SET_WPARAM(wParam, POINTER_MESSAGE_FLAG_CANCELED)
|
||||
|
||||
typedef BOOL (API * GHOST_WIN32_GetPointerInfo)(UINT32 pointerId, POINTER_INFO *pointerInfo);
|
||||
typedef BOOL (API * GHOST_WIN32_GetPointerPenInfo)(UINT32 pointerId, POINTER_PEN_INFO *penInfo);
|
||||
typedef BOOL (API * GHOST_WIN32_GetPointerTouchInfo)(UINT32 pointerId, POINTER_TOUCH_INFO *penInfo);
|
||||
|
||||
struct GHOST_PointerInfoWin32 {
|
||||
GHOST_TInt32 pointerId;
|
||||
GHOST_TInt32 isInContact;
|
||||
GHOST_TInt32 isPrimary;
|
||||
GHOST_TSuccess hasButtonMask;
|
||||
GHOST_TButtonMask buttonMask;
|
||||
POINT pixelLocation;
|
||||
GHOST_TabletData tabletData;
|
||||
};
|
||||
|
||||
/**
|
||||
* GHOST window on M$ Windows OSs.
|
||||
|
@ -316,7 +378,9 @@ public:
|
|||
return &m_tabletData;
|
||||
}
|
||||
|
||||
void setTabletData(GHOST_TabletData * tabletData);
|
||||
bool useTabletAPI(GHOST_TTabletAPI api) const;
|
||||
void getPointerInfo(WPARAM wParam);
|
||||
|
||||
void processWin32PointerEvent(WPARAM wParam);
|
||||
void processWin32TabletActivateEvent(WORD state);
|
||||
|
@ -330,6 +394,8 @@ public:
|
|||
|
||||
GHOST_TUns16 getDPIHint() override;
|
||||
|
||||
GHOST_TSuccess getPointerInfo(GHOST_PointerInfoWin32 *pointerInfo, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
/** if the window currently resizing */
|
||||
bool m_inLiveResize;
|
||||
|
||||
|
@ -445,6 +511,7 @@ private:
|
|||
HMODULE m_user32;
|
||||
GHOST_WIN32_GetPointerInfo m_fpGetPointerInfo;
|
||||
GHOST_WIN32_GetPointerPenInfo m_fpGetPointerPenInfo;
|
||||
GHOST_WIN32_GetPointerTouchInfo m_fpGetPointerTouchInfo;
|
||||
|
||||
/** Hwnd to parent window */
|
||||
GHOST_TEmbedderWindowID m_parentWindowHwnd;
|
||||
|
|
Loading…
Reference in New Issue