Added mouse events to Windows uiArea.

This commit is contained in:
Pietro Gagliardi 2015-09-11 16:01:54 -04:00
parent 018c18a74d
commit 1a78c4c2f1
7 changed files with 323 additions and 25 deletions

View File

@ -106,7 +106,7 @@ static int handlerRedrawOnResize(uiAreaHandler *a, uiArea *area)
static void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e) static void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)
{ {
printf("mouse (%d,%d):(%d,%d) dn:%d up:%d count:%d mods:%x held:%x\n", printf("mouse (%d,%d):(%d,%d) down:%d up:%d count:%d mods:%x held:%x\n",
(int) e->X, (int) e->X,
(int) e->Y, (int) e->Y,
(int) e->HScrollPos, (int) e->HScrollPos,

View File

@ -11,6 +11,7 @@ struct uiArea {
intmax_t vscrollpos; intmax_t vscrollpos;
int hwheelCarry; int hwheelCarry;
int vwheelCarry; int vwheelCarry;
clickCounter cc;
}; };
static void doPaint(uiArea *a, HDC dc, RECT *client, RECT *clip) static void doPaint(uiArea *a, HDC dc, RECT *client, RECT *clip)
@ -250,6 +251,67 @@ static void vwheelscroll(uiArea *a, WPARAM wParam, LPARAM lParam)
wheelscroll(a, SB_VERT, &p, wParam, lParam); wheelscroll(a, SB_VERT, &p, wParam, lParam);
} }
static uiModifiers getModifiers(void)
{
uiModifiers m = 0;
if ((GetKeyState(VK_CONTROL) & 0x80) != 0)
m |= uiModifierCtrl;
if ((GetKeyState(VK_MENU) & 0x80) != 0)
m |= uiModifierAlt;
if ((GetKeyState(VK_SHIFT) & 0x80) != 0)
m |= uiModifierShift;
if ((GetKeyState(VK_LWIN) & 0x80) != 0)
m |= uiModifierSuper;
if ((GetKeyState(VK_RWIN) & 0x80) != 0)
m |= uiModifierSuper;
return m;
}
static void areaMouseEvent(uiArea *a, uintmax_t down, uintmax_t up, WPARAM wParam, LPARAM lParam)
{
uiAreaMouseEvent me;
uintmax_t button;
me.X = GET_X_LPARAM(lParam);
me.Y = GET_Y_LPARAM(lParam);
// do not cap to the client rect in the case of a capture
me.HScrollPos = a->hscrollpos;
me.VScrollPos = a->vscrollpos;
me.Down = down;
me.Up = up;
me.Count = 0;
if (me.Down != 0)
// GetMessageTime() returns LONG and GetDoubleClckTime() returns UINT, which are int32 and uint32, respectively, but we don't need to worry about the signedness because for the same bit widths and two's complement arithmetic, s1-s2 == u1-u2 if bits(s1)==bits(s2) and bits(u1)==bits(u2) (and Windows requires two's complement: http://blogs.msdn.com/b/oldnewthing/archive/2005/05/27/422551.aspx)
// signedness isn't much of an issue for these calls anyway because http://stackoverflow.com/questions/24022225/what-are-the-sign-extension-rules-for-calling-windows-api-functions-stdcall-t and that we're only using unsigned values (think back to how you (didn't) handle signedness in assembly language) AND because of the above AND because the statistics below (time interval and width/height) really don't make sense if negative
me.Count = clickCounterClick(&(a->cc), me.Down,
me.X, me.Y,
GetMessageTime(), GetDoubleClickTime(),
GetSystemMetrics(SM_CXDOUBLECLK) / 2,
GetSystemMetrics(SM_CYDOUBLECLK) / 2);
// though wparam will contain control and shift state, let's just one function to get modifiers for both keyboard and mouse events; it'll work the same anyway since we have to do this for alt and windows key (super)
me.Modifiers = getModifiers();
button = me.Down;
if (button == 0)
button = me.Up;
me.Held1To64 = 0;
if (button != 1 && (wParam & MK_LBUTTON) != 0)
me.Held1To64 |= 1 << 0;
if (button != 2 && (wParam & MK_MBUTTON) != 0)
me.Held1To64 |= 1 << 1;
if (button != 3 && (wParam & MK_RBUTTON) != 0)
me.Held1To64 |= 1 << 2;
if (button != 4 && (wParam & MK_XBUTTON1) != 0)
me.Held1To64 |= 1 << 3;
if (button != 5 && (wParam & MK_XBUTTON2) != 0)
me.Held1To64 |= 1 << 4;
(*(a->ah->MouseEvent))(a->ah, a, &me);
}
static LRESULT CALLBACK areaWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) static LRESULT CALLBACK areaWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{ {
uiArea *a; uiArea *a;
@ -301,10 +363,46 @@ static LRESULT CALLBACK areaWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM
case WM_MOUSEWHEEL: case WM_MOUSEWHEEL:
vwheelscroll(a, wParam, lParam); vwheelscroll(a, wParam, lParam);
return 0; return 0;
case WM_ACTIVATE:
// don't keep the double-click timer running if the user switched programs in between clicks
clickCounterReset(&(a->cc));
return 0;
case WM_MOUSEMOVE:
areaMouseEvent(a, 0, 0, wParam, lParam);
return 0;
case WM_LBUTTONDOWN: case WM_LBUTTONDOWN:
// TODO
SetFocus(a->hwnd); SetFocus(a->hwnd);
break; areaMouseEvent(a, 1, 0, wParam, lParam);
return 0;
case WM_LBUTTONUP:
areaMouseEvent(a, 0, 1, wParam, lParam);
return 0;
case WM_MBUTTONDOWN:
SetFocus(a->hwnd);
areaMouseEvent(a, 2, 0, wParam, lParam);
return 0;
case WM_MBUTTONUP:
areaMouseEvent(a, 0, 2, wParam, lParam);
return 0;
case WM_RBUTTONDOWN:
SetFocus(a->hwnd);
areaMouseEvent(a, 3, 0, wParam, lParam);
return 0;
case WM_RBUTTONUP:
areaMouseEvent(a, 0, 3, wParam, lParam);
return 0;
case WM_XBUTTONDOWN:
SetFocus(a->hwnd);
// values start at 1; we want them to start at 4
areaMouseEvent(a,
GET_XBUTTON_WPARAM(wParam) + 3, 0,
GET_KEYSTATE_WPARAM(wParam), lParam);
return TRUE; // XBUTTON messages are different!
case WM_XBUTTONUP:
areaMouseEvent(a,
0, GET_XBUTTON_WPARAM(wParam) + 3,
GET_KEYSTATE_WPARAM(wParam), lParam);
return TRUE; // XBUTTON messages are different!
} }
return DefWindowProc(hwnd, uMsg, wParam, lParam); return DefWindowProc(hwnd, uMsg, wParam, lParam);
} }
@ -345,6 +443,8 @@ HWND makeArea(DWORD exstyle, DWORD style, int x, int y, int cx, int cy, HWND par
x, y, cx, cy, x, y, cx, cy,
parent, NULL, hInstance, a); parent, NULL, hInstance, a);
clickCounterReset(&(a->cc));
return a->hwnd; return a->hwnd;
} }

View File

@ -2,6 +2,7 @@
#include "../windows/winapi.h" #include "../windows/winapi.h"
#include <stdint.h> #include <stdint.h>
#include "ui.h" #include "ui.h"
#include "uipriv.h"
extern HINSTANCE hInstance; extern HINSTANCE hInstance;

161
winarea/events.c Normal file
View File

@ -0,0 +1,161 @@
// 29 march 2014
// TODO remove
#include <stdint.h>
#include "ui.h"
#include "uipriv.h"
/*
Windows and GTK+ have a limit of 2 and 3 clicks, respectively, natively supported. Fortunately, we can simulate the double/triple-click behavior to build higher-order clicks. We can use the same algorithm Windows uses on both:
http://blogs.msdn.com/b/oldnewthing/archive/2004/10/18/243925.aspx
For GTK+, we pull the double-click time and double-click distance, which work the same as the equivalents on Windows (so the distance is in all directions), from the GtkSettings system.
On GTK+ this will also allow us to discard the GDK_BUTTON_2PRESS and GDK_BUTTON_3PRESS events, so the button press stream will be just like on other platforms.
Thanks to mclasen, garnacho_, halfline, and tristan in irc.gimp.net/#gtk+.
*/
// x, y, xdist, ydist, and c.rect must have the same units
// so must time, maxTime, and c.prevTime
uintmax_t clickCounterClick(clickCounter *c, uintmax_t button, intmax_t x, intmax_t y, uintptr_t time, uintptr_t maxTime, intmax_t xdist, intmax_t ydist)
{
// different button than before? if so, don't count
if (button != c->curButton)
c->count = 0;
// (x, y) in the allowed region for a double-click? if not, don't count
if (x < c->rectX0)
c->count = 0;
if (y < c->rectY0)
c->count = 0;
if (x >= c->rectX1)
c->count = 0;
if (y >= c->rectY1)
c->count = 0;
// too slow? if so, don't count
// note the below expression; time > (c.prevTime + maxTime) can overflow!
if ((time - c->prevTime) > maxTime) // too slow; don't count
c->count = 0;
c->count++; // if either of the above ifs happened, this will make the click count 1; otherwise it will make the click count 2, 3, 4, 5, ...
// now we need to update the internal structures for the next test
c->curButton = button;
c->prevTime = time;
c->rectX0 = x - xdist;
c->rectY0 = y - ydist;
c->rectX1 = x + xdist;
c->rectY1 = y + ydist;
return c->count;
}
void clickCounterReset(clickCounter *c)
{
c->curButton = 0;
c->rectX0 = 0;
c->rectY0 = 0;
c->rectX1 = 0;
c->rectY1 = 0;
c->prevTime = 0;
c->count = 0;
}
/*
For position independence across international keyboard layouts, typewriter keys are read using scancodes (which are always set 1).
Windows provides the scancodes directly in the LPARAM.
GTK+ provides the scancodes directly from the underlying window system via GdkEventKey.hardware_keycode.
On X11, this is scancode + 8 (because X11 keyboard codes have a range of [8,255]).
Wayland is guaranteed to give the same result (thanks ebassi in irc.gimp.net/#gtk+).
On Linux, where evdev is used instead of polling scancodes directly from the keyboard, evdev's typewriter section key code constants are the same as scancodes anyway, so the rules above apply.
Typewriter section scancodes are the same across international keyboards with some exceptions that have been accounted for (see KeyEvent's documentation); see http://www.quadibloc.com/comp/scan.htm for details.
Non-typewriter keys can be handled safely using constants provided by the respective backend API.
Because GTK+ keysyms may or may not obey Num Lock, we also handle the 0-9 and . keys on the numeric keypad with scancodes (they match too).
*/
/*
TODO
// use uintptr to be safe; the size of the scancode/hardware key code field on each platform is different
var scancodeKeys = map[uintptr]byte{
0x02: '1',
0x03: '2',
0x04: '3',
0x05: '4',
0x06: '5',
0x07: '6',
0x08: '7',
0x09: '8',
0x0A: '9',
0x0B: '0',
0x0C: '-',
0x0D: '=',
0x0E: '\b',
0x0F: '\t',
0x10: 'q',
0x11: 'w',
0x12: 'e',
0x13: 'r',
0x14: 't',
0x15: 'y',
0x16: 'u',
0x17: 'i',
0x18: 'o',
0x19: 'p',
0x1A: '[',
0x1B: ']',
0x1C: '\n',
0x1E: 'a',
0x1F: 's',
0x20: 'd',
0x21: 'f',
0x22: 'g',
0x23: 'h',
0x24: 'j',
0x25: 'k',
0x26: 'l',
0x27: ';',
0x28: '\'',
0x29: '`',
0x2B: '\\',
0x2C: 'z',
0x2D: 'x',
0x2E: 'c',
0x2F: 'v',
0x30: 'b',
0x31: 'n',
0x32: 'm',
0x33: ',',
0x34: '.',
0x35: '/',
0x39: ' ',
}
var scancodeExtKeys = map[uintptr]ExtKey{
0x47: N7,
0x48: N8,
0x49: N9,
0x4B: N4,
0x4C: N5,
0x4D: N6,
0x4F: N1,
0x50: N2,
0x51: N3,
0x52: N0,
0x53: NDot,
}
func fromScancode(scancode uintptr) (ke KeyEvent, ok bool) {
if key, ok := scancodeKeys[scancode]; ok {
ke.Key = key
return ke, true
}
if extkey, ok := scancodeExtKeys[scancode]; ok {
ke.ExtKey = extkey
return ke, true
}
return ke, false
}
*/

View File

@ -110,6 +110,20 @@ static int handlerRedrawOnResize(uiAreaHandler *a, uiArea *area)
return 1; return 1;
} }
static void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)
{
printf("mouse (%d,%d):(%d,%d) down:%d up:%d count:%d mods:%x held:%I64x\n",
(int) e->X,
(int) e->Y,
(int) e->HScrollPos,
(int) e->VScrollPos,
(int) e->Down,
(int) e->Up,
(int) e->Count,
(uint32_t) e->Modifiers,
e->Held1To64);
}
static void repos(HWND hwnd) static void repos(HWND hwnd)
{ {
RECT r; RECT r;
@ -161,6 +175,7 @@ int main(void)
h.ah.HScrollMax = handlerHScrollMax; h.ah.HScrollMax = handlerHScrollMax;
h.ah.VScrollMax = handlerVScrollMax; h.ah.VScrollMax = handlerVScrollMax;
h.ah.RedrawOnResize = handlerRedrawOnResize; h.ah.RedrawOnResize = handlerRedrawOnResize;
h.ah.MouseEvent = handlerMouseEvent;
registerAreaClass(); registerAreaClass();

View File

@ -3,6 +3,7 @@
typedef struct uiArea uiArea; typedef struct uiArea uiArea;
typedef struct uiAreaHandler uiAreaHandler; typedef struct uiAreaHandler uiAreaHandler;
typedef struct uiAreaDrawParams uiAreaDrawParams; typedef struct uiAreaDrawParams uiAreaDrawParams;
typedef struct uiAreaMouseEvent uiAreaMouseEvent;
typedef struct uiDrawContext uiDrawContext; typedef struct uiDrawContext uiDrawContext;
@ -11,6 +12,7 @@ struct uiAreaHandler {
uintmax_t (*HScrollMax)(uiAreaHandler *, uiArea *); uintmax_t (*HScrollMax)(uiAreaHandler *, uiArea *);
uintmax_t (*VScrollMax)(uiAreaHandler *, uiArea *); uintmax_t (*VScrollMax)(uiAreaHandler *, uiArea *);
int (*RedrawOnResize)(uiAreaHandler *, uiArea *); int (*RedrawOnResize)(uiAreaHandler *, uiArea *);
void (*MouseEvent)(uiAreaHandler *, uiArea *, uiAreaMouseEvent *);
}; };
struct uiAreaDrawParams { struct uiAreaDrawParams {
@ -115,25 +117,30 @@ void uiDrawFill(uiDrawContext *, uiDrawFillMode);
// - solid colors, arbitrary spaces // - solid colors, arbitrary spaces
// - shadows // - shadows
// arcs typedef enum uiModifiers uiModifiers;
// cairo_arc/arc_negative
// - parameters: center, radius, angle1 radians, angle2 radins enum uiModifiers {
// - if angle2 < angle1, TODO uiModifierCtrl = 1 << 0,
// - if angle2 > angle1, TODO uiModifierAlt = 1 << 1,
// - line segment from current point to beginning of arc uiModifierShift = 1 << 2,
// - arc: clockwise, arc_negative: counterclockwise uiModifierSuper = 1 << 3,
// - circular };
// GDI Arc/Pie
// - parameters: bounding box, start point, end point struct uiAreaMouseEvent {
// - current position not used/changed // notes:
// - either clockwise or counterclockwise // - relative to content rect
// - elliptical intmax_t X;
// GDI ArcTo intmax_t Y;
// - same as Arc except line segment from current point to beginning of arc
// - there does not appear to be a PieTo intmax_t HScrollPos;
// GDI AngleArc intmax_t VScrollPos;
// - parameters: center, radius, angle1 degrees, angle2 degrees
// - line segment from current position to beginning of arc uintmax_t Down;
// - counterclockwise uintmax_t Up;
// - circular
// - can be used to draw pies too; MSDN example demonstrates uintmax_t Count;
uiModifiers Modifiers;
uint64_t Held1To64;
};

14
winarea/uipriv.h Normal file
View File

@ -0,0 +1,14 @@
typedef struct clickCounter clickCounter;
// you should call Reset() to zero-initialize a new instance
// it doesn't matter that all the non-count fields are zero: the first click will fail the curButton test straightaway, so it'll return 1 and set the rest of the structure accordingly
struct clickCounter {
uintmax_t curButton;
intmax_t rectX0;
intmax_t rectY0;
intmax_t rectX1;
intmax_t rectY1;
uintptr_t prevTime;
uintmax_t count;
};
extern uintmax_t clickCounterClick(clickCounter *, uintmax_t, intmax_t, intmax_t, uintptr_t, uintptr_t, intmax_t, intmax_t);
extern void clickCounterReset(clickCounter *);