Fixed mouse capture behavior. It's not as clean as it could be, but it'll do.

This commit is contained in:
Pietro Gagliardi 2015-12-18 19:38:21 -05:00
parent 4b114f2764
commit 444a7d3045
2 changed files with 84 additions and 23 deletions

View File

@ -22,6 +22,7 @@ struct uiArea {
BOOL capturing; BOOL capturing;
BOOL inside; BOOL inside;
BOOL tracking;
ID2D1HwndRenderTarget *rt; ID2D1HwndRenderTarget *rt;
}; };

View File

@ -19,16 +19,87 @@ static uiModifiers getModifiers(void)
return m; return m;
} }
/*
Windows doesn't natively support mouse crossing events.
TrackMouseEvent() (and its comctl32.dll wrapper _TrackMouseEvent()) both allow for a window to receive the WM_MOUSELEAVE message when the mouse leaves the client area. There's no equivalent WM_MOUSEENTER because it can be simulated (https://blogs.msdn.microsoft.com/oldnewthing/20031013-00/?p=42193).
Unfortunately, WM_MOUSELEAVE does not get generated while the mouse is captured. We need to capture for drag behavior to work properly, so this isn't going to mix well.
So what we do:
- on WM_MOUSEMOVE, if we don't have the capture, start tracking
- this will handle the case of the capture being released while still in the area
- on WM_MOUSELEAVE, mark that we are no longer tracking
- Windows has already done the work of that for us; it's just a flag we use for the next part
- when starting capture, stop tracking if we are tracking
- if capturing, manually check if the pointer is in the client rect on each area event
*/
static void track(uiArea *a, BOOL tracking)
{
TRACKMOUSEEVENT tm;
// do nothing if there's no change
if (a->tracking && tracking)
return;
if (!a->tracking && !tracking)
return;
a->tracking = tracking;
ZeroMemory(&tm, sizeof (TRACKMOUSEEVENT));
tm.cbSize = sizeof (TRACKMOUSEEVENT);
tm.dwFlags = TME_LEAVE;
if (!a->tracking)
tm.dwFlags |= TME_CANCEL;
tm.hwndTrack = a->hwnd;
if (_TrackMouseEvent(&tm) == 0)
logLastError("error setting up mouse leave events in onMouseEntered()");
}
static void capture(uiArea *a, BOOL capturing)
{
// do nothing if there's no change
if (a->capturing && capturing)
return;
if (!a->capturing && !capturing)
return;
// change flag first as ReleaseCapture() sends WM_CAPTURECHANGED
a->capturing = capturing;
if (a->capturing) {
track(a, FALSE);
SetCapture(a->hwnd);
} else
if (ReleaseCapture() == 0)
logLastError("error releasing capture on drag in capture()");
}
static void areaMouseEvent(uiArea *a, uintmax_t down, uintmax_t up, WPARAM wParam, LPARAM lParam) static void areaMouseEvent(uiArea *a, uintmax_t down, uintmax_t up, WPARAM wParam, LPARAM lParam)
{ {
uiAreaMouseEvent me; uiAreaMouseEvent me;
uintmax_t button; uintmax_t button;
POINT clientpt;
RECT client;
BOOL inClient;
double xpix, ypix; double xpix, ypix;
FLOAT dpix, dpiy; FLOAT dpix, dpiy;
D2D1_SIZE_F size; D2D1_SIZE_F size;
if (a->rt == NULL) if (a->capturing) {
a->rt = makeHWNDRenderTarget(a->hwnd); clientpt.x = GET_X_LPARAM(lParam);
clientpt.y = GET_Y_LPARAM(lParam);
if (GetClientRect(a->hwnd, &client) == 0)
logLastError("TODO");
inClient = PtInRect(&client, clientpt);
if (inClient && !a->inside) {
a->inside = TRUE;
(*(a->ah->MouseCrossed))(a->ah, a, 0);
clickCounterReset(&(a->cc));
} else if (!inClient && a->inside) {
a->inside = FALSE;
(*(a->ah->MouseCrossed))(a->ah, a, 1);
clickCounterReset(&(a->cc));
}
}
xpix = (double) GET_X_LPARAM(lParam); xpix = (double) GET_X_LPARAM(lParam);
ypix = (double) GET_Y_LPARAM(lParam); ypix = (double) GET_Y_LPARAM(lParam);
@ -77,43 +148,32 @@ static void areaMouseEvent(uiArea *a, uintmax_t down, uintmax_t up, WPARAM wPar
me.Held1To64 |= 1 << 4; me.Held1To64 |= 1 << 4;
// on Windows, we have to capture on drag ourselves // on Windows, we have to capture on drag ourselves
if (me.Down != 0 && !a->capturing) { if (me.Down != 0)
SetCapture(a->hwnd); capture(a, TRUE);
a->capturing = TRUE;
}
// only release capture when all buttons released // only release capture when all buttons released
if (me.Up != 0 && a->capturing && me.Held1To64 == 0) { if (me.Up != 0 && me.Held1To64 == 0)
// unset flag first as ReleaseCapture() sends WM_CAPTURECHANGED capture(a, FALSE);
a->capturing = FALSE;
if (ReleaseCapture() == 0)
logLastError("error releasing capture on drag in areaMouseEvent()");
}
(*(a->ah->MouseEvent))(a->ah, a, &me); (*(a->ah->MouseEvent))(a->ah, a, &me);
} }
// see https://blogs.msdn.microsoft.com/oldnewthing/20031013-00/?p=42193 // TODO genericize this so it can be called above
// TODO this does not work while captured
static void onMouseEntered(uiArea *a) static void onMouseEntered(uiArea *a)
{ {
TRACKMOUSEEVENT tm;
if (a->inside) if (a->inside)
return; return;
ZeroMemory(&tm, sizeof (TRACKMOUSEEVENT)); if (a->capturing) // we handle mouse crossing in areaMouseEvent()
tm.cbSize = sizeof (TRACKMOUSEEVENT); return;
tm.dwFlags = TME_LEAVE; track(a, TRUE);
tm.hwndTrack = a->hwnd;
if (_TrackMouseEvent(&tm) == 0)
logLastError("error setting up mouse leave events in onMouseEntered()");
a->inside = TRUE;
(*(a->ah->MouseCrossed))(a->ah, a, 0); (*(a->ah->MouseCrossed))(a->ah, a, 0);
// TODO figure out why we did this to begin with; either we do it on both GTK+ and Windows or not at all // TODO figure out why we did this to begin with; either we do it on both GTK+ and Windows or not at all
clickCounterReset(&(a->cc)); clickCounterReset(&(a->cc));
} }
// TODO genericize it so that it can be called above
static void onMouseLeft(uiArea *a) static void onMouseLeft(uiArea *a)
{ {
a->tracking = FALSE;
a->inside = FALSE; a->inside = FALSE;
(*(a->ah->MouseCrossed))(a->ah, a, 1); (*(a->ah->MouseCrossed))(a->ah, a, 1);
// TODO figure out why we did this to begin with; either we do it on both GTK+ and Windows or not at all // TODO figure out why we did this to begin with; either we do it on both GTK+ and Windows or not at all