diff options
Diffstat (limited to 'src/nigui/private/windows/platform_impl.nim')
| -rwxr-xr-x | src/nigui/private/windows/platform_impl.nim | 1278 |
1 files changed, 1278 insertions, 0 deletions
diff --git a/src/nigui/private/windows/platform_impl.nim b/src/nigui/private/windows/platform_impl.nim new file mode 100755 index 0000000..c4c3010 --- /dev/null +++ b/src/nigui/private/windows/platform_impl.nim @@ -0,0 +1,1278 @@ +# NiGui - Win32 platform-specific code - part 3 + +# This file will be included in "nigui.nim". + +# Idendifiers, which are only used in this file, are marked with a leading "p". + +# Imports: +# math, os, strutils, times are imported by nigui.nim +import windows +import tables + + +# ---------------------------------------------------------------------------------------- +# Internal Things +# ---------------------------------------------------------------------------------------- + +const pTopLevelWindowClass = "1" +const pContainerWindowClass = "2" +const pCustomControlWindowClass = "3" + +var pDefaultParentWindow: pointer +var pKeyState: KeyState + +# needed to calculate clicks: +var pLastMouseButtonDownControl: Control +var pLastMouseButtonDownControlX: int +var pLastMouseButtonDownControlY: int + +proc pRaiseLastOSError(showAlert = true) = + let e = osLastError() + raiseError(osErrorMsg(e).strip & " (OS Error Code: " & $e & ")", showAlert) + +proc pCheckGdiplusStatus(status: int32, showAlert = true) = + if status != 0: + if status == 7: + pRaiseLastOSError(showAlert) + else: + raiseError("A GDI+ error occured. (Status: " & $status & ")", showAlert) + +proc pColorToRGB32(color: Color): RGB32 = + result.red = color.red + result.green = color.green + result.blue = color.blue + +proc pRgb32ToColor(color: RGB32): Color = + result.red = color.red + result.green = color.green + result.blue = color.blue + +proc pColorToARGB(color: Color): ARGB = + result.red = color.red + result.green = color.green + result.blue = color.blue + result.alpha = color.alpha + +proc pUtf8ToUtf16(s: string): string = + # result is terminated with 2 null bytes + if s.len == 0: + return "\0" + var characters = MultiByteToWideChar(CP_UTF8, 0, s, s.len.int32, nil, 0) # request number of characters + if characters == 0: pRaiseLastOSError() + result = newString(characters * 2 + 1) + characters = MultiByteToWideChar(CP_UTF8, 0, s, s.len.int32, result, characters.int32) # do the conversion + result[characters * 2] = '\0' + result[characters * 2 + 1] = '\0' + if characters == 0: pRaiseLastOSError() + +proc pUtf16ToUtf8(s: string, searchEnd = false): string = + if s.len == 0: + return "" + var characters = s.len div 2 + if searchEnd: + # Search end of utf16 string: + var i = 0 + while i < s.len - 1 and s[i].ord != 0: + i.inc(2) + characters = i div 2 + var bytes = WideCharToMultiByte(CP_UTF8, 0, s, characters.int32, nil, 0, nil, nil) # request number of bytes + if bytes == 0: pRaiseLastOSError() + result = newString(bytes) + bytes = WideCharToMultiByte(CP_UTF8, 0, s, characters.int32, result, bytes.int32, nil, nil) # do the conversion + if bytes == 0: pRaiseLastOSError() + +proc pUnicodeCharToUtf8(unicode: int): string = + var widestring = newString(2) + widestring[0] = chr(unicode mod 256) + widestring[1] = chr(unicode div 256) + result = widestring.pUtf16ToUtf8 + +proc pUtf16ToUnicode(s: string): int = s[0].ord + s[1].ord * 256 + +proc pShowWindow(hWnd: pointer, nCmdShow: int32) = discard ShowWindow(hWnd, nCmdShow) + +proc pSetWindowLong(hWnd: pointer, nIndex, dwNewLong: int32) = + let result = SetWindowLongA(hWnd, nIndex, dwNewLong) + if result == 0: pRaiseLastOSError() + +proc pDestroyWindow(hWnd: pointer) = + let result = DestroyWindow(hWnd) + if not result: pRaiseLastOSError() + +proc pSetParent(hWndChild, hWndNewParent: pointer) = + let result = SetParent(hWndChild, hWndNewParent) + if result == nil: pRaiseLastOSError() + +proc pSetWindowText(hWnd: pointer, s: string) = + let result = SetWindowTextW(hWnd, s.pUtf8ToUtf16) + if not result: pRaiseLastOSError() + +proc pGetWindowText(hWnd: pointer): string = + let characters = GetWindowTextLengthW(hWnd) + result = newString(characters * 2) + var res = GetWindowTextW(hWnd, result, characters * 2 + 1) + if res != characters: pRaiseLastOSError() + result = result.pUtf16ToUtf8 + +proc pSetWindowPos(wnd: pointer, x, y, cx, cy: int, uFlags: int32 = 0) = + var result = SetWindowPos(wnd, nil, x.int32, y.int32, cx.int32, cy.int32, uFlags) + if not result: pRaiseLastOSError() + +proc pGetClientRect(wnd: pointer): Rect = + if not GetClientRect(wnd, result): pRaiseLastOSError() + +proc pGetWindowRect(wnd: pointer): Rect = + if not GetWindowRect(wnd, result): pRaiseLastOSError() + +proc pCreateWindowEx(dwExStyle: int32, lpClassName, lpWindowName: cstring, dwStyle: int32, x, y, nWidth, nHeight: int, hWndParent, hMenu, hInstance, lpParam: pointer): pointer = + result = CreateWindowExA(dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam) + if result == nil: pRaiseLastOSError() + +# proc pGetStockObject(fnObject: int32): pointer = + # result = GetStockObject(fnObject) + # if result == nil: pRaiseLastOSError() + +proc pCreateWindowExWithUserdata(lpClassName: cstring, dwStyle, dwExStyle: int32, hWndParent, userdata: pointer = nil): pointer = + result = pCreateWindowEx(dwExStyle, lpClassName, nil, dwStyle, 0, 0, 0, 0, hWndParent, nil, nil, nil) + if userdata != nil: + discard SetWindowLongPtrW(result, GWLP_USERDATA, userdata) + # Set default font: + # discard SendMessageA(result, WM_SETFONT, pGetStockObject(DEFAULT_GUI_FONT), cast[pointer](true)) + # Set window proc: + # discard SetWindowLongPtrW(result, GWLP_WNDPROC, pCommonWndProc) + +proc pEnableVisualStyles() = + # Without this, controls have style of Windows 95 + const MaxLength = 500 + var dir = newString(MaxLength) + if GetSystemDirectoryA(dir[0].addr, MaxLength) == 0: pRaiseLastOSError() + var actCtx: ActCtx + actCtx.cbSize = ActCtx.sizeof.int32 + actCtx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID or ACTCTX_FLAG_SET_PROCESS_DEFAULT or ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID + actCtx.lpSource = "shell32.dll" + actCtx.wProcessorArchitecture = 0 + actCtx.wLangId = 0 + actCtx.lpAssemblyDirectory = dir + actCtx.lpResourceName = cast[cstring](124) + actCtx.lpApplicationName = nil + actCtx.hModule = nil + var context = CreateActCtxA(actCtx.addr) + if context == INVALID_HANDLE_VALUE: pRaiseLastOSError() + # has no effect: + # var ulpActivationCookie = false + # if not ActivateActCtx(context, ulpActivationCookie.addr): pRaiseLastOSError() + +proc pRegisterWindowClass(className: cstring, wndProc: pointer, style: int32 = 0) = + var class: WndClassEx + class.cbSize = WndClassEx.sizeof.int32 + class.lpszClassName = className + class.lpfnWndProc = wndProc + class.style = style + class.cbClsExtra = 0 + class.cbWndExtra = 0 + class.hInstance = nil + class.hIcon = nil + class.hCursor = LoadCursorA(nil, cast[cstring](IDC_ARROW)) + class.hbrBackground = CreateSolidBrush(GetSysColor(COLOR_BTNFACE)) # default background + class.lpszMenuName = nil + class.hIconSm = nil + if RegisterClassExA(class) == 0: pRaiseLastOSError() + +proc pCommonWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer = + case uMsg + of WM_COMMAND: + if wParam.hiWord == EN_CHANGE: + let control = cast[Control](GetWindowLongPtrW(lParam, GWLP_USERDATA)) + var evt = new TextChangeEvent + control.handleTextChangeEvent(evt) + of WM_CTLCOLOREDIT: + let control = cast[Control](GetWindowLongPtrW(lParam, GWLP_USERDATA)) + discard SetTextColor(wParam, control.textColor.pColorToRGB32()) + # discard SetBkColor(wParam, control.backgroundColor.pColorToRGB32()) + # does not cover complete background + return GetCurrentObject(wParam, OBJ_BRUSH) + else: + discard + result = DefWindowProcA(hWnd, uMsg, wParam, lParam) + +proc pKeyvalToKey(keyval: int): Key = + case keyval + of 48: Key_Number0 + of 49: Key_Number1 + of 50: Key_Number2 + of 51: Key_Number3 + of 52: Key_Number4 + of 53: Key_Number5 + of 54: Key_Number6 + of 55: Key_Number7 + of 56: Key_Number8 + of 57: Key_Number9 + of 65: Key_A + of 66: Key_B + of 67: Key_C + of 68: Key_D + of 69: Key_E + of 70: Key_F + of 71: Key_G + of 72: Key_H + of 73: Key_I + of 74: Key_J + of 75: Key_K + of 76: Key_L + of 77: Key_M + of 78: Key_N + of 79: Key_O + of 80: Key_P + of 81: Key_Q + of 82: Key_R + of 83: Key_S + of 84: Key_T + of 85: Key_U + of 86: Key_V + of 87: Key_W + of 88: Key_X + of 89: Key_Y + of 90: Key_Z + of 32: Key_Space + of 9: Key_Tab + of 13: Key_Return + of 27: Key_Escape + of 45: Key_Insert + of 46: Key_Delete + of 8: Key_Backspace + of 37: Key_Left + of 38: Key_Up + of 39: Key_Right + of 40: Key_Down + of 35: Key_End + of 36: Key_Home + of 33: Key_PageUp + of 34: Key_PageDown + else: Key_None + +proc pHandleWMKEYDOWN(window: Window, control: Control, wParam, lParam: pointer) = + var windowEvent = new WindowKeyEvent + windowEvent.window = window + windowEvent.key = pKeyvalToKey(cast[int](wParam)) + if windowEvent.key == Key_None: + echo "Unkown key value: ", cast[int](wParam) + return + if not GetKeyboardState(pKeyState): pRaiseLastOSError() + var widestring = newString(2) + let scancode = int32((cast[int](lParam) shr 8) and 0xFFFFFF00) + let ret = ToUnicode(cast[int](wParam).int32, scancode, pKeyState, widestring, 1, 0) + if ret == 1: + windowEvent.unicode = widestring.pUtf16ToUnicode + windowEvent.character = windowEvent.unicode.pUnicodeCharToUtf8 + else: + windowEvent.character = "" + window.handleKeyDownEvent(windowEvent) + + + # var windowEvent = new WindowKeyEvent + # windowEvent.window = window + # windowEvent.character = $chr(cast[int](wParam)) + # windowEvent.key = pKeyvalToKey(cast[int](wParam)) + # window.handleKeyDownEvent(windowEvent) + + if control != nil: + var controlEvent = new ControlKeyEvent + controlEvent.control = control + controlEvent.key = windowEvent.key + controlEvent.unicode = windowEvent.unicode + controlEvent.character = windowEvent.character + control.handleKeyDownEvent(controlEvent) + # if controlEvent.cancel: + # return nil # key is still inserted in text area + + +proc pWindowWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer {.cdecl.} = + case uMsg + of WM_CLOSE: + let window = cast[WindowImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + window.dispose() + return cast[pointer](true) # keeps the window open, else the window will be destroyed + of WM_SIZE: + let window = cast[WindowImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + if window != nil: + var rect = pGetWindowRect(window.fHandle) + window.width = rect.right - rect.left + window.height = rect.bottom - rect.top + rect = pGetClientRect(window.fHandle) + window.fClientWidth = rect.right - rect.left + window.fClientHeight = rect.bottom - rect.top + window.triggerRelayout() + of WM_MOVE: + let window = cast[WindowImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + if window != nil: + var rect = pGetWindowRect(window.fHandle) + window.fX = rect.left + window.fY = rect.top + # echo "WM_MOVE: " & $rect.left & ", " & $rect.top + of WM_SETFOCUS: + discard + #echo "window WM_SETFOCUS" + # not called? + of WM_DROPFILES: + let window = cast[WindowImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + var files: seq[string] = @[] + let count = DragQueryFileW(wParam, 0xFFFFFFFF.uint32, nil, 0) + for i in 0..count - 1: + let characters = DragQueryFileW(wParam, i.uint32 , nil, 0) + var filename = newString(characters * 2) + discard DragQueryFileW(wParam, i.uint32, filename, characters + 1) + files.add(filename.pUtf16ToUtf8) + DragFinish(wParam) + var event = new DropFilesEvent + event.window = window + event.files = files + window.handleDropFilesEvent(event) + # of WM_CHAR: + # not triggered for all key + # echo "window WM_CHAR: " + # let window = cast[Window](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + # if window != nil: + # var unicode = cast[int](wParam) + # var event = new WindowKeyEvent + # event.window = window + # event.unicode = unicode + # event.character = unicode.pUnicodeCharToUtf8 + # echo event.character[0].ord + # window.handleKeyDownEvent(event) + of WM_KEYDOWN: + # echo "window WM_KEYDOWN" + + # if (cast[int](lParam) and 0x40000000) != 0x40000000: + # echo "window WM_KEYDOWN first" + # else: + # echo "window WM_KEYDOWN" + + # echo int((cast[int](lParam) shr 8) and 0xFFFFFF00) + + let window = cast[Window](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + if window != nil: + pHandleWMKEYDOWN(window, nil, wParam, lParam) + else: + discard + result = pCommonWndProc(hWnd, uMsg, wParam, lParam) + +proc pContainerWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer {.cdecl.} + +proc pCommonControlWndProc(origWndProc, hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer + +proc pCustomControlWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer {.cdecl.} + +proc pInitGdiplus() = + var input: GdiplusStartupInput + input.GdiplusVersion = 1 + var gidplus: pointer = nil + pCheckGdiplusStatus(GdiplusStartup(gidplus, input, nil)) + +proc pGetTextSize(hDC, font: pointer, text: string): Size = + let wideText = text.pUtf8ToUtf16 + discard SelectObject(hdc, font) + discard GetTextExtentPoint32W(hdc, wideText, (wideText.len div 2).int32, result) + + +# ---------------------------------------------------------------------------------------- +# App Procedures +# ---------------------------------------------------------------------------------------- + +proc init(app: App) = + pInitGdiplus() + pEnableVisualStyles() + pRegisterWindowClass(pTopLevelWindowClass, pWindowWndProc) + pRegisterWindowClass(pCustomControlWindowClass, pCustomControlWndProc, CS_HREDRAW or CS_VREDRAW) + pRegisterWindowClass(pContainerWindowClass, pContainerWndProc) + pDefaultParentWindow = pCreateWindowEx(0, pTopLevelWindowClass, nil, 0, 0, 0, 0, 0, nil, nil, nil, nil) + app.defaultTextColor = GetSysColor(COLOR_WINDOWTEXT).pRgb32ToColor() + app.defaultBackgroundColor = GetSysColor(COLOR_BTNFACE).pRgb32ToColor() + app.defaultFontFamily = "Arial" + fScrollbarSize = GetSystemMetrics(SM_CXVSCROLL) + +proc runMainLoop() = + var msg: Msg + while GetMessageA(msg.addr, nil, 0, 0): + discard TranslateMessage(msg.addr) + discard DispatchMessageA(msg.addr) + +proc processEvents(app: App) = + var msg: Msg + while PeekMessageA(msg.addr, nil, 0, 0, PM_REMOVE): + discard TranslateMessage(msg.addr) + discard DispatchMessageA(msg.addr) + + +# ---------------------------------------------------------------------------------------- +# Dialogs +# ---------------------------------------------------------------------------------------- + +proc alert(window: Window, message: string, title = "Message") = + var hWnd: pointer + if window != nil: + hWnd = cast[WindowImpl](window).fHandle + MessageBoxW(hWnd, message.pUtf8ToUtf16, title.pUtf8ToUtf16, 0) + +method run*(dialog: OpenFileDialog) = + const maxCharacters = 5000 + dialog.files = @[] + var ofn: OpenFileName + ofn.lStructSize = OpenFileName.sizeOf.int32 + ofn.nMaxFile = maxCharacters + ofn.lpstrInitialDir = getCurrentDir().pUtf8ToUtf16() + ofn.Flags = OFN_FILEMUSTEXIST + if dialog.multiple: + ofn.Flags = ofn.Flags or OFN_ALLOWMULTISELECT or OFN_EXPLORER + var s = newString(maxCharacters * 2) + ofn.lpstrFile = s + let ret = GetOpenFileNameW(ofn) + if ret: + var dirOrFirstFile: string + # Split selected file names: + while s[0].ord != 0: + var i = 0 + while i < s.len - 1 and s[i].ord != 0: + i.inc(2) + let filename = s.substr(0, i - 1).pUtf16ToUtf8() + if dirOrFirstFile == nil: + dirOrFirstFile = filename + else: + dialog.files.add(dirOrFirstFile / filename) + s = s.substr(i + 2) + if dialog.files.len == 0: + dialog.files.add(dirOrFirstFile) + else: + let e = CommDlgExtendedError() + if e != 0: + raiseError("CommDlg Error Code: " & $e) + +method run(dialog: SaveFileDialog) = + const maxCharacters = 500 + var ofn: OpenFileName + ofn.lStructSize = OpenFileName.sizeOf.int32 + ofn.nMaxFile = maxCharacters + ofn.lpstrInitialDir = getCurrentDir().pUtf8ToUtf16() + if dialog.defaultExtension.len > 0: + ofn.lpstrDefExt = pUtf8ToUtf16(dialog.defaultExtension) + ofn.lpstrFilter = pUtf8ToUtf16(dialog.defaultExtension & "\0*." & dialog.defaultExtension & "\0All files\0*.*") + ofn.Flags = OFN_OVERWRITEPROMPT + var s = newString(maxCharacters * 2) + if dialog.defaultName.len > 0: + let temp = pUtf8ToUtf16(dialog.defaultName) + for i in 0..temp.len: + s[i] = temp[i] + ofn.lpstrFile = s + let ret = GetSaveFileNameW(ofn) + if ret: + dialog.file = pUtf16ToUtf8(s, true) + else: + dialog.file = "" + let e = CommDlgExtendedError() + if e != 0: + raiseError("CommDlg Error Code: " & $e) + + +# ---------------------------------------------------------------------------------------- +# Timers +# ---------------------------------------------------------------------------------------- + +type TimerEntry = object + timerProc: TimerProc + data: pointer + +var pTimers = initTable[int64, TimerEntry]() + +proc pTimerFunction(hwnd: pointer, uMsg: int32, idEvent: pointer, dwTime: int32) {.cdecl.} = + discard KillTimer(hwnd, idEvent) + let timerEntry = pTimers.getOrDefault(cast[int](idEvent)) + var event = new TimerEvent + event.timer = cast[Timer](idEvent) + event.data = timerEntry.data + timerEntry.timerProc(event) + pTimers.del(cast[int](idEvent)) + +proc pRepeatingTimerFunction(hwnd: pointer, uMsg: int32, idEvent: pointer, dwTime: int32) {.cdecl.} = + let timerEntry = pTimers.getOrDefault(cast[int](idEvent)) + var event = new TimerEvent + event.timer = cast[Timer](idEvent) + event.data = timerEntry.data + timerEntry.timerProc(event) + +proc startTimer(milliSeconds: int, timerProc: TimerProc, data: pointer = nil): Timer = + result = cast[Timer](SetTimer(nil, nil, milliSeconds.int32, pTimerFunction)) + var timerEntry: TimerEntry + timerEntry.timerProc = timerProc + timerEntry.data = data + pTimers[cast[int](result)] = timerEntry + +proc startRepeatingTimer(milliSeconds: int, timerProc: TimerProc, data: pointer = nil): Timer = + result = cast[Timer](SetTimer(nil, nil, milliSeconds.int32, pRepeatingTimerFunction)) + var timerEntry: TimerEntry + timerEntry.timerProc = timerProc + timerEntry.data = data + pTimers[cast[int](result)] = timerEntry + +proc stop(timer: var Timer) = + if cast[int](timer) != inactiveTimer: + let timerEntry = pTimers.getOrDefault(cast[int](timer)) + pTimers.del(cast[int](timer)) + discard KillTimer(nil, cast[pointer](timer)) + timer = cast[Timer](inactiveTimer) + + +# ---------------------------------------------------------------------------------------- +# Canvas +# ---------------------------------------------------------------------------------------- + +proc pUpdateFont(canvas: Canvas) = + let canvasImpl = cast[CanvasImpl](canvas) + if canvasImpl.fFont == nil: + var fontFamily: pointer + pCheckGdiplusStatus(GdipCreateFontFamilyFromName(canvas.fontFamily.pUtf8ToUtf16(), nil, fontFamily)) + pCheckGdiplusStatus(GdipCreateFont(fontFamily, canvas.fontSize.float, 0, UnitPixel, canvasImpl.fFont)) + pCheckGdiplusStatus(GdipDeleteFontFamily(fontFamily)) + +proc pDeleteFont(canvas: CanvasImpl) = + if canvas.fFont != nil: + pCheckGdiplusStatus(GdipDeleteFont(canvas.fFont)) + canvas.fFont = nil + +proc pDeleteFontBrush(canvas: CanvasImpl) = + if canvas.fFontBrush != nil: + pCheckGdiplusStatus(GdipDeleteBrush(canvas.fFontBrush)) + canvas.fFontBrush = nil + +proc pDeleteAreaBrush(canvas: CanvasImpl) = + if canvas.fAreaBrush != nil: + pCheckGdiplusStatus(GdipDeleteBrush(canvas.fAreaBrush)) + canvas.fAreaBrush = nil + +proc pDeleteLinePen(canvas: CanvasImpl) = + if canvas.fLinePen != nil: + pCheckGdiplusStatus(GdipDeletePen(canvas.fLinePen)) + canvas.fLinePen = nil + +method destroy(canvas: CanvasImpl) = + procCall canvas.Canvas.destroy() + let canvasImpl = cast[CanvasImpl](canvas) + if canvasImpl.fBitmap == nil: + pDeleteFont(canvas) + pDeleteFontBrush(canvas) + pDeleteLinePen(canvas) + pDeleteAreaBrush(canvas) + if canvas.fGraphics != nil: + pCheckGdiplusStatus(GdipDeleteGraphics(canvas.fGraphics)) + +method drawText(canvas: Canvas, text: string, x, y = 0) = + let canvasImpl = cast[CanvasImpl](canvas) + if canvasImpl.fGraphics == nil: + raiseError("Canvas is not in drawing state.") + canvas.pUpdateFont() + if canvasImpl.fFontBrush == nil: + pCheckGdiplusStatus(GdipCreateSolidFill(canvas.textColor.pColorToARGB(), canvasImpl.fFontBrush)) + var rect: RectF + rect.x = x.float + rect.y = y.float + pCheckGdiplusStatus(GdipDrawString(canvasImpl.fGraphics, text.pUtf8ToUtf16(), -1, canvasImpl.fFont, rect, nil, canvasImpl.fFontBrush)) + +method drawLine(canvas: Canvas, x1, y1, x2, y2: int) = + let canvasImpl = cast[CanvasImpl](canvas) + if canvasImpl.fGraphics == nil: + raiseError("Canvas is not in drawing state.") + if canvasImpl.fLinePen == nil: + pCheckGdiplusStatus(GdipCreatePen1(canvas.lineColor.pColorToARGB(), 1, UnitPixel, canvasImpl.fLinePen)) + pCheckGdiplusStatus(GdipDrawLineI(canvasImpl.fGraphics, canvasImpl.fLinePen, x1.int32, y1.int32, x2.int32, y2.int32)) + +method drawRectArea(canvas: Canvas, x, y, width, height: int) = + let canvasImpl = cast[CanvasImpl](canvas) + if canvasImpl.fGraphics == nil: + raiseError("Canvas is not in drawing state.") + if canvasImpl.fAreaBrush == nil: + pCheckGdiplusStatus(GdipCreateSolidFill(canvas.areaColor.pColorToARGB(), canvasImpl.fAreaBrush)) + pCheckGdiplusStatus(GdipFillRectangleI(canvasImpl.fGraphics, canvasImpl.fAreaBrush, x.int32, y.int32, width.int32, height.int32)) + +method drawRectOutline(canvas: Canvas, x, y, width, height: int) = + let canvasImpl = cast[CanvasImpl](canvas) + if canvasImpl.fGraphics == nil: + raiseError("Canvas is not in drawing state.") + var pen: pointer + pCheckGdiplusStatus(GdipCreatePen1(canvas.lineColor.pColorToARGB(), 1, UnitPixel, pen)) + pCheckGdiplusStatus(GdipDrawRectangleI(canvasImpl.fGraphics, pen, x.int32, y.int32, width.int32, height.int32)) + +method drawImage(canvas: Canvas, image: Image, x, y = 0, width, height = -1) = + var drawWith = image.width + var drawHeight = image.height + if width != -1: + drawWith = width + drawHeight = int(drawHeight * drawWith / image.width) + if height != -1: + drawHeight = height + let canvasImpl = cast[CanvasImpl](canvas) + let imageCanvas = cast[CanvasImpl](image.canvas) + if canvasImpl.fGraphics == nil: + raiseError("Canvas is not in drawing state.") + pCheckGdiplusStatus(GdipDrawImageRectI(canvasImpl.fGraphics, imageCanvas.fBitmap, x.int32, y.int32, drawWith.int32, drawHeight.int32)) + +method setPixel(canvas: Canvas, x, y: int, color: Color) = + let canvasImpl = cast[CanvasImpl](canvas) + if canvasImpl.fBitmap == nil: + if canvasImpl.fDC == nil: + raiseError("Canvas is not in drawing state.") + discard SetPixel(canvasImpl.fDC, x.int32, y.int32, color.pColorToRGB32) + else: + let imageCanvas = cast[CanvasImpl](canvas) + pCheckGdiplusStatus(GdipBitmapSetPixel(imageCanvas.fBitmap, x.int32, y.int32, color.pColorToARGB())) + +method `fontFamily=`(canvas: CanvasImpl, fontFamily: string) = + procCall canvas.Canvas.`fontFamily=`(fontFamily) + canvas.pDeleteFont() + +method `fontSize=`(canvas: CanvasImpl, fontSize: int) = + procCall canvas.Canvas.`fontSize=`(fontSize) + canvas.pDeleteFont() + +method `textColor=`(canvas: CanvasImpl, color: Color) = + procCall canvas.Canvas.`textColor=`(color) + canvas.pDeleteFontBrush() + +method `lineColor=`(canvas: CanvasImpl, color: Color) = + procCall canvas.Canvas.`lineColor=`(color) + pDeleteLinePen(canvas) + +method `areaColor=`(canvas: CanvasImpl, color: Color) = + procCall canvas.Canvas.`areaColor=`(color) + pDeleteAreaBrush(canvas) + +proc pGetTextSize(canvas: Canvas, text: string): Size = + let canvasImpl = cast[CanvasImpl](canvas) + canvas.pUpdateFont() + var rect: RectF + var boundingBox: RectF + pCheckGdiplusStatus(GdipMeasureString(canvasImpl.fGraphics, text.pUtf8ToUtf16(), -1, canvasImpl.fFont, rect, nil, boundingBox, nil, nil)) + result.cx = boundingBox.width.int32 + result.cy = boundingBox.height.int32 + +method getTextLineWidth(canvas: CanvasImpl, text: string): int = canvas.pGetTextSize(text).cx + +method getTextLineHeight(canvas: CanvasImpl): int = canvas.pGetTextSize("a").cy + + +# ---------------------------------------------------------------------------------------- +# Image +# ---------------------------------------------------------------------------------------- + +method resize(image: Image, width, height: int) = + let canvas = cast[CanvasImpl](image.canvas) + if canvas.fBitmap != nil: + pCheckGdiplusStatus(GdipDisposeImage(canvas.fBitmap), false) + pCheckGdiplusStatus(GdipDeleteGraphics(canvas.fGraphics)) + canvas.fBitmap = nil + canvas.fGraphics = nil + var dc = CreateCompatibleDC(nil) + pCheckGdiplusStatus(GdipCreateFromHDC(dc, canvas.fGraphics)) + pCheckGdiplusStatus(GdipCreateBitmapFromGraphics(width.int32, height.int32, canvas.fGraphics, canvas.fBitmap)) + pCheckGdiplusStatus(GdipGetImageGraphicsContext(canvas.fBitmap, canvas.fGraphics)) # it's a new Graphic + image.canvas.fWidth = width + image.canvas.fHeight = height + +method loadFromFile(image: Image, filePath: string) = + let canvas = cast[CanvasImpl](image.canvas) + if canvas.fBitmap != nil: + pCheckGdiplusStatus(GdipDisposeImage(canvas.fBitmap), false) + pCheckGdiplusStatus(GdipDeleteGraphics(canvas.fGraphics)) + canvas.fBitmap = nil + canvas.fGraphics = nil + pCheckGdiplusStatus(GdipCreateBitmapFromFile(filePath.pUtf8ToUtf16(), canvas.fBitmap), false) + pCheckGdiplusStatus(GdipGetImageGraphicsContext(canvas.fBitmap, canvas.fGraphics)) + var width, height: int32 + pCheckGdiplusStatus(GdipGetImageWidth(canvas.fBitmap, width)) + pCheckGdiplusStatus(GdipGetImageHeight(canvas.fBitmap, height)) + image.canvas.fWidth = width + image.canvas.fHeight = height + +method saveToPngFile(image: Image, filePath: string) = + let canvas = cast[CanvasImpl](image.canvas) + var clsidEncoder: GUID + clsidEncoder.Data1 = 0x557cf406 + clsidEncoder.Data2 = 0x11d31a04 + clsidEncoder.Data3 = 0x0000739a + clsidEncoder.Data4 = 0x2ef31ef8 + pCheckGdiplusStatus(GdipSaveImageToFile(canvas.fBitmap, filePath.pUtf8ToUtf16(), clsidEncoder.addr, nil), false) + +method saveToJpegFile(image: Image, filePath: string, quality = 80) = + let canvas = cast[CanvasImpl](image.canvas) + var clsidEncoder: GUID + clsidEncoder.Data1 = 0x557cf401 + clsidEncoder.Data2 = 0x11d31a04 + clsidEncoder.Data3 = 0x0000739a + clsidEncoder.Data4 = 0x2ef31ef8 + # TODO: pass quality + pCheckGdiplusStatus(GdipSaveImageToFile(canvas.fBitmap, filePath.pUtf8ToUtf16(), clsidEncoder.addr, nil), false) + + +# ---------------------------------------------------------------------------------------- +# Window +# ---------------------------------------------------------------------------------------- + +proc init(window: WindowImpl) = + var dwStyle: int32 = WS_OVERLAPPEDWINDOW + window.fHandle = pCreateWindowExWithUserdata(pTopLevelWindowClass, dwStyle, 0, nil, cast[pointer](window)) + DragAcceptFiles(window.fHandle, true) + window.Window.init() + +method destroy(window: WindowImpl) = + if window.fModalParent != nil: + discard EnableWindow(window.fModalParent.fHandle, true) + procCall window.Window.destroy() + pDestroyWindow(window.fHandle) + window.fHandle = nil + +method `visible=`(window: WindowImpl, visible: bool) = + procCall window.Window.`visible=`(visible) + if visible: + pShowWindow(window.fHandle, SW_SHOW) + else: + pShowWindow(window.fHandle, SW_HIDE) + +method showModal(window, parent: WindowImpl) = + # Set window owner, to hide it from the taskbar + discard SetWindowLongPtrW(window.fHandle, GWL_HWNDPARENT, parent.fHandle) + + # Hide minimize and maximize buttons: + pSetWindowLong(window.fHandle, GWL_STYLE, WS_CAPTION or WS_THICKFRAME or WS_SYSMENU) + # pSetWindowLong(window.fHandle, GWL_EXSTYLE, WS_EX_TOOLWINDOW) # does not look good + + window.fModalParent = parent + window.visible = true + discard EnableWindow(parent.fHandle, false) + +proc pUpdatePosition(window: WindowImpl) = + pSetWindowPos(window.fHandle, window.x, window.y, -1, -1, SWP_NOSIZE) + # discard MoveWindow(window.fHandle, window.x.int32, window.y.int32, window.width.int32, window.height.int32, false) + # no difference + +proc pUpdateSize(window: WindowImpl) = pSetWindowPos(window.fHandle, -1, -1, window.width, window.height, SWP_NOMOVE) + +method `x=`(window: WindowImpl, x: int) = + procCall window.Window.`x=`(x) + window.pUpdatePosition() + +method `y=`(window: WindowImpl, y: int) = + procCall window.Window.`y=`(y) + window.pUpdatePosition() + +method centerOnScreen(window: WindowImpl) = + let desktop = GetDesktopWindow() + var rect: Rect + discard SystemParametersInfoA(SPI_GETWORKAREA, 0, rect.addr, 0) + window.fX = rect.left + (rect.right - window.width) div 2 + window.fY = rect.top + (rect.bottom - window.height) div 2 + window.pUpdatePosition() + + # TODO: regard multiple monitors + # var m = MonitorFromRect(rect, 0) + # var mi: MonitorInfo + # discard GetMonitorInfoA(m, mi) + # echo "GetMonitorInfoA: " & $mi.rcMonitor.left + # echo "GetMonitorInfoA: " & $mi.rcWork.left + +method `width=`*(window: WindowImpl, width: int) = + procCall window.Window.`width=`(width) + window.pUpdateSize() + +method `height=`*(window: WindowImpl, height: int) = + procCall window.Window.`height=`(height) + window.pUpdateSize() + +method `title=`(window: WindowImpl, title: string) = + procCall window.Window.`title=`(title) + pSetWindowText(window.fHandle, window.title) + +method `control=`(window: WindowImpl, control: ControlImpl) = + if window.control != nil: + pSetParent(cast[ControlImpl](window.control).fHandle, pDefaultParentWindow) + window.control.fParentWindow = nil + procCall window.Window.`control=`(control) + pSetParent(control.fHandle, window.fHandle) + +method `iconPath=`(window: WindowImpl, iconPath: string) = + procCall window.Window.`iconPath=`(iconPath) + var bitmap: pointer + pCheckGdiplusStatus(GdipCreateBitmapFromFile(iconPath.pUtf8ToUtf16(), bitmap)) + var icon: pointer + pCheckGdiplusStatus(GdipGetHicon(bitmap, icon)) + discard SendMessageA(window.fHandle, WM_SETICON, cast[pointer](ICON_BIG), icon) + discard SendMessageA(window.fHandle, WM_SETICON, cast[pointer](ICON_SMALL), icon) + + +# ---------------------------------------------------------------------------------------- +# Control +# ---------------------------------------------------------------------------------------- + +method pUpdateScrollBar(control: ControlImpl) + +proc init(control: ControlImpl) = + if control.fHandle == nil: + var dwStyle: int32 = WS_CHILD + control.fHandle = pCreateWindowExWithUserdata(pCustomControlWindowClass, dwStyle, 0, pDefaultParentWindow, cast[pointer](control)) + procCall control.Control.init() + +method destroy(control: ControlImpl) = + procCall control.Control.destroy() + if control.canvas != nil: + control.canvas.destroy() + pDestroyWindow(control.fHandle) + +method `visible=`(control: ControlImpl, visible: bool) = + procCall control.Control.`visible=`(visible) + if visible: + pShowWindow(control.fHandle, SW_SHOW) + else: + pShowWindow(control.fHandle, SW_HIDE) + +method setSize(control: ControlImpl, width, height: int) = + procCall control.Control.setSize(width, height) + pSetWindowPos(control.fHandle, -1, -1, width, height, SWP_NOMOVE) + pUpdateScrollBar(control) + +method setPosition(control: ControlImpl, x, y: int) = + procCall control.Control.setPosition(x, y) + pSetWindowPos(control.fHandle, x, y, -1, -1, SWP_NOSIZE) + +method pUpdateScrollBar(control: ControlImpl) = + if control.fScrollableWidth == -1 and control.fScrollableHeight == -1: + return + # echo "control.pUpdateScrollBar " & control.tag + + # Calculation of scrollbar settings: + + control.fXScrollEnabled = false + control.fYScrollEnabled = false + + if control.scrollableWidth > control.width: + control.fXScrollEnabled = true + if control.scrollableHeight > control.height: + control.fYScrollEnabled = true + + if control.fXScrollEnabled and not control.fYScrollEnabled and control.scrollableHeight > control.height - fScrollbarSize: + control.fYScrollEnabled = true + if control.fYScrollEnabled and not control.fXScrollEnabled and control.scrollableWidth > control.width - fScrollbarSize: + control.fXScrollEnabled = true + + # Apply settings: + + discard ShowScrollBar(control.fHandle, SB_HORZ, control.fXScrollEnabled) + if control.fXScrollEnabled: + var si: ScrollInfo + si.cbSize = ScrollInfo.sizeOf.int32 + si.fMask = SIF_ALL + si.nMin = 0 + si.nMax = control.fScrollableWidth.int32 + if control.fYScrollEnabled: + si.nMax.inc(fScrollbarSize) + si.nPage = control.width.int32 + si.nPos = control.fXScrollPos.int32 + si.nTrackPos = 0 + discard SetScrollInfo(control.fHandle, SB_HORZ, si, false) + # Ensure that scroll pos is within range: + control.fXScrollPos = max(min(control.fXScrollPos, si.nMax - control.width), 0) + else: + control.fXScrollPos = 0 + + discard ShowScrollBar(control.fHandle, SB_VERT, control.fYScrollEnabled) + if control.fYScrollEnabled: + var si: ScrollInfo + si.cbSize = ScrollInfo.sizeOf.int32 + si.fMask = SIF_ALL + si.nMin = 0 + si.nMax = control.fScrollableHeight.int32 + if control.fXScrollEnabled: + si.nMax.inc(fScrollbarSize) + si.nPage = control.height.int32 + si.nPos = control.fYScrollPos.int32 + si.nTrackPos = 0 + discard SetScrollInfo(control.fHandle, SB_VERT, si, false) + # Ensure that scroll pos is within range: + control.fYScrollPos = max(min(control.fYScrollPos, si.nMax - control.height), 0) + else: + control.fYScrollPos = 0 + +method `xScrollPos=`(control: ControlImpl, xScrollPos: int) = + procCall control.Control.`xScrollPos=`(xScrollPos) + control.pUpdateScrollBar() + control.forceRedraw() + +method `yScrollPos=`(control: ControlImpl, yScrollPos: int) = + procCall control.Control.`yScrollPos=`(yScrollPos) + control.pUpdateScrollBar() + control.forceRedraw() + +method `scrollableWidth=`(control: ControlImpl, scrollableWidth: int) = + procCall control.Control.`scrollableWidth=`(scrollableWidth) + control.pUpdateScrollBar() + +method `scrollableHeight=`(control: ControlImpl, scrollableHeight: int) = + procCall control.Control.`scrollableHeight=`(scrollableHeight) + control.pUpdateScrollBar() + +method forceRedraw(control: ControlImpl) = discard InvalidateRect(control.fHandle, nil, true) + +proc pUpdateFont(control: ControlImpl) = + if control.fFont != nil: + discard DeleteObject(control.fFont) + control.fFont = CreateFontA(control.fontSize.int32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, control.fontFamily) + discard SendMessageA(control.fHandle, WM_SETFONT, control.fFont, cast[pointer](true)) + +method setFontFamily(control: ControlImpl, fontFamily: string) = + procCall control.Control.setFontFamily(fontFamily) + control.pUpdateFont() + +method setFontSize(control: ControlImpl, fontSize: int) = + procCall control.Control.setFontSize(fontSize) + control.pUpdateFont() + +# method `setBackgroundColor=`(control: ControlImpl, color: Color) = + # procCall control.Control.setBackgroundColor(color) + # var brush = CreateSolidBrush(color.pColorToRGB32()) + # discard SetClassLongPtrA(control.fHandle, GCLP_HBRBACKGROUND, brush) + # no effect + +proc pGetTextSize(control: ControlImpl, text: string): Size = + let hdc = GetDC(control.fHandle) + result = pGetTextSize(hdc, control.fFont, text) + discard DeleteDC(hdc) + +method focus(control: ControlImpl) = + discard SetFocus(control.fHandle) + +method getTextLineWidth(control: ControlImpl, text: string): int = control.pGetTextSize(text).cx + +method getTextLineHeight(control: ControlImpl): int = control.pGetTextSize("a").cy + +proc pCommonControlWndProc_Scroll(origWndProc, hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer = + const lineSize = 15 + case wParam.loWord + of SB_THUMBPOSITION, SB_THUMBTRACK: + let control = cast[ControlImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + if control != nil: + if uMsg == WM_HSCROLL: + control.xScrollPos = wParam.hiWord + else: + control.yScrollPos = wParam.hiWord + of SB_LINELEFT: + let control = cast[ControlImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + if uMsg == WM_HSCROLL: + control.xScrollPos = control.xScrollPos - lineSize + else: + control.yScrollPos = control.yScrollPos - lineSize + of SB_PAGELEFT: + let control = cast[ControlImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + if uMsg == WM_HSCROLL: + control.xScrollPos = control.xScrollPos - control.width + else: + control.yScrollPos = control.yScrollPos - control.height + of SB_LINERIGHT: + let control = cast[ControlImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + if uMsg == WM_HSCROLL: + control.xScrollPos = control.xScrollPos + lineSize + else: + control.yScrollPos = control.yScrollPos + lineSize + of SB_PAGERIGHT: + let control = cast[ControlImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + if uMsg == WM_HSCROLL: + control.xScrollPos = control.xScrollPos + control.width + else: + control.yScrollPos = control.yScrollPos + control.height + else: + discard + +proc pCommonControlWndProc(origWndProc, hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer = + case uMsg + of WM_KEYDOWN: + let control = cast[Control](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + if control != nil: + # echo "control WM_KEYDOWN" + pHandleWMKEYDOWN(control.parentWindow, control, wParam, lParam) + + # of WM_KEYUP: + # return nil # key is still inserted in text area + + of WM_LBUTTONDOWN, WM_RBUTTONDOWN, WM_MBUTTONDOWN: + let control = cast[Control](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + if control != nil: + discard SetFocus(control.parentWindow.fHandle) + # TODO: request if is focusable + var button: MouseButton + var x = loWord(lParam) + var y = hiWord(lParam) + case uMsg + of WM_LBUTTONDOWN: button = MouseButton_Left + of WM_RBUTTONDOWN: button = MouseButton_Right + of WM_MBUTTONDOWN: button = MouseButton_Middle + else: discard + var event = new MouseButtonEvent + event.control = control + event.button = button + event.x = x + event.y = y + control.handleMouseButtonDownEvent(event) + pLastMouseButtonDownControl = control + pLastMouseButtonDownControlX = x + pLastMouseButtonDownControlY = y + of WM_LBUTTONUP, WM_RBUTTONUP, WM_MBUTTONUP: + let control = cast[Control](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + if control != nil: + var button: MouseButton + var x = loWord(lParam) + var y = hiWord(lParam) + if x >= 0 and x < control.width and y >= 0 and y < control.height: + # send event only, when mouse was over control + case uMsg + of WM_LBUTTONUP: button = MouseButton_Left + of WM_RBUTTONUP: button = MouseButton_Right + of WM_MBUTTONUP: button = MouseButton_Middle + else: discard + var event = new MouseButtonEvent + event.control = control + event.button = button + event.x = x + event.y = y + control.handleMouseButtonUpEvent(event) + if uMsg == WM_LBUTTONUP and control == pLastMouseButtonDownControl and abs(x - pLastMouseButtonDownControlX) <= clickMaxXYMove and abs(y - pLastMouseButtonDownControlY) <= clickMaxXYMove: + var clickEvent = new ClickEvent + clickEvent.control = control + control.handleClickEvent(clickEvent) + of WM_HSCROLL, WM_VSCROLL: + result = pCommonControlWndProc_Scroll(origWndProc, hWnd, uMsg, wParam, lParam) + else: + discard + result = CallWindowProcW(origWndProc, hWnd, uMsg, wParam, lParam) + +proc pCustomControlWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer = + case uMsg + of WM_PAINT: + let control = cast[ControlImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + if control != nil: + var ps: PaintStruct + var dc = BeginPaint(hWnd, ps) + if dc == nil: pRaiseLastOSError() + var event = new DrawEvent + event.control = control + var canvas = cast[CanvasImpl](control.canvas) + if canvas == nil: + canvas = newCanvas(control) + else: + if canvas.fFont != nil: + discard SelectObject(dc, canvas.fFont) + canvas.fDC = dc + pCheckGdiplusStatus(GdipCreateFromHDC(dc, canvas.fGraphics)) + discard SetBkMode(dc, TRANSPARENT) + control.handleDrawEvent(event) + discard EndPaint(hWnd, ps) + canvas.fDC = nil + canvas.fGraphics = nil + of WM_MOUSEWHEEL: + let scrolled = wParam.hiWord div 120 + echo "wheel: " & $scrolled + # of WM_ERASEBKGND: # no effect + # return false + of WM_SETFOCUS: + # echo "control WM_SETFOCUS" + discard + else: + discard + result = pCommonControlWndProc(pCommonWndProc, hWnd, uMsg, wParam, lParam) + + +# ---------------------------------------------------------------------------------------- +# Container +# ---------------------------------------------------------------------------------------- + +proc pContainerWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer = + case uMsg + of WM_ERASEBKGND: + let control = cast[ControlImpl](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + if control != nil: + var brush = CreateSolidBrush(control.backgroundColor.pColorToRGB32) + var rect = pGetClientRect(control.fHandle) + discard FillRect(wParam, rect, brush) + return + else: + discard + result = pCustomControlWndProc(hWnd, uMsg, wParam, lParam) + +proc init(container: ContainerImpl) = + var dwStyle: int32 = WS_CHILD + container.fHandle = pCreateWindowExWithUserdata(pContainerWindowClass, dwStyle, 0, pDefaultParentWindow, cast[pointer](container)) + # ScrollWnd: + container.fScrollWndHandle = pCreateWindowExWithUserdata(pContainerWindowClass, dwStyle, 0, container.fHandle, cast[pointer](container)) + pShowWindow(container.fScrollWndHandle, SW_SHOW) + # Inner: + container.fInnerHandle = pCreateWindowExWithUserdata(pContainerWindowClass, dwStyle, 0, container.fScrollWndHandle, cast[pointer](container)) + pShowWindow(container.fInnerHandle, SW_SHOW) + container.Container.init() + +proc pUpdateScrollWnd(container: ContainerImpl) = + let padding = container.getPadding() + let width = container.width - padding.left - padding.right + let height = container.height - padding.top - padding.bottom + pSetWindowPos(container.fScrollWndHandle, padding.left, padding.top, width, height) + +method `frame=`(container: ContainerImpl, frame: Frame) = + procCall container.Container.`frame=`(frame) + if frame != nil: + pSetParent(frame.fHandle, container.fHandle) + container.pUpdateScrollWnd() + +method add(container: ContainerImpl, control: ControlImpl) = + procCall container.Container.add(control) + pSetParent(control.fHandle, container.fInnerHandle) + +method remove(container: ContainerImpl, control: ControlImpl) = + procCall container.Container.remove(control) + pSetParent(control.fHandle, pDefaultParentWindow) + +method setInnerSize(container: ContainerImpl, width, height: int) = + procCall container.Container.setInnerSize(width, height) + pSetWindowPos(container.fInnerHandle, -1, -1, width, height, SWP_NOMOVE) + +method setSize(container: ContainerImpl, width, height: int) = + procCall container.Container.setSize(width, height) + container.pUpdateScrollWnd() + +proc pSetInnerPos(container: ContainerImpl) = + pSetWindowPos(container.fInnerHandle, -container.xScrollPos, -container.yScrollPos, -1, -1, SWP_NOSIZE) + +method `xScrollPos=`(container: ContainerImpl, xScrollPos: int) = + procCall container.ControlImpl.`xScrollPos=`(xScrollPos) + container.pSetInnerPos() + +method `yScrollPos=`(container: ContainerImpl, yScrollPos: int) = + procCall container.ControlImpl.`yScrollPos=`(yScrollPos) + container.pSetInnerPos() + + +# ---------------------------------------------------------------------------------------- +# Frame +# ---------------------------------------------------------------------------------------- + +proc init(frame: NativeFrame) = + const dwStyle = WS_CHILD or BS_GROUPBOX or WS_GROUP + frame.fHandle = pCreateWindowExWithUserdata("BUTTON", dwStyle, 0, pDefaultParentWindow, cast[pointer](frame)) + frame.Frame.init() + +method `text=`(frame: NativeFrame, text: string) = + procCall frame.Frame.`text=`(text) + pSetWindowText(frame.fHandle, text) + +method naturalWidth(frame: NativeFrame): int = frame.getTextLineWidth(frame.text) + 10 + +method getPadding(frame: NativeFrame): Spacing = + result = procCall frame.Frame.getPadding() + result.top = frame.getTextLineHeight() * frame.text.countLines + 2 + + +# ---------------------------------------------------------------------------------------- +# Button +# ---------------------------------------------------------------------------------------- + +var pButtonOrigWndProc: pointer + +proc pButtonWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer {.cdecl.} = + case uMsg + of WM_KEYDOWN: + let button = cast[Button](GetWindowLongPtrW(hWnd, GWLP_USERDATA)) + # if button != nil and (cast[int](wParam) == 13 or cast[int](wParam) == 32): + if button != nil and cast[int](wParam) == 13: + var event = new ClickEvent + event.control = button + button.handleClickEvent(event) + else: + discard + result = pCommonControlWndProc(pButtonOrigWndProc, hWnd, uMsg, wParam, lParam) + +proc init(button: NativeButton) = + button.fHandle = pCreateWindowExWithUserdata("BUTTON", WS_CHILD or WS_TABSTOP, 0, pDefaultParentWindow, cast[pointer](button)) + # WS_TABSTOP does not work, why? + pButtonOrigWndProc = SetWindowLongPtrW(button.fHandle, GWLP_WNDPROC, pButtonWndProc) + button.Button.init() + +method `text=`(button: NativeButton, text: string) = + procCall button.Button.`text=`(text) + pSetWindowText(button.fHandle, text) + + +# ---------------------------------------------------------------------------------------- +# Label +# ---------------------------------------------------------------------------------------- + +proc init(label: NativeLabel) = + label.fHandle = pCreateWindowExWithUserdata("STATIC", WS_CHILD or SS_CENTERIMAGE, 0, pDefaultParentWindow, cast[pointer](label)) + label.Label.init() + +method `text=`(label: NativeLabel, text: string) = + procCall label.Label.`text=`(text) + pSetWindowText(label.fHandle, text) + + +# ---------------------------------------------------------------------------------------- +# TextBox +# ---------------------------------------------------------------------------------------- + +var pTextBoxOrigWndProc: pointer + +proc pTextBoxWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer {.cdecl.} = + result = pCommonControlWndProc(pTextBoxOrigWndProc, hWnd, uMsg, wParam, lParam) + +proc init(textBox: NativeTextBox) = + textBox.fHandle = pCreateWindowExWithUserdata("EDIT", WS_CHILD, WS_EX_CLIENTEDGE, pDefaultParentWindow, cast[pointer](textBox)) + pTextBoxOrigWndProc = SetWindowLongPtrW(textBox.fHandle, GWLP_WNDPROC, pTextBoxWndProc) + textBox.TextBox.init() + +method text(textBox: NativeTextBox): string = pGetWindowText(textBox.fHandle) + +method `text=`(textBox: NativeTextBox, text: string) = pSetWindowText(textBox.fHandle, text) + +method naturalHeight(textBox: NativeTextBox): int = textBox.getTextLineHeight() + 9 # add padding + + +# ---------------------------------------------------------------------------------------- +# TextArea +# ---------------------------------------------------------------------------------------- + +var pTextAreaOrigWndProc: pointer + +proc pTextAreaWndProc(hWnd: pointer, uMsg: int32, wParam, lParam: pointer): pointer {.cdecl.} = + # Handle Ctrl+A: + # TODO: Move this to handleKeyDownEvent(), so it's overridable by the control + if uMsg == WM_KEYDOWN and cast[char](wParam) == 'A' and GetKeyState(VK_CONTROL) <= -127: + discard SendMessageA(hwnd, EM_SETSEL, nil, cast[pointer](-1)) + return nil + result = pCommonControlWndProc(pTextAreaOrigWndProc, hWnd, uMsg, wParam, lParam) + +proc init(textArea: NativeTextArea) = + var dwStyle: int32 = WS_CHILD or ES_MULTILINE or WS_VSCROLL # with wrap + # var dwStyle: int32 = WS_CHILD or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL # no wrap + var dwExStyle: int32 = WS_EX_CLIENTEDGE + textArea.fHandle = pCreateWindowExWithUserdata("EDIT", dwStyle, dwExStyle, pDefaultParentWindow, cast[pointer](textArea)) + pTextAreaOrigWndProc = SetWindowLongPtrW(textArea.fHandle, GWLP_WNDPROC, pTextAreaWndProc) + textArea.TextArea.init() + +method text(textArea: NativeTextArea): string = pGetWindowText(textArea.fHandle) + +method `text=`(textArea: NativeTextArea, text: string) = pSetWindowText(textArea.fHandle, text) + +method scrollToBottom(textArea: NativeTextArea) = + # select all + discard SendMessageA(textArea.fHandle, EM_SETSEL, nil, cast[pointer](-1)) + # unselect and stay at the end pos + discard SendMessageA(textArea.fHandle, EM_SETSEL, cast[pointer](-1), cast[pointer](-1)) + # set scrollcaret to the current pos + discard SendMessageA(textArea.fHandle, EM_SCROLLCARET, nil, nil) + +method `wrap=`(textArea: NativeTextArea, wrap: bool) = + procCall textArea.TextArea.`wrap=`(wrap) + # TODO: allow to enable/disable word draw at runtime + # It seems that this is not possible. + # Word wrap depends on whether dwStyle contains WS_HSCROLL at window creation. + # Changing the style later has not the wanted effect. |
