diff --git a/area_windows.go b/area_windows.go index 01080e5..3559bd4 100644 --- a/area_windows.go +++ b/area_windows.go @@ -24,6 +24,32 @@ var ( areaWndClassNumLock sync.Mutex ) +func getScrollPos(hwnd _HWND) (xpos int32, ypos int32) { + var si _SCROLLINFO + + si.cbSize = uint32(unsafe.Sizeof(si)) + si.fMask = _SIF_POS | _SIF_TRACKPOS + r1, _, err := _getScrollInfo.Call( + uintptr(hwnd), + uintptr(_SB_HORZ), + uintptr(unsafe.Pointer(&si))) + if r1 == 0 { // failure + panic(fmt.Errorf("error getting horizontal scroll position for Area: %v", err)) + } + xpos = si.nPos + si.cbSize = uint32(unsafe.Sizeof(si)) // MSDN example code reinitializes this each time, so we'll do it too just to be safe + si.fMask = _SIF_POS | _SIF_TRACKPOS + r1, _, err = _getScrollInfo.Call( + uintptr(hwnd), + uintptr(_SB_VERT), + uintptr(unsafe.Pointer(&si))) + if r1 == 0 { // failure + panic(fmt.Errorf("error getting vertical scroll position for Area: %v", err)) + } + ypos = si.nPos + return xpos, ypos +} + var ( _getUpdateRect = user32.NewProc("GetUpdateRect") _beginPaint = user32.NewProc("BeginPaint") @@ -51,7 +77,6 @@ func paintArea(s *sysData) { var xrect _RECT var ps _PAINTSTRUCT - var si _SCROLLINFO // TODO send _TRUE if we want to erase the clip area r1, _, _ := _getUpdateRect.Call( @@ -62,36 +87,17 @@ func paintArea(s *sysData) { return } - si.cbSize = uint32(unsafe.Sizeof(si)) - si.fMask = _SIF_POS | _SIF_TRACKPOS - r1, _, err := _getScrollInfo.Call( - uintptr(s.hwnd), - uintptr(_SB_HORZ), - uintptr(unsafe.Pointer(&si))) - if r1 == 0 { // failure - panic(fmt.Errorf("error getting horizontal scroll position for Area repaint: %v", err)) - } - hscroll := int(si.nPos) - si.cbSize = uint32(unsafe.Sizeof(si)) // MSDN example code reinitializes this each time, so we'll do it too just to be safe - si.fMask = _SIF_POS | _SIF_TRACKPOS - r1, _, err = _getScrollInfo.Call( - uintptr(s.hwnd), - uintptr(_SB_VERT), - uintptr(unsafe.Pointer(&si))) - if r1 == 0 { // failure - panic(fmt.Errorf("error getting vertical scroll position for Area repaint: %v", err)) - } - vscroll := int(si.nPos) + hscroll, vscroll := getScrollPos(s.hwnd) cliprect := image.Rect(int(xrect.Left), int(xrect.Top), int(xrect.Right), int(xrect.Bottom)) - cliprect = cliprect.Add(image.Pt(hscroll, vscroll)) // adjust by scroll position + cliprect = cliprect.Add(image.Pt(int(hscroll), int(vscroll))) // adjust by scroll position // make sure the cliprect doesn't fall outside the size of the Area cliprect = cliprect.Intersect(image.Rect(0, 0, 320, 240)) // TODO change when adding resizing if cliprect.Empty() { // still no update rect return } - r1, _, err = _beginPaint.Call( + r1, _, err := _beginPaint.Call( uintptr(s.hwnd), uintptr(unsafe.Pointer(&ps))) if r1 == 0 { // failure @@ -300,6 +306,57 @@ func adjustAreaScrollbars(hwnd _HWND) { uintptr(_TRUE)) // redraw the scroll bar } +var ( + _getKeyState = user32.NewProc("GetKeyState") +) + +func getModifiers() (m Modifiers) { + down := func(x uintptr) bool { + r1, _, _ := _getKeyState.Call(x) + return (r1 & 0x80) != 0 + } + + if down(_VK_CONTROL) { + m |= Ctrl + } + if down(_VK_MENU) { + m |= Alt + } + if down(_VK_SHIFT) { + m |= Shift + } + // TODO windows key (super) + return m +} + +func areaMouseEvent(s *sysData, button uint, up bool, count uint, wparam _WPARAM, lparam _LPARAM) { + var me MouseEvent + + xpos, ypos := getScrollPos(s.hwnd) // mouse coordinates are relative to control; make them relative to Area + xpos += lparam._X() + ypos += lparam._Y() + me.Pos = image.Pt(int(xpos), int(ypos)) + if up { + me.Up = button + } else { + me.Down = button + me.Count = count + } + // though wparam will contain control and shift state, let's use 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() + if button != 1 && (wparam & _MK_LBUTTON) != 0 { + me.Held = append(me.Held, 1) + } + if button != 2 && (wparam & _MK_MBUTTON) != 0 { + me.Held = append(me.Held, 2) + } + if button != 3 && (wparam & _MK_RBUTTON) != 0 { + me.Held = append(me.Held, 3) + } + // TODO XBUTTONs? + s.handler.Mouse(me) +} + func areaWndProc(s *sysData) func(hwnd _HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) _LRESULT { return func(hwnd _HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) _LRESULT { switch uMsg { @@ -315,6 +372,37 @@ func areaWndProc(s *sysData) func(hwnd _HWND, uMsg uint32, wParam _WPARAM, lPara case _WM_SIZE: adjustAreaScrollbars(hwnd) // don't use s.hwnd; this message can be sent before that's loaded return 0 + case _WM_MOUSEMOVE: + areaMouseEvent(s, 0, false, 0, wParam, lParam) + return 0 + case _WM_LBUTTONDOWN: + areaMouseEvent(s, 1, false, 1, wParam, lParam) + return 0 + case _WM_LBUTTONDBLCLK: + areaMouseEvent(s, 1, false, 2, wParam, lParam) + return 0 + case _WM_LBUTTONUP: + areaMouseEvent(s, 1, true, 0, wParam, lParam) + return 0 + case _WM_MBUTTONDOWN: + areaMouseEvent(s, 2, false, 1, wParam, lParam) + return 0 + case _WM_MBUTTONDBLCLK: + areaMouseEvent(s, 2, false, 2, wParam, lParam) + return 0 + case _WM_MBUTTONUP: + areaMouseEvent(s, 2, true, 0, wParam, lParam) + return 0 + case _WM_RBUTTONDOWN: + areaMouseEvent(s, 3, false, 1, wParam, lParam) + return 0 + case _WM_RBUTTONDBLCLK: + areaMouseEvent(s, 3, false, 2, wParam, lParam) + return 0 + case _WM_RBUTTONUP: + areaMouseEvent(s, 3, true, 0, wParam, lParam) + return 0 + // TODO XBUTTONs? default: r1, _, _ := defWindowProc.Call( uintptr(hwnd), diff --git a/common_windows.go b/common_windows.go index ee09650..acc3b2e 100644 --- a/common_windows.go +++ b/common_windows.go @@ -50,6 +50,20 @@ func _MAKEINTRESOURCE(what uint16) uintptr { return uintptr(what) } +func (l _LPARAM) _X() int32 { + // according to windowsx.h + loword := uint16(l & 0xFFFF) + short := int16(loword) // convert to signed... + return int32(short) // ...and sign extend +} + +func (l _LPARAM) _Y() int32 { + // according to windowsx.h + hiword := uint16((l & 0xFFFF0000) >> 16) + short := int16(hiword) // convert to signed... + return int32(short) // ...and sign extend +} + type _POINT struct { X int32 Y int32 diff --git a/events_windows.go b/events_windows.go new file mode 100644 index 0000000..e03f095 --- /dev/null +++ b/events_windows.go @@ -0,0 +1,215 @@ +// 25 march 2014 + +package ui + +// Virtual key codes. +const ( + // from winuser.h + _VK_LBUTTON = 0x01 + _VK_RBUTTON = 0x02 + _VK_CANCEL = 0x03 + _VK_MBUTTON = 0x04 + _VK_XBUTTON1 = 0x05 + _VK_XBUTTON2 = 0x06 + _VK_BACK = 0x08 + _VK_TAB = 0x09 + _VK_CLEAR = 0x0C + _VK_RETURN = 0x0D + _VK_SHIFT = 0x10 + _VK_CONTROL = 0x11 + _VK_MENU = 0x12 + _VK_PAUSE = 0x13 + _VK_CAPITAL = 0x14 + _VK_KANA = 0x15 + _VK_HANGEUL = 0x15 + _VK_HANGUL = 0x15 + _VK_JUNJA = 0x17 + _VK_FINAL = 0x18 + _VK_HANJA = 0x19 + _VK_KANJI = 0x19 + _VK_ESCAPE = 0x1B + _VK_CONVERT = 0x1C + _VK_NONCONVERT = 0x1D + _VK_ACCEPT = 0x1E + _VK_MODECHANGE = 0x1F + _VK_SPACE = 0x20 + _VK_PRIOR = 0x21 + _VK_NEXT = 0x22 + _VK_END = 0x23 + _VK_HOME = 0x24 + _VK_LEFT = 0x25 + _VK_UP = 0x26 + _VK_RIGHT = 0x27 + _VK_DOWN = 0x28 + _VK_SELECT = 0x29 + _VK_PRINT = 0x2A + _VK_EXECUTE = 0x2B + _VK_SNAPSHOT = 0x2C + _VK_INSERT = 0x2D + _VK_DELETE = 0x2E + _VK_HELP = 0x2F + _VK_LWIN = 0x5B + _VK_RWIN = 0x5C + _VK_APPS = 0x5D + _VK_SLEEP = 0x5F + _VK_NUMPAD0 = 0x60 + _VK_NUMPAD1 = 0x61 + _VK_NUMPAD2 = 0x62 + _VK_NUMPAD3 = 0x63 + _VK_NUMPAD4 = 0x64 + _VK_NUMPAD5 = 0x65 + _VK_NUMPAD6 = 0x66 + _VK_NUMPAD7 = 0x67 + _VK_NUMPAD8 = 0x68 + _VK_NUMPAD9 = 0x69 + _VK_MULTIPLY = 0x6A + _VK_ADD = 0x6B + _VK_SEPARATOR = 0x6C + _VK_SUBTRACT = 0x6D + _VK_DECIMAL = 0x6E + _VK_DIVIDE = 0x6F + _VK_F1 = 0x70 + _VK_F2 = 0x71 + _VK_F3 = 0x72 + _VK_F4 = 0x73 + _VK_F5 = 0x74 + _VK_F6 = 0x75 + _VK_F7 = 0x76 + _VK_F8 = 0x77 + _VK_F9 = 0x78 + _VK_F10 = 0x79 + _VK_F11 = 0x7A + _VK_F12 = 0x7B + _VK_F13 = 0x7C + _VK_F14 = 0x7D + _VK_F15 = 0x7E + _VK_F16 = 0x7F + _VK_F17 = 0x80 + _VK_F18 = 0x81 + _VK_F19 = 0x82 + _VK_F20 = 0x83 + _VK_F21 = 0x84 + _VK_F22 = 0x85 + _VK_F23 = 0x86 + _VK_F24 = 0x87 + _VK_NUMLOCK = 0x90 + _VK_SCROLL = 0x91 + _VK_OEM_NEC_EQUAL = 0x92 + _VK_OEM_FJ_JISHO = 0x92 + _VK_OEM_FJ_MASSHOU = 0x93 + _VK_OEM_FJ_TOUROKU = 0x94 + _VK_OEM_FJ_LOYA = 0x95 + _VK_OEM_FJ_ROYA = 0x96 + _VK_LSHIFT = 0xA0 + _VK_RSHIFT = 0xA1 + _VK_LCONTROL = 0xA2 + _VK_RCONTROL = 0xA3 + _VK_LMENU = 0xA4 + _VK_RMENU = 0xA5 + _VK_BROWSER_BACK = 0xA6 + _VK_BROWSER_FORWARD = 0xA7 + _VK_BROWSER_REFRESH = 0xA8 + _VK_BROWSER_STOP = 0xA9 + _VK_BROWSER_SEARCH = 0xAA + _VK_BROWSER_FAVORITES = 0xAB + _VK_BROWSER_HOME = 0xAC + _VK_VOLUME_MUTE = 0xAD + _VK_VOLUME_DOWN = 0xAE + _VK_VOLUME_UP = 0xAF + _VK_MEDIA_NEXT_TRACK = 0xB0 + _VK_MEDIA_PREV_TRACK = 0xB1 + _VK_MEDIA_STOP = 0xB2 + _VK_MEDIA_PLAY_PAUSE = 0xB3 + _VK_LAUNCH_MAIL = 0xB4 + _VK_LAUNCH_MEDIA_SELECT = 0xB5 + _VK_LAUNCH_APP1 = 0xB6 + _VK_LAUNCH_APP2 = 0xB7 + _VK_OEM_1 = 0xBA + _VK_OEM_PLUS = 0xBB + _VK_OEM_COMMA = 0xBC + _VK_OEM_MINUS = 0xBD + _VK_OEM_PERIOD = 0xBE + _VK_OEM_2 = 0xBF + _VK_OEM_3 = 0xC0 + _VK_OEM_4 = 0xDB + _VK_OEM_5 = 0xDC + _VK_OEM_6 = 0xDD + _VK_OEM_7 = 0xDE + _VK_OEM_8 = 0xDF + _VK_OEM_AX = 0xE1 + _VK_OEM_102 = 0xE2 + _VK_ICO_HELP = 0xE3 + _VK_ICO_00 = 0xE4 + _VK_PROCESSKEY = 0xE5 + _VK_ICO_CLEAR = 0xE6 + _VK_PACKET = 0xE7 + _VK_OEM_RESET = 0xE9 + _VK_OEM_JUMP = 0xEA + _VK_OEM_PA1 = 0xEB + _VK_OEM_PA2 = 0xEC + _VK_OEM_PA3 = 0xED + _VK_OEM_WSCTRL = 0xEE + _VK_OEM_CUSEL = 0xEF + _VK_OEM_ATTN = 0xF0 + _VK_OEM_FINISH = 0xF1 + _VK_OEM_COPY = 0xF2 + _VK_OEM_AUTO = 0xF3 + _VK_OEM_ENLW = 0xF4 + _VK_OEM_BACKTAB = 0xF5 + _VK_ATTN = 0xF6 + _VK_CRSEL = 0xF7 + _VK_EXSEL = 0xF8 + _VK_EREOF = 0xF9 + _VK_PLAY = 0xFA + _VK_ZOOM = 0xFB + _VK_NONAME = 0xFC + _VK_PA1 = 0xFD + _VK_OEM_CLEAR = 0xFE +) + +// Mouse event modifier masks. +const ( + // from winuser.h + _MK_LBUTTON = 0x0001 + _MK_RBUTTON = 0x0002 + _MK_SHIFT = 0x0004 + _MK_CONTROL = 0x0008 + _MK_MBUTTON = 0x0010 + _MK_XBUTTON1 = 0x0020 + _MK_XBUTTON2 = 0x0040 +) + +// Window mouse event messages. +const ( + // from winuser.h + _WM_MOUSEFIRST = 0x0200 + _WM_MOUSEMOVE = 0x0200 + _WM_LBUTTONDOWN = 0x0201 + _WM_LBUTTONUP = 0x0202 + _WM_LBUTTONDBLCLK = 0x0203 + _WM_RBUTTONDOWN = 0x0204 + _WM_RBUTTONUP = 0x0205 + _WM_RBUTTONDBLCLK = 0x0206 + _WM_MBUTTONDOWN = 0x0207 + _WM_MBUTTONUP = 0x0208 + _WM_MBUTTONDBLCLK = 0x0209 + _WM_MOUSEWHEEL = 0x020A + _WM_XBUTTONDOWN = 0x020B + _WM_XBUTTONUP = 0x020C + _WM_XBUTTONDBLCLK = 0x020D +) + +// Window keyboard event messages and related constants. +const ( + // from winuser.h + _WM_KEYDOWN = 0x0100 + _WM_KEYUP = 0x0101 + _WM_CHAR = 0x0102 + _WM_DEADCHAR = 0x0103 + _WM_SYSKEYDOWN = 0x0104 + _WM_SYSKEYUP = 0x0105 + _WM_SYSCHAR = 0x0106 + _WM_SYSDEADCHAR = 0x0107 + _WM_UNICHAR = 0x0109 + _UNICODE_NOCHAR = 0xFFFF // used by _WM_UNICHAR +) diff --git a/test/main.go b/test/main.go index 056ab1a..3a20ff8 100644 --- a/test/main.go +++ b/test/main.go @@ -132,7 +132,7 @@ func (a *areaHandler) Paint(rect image.Rectangle) *image.NRGBA { return a.img.SubImage(rect).(*image.NRGBA) } func (a *areaHandler) Mouse(e MouseEvent) { -// fmt.Printf("%#v\n", e) + fmt.Printf("%#v\n", e) } func (a *areaHandler) Key(e KeyEvent) bool { fmt.Printf("%#v\n", e) diff --git a/todo.md b/todo.md index 4a81bae..ad853ae 100644 --- a/todo.md +++ b/todo.md @@ -48,6 +48,7 @@ important things: - make sure scrollbars in Listbox work identically on all platforms (specifically the existence and autohiding of both horizontal and vertical scrollbars) - pin down this behavior; also note non-editability - the size of Listboxes on Windows does not fill the requested space completely (wait, wasn't there a style that governed this?) +- make sure mouse events don't trigger if the control size is larger than the Area size and the mouse event happens outside the Area range on all platforms super ultra important things: - the windows build appears to be unstable: