From 645250e1cd8ce9bc1faea599df7a1b05836bfeb8 Mon Sep 17 00:00:00 2001 From: Bent Bisballe Nyeng Date: Tue, 29 Dec 2020 16:09:43 +0100 Subject: Split UI code into application/plugin UI and UI library. --- dggui/nativewindow_win32.cc | 584 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 584 insertions(+) create mode 100644 dggui/nativewindow_win32.cc (limited to 'dggui/nativewindow_win32.cc') diff --git a/dggui/nativewindow_win32.cc b/dggui/nativewindow_win32.cc new file mode 100644 index 0000000..4b31130 --- /dev/null +++ b/dggui/nativewindow_win32.cc @@ -0,0 +1,584 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/*************************************************************************** + * nativewindow_win32.cc + * + * Fri Dec 28 18:45:52 CET 2012 + * Copyright 2012 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of DrumGizmo. + * + * DrumGizmo is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * DrumGizmo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with DrumGizmo; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include "nativewindow_win32.h" + +#include +#include +#include + +#include "window.h" + +namespace GUI +{ + +static BOOL trackMouse(HWND hwnd) +{ + TRACKMOUSEEVENT ev{}; + ev.cbSize = sizeof(ev); + ev.dwFlags = TME_HOVER | TME_LEAVE; + ev.hwndTrack = hwnd; + ev.dwHoverTime = 1; + return TrackMouseEvent(&ev); +} + +LRESULT CALLBACK NativeWindowWin32::dialogProc(HWND hwnd, UINT msg, + WPARAM wp, LPARAM lp) +{ + NativeWindowWin32* native = + (NativeWindowWin32*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + // NOTE: 'native' is nullptr intil the WM_CREATE message has been handled. + if(!native) + { + return DefWindowProc(hwnd, msg, wp, lp); + } + + Window& window = native->window; + + switch(msg) + { + case WM_SIZE: + if(wp > 4) + { + // Bogus value - ignore + break; + } + { + auto resizeEvent = std::make_shared(); + resizeEvent->width = LOWORD(lp); + resizeEvent->height = HIWORD(lp); + native->event_queue.push_back(resizeEvent); + } + break; + + case WM_MOVE: + { + auto moveEvent = std::make_shared(); + moveEvent->x = (short)LOWORD(lp); + moveEvent->y = (short)HIWORD(lp); + native->event_queue.push_back(moveEvent); + } + break; + + case WM_CLOSE: + { + auto closeEvent = std::make_shared(); + native->event_queue.push_back(closeEvent); + } + return 0; // Do not call DefWindowProc for this event. +// HWND child, old; +// old = 0; + +// numDialogs--; + +// while(old != (child = GetNextDlgGroupItem(hwnd, hwnd, false))) { +// old = child; +// EndDialog(child, 0); +// } + +// if(numDialogs) EndDialog(hwnd, 0); +// else PostQuitMessage(0); +// return 0; + case WM_MOUSEMOVE: + { + trackMouse(native->m_hwnd); + auto mouseMoveEvent = std::make_shared(); + mouseMoveEvent->x = (short)LOWORD(lp); + mouseMoveEvent->y = (short)HIWORD(lp); + native->last_mouse_position = { mouseMoveEvent->x, mouseMoveEvent->y }; + + if(!native->mouse_in_window) + { + auto enterEvent = std::make_shared(); + enterEvent->x = native->last_mouse_position.first; + enterEvent->y = native->last_mouse_position.second; + native->event_queue.push_back(enterEvent); + native->mouse_in_window = true; + } + native->event_queue.push_back(mouseMoveEvent); + } + break; + + case WM_MOUSEWHEEL: + { + auto scrollEvent = std::make_shared(); + + // NOTE: lp is coordinates in screen space, not client space. + POINT p; + p.x = (short)LOWORD(lp); + p.y = (short)HIWORD(lp); + ScreenToClient(hwnd, &p); + + scrollEvent->x = p.x; + scrollEvent->y = p.y; + scrollEvent->delta = -1 * (short)HIWORD(wp) / 60.0f; + native->event_queue.push_back(scrollEvent); + } + break; + + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_LBUTTONDOWN: + case WM_RBUTTONUP: + case WM_RBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MBUTTONDBLCLK: + case WM_MBUTTONDOWN: + { + auto buttonEvent = std::make_shared(); + + buttonEvent->x = (short)LOWORD(lp); + buttonEvent->y = (short)HIWORD(lp); + + if(msg == WM_LBUTTONUP || + msg == WM_LBUTTONDBLCLK || + msg == WM_LBUTTONDOWN) + { + buttonEvent->button = MouseButton::left; + } + else if(msg == WM_MBUTTONUP || + msg == WM_MBUTTONDBLCLK || + msg == WM_MBUTTONDOWN) + { + buttonEvent->button = MouseButton::middle; + } + else if(msg == WM_RBUTTONUP || + msg == WM_RBUTTONDBLCLK || + msg == WM_RBUTTONDOWN) + { + buttonEvent->button = MouseButton::right; + } + else + { + break; // unknown button + } + + // Double-clicking the a mouse button actually generates a sequence + // of four messages: WM_xBUTTONDOWN, WM_xBUTTONUP, WM_xBUTTONDBLCLK, and + // WM_xBUTTONUP. In other words the second WM_xBUTTONDOWN is replaced by a + // WM_xBUTTONDBLCLK. We simply 'return it' as a WM_xBUTTONDOWN but set the + // doubleClick boolean hint accordingly. + if(msg == WM_LBUTTONUP || + msg == WM_RBUTTONUP || + msg == WM_MBUTTONUP) + { + buttonEvent->direction = Direction::up; + } + else if(msg == WM_LBUTTONDOWN || + msg == WM_RBUTTONDOWN || + msg == WM_MBUTTONDOWN || + msg == WM_LBUTTONDBLCLK || + msg == WM_RBUTTONDBLCLK || + msg == WM_MBUTTONDBLCLK) + { + buttonEvent->direction = Direction::down; + } + + buttonEvent->doubleClick = (msg == WM_LBUTTONDBLCLK || + msg == WM_RBUTTONDBLCLK || + msg == WM_MBUTTONDBLCLK); + + native->event_queue.push_back(buttonEvent); + } + break; + + case WM_KEYDOWN: + case WM_KEYUP: + { + auto keyEvent = std::make_shared(); + + switch(wp) { + case VK_LEFT: keyEvent->keycode = Key::left; break; + case VK_RIGHT: keyEvent->keycode = Key::right; break; + case VK_UP: keyEvent->keycode = Key::up; break; + case VK_DOWN: keyEvent->keycode = Key::down; break; + case VK_BACK: keyEvent->keycode = Key::backspace; break; + case VK_DELETE: keyEvent->keycode = Key::deleteKey; break; + case VK_HOME: keyEvent->keycode = Key::home; break; + case VK_END: keyEvent->keycode = Key::end; break; + case VK_PRIOR: keyEvent->keycode = Key::pageUp; break; + case VK_NEXT: keyEvent->keycode = Key::pageDown; break; + case VK_RETURN: keyEvent->keycode = Key::enter; break; + default: keyEvent->keycode = Key::unknown; break; + } + + keyEvent->text = ""; + keyEvent->direction = + (msg == WM_KEYDOWN) ? Direction::down : Direction::up; + + native->event_queue.push_back(keyEvent); + } + break; + + case WM_CHAR: + { + if(wp >= ' ') // Filter control chars. + { + auto keyEvent = std::make_shared(); + keyEvent->keycode = Key::character; + keyEvent->text += (char)wp; + keyEvent->direction = Direction::up; + native->event_queue.push_back(keyEvent); + } + } + break; + + case WM_PAINT: + { + RECT rect; + GetUpdateRect(hwnd, &rect, FALSE); + + // Bypass partial update, which is apparrently broken. + rect.left = 0; + rect.top = 0; + rect.right = window.wpixbuf.width; + rect.bottom = window.wpixbuf.height; + + auto repaintEvent = std::make_shared(); + repaintEvent->x = rect.left; + repaintEvent->y = rect.top; + repaintEvent->width = rect.right - rect.left; + repaintEvent->height = rect.bottom - rect.top; + native->event_queue.push_back(repaintEvent); + + // Move to window.h (in class) + HDC pDC; + HBITMAP old; + HBITMAP ourbitmap; + int* framebuf; + PixelBuffer& px = window.wpixbuf; + + { // Create bitmap + HDC hDC; + BITMAPINFO bitmapinfo; + hDC = CreateCompatibleDC(nullptr); + bitmapinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bitmapinfo.bmiHeader.biWidth = px.width; + bitmapinfo.bmiHeader.biHeight = -px.height; // top-down + bitmapinfo.bmiHeader.biPlanes = 1; + bitmapinfo.bmiHeader.biBitCount = 32; + bitmapinfo.bmiHeader.biCompression = BI_RGB; + bitmapinfo.bmiHeader.biSizeImage = 0; + bitmapinfo.bmiHeader.biClrUsed = 256; + bitmapinfo.bmiHeader.biClrImportant = 256; + ourbitmap = CreateDIBSection(hDC, &bitmapinfo, + DIB_RGB_COLORS, (void**)&framebuf, 0, 0); + pDC=CreateCompatibleDC(nullptr); + old = (HBITMAP__*)SelectObject(pDC, ourbitmap); + DeleteDC(hDC); + } + + int from_x = rect.left; + int to_x = std::min(rect.right, (long)px.width); + int from_y = rect.top; + int to_y = std::min(rect.bottom, (long)px.height); + { // Copy PixelBuffer to framebuffer + int idx = 0; + for(int y = from_y; y < to_y; ++y) + { + for(int x = from_x; x < to_x; ++x) + { + *(framebuf + idx) = RGB(px.buf[(x + y * px.width) * 3 + 2], + px.buf[(x + y * px.width) * 3 + 1], + px.buf[(x + y * px.width) * 3 + 0]); + ++idx; + } + } + } + + PAINTSTRUCT ps; + HDC hdc = BeginPaint(native->m_hwnd, &ps); + BitBlt(hdc, from_x, from_y, to_x, to_y, pDC, from_x, from_y, SRCCOPY); + EndPaint(native->m_hwnd, &ps); + + { // Destroy bitmap (move to window.cc) + SelectObject(pDC,old); + DeleteDC(pDC); + DeleteObject(ourbitmap); + } + } + break; + + case WM_MOUSELEAVE: + { + auto leaveEvent = std::make_shared(); + leaveEvent->x = native->last_mouse_position.first; + leaveEvent->y = native->last_mouse_position.second; + native->event_queue.push_back(leaveEvent); + native->mouse_in_window = false; + } + break; + } + + return DefWindowProc(hwnd, msg, wp, lp); +} + +LRESULT CALLBACK NativeWindowWin32::subClassProc(HWND hwnd, UINT msg, + WPARAM wp, LPARAM lp, + UINT_PTR id, DWORD_PTR data) +{ + NativeWindowWin32* native = (NativeWindowWin32*)data; + + // NOTE: 'native' is nullptr intil the WM_CREATE message has been handled. + if(!native) + { + return DefWindowProc(hwnd, msg, wp, lp); + } + + switch(msg) + { + case WM_SIZE: + if(wp > 4) + { + // Bogus value - ignore + break; + } + { + // Parent window size changed, replicate this size in inner window. + int width = LOWORD(lp); + int height = HIWORD(lp); + SetWindowPos(native->m_hwnd, nullptr, -1, -1, width, height, SWP_NOMOVE); + } + break; + } + + return DefSubclassProc(hwnd, msg, wp, lp); +} + +NativeWindowWin32::NativeWindowWin32(void* native_window, Window& window) + : window(window) +{ + WNDCLASSEX wcex{}; + + //Time to register a window class. + //Generic flags and everything. cbWndExtra is the size of a pointer to an + // object - we need this in the wndproc handler. + + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_DBLCLKS;//class_style; + wcex.lpfnWndProc = (WNDPROC)dialogProc; + wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); + // Set data: + wcex.cbWndExtra = sizeof(NativeWindowWin32*); // Size of data. + wcex.hInstance = GetModuleHandle(nullptr); + + // if(ex_style && WS_EX_TRANSPARENT == WS_EX_TRANSPARENT) { + // wcex.hbrBackground = nullptr; + // } else { + wcex.hbrBackground = nullptr;//(HBRUSH) COLOR_BACKGROUND + 1; + // } + + const char* name = "DrumGizmoClass"; + char* c_name = (char*)malloc(strlen(name) + 1); + strcpy(c_name, name); + wcex.lpszClassName = m_className = c_name; + + RegisterClassEx(&wcex); + + parent_window = (HWND)native_window; + + int width = 1, height = 1; + if(parent_window) + { + // Listen in on parent size changes. + SetWindowSubclass(parent_window, subClassProc, 42, (LONG_PTR)this); + + // Resize newly created window to fit into parent. + RECT rect; + GetClientRect(parent_window, &rect); + + auto resizeEvent = std::make_shared(); + width = resizeEvent->width = rect.right - rect.left; + height = resizeEvent->height = rect.bottom - rect.top; + event_queue.push_back(resizeEvent); + } + + m_hwnd = CreateWindowEx(0/*ex_style*/, m_className, + "DGBasisWidget", + (native_window?WS_CHILD:WS_OVERLAPPEDWINDOW) | + (native_window?WS_VISIBLE:0), + 0, 0, + width, height, + parent_window, nullptr, + GetModuleHandle(nullptr), nullptr); + + SetWindowLongPtr(m_hwnd, GWLP_USERDATA, (LONG_PTR)this); + + // Set up initial tracking of the mouse leave events + trackMouse(m_hwnd); +} + +NativeWindowWin32::~NativeWindowWin32() +{ + if(parent_window) + { + RemoveWindowSubclass(parent_window, subClassProc, 42); + } + DestroyWindow(m_hwnd); + UnregisterClass(m_className, GetModuleHandle(nullptr)); + free(m_className); +} + +void NativeWindowWin32::setFixedSize(std::size_t width, std::size_t height) +{ + resize(width, height); + LONG style = GetWindowLong(m_hwnd, GWL_STYLE); + style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX); + SetWindowLong(m_hwnd, GWL_STYLE, style); +} + +void NativeWindowWin32::setAlwaysOnTop(bool always_on_top) +{ + this->always_on_top = always_on_top; + SetWindowPos(m_hwnd, always_on_top ? HWND_TOPMOST : nullptr, + 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); +} + +void NativeWindowWin32::resize(std::size_t width, std::size_t height) +{ + auto hwnd = m_hwnd; + //if(parent_window) + //{ + // hwnd = parent_window; + //} + + // Set requested size on the window (or parent) + SetWindowPos(hwnd, always_on_top ? HWND_TOPMOST : nullptr, + -1, -1, (int)width, (int)height, SWP_NOMOVE); + + // Ask the client window what size it actually got + RECT rect; + GetClientRect(m_hwnd, &rect); + int w = width - rect.right; + int h = height - rect.bottom; + + // Set the compensated size on the window (or parent) + SetWindowPos(hwnd, always_on_top ? HWND_TOPMOST : nullptr, + -1, -1, width + w, height + h, SWP_NOMOVE); +} + +std::pair NativeWindowWin32::getSize() const +{ + RECT rect; + GetClientRect(m_hwnd, &rect); + return std::make_pair(rect.right - rect.left, rect.bottom - rect.top); +} + +void NativeWindowWin32::move(int x, int y) +{ + SetWindowPos(m_hwnd, always_on_top ? HWND_TOPMOST : nullptr, + (int)x, (int)y, -1, -1, SWP_NOSIZE); +} + +std::pair NativeWindowWin32::getPosition() const +{ + RECT rect; + GetClientRect(m_hwnd, &rect); + return std::make_pair(rect.left, rect.top); +} + +void NativeWindowWin32::show() +{ + ShowWindow(m_hwnd, SW_SHOW); +} + +void NativeWindowWin32::hide() +{ + ShowWindow(m_hwnd, SW_HIDE); +} + +bool NativeWindowWin32::visible() const +{ + return IsWindowVisible(m_hwnd); +} + +void NativeWindowWin32::redraw(const Rect& dirty_rect) +{ + // Send WM_PAINT message. Buffer transfering is handled in MessageHandler. + if(parent_window == nullptr) + { + RECT rect = + { + (long)dirty_rect.x1, + (long)dirty_rect.y1, + (long)dirty_rect.x2, + (long)dirty_rect.y2 + }; + RedrawWindow(m_hwnd, &rect, nullptr, RDW_INVALIDATE); + UpdateWindow(m_hwnd); + } + else + { + InvalidateRect(m_hwnd, 0, TRUE); + } +} + +void NativeWindowWin32::setCaption(const std::string &caption) +{ + SetWindowText(m_hwnd, caption.c_str()); +} + +void NativeWindowWin32::grabMouse(bool grab) +{ + if(grab) + { + SetCapture(m_hwnd); + } + else + { + ReleaseCapture(); + } +} + +EventQueue NativeWindowWin32::getEvents() +{ + MSG msg; + while(PeekMessage(&msg, m_hwnd, 0, 0, PM_REMOVE) != 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + EventQueue events; + std::swap(events, event_queue); + return events; +} + +void* NativeWindowWin32::getNativeWindowHandle() const +{ + return (void*)m_hwnd; +} + +Point NativeWindowWin32::translateToScreen(const Point& point) +{ + POINT p{ point.x, point.y }; + ClientToScreen(m_hwnd, &p); + return { p.x, p.y }; +} + +} // GUI:: -- cgit v1.2.3