342 lines
8.9 KiB
C
342 lines
8.9 KiB
C
/*
|
|
* Vinci
|
|
*
|
|
* Copyright (C) 2025 Orastron Srl unipersonale
|
|
*
|
|
* Vinci is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, version 3 of the License.
|
|
*
|
|
* Vinci 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Vinci. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* File author: Paolo Marrone, Stefano D'Angelo
|
|
*/
|
|
|
|
#include "vinci.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
|
|
struct window {
|
|
vinci *g;
|
|
window *next;
|
|
HWND handle;
|
|
HBITMAP bitmap;
|
|
BITMAPINFOHEADER bitmap_info;
|
|
uint8_t *bgra;
|
|
char mouse_tracking;
|
|
void *data;
|
|
window_cbs cbs;
|
|
};
|
|
|
|
struct vinci {
|
|
window *windows;
|
|
char className[66];
|
|
};
|
|
|
|
static uint32_t get_mouse_state(WPARAM wParam) {
|
|
return (wParam & (MK_LBUTTON | MK_RBUTTON)) | ((wParam & MK_MBUTTON) >> 2);
|
|
}
|
|
|
|
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
|
window *w = (window*) GetWindowLongPtrA(hwnd, 0);
|
|
if (w == NULL)
|
|
return DefWindowProc(hwnd, msg, wParam, lParam);
|
|
|
|
POINTS points;
|
|
switch(msg)
|
|
{
|
|
case WM_LBUTTONDOWN:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
if (w->cbs.on_mouse_press) {
|
|
points = MAKEPOINTS(lParam);
|
|
SetCapture(hwnd);
|
|
w->cbs.on_mouse_press(w, points.x, points.y, get_mouse_state(wParam));
|
|
}
|
|
break;
|
|
case WM_LBUTTONUP:
|
|
case WM_MBUTTONUP:
|
|
case WM_RBUTTONUP:
|
|
if (w->cbs.on_mouse_release) {
|
|
points = MAKEPOINTS(lParam);
|
|
if (get_mouse_state(wParam) == 0) {
|
|
ReleaseCapture();
|
|
}
|
|
w->cbs.on_mouse_release(w, points.x, points.y, get_mouse_state(wParam));
|
|
}
|
|
break;
|
|
case WM_MOUSEMOVE:
|
|
points = MAKEPOINTS(lParam);
|
|
if (!w->mouse_tracking) {
|
|
if (w->cbs.on_mouse_enter)
|
|
w->cbs.on_mouse_enter(w, points.x, points.y, 1);
|
|
TRACKMOUSEEVENT tme;
|
|
tme.cbSize = sizeof(tme);
|
|
tme.hwndTrack = hwnd;
|
|
tme.dwFlags = TME_HOVER | TME_LEAVE;
|
|
tme.dwHoverTime = HOVER_DEFAULT;
|
|
TrackMouseEvent(&tme);
|
|
w->mouse_tracking = 1;
|
|
}
|
|
if (w->cbs.on_mouse_move)
|
|
w->cbs.on_mouse_move(w, points.x, points.y, get_mouse_state(wParam));
|
|
break;
|
|
case WM_MOUSELEAVE:
|
|
w->mouse_tracking = 0;
|
|
if (w->cbs.on_mouse_leave) {
|
|
points = MAKEPOINTS(lParam);
|
|
w->cbs.on_mouse_leave(w, points.x, points.y, get_mouse_state(wParam));
|
|
}
|
|
break;
|
|
case WM_KEYDOWN:
|
|
if (w->cbs.on_key_press)
|
|
w->cbs.on_key_press(w, wParam, 1);
|
|
break;
|
|
case WM_KEYUP:
|
|
if (w->cbs.on_key_release)
|
|
w->cbs.on_key_release(w, wParam, 1);
|
|
break;
|
|
case WM_SIZE:
|
|
{
|
|
const uint32_t width = LOWORD(lParam);
|
|
const uint32_t height = HIWORD(lParam);
|
|
DeleteObject(w->bitmap);
|
|
w->bitmap_info.biWidth = width;
|
|
w->bitmap_info.biHeight = -height;
|
|
HDC dc = GetDC(w->handle);
|
|
w->bitmap = CreateDIBSection(dc, (BITMAPINFO*)&w->bitmap_info, DIB_RGB_COLORS, (void **)&w->bgra, NULL, 0);
|
|
ReleaseDC(w->handle, dc);
|
|
if (w->cbs.on_window_resize)
|
|
w->cbs.on_window_resize(w, width, height);
|
|
}
|
|
break;
|
|
case WM_PAINT:
|
|
{
|
|
RECT cr;;
|
|
GetClientRect(w->handle, &cr);
|
|
RECT r;
|
|
if (GetUpdateRect(hwnd, &r, 0)) {
|
|
PAINTSTRUCT ps;
|
|
HDC dc = BeginPaint(hwnd, &ps);
|
|
SetDIBitsToDevice(dc, r.left, r.top, r.right - r.left, r.bottom - r.top, r.left, cr.bottom - cr.top - r.bottom, 0, -w->bitmap_info.biHeight, w->bgra, (BITMAPINFO*)&w->bitmap_info, DIB_RGB_COLORS);
|
|
EndPaint(hwnd, &ps);
|
|
}
|
|
}
|
|
break;
|
|
case WM_DESTROY:
|
|
{
|
|
if (w->cbs.on_window_close)
|
|
w->cbs.on_window_close(w);
|
|
}
|
|
break;
|
|
}
|
|
return DefWindowProc(hwnd, msg, wParam, lParam);
|
|
}
|
|
|
|
vinci* vinci_new(void) {
|
|
vinci *g = (vinci*)malloc(sizeof(vinci));
|
|
if (!g)
|
|
return NULL;
|
|
snprintf(g->className, 66, "vc%p", (void*)g); // Nice random name
|
|
|
|
WNDCLASSEX wc;
|
|
wc.cbSize = sizeof(WNDCLASSEX);
|
|
wc.style = 0;
|
|
wc.lpfnWndProc = WndProc;
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = sizeof (window*); // To store window ptr into hwnd
|
|
wc.hInstance = NULL;
|
|
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
|
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wc.hbrBackground = (HBRUSH)(WHITE_BRUSH);
|
|
wc.lpszMenuName = NULL;
|
|
wc.lpszClassName = g->className;
|
|
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
|
|
if (!RegisterClassEx(&wc)) {
|
|
free(g);
|
|
return NULL;
|
|
}
|
|
g->windows = NULL;
|
|
return g;
|
|
}
|
|
|
|
void vinci_destroy(vinci *g) {
|
|
UnregisterClass(g->className, NULL);
|
|
free(g);
|
|
}
|
|
|
|
void vinci_idle(vinci *g) {
|
|
(void) g;
|
|
MSG msg;
|
|
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
|
|
window* window_new(vinci *g, void* parent, uint32_t width, uint32_t height, window_cbs *cbs) {
|
|
window *w = (window*)malloc(sizeof(window));
|
|
if (w == NULL)
|
|
return NULL;
|
|
memset((void*) w, 0, sizeof(window));
|
|
|
|
w->g = g;
|
|
w->handle = CreateWindowEx(0, g->className, NULL, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, NULL, NULL);
|
|
if (w->handle == NULL) {
|
|
free(w);
|
|
return NULL;
|
|
}
|
|
SetWindowLongPtrW(w->handle, 0, (LONG_PTR) w);
|
|
ZeroMemory(&w->bitmap_info, sizeof(BITMAPINFOHEADER));
|
|
w->bitmap_info.biSize = sizeof(BITMAPINFOHEADER);
|
|
w->bitmap_info.biWidth = width;
|
|
w->bitmap_info.biHeight = -height;
|
|
w->bitmap_info.biPlanes = 1;
|
|
w->bitmap_info.biBitCount = 32;
|
|
w->bitmap_info.biCompression = BI_RGB;
|
|
HDC dc = GetDC(w->handle);
|
|
w->bitmap = CreateDIBSection(dc, (BITMAPINFO*)&w->bitmap_info, DIB_RGB_COLORS, (void **)&w->bgra, NULL, 0);
|
|
ReleaseDC(w->handle, dc);
|
|
if (w->bitmap == NULL) {
|
|
DestroyWindow(w->handle);
|
|
free(w);
|
|
return NULL;
|
|
}
|
|
|
|
if (parent) {
|
|
SetParent(w->handle, (HWND)parent);
|
|
SetWindowLong(w->handle, GWL_STYLE, GetWindowLong(w->handle, GWL_STYLE) & ~(WS_BORDER | WS_SIZEBOX | WS_DLGFRAME));
|
|
SetWindowPos(w->handle, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); // SWP_FRAMECHANGED triggers WndProc call with WM_SIZE right here. May they die hard
|
|
}
|
|
|
|
w->next = NULL;
|
|
if (g->windows == NULL)
|
|
g->windows = w;
|
|
else {
|
|
window *n = g->windows;
|
|
while (n->next)
|
|
n = n->next;
|
|
n->next = w;
|
|
}
|
|
|
|
TRACKMOUSEEVENT tme;
|
|
tme.cbSize = sizeof(TRACKMOUSEEVENT);
|
|
tme.dwFlags = TME_LEAVE;
|
|
tme.hwndTrack = w->handle;
|
|
TrackMouseEvent(&tme);
|
|
w->mouse_tracking = 1;
|
|
|
|
w->data = NULL;
|
|
w->cbs = *cbs;
|
|
UpdateWindow(w->handle);
|
|
|
|
return w;
|
|
}
|
|
|
|
void window_free(window *w) {
|
|
if (w->g->windows == w)
|
|
w->g->windows = w->next;
|
|
else {
|
|
window *n = w->g->windows;
|
|
while (n->next != w)
|
|
n = n->next;
|
|
n->next = w->next;
|
|
}
|
|
DeleteObject(w->bitmap);
|
|
//DestroyWindow(w->handle); // This calls WM_DESTROY. So this should used to actively close a window, not as a cb
|
|
free(w);
|
|
}
|
|
|
|
static inline int mini(int a, int b) {
|
|
return a < b ? a : b;
|
|
}
|
|
|
|
static inline int maxi(int a, int b) {
|
|
return a > b ? a : b;
|
|
}
|
|
|
|
void window_draw(window *w, unsigned char *data, int32_t dx, int32_t dy, int32_t dw, int32_t dh, int32_t wx, int32_t wy, int32_t width, int32_t height) {
|
|
(void) dh;
|
|
|
|
RECT cr;
|
|
GetClientRect(w->handle, &cr);
|
|
|
|
const int wx_ = maxi(wx, cr.left);
|
|
const int wy_ = maxi(wy, cr.top);
|
|
const int width_ = mini(width - (wx_ - wx), cr.right - wx);
|
|
const int height_ = mini(height - (wy_ - wy), cr.bottom - wy);
|
|
if (width_ <= 0 || height_ <= 0 )
|
|
return;
|
|
dx = dx + (wx_ - wx);
|
|
dy = dy + (wy_ - wy);
|
|
wx = wx_;
|
|
wy = wy_;
|
|
width = width_;
|
|
height = height_;
|
|
|
|
uint32_t *src = ((uint32_t *)data) + dw * dy + dx;
|
|
uint32_t *dest = ((uint32_t *)w->bgra) + w->bitmap_info.biWidth * wy + wx;
|
|
for (uint32_t h = height; h; h--, src += dw, dest += w->bitmap_info.biWidth) {
|
|
//memcpy(dest, src, 4 * width);
|
|
for (int32_t x = 0; x < width; x++) // TODO: BIG ENDIAN AND LITTLEENDIAN IFDEF
|
|
dest[x] = ((src[x] >> 16) & 0x00000000ff) | ((src[x]) & 0x0000ff00) | ((src[x] << 16) & 0x00ff0000);
|
|
}
|
|
|
|
RECT r = {
|
|
wx, // left
|
|
wy, // top
|
|
wx + width, // right
|
|
wy + height // bottom
|
|
};
|
|
|
|
RedrawWindow(w->handle, &r, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
|
|
}
|
|
|
|
void window_resize(window *w, uint32_t width, uint32_t height) {
|
|
SetWindowPos(w->handle, NULL, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER);
|
|
}
|
|
|
|
void window_move(window *w, uint32_t x, uint32_t y) {
|
|
SetWindowPos(w->handle, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
|
|
}
|
|
|
|
uint32_t window_get_width(window *w) {
|
|
RECT r;
|
|
GetClientRect(w->handle, &r);
|
|
return r.right - r.left;
|
|
}
|
|
|
|
uint32_t window_get_height(window *w) {
|
|
RECT r;
|
|
GetClientRect(w->handle, &r);
|
|
return r.bottom - r.top;
|
|
}
|
|
|
|
void *window_get_handle(window *w) {
|
|
return w->handle;
|
|
}
|
|
|
|
void window_show(window *w) {
|
|
ShowWindow(w->handle, SW_SHOW);
|
|
}
|
|
|
|
void window_hide(window *w) {
|
|
ShowWindow(w->handle, SW_HIDE);
|
|
}
|
|
|
|
void window_set_data(window *w, void *data) {
|
|
w->data = data;
|
|
}
|
|
|
|
void *window_get_data(window *w) {
|
|
return w->data;
|
|
}
|