Added mouse events to Windows uiArea.
This commit is contained in:
parent
018c18a74d
commit
1a78c4c2f1
|
@ -106,7 +106,7 @@ static int handlerRedrawOnResize(uiAreaHandler *a, uiArea *area)
|
|||
|
||||
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->Y,
|
||||
(int) e->HScrollPos,
|
||||
|
|
104
winarea/area.c
104
winarea/area.c
|
@ -11,6 +11,7 @@ struct uiArea {
|
|||
intmax_t vscrollpos;
|
||||
int hwheelCarry;
|
||||
int vwheelCarry;
|
||||
clickCounter cc;
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
uiArea *a;
|
||||
|
@ -301,10 +363,46 @@ static LRESULT CALLBACK areaWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM
|
|||
case WM_MOUSEWHEEL:
|
||||
vwheelscroll(a, wParam, lParam);
|
||||
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:
|
||||
// TODO
|
||||
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);
|
||||
}
|
||||
|
@ -345,6 +443,8 @@ HWND makeArea(DWORD exstyle, DWORD style, int x, int y, int cx, int cy, HWND par
|
|||
x, y, cx, cy,
|
||||
parent, NULL, hInstance, a);
|
||||
|
||||
clickCounterReset(&(a->cc));
|
||||
|
||||
return a->hwnd;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "../windows/winapi.h"
|
||||
#include <stdint.h>
|
||||
#include "ui.h"
|
||||
#include "uipriv.h"
|
||||
|
||||
extern HINSTANCE hInstance;
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
*/
|
|
@ -110,6 +110,20 @@ static int handlerRedrawOnResize(uiAreaHandler *a, uiArea *area)
|
|||
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)
|
||||
{
|
||||
RECT r;
|
||||
|
@ -161,6 +175,7 @@ int main(void)
|
|||
h.ah.HScrollMax = handlerHScrollMax;
|
||||
h.ah.VScrollMax = handlerVScrollMax;
|
||||
h.ah.RedrawOnResize = handlerRedrawOnResize;
|
||||
h.ah.MouseEvent = handlerMouseEvent;
|
||||
|
||||
registerAreaClass();
|
||||
|
||||
|
|
51
winarea/ui.h
51
winarea/ui.h
|
@ -3,6 +3,7 @@
|
|||
typedef struct uiArea uiArea;
|
||||
typedef struct uiAreaHandler uiAreaHandler;
|
||||
typedef struct uiAreaDrawParams uiAreaDrawParams;
|
||||
typedef struct uiAreaMouseEvent uiAreaMouseEvent;
|
||||
|
||||
typedef struct uiDrawContext uiDrawContext;
|
||||
|
||||
|
@ -11,6 +12,7 @@ struct uiAreaHandler {
|
|||
uintmax_t (*HScrollMax)(uiAreaHandler *, uiArea *);
|
||||
uintmax_t (*VScrollMax)(uiAreaHandler *, uiArea *);
|
||||
int (*RedrawOnResize)(uiAreaHandler *, uiArea *);
|
||||
void (*MouseEvent)(uiAreaHandler *, uiArea *, uiAreaMouseEvent *);
|
||||
};
|
||||
|
||||
struct uiAreaDrawParams {
|
||||
|
@ -115,25 +117,30 @@ void uiDrawFill(uiDrawContext *, uiDrawFillMode);
|
|||
// - solid colors, arbitrary spaces
|
||||
// - shadows
|
||||
|
||||
// arcs
|
||||
// cairo_arc/arc_negative
|
||||
// - parameters: center, radius, angle1 radians, angle2 radins
|
||||
// - if angle2 < angle1, TODO
|
||||
// - if angle2 > angle1, TODO
|
||||
// - line segment from current point to beginning of arc
|
||||
// - arc: clockwise, arc_negative: counterclockwise
|
||||
// - circular
|
||||
// GDI Arc/Pie
|
||||
// - parameters: bounding box, start point, end point
|
||||
// - current position not used/changed
|
||||
// - either clockwise or counterclockwise
|
||||
// - elliptical
|
||||
// GDI ArcTo
|
||||
// - same as Arc except line segment from current point to beginning of arc
|
||||
// - there does not appear to be a PieTo
|
||||
// GDI AngleArc
|
||||
// - parameters: center, radius, angle1 degrees, angle2 degrees
|
||||
// - line segment from current position to beginning of arc
|
||||
// - counterclockwise
|
||||
// - circular
|
||||
// - can be used to draw pies too; MSDN example demonstrates
|
||||
typedef enum uiModifiers uiModifiers;
|
||||
|
||||
enum uiModifiers {
|
||||
uiModifierCtrl = 1 << 0,
|
||||
uiModifierAlt = 1 << 1,
|
||||
uiModifierShift = 1 << 2,
|
||||
uiModifierSuper = 1 << 3,
|
||||
};
|
||||
|
||||
struct uiAreaMouseEvent {
|
||||
// notes:
|
||||
// - relative to content rect
|
||||
intmax_t X;
|
||||
intmax_t Y;
|
||||
|
||||
intmax_t HScrollPos;
|
||||
intmax_t VScrollPos;
|
||||
|
||||
uintmax_t Down;
|
||||
uintmax_t Up;
|
||||
|
||||
uintmax_t Count;
|
||||
|
||||
uiModifiers Modifiers;
|
||||
|
||||
uint64_t Held1To64;
|
||||
};
|
||||
|
|
|
@ -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 *);
|
Loading…
Reference in New Issue