Added support for cross-compiling to mingw (#5)
[full-beans.git] / fenster.h
blob44ebc4a96ee5af69422c2aca119afb92c21269e9
1 /*
2 ** Copyright (c) 2024 Serge Zaitsev
3 **
4 ** Permission is hereby granted, free of charge, to any person obtaining a copy
5 ** of this software and associated documentation files (the "Software"), to
6 ** deal in the Software without restriction, including without limitation the
7 ** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 ** sell copies of the Software, and to permit persons to whom the Software is
9 ** furnished to do so, subject to the following conditions:
11 ** The above copyright notice and this permission notice shall be included in
12 ** all copies or substantial portions of the Software.
14 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 ** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 ** IN THE SOFTWARE.
23 #ifndef FENSTER_H
24 #define FENSTER_H
26 #if defined(__APPLE__)
27 #include <CoreGraphics/CoreGraphics.h>
28 #include <objc/NSObjCRuntime.h>
29 #include <objc/objc-runtime.h>
30 #elif defined(_WIN32)
31 #include <windows.h>
32 #else
33 #define _DEFAULT_SOURCE 1
34 #include <X11/XKBlib.h>
35 #include <X11/Xlib.h>
36 #include <X11/keysym.h>
37 #include <time.h>
38 #endif
40 #include <stdint.h>
41 #include <stdlib.h>
42 #include <string.h>
44 struct fenster {
45 const char *title;
46 const int width;
47 const int height;
48 uint32_t *buf;
49 int keys[256]; /* keys are mostly ASCII, but arrows are 17..20 */
50 int mod; /* mod is 4 bits mask, ctrl=1, shift=2, alt=4, meta=8 */
51 int x;
52 int y;
53 int mouse;
54 #if defined(__APPLE__)
55 id wnd;
56 #elif defined(_WIN32)
57 HWND hwnd;
58 #else
59 Display *dpy;
60 Window w;
61 GC gc;
62 XImage *img;
63 #endif
66 #ifndef FENSTER_API
67 #define FENSTER_API extern
68 #endif
69 FENSTER_API int fenster_open(struct fenster *f);
70 FENSTER_API int fenster_loop(struct fenster *f);
71 FENSTER_API void fenster_close(struct fenster *f);
72 FENSTER_API void fenster_sleep(int64_t ms);
73 FENSTER_API int64_t fenster_time(void);
74 #define fenster_pixel(f, x, y) ((f)->buf[((y) * (f)->width) + (x)])
76 #ifndef FENSTER_HEADER
77 #if defined(__APPLE__)
78 #define msg(r, o, s) ((r(*)(id, SEL))objc_msgSend)(o, sel_getUid(s))
79 #define msg1(r, o, s, A, a) \
80 ((r(*)(id, SEL, A))objc_msgSend)(o, sel_getUid(s), a)
81 #define msg2(r, o, s, A, a, B, b) \
82 ((r(*)(id, SEL, A, B))objc_msgSend)(o, sel_getUid(s), a, b)
83 #define msg3(r, o, s, A, a, B, b, C, c) \
84 ((r(*)(id, SEL, A, B, C))objc_msgSend)(o, sel_getUid(s), a, b, c)
85 #define msg4(r, o, s, A, a, B, b, C, c, D, d) \
86 ((r(*)(id, SEL, A, B, C, D))objc_msgSend)(o, sel_getUid(s), a, b, c, d)
88 #define cls(x) ((id)objc_getClass(x))
90 extern id const NSDefaultRunLoopMode;
91 extern id const NSApp;
93 static void fenster_draw_rect(id v, SEL s, CGRect r) {
94 (void)r, (void)s;
95 struct fenster *f = (struct fenster *)objc_getAssociatedObject(v, "fenster");
96 CGContextRef context =
97 msg(CGContextRef, msg(id, cls("NSGraphicsContext"), "currentContext"),
98 "graphicsPort");
99 CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
100 CGDataProviderRef provider = CGDataProviderCreateWithData(
101 NULL, f->buf, f->width * f->height * 4, NULL);
102 CGImageRef img =
103 CGImageCreate(f->width, f->height, 8, 32, f->width * 4, space,
104 kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little,
105 provider, NULL, false, kCGRenderingIntentDefault);
106 CGColorSpaceRelease(space);
107 CGDataProviderRelease(provider);
108 CGContextDrawImage(context, CGRectMake(0, 0, f->width, f->height), img);
109 CGImageRelease(img);
112 static BOOL fenster_should_close(id v, SEL s, id w) {
113 (void)v, (void)s, (void)w;
114 msg1(void, NSApp, "terminate:", id, NSApp);
115 return YES;
118 FENSTER_API int fenster_open(struct fenster *f) {
119 msg(id, cls("NSApplication"), "sharedApplication");
120 msg1(void, NSApp, "setActivationPolicy:", NSInteger, 0);
121 f->wnd = msg4(id, msg(id, cls("NSWindow"), "alloc"),
122 "initWithContentRect:styleMask:backing:defer:", CGRect,
123 CGRectMake(0, 0, f->width, f->height), NSUInteger, 3,
124 NSUInteger, 2, BOOL, NO);
125 Class windelegate =
126 objc_allocateClassPair((Class)cls("NSObject"), "FensterDelegate", 0);
127 class_addMethod(windelegate, sel_getUid("windowShouldClose:"),
128 (IMP)fenster_should_close, "c@:@");
129 objc_registerClassPair(windelegate);
130 msg1(void, f->wnd, "setDelegate:", id,
131 msg(id, msg(id, (id)windelegate, "alloc"), "init"));
132 Class c = objc_allocateClassPair((Class)cls("NSView"), "FensterView", 0);
133 class_addMethod(c, sel_getUid("drawRect:"), (IMP)fenster_draw_rect, "i@:@@");
134 objc_registerClassPair(c);
136 id v = msg(id, msg(id, (id)c, "alloc"), "init");
137 msg1(void, f->wnd, "setContentView:", id, v);
138 objc_setAssociatedObject(v, "fenster", (id)f, OBJC_ASSOCIATION_ASSIGN);
140 id title = msg1(id, cls("NSString"), "stringWithUTF8String:", const char *,
141 f->title);
142 msg1(void, f->wnd, "setTitle:", id, title);
143 msg1(void, f->wnd, "makeKeyAndOrderFront:", id, nil);
144 msg(void, f->wnd, "center");
145 msg1(void, NSApp, "activateIgnoringOtherApps:", BOOL, YES);
146 return 0;
149 FENSTER_API void fenster_close(struct fenster *f) {
150 msg(void, f->wnd, "close");
153 // clang-format off
154 static const uint8_t FENSTER_KEYCODES[128] = {65,83,68,70,72,71,90,88,67,86,0,66,81,87,69,82,89,84,49,50,51,52,54,53,61,57,55,45,56,48,93,79,85,91,73,80,10,76,74,39,75,59,92,44,47,78,77,46,9,32,96,8,0,27,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,2,3,127,0,5,0,4,0,20,19,18,17,0};
155 // clang-format on
156 FENSTER_API int fenster_loop(struct fenster *f) {
157 msg1(void, msg(id, f->wnd, "contentView"), "setNeedsDisplay:", BOOL, YES);
158 id ev = msg4(id, NSApp,
159 "nextEventMatchingMask:untilDate:inMode:dequeue:", NSUInteger,
160 NSUIntegerMax, id, NULL, id, NSDefaultRunLoopMode, BOOL, YES);
161 if (!ev)
162 return 0;
163 NSUInteger evtype = msg(NSUInteger, ev, "type");
164 switch (evtype) {
165 case 1: /* NSEventTypeMouseDown */
166 f->mouse |= 1;
167 break;
168 case 2: /* NSEventTypeMouseUp*/
169 f->mouse &= ~1;
170 break;
171 case 5:
172 case 6: { /* NSEventTypeMouseMoved */
173 CGPoint xy = msg(CGPoint, ev, "locationInWindow");
174 f->x = (int)xy.x;
175 f->y = (int)(f->height - xy.y);
176 return 0;
178 case 10: /*NSEventTypeKeyDown*/
179 case 11: /*NSEventTypeKeyUp:*/ {
180 NSUInteger k = msg(NSUInteger, ev, "keyCode");
181 f->keys[k < 127 ? FENSTER_KEYCODES[k] : 0] = evtype == 10;
182 NSUInteger mod = msg(NSUInteger, ev, "modifierFlags") >> 17;
183 f->mod = (mod & 0xc) | ((mod & 1) << 1) | ((mod >> 1) & 1);
184 return 0;
187 msg1(void, NSApp, "sendEvent:", id, ev);
188 return 0;
190 #elif defined(_WIN32)
191 // clang-format off
192 static const uint8_t FENSTER_KEYCODES[] = {0,27,49,50,51,52,53,54,55,56,57,48,45,61,8,9,81,87,69,82,84,89,85,73,79,80,91,93,10,0,65,83,68,70,71,72,74,75,76,59,39,96,0,92,90,88,67,86,66,78,77,44,46,47,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,17,3,0,20,0,19,0,5,18,4,26,127};
193 // clang-format on
194 typedef struct BINFO{
195 BITMAPINFOHEADER bmiHeader;
196 RGBQUAD bmiColors[3];
197 }BINFO;
198 static LRESULT CALLBACK fenster_wndproc(HWND hwnd, UINT msg, WPARAM wParam,
199 LPARAM lParam) {
200 struct fenster *f = (struct fenster *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
201 switch (msg) {
202 case WM_PAINT: {
203 PAINTSTRUCT ps;
204 HDC hdc = BeginPaint(hwnd, &ps);
205 HDC memdc = CreateCompatibleDC(hdc);
206 HBITMAP hbmp = CreateCompatibleBitmap(hdc, f->width, f->height);
207 HBITMAP oldbmp = SelectObject(memdc, hbmp);
208 BINFO bi = {{sizeof(bi), f->width, -f->height, 1, 32, BI_BITFIELDS}};
209 bi.bmiColors[0].rgbRed = 0xff;
210 bi.bmiColors[1].rgbGreen = 0xff;
211 bi.bmiColors[2].rgbBlue = 0xff;
212 SetDIBitsToDevice(memdc, 0, 0, f->width, f->height, 0, 0, 0, f->height,
213 f->buf, (BITMAPINFO *)&bi, DIB_RGB_COLORS);
214 BitBlt(hdc, 0, 0, f->width, f->height, memdc, 0, 0, SRCCOPY);
215 SelectObject(memdc, oldbmp);
216 DeleteObject(hbmp);
217 DeleteDC(memdc);
218 EndPaint(hwnd, &ps);
219 } break;
220 case WM_CLOSE:
221 DestroyWindow(hwnd);
222 break;
223 case WM_LBUTTONDOWN:
224 case WM_LBUTTONUP:
225 f->mouse = (msg == WM_LBUTTONDOWN);
226 break;
227 case WM_MOUSEMOVE:
228 f->y = HIWORD(lParam), f->x = LOWORD(lParam);
229 break;
230 case WM_KEYDOWN:
231 case WM_KEYUP: {
232 f->mod = ((GetKeyState(VK_CONTROL) & 0x8000) >> 15) |
233 ((GetKeyState(VK_SHIFT) & 0x8000) >> 14) |
234 ((GetKeyState(VK_MENU) & 0x8000) >> 13) |
235 (((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & 0x8000) >> 12);
236 f->keys[FENSTER_KEYCODES[HIWORD(lParam) & 0x1ff]] = !((lParam >> 31) & 1);
237 } break;
238 case WM_DESTROY:
239 PostQuitMessage(0);
240 break;
241 default:
242 return DefWindowProc(hwnd, msg, wParam, lParam);
244 return 0;
247 FENSTER_API int fenster_open(struct fenster *f) {
248 HINSTANCE hInstance = GetModuleHandle(NULL);
249 WNDCLASSEX wc = {0};
250 wc.cbSize = sizeof(WNDCLASSEX);
251 wc.style = CS_VREDRAW | CS_HREDRAW;
252 wc.lpfnWndProc = fenster_wndproc;
253 wc.hInstance = hInstance;
254 wc.lpszClassName = f->title;
255 RegisterClassEx(&wc);
256 f->hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, f->title, f->title,
257 WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
258 f->width, f->height, NULL, NULL, hInstance, NULL);
260 if (f->hwnd == NULL)
261 return -1;
262 SetWindowLongPtr(f->hwnd, GWLP_USERDATA, (LONG_PTR)f);
263 ShowWindow(f->hwnd, SW_NORMAL);
264 UpdateWindow(f->hwnd);
265 return 0;
268 FENSTER_API void fenster_close(struct fenster *f) { (void)f; }
270 FENSTER_API int fenster_loop(struct fenster *f) {
271 MSG msg;
272 while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
273 if (msg.message == WM_QUIT)
274 return -1;
275 TranslateMessage(&msg);
276 DispatchMessage(&msg);
278 InvalidateRect(f->hwnd, NULL, TRUE);
279 return 0;
281 #else
282 // clang-format off
283 static int FENSTER_KEYCODES[124] = {XK_BackSpace,8,XK_Delete,127,XK_Down,18,XK_End,5,XK_Escape,27,XK_Home,2,XK_Insert,26,XK_Left,20,XK_Page_Down,4,XK_Page_Up,3,XK_Return,10,XK_Right,19,XK_Tab,9,XK_Up,17,XK_apostrophe,39,XK_backslash,92,XK_bracketleft,91,XK_bracketright,93,XK_comma,44,XK_equal,61,XK_grave,96,XK_minus,45,XK_period,46,XK_semicolon,59,XK_slash,47,XK_space,32,XK_a,65,XK_b,66,XK_c,67,XK_d,68,XK_e,69,XK_f,70,XK_g,71,XK_h,72,XK_i,73,XK_j,74,XK_k,75,XK_l,76,XK_m,77,XK_n,78,XK_o,79,XK_p,80,XK_q,81,XK_r,82,XK_s,83,XK_t,84,XK_u,85,XK_v,86,XK_w,87,XK_x,88,XK_y,89,XK_z,90,XK_0,48,XK_1,49,XK_2,50,XK_3,51,XK_4,52,XK_5,53,XK_6,54,XK_7,55,XK_8,56,XK_9,57};
284 // clang-format on
285 FENSTER_API int fenster_open(struct fenster *f) {
286 f->dpy = XOpenDisplay(NULL);
287 int screen = DefaultScreen(f->dpy);
288 f->w = XCreateSimpleWindow(f->dpy, RootWindow(f->dpy, screen), 0, 0, f->width,
289 f->height, 0, BlackPixel(f->dpy, screen),
290 WhitePixel(f->dpy, screen));
291 f->gc = XCreateGC(f->dpy, f->w, 0, 0);
292 XSelectInput(f->dpy, f->w,
293 ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask |
294 ButtonReleaseMask | PointerMotionMask);
295 XStoreName(f->dpy, f->w, f->title);
296 XMapWindow(f->dpy, f->w);
297 XSync(f->dpy, f->w);
298 f->img = XCreateImage(f->dpy, DefaultVisual(f->dpy, 0), 24, ZPixmap, 0,
299 (char *)f->buf, f->width, f->height, 32, 0);
300 return 0;
302 FENSTER_API void fenster_close(struct fenster *f) { XCloseDisplay(f->dpy); }
303 FENSTER_API int fenster_loop(struct fenster *f) {
304 XEvent ev;
305 XPutImage(f->dpy, f->w, f->gc, f->img, 0, 0, 0, 0, f->width, f->height);
306 XFlush(f->dpy);
307 while (XPending(f->dpy)) {
308 XNextEvent(f->dpy, &ev);
309 switch (ev.type) {
310 case ButtonPress:
311 case ButtonRelease:
312 f->mouse = (ev.type == ButtonPress);
313 break;
314 case MotionNotify:
315 f->x = ev.xmotion.x, f->y = ev.xmotion.y;
316 break;
317 case KeyPress:
318 case KeyRelease: {
319 int m = ev.xkey.state;
320 int k = XkbKeycodeToKeysym(f->dpy, ev.xkey.keycode, 0, 0);
321 for (unsigned int i = 0; i < 124; i += 2) {
322 if (FENSTER_KEYCODES[i] == k) {
323 f->keys[FENSTER_KEYCODES[i + 1]] = (ev.type == KeyPress);
324 break;
327 f->mod = (!!(m & ControlMask)) | (!!(m & ShiftMask) << 1) |
328 (!!(m & Mod1Mask) << 2) | (!!(m & Mod4Mask) << 3);
329 } break;
332 return 0;
334 #endif
336 #ifdef _WIN32
337 FENSTER_API void fenster_sleep(int64_t ms) { Sleep(ms); }
338 FENSTER_API int64_t fenster_time() {
339 LARGE_INTEGER freq, count;
340 QueryPerformanceFrequency(&freq);
341 QueryPerformanceCounter(&count);
342 return (int64_t)(count.QuadPart * 1000.0 / freq.QuadPart);
344 #else
345 FENSTER_API void fenster_sleep(int64_t ms) {
346 struct timespec ts;
347 ts.tv_sec = ms / 1000;
348 ts.tv_nsec = (ms % 1000) * 1000000;
349 nanosleep(&ts, NULL);
351 FENSTER_API int64_t fenster_time(void) {
352 struct timespec time;
353 clock_gettime(CLOCK_REALTIME, &time);
354 return time.tv_sec * 1000 + (time.tv_nsec / 1000000);
356 #endif
358 #ifdef __cplusplus
359 class Fenster {
360 struct fenster f;
361 int64_t now;
363 public:
364 Fenster(const int w, const int h, const char *title)
365 : f{.title = title, .width = w, .height = h} {
366 this->f.buf = new uint32_t[w * h];
367 clear();
368 this->now = fenster_time();
369 fenster_open(&this->f);
371 ~Fenster() {
372 fenster_close(&this->f);
373 delete[] this->f.buf;
375 void clear() { memset(this->f.buf, 0, this->f.width * this->f.height * 4); }
376 bool loop(const int fps) {
377 int64_t t = fenster_time();
378 int64_t paint_time = t - this->now;
379 int64_t frame_budget = 1000 / fps;
380 int64_t sleep_time = frame_budget - paint_time;
381 if (sleep_time > 0) {
382 fenster_sleep(sleep_time);
384 this->now = t;
385 return fenster_loop(&this->f) == 0;
387 void paint() { fenster_loop(&this->f); }
388 inline uint32_t &px(const int x, const int y) {
389 return fenster_pixel(&this->f, x, y);
391 bool key(int c) { return c >= 0 && c < 128 ? this->f.keys[c] : false; }
392 int x() { return this->f.x; }
393 int y() { return this->f.y; }
394 int mouse() { return this->f.mouse; }
395 int mod() { return this->f.mod; }
396 long width() { return this->f.width; }
397 long height() { return this->f.height; }
399 #endif /* __cplusplus */
401 #endif /* !FENSTER_HEADER */
402 #endif /* FENSTER_H */