mirror of
https://github.com/zserge/fenster.git
synced 2025-04-20 00:18:57 +03:00
initial commit
This commit is contained in:
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Serge Zaitsev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
15
Makefile
Normal file
15
Makefile
Normal file
@ -0,0 +1,15 @@
|
||||
CFLAGS ?= -Wall -Wextra -std=c99
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
LDFLAGS = -lgdi32
|
||||
else
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
LDFLAGS = -framework Cocoa
|
||||
else
|
||||
LDFLAGS = -lX11
|
||||
endif
|
||||
endif
|
||||
|
||||
example: example.c fenster.h
|
||||
$(CC) example.c -Os -o $@ $(CFLAGS) $(LDFLAGS)
|
88
README.md
Normal file
88
README.md
Normal file
@ -0,0 +1,88 @@
|
||||
# Fenster
|
||||
|
||||
Fenster /ˈfɛnstɐ/ -- a German language word for "window".
|
||||
|
||||
This library provides the most minimal and highly opinionated way to display a cross-platform 2D canvas. If you remember Borland BGI or drawing things in QBASIC or `INT 10h`- you know what I mean.
|
||||
|
||||
## What it does for you
|
||||
|
||||
* Single application window of given size with a title.
|
||||
* Application lifecycle and system events are all handled automatically.
|
||||
* Minimal 24-bit RGB framebuffer.
|
||||
* Cross-platform keyboard events (keycodes).
|
||||
* Cross-platform timers to have a stable FPS rate.
|
||||
* It's a single header in plain C99 of ~300LOC with no memory allocations.
|
||||
|
||||
## What it might do for you in the next version
|
||||
|
||||
* Mouse events (at least left button click + XY)
|
||||
* Audio playback (WinMM, CoreAudio, ALSA)
|
||||
* Bindings for Go, Zig, Rust, Nim, Python...
|
||||
|
||||
## What it will never do for you
|
||||
|
||||
* GUI widgets - use Qt or Gtk or WxWidgets.
|
||||
* Complex drawing routines or OpenGL - use Sokol or MiniFB or Tigr.
|
||||
* Low-latency audio - use PortAudio, RtAudio, libsoundio etc.
|
||||
|
||||
In other words, you get a single super tiny C file, with a very simple API and it allows you to build graphical apps (simple games, emulators) in a very low-level retrocomputing manner.
|
||||
|
||||
## Example
|
||||
|
||||
Here's how to draw white noise:
|
||||
|
||||
```c
|
||||
#include "fenster.h"
|
||||
#define W 320
|
||||
#define H 240
|
||||
int main() {
|
||||
uint32_t buf[W * H];
|
||||
struct fenster f = { .title = "hello", .width = W, .height = H, .buf = buf };
|
||||
fenster_open(&f);
|
||||
while (fenster_loop(&f) == 0) {
|
||||
for (int i = 0; i < 320; i++) {
|
||||
for (int j = 0; j < 240; j++) {
|
||||
fenster_pixel(&f, i, j) = rand();
|
||||
}
|
||||
}
|
||||
}
|
||||
fenster_close(&f);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Compile it and run:
|
||||
|
||||
```
|
||||
# Linux
|
||||
cc main.c -lX11 -o main && ./main
|
||||
# macOS
|
||||
cc main.c -framework Cocoa -o main && ./main
|
||||
# windows
|
||||
# TODO, but link with -lgdi32
|
||||
```
|
||||
|
||||
That's it.
|
||||
|
||||
## API
|
||||
|
||||
```c
|
||||
struct fenster {
|
||||
const char *title; /* window title */
|
||||
const int width; /* window width */
|
||||
const int height; /* window height */
|
||||
uint32_t *buf; /* window pixels, 24-bit RGB, row by row, pixel by pixel */
|
||||
int keys[256]; /* keys are mostly ASCII, but arrows are 17..20 */
|
||||
int mod; /* mod is 4 bits mask, ctrl=1, shift=2, alt=4, meta=8 */
|
||||
};
|
||||
```
|
||||
|
||||
* `int fenster_open(struct fenster *f)` - opens a new app window.
|
||||
* `int fenster_loop(struct fenster *f)` - handles system events and refreshes the canvas. Returns negative values when app window is clsoed.
|
||||
* `void fenster_close(struct fenster *f)` - closes the window and exists the graphical app.
|
||||
* `void fenster_sleep(int ms)` - pauses for `ms` milliseconds.
|
||||
* `int64_t fenster_time()` - returns current time in milliseconds.
|
||||
|
||||
## License
|
||||
|
||||
Code is distributed under MIT license, feel free to use it in your proprietary projects as well.
|
237
example.c
Normal file
237
example.c
Normal file
@ -0,0 +1,237 @@
|
||||
#include "fenster.h"
|
||||
|
||||
#define W 320
|
||||
#define H 240
|
||||
|
||||
/* ============================================================
|
||||
* A very minimal example of a Fenster app:
|
||||
* - Opens a window
|
||||
* - Starts a loop
|
||||
* - Changes pixel colours based on some "shader" formula
|
||||
* - Sleeps if needed to maintain a frame rate of 60 FPS
|
||||
* - Closes a window
|
||||
* ============================================================ */
|
||||
static int demo_minimal_framebuffer() {
|
||||
uint32_t buf[W * H];
|
||||
struct fenster f = {
|
||||
.title = "hello",
|
||||
.width = W,
|
||||
.height = H,
|
||||
.buf = buf,
|
||||
};
|
||||
fenster_open(&f);
|
||||
uint32_t t = 0;
|
||||
int64_t now = fenster_time();
|
||||
while (fenster_loop(&f) == 0) {
|
||||
t++;
|
||||
for (int i = 0; i < 320; i++) {
|
||||
for (int j = 0; j < 240; j++) {
|
||||
/* White noise: */
|
||||
/* fenster_pixel(&f, i, j) = (rand() << 16) ^ (rand() << 8) ^ rand(); */
|
||||
|
||||
/* Colourful and moving: */
|
||||
/* fenster_pixel(&f, i, j) = i * j * t; */
|
||||
|
||||
/* Munching squares: */
|
||||
fenster_pixel(&f, i, j) = i ^ j ^ t;
|
||||
}
|
||||
}
|
||||
int64_t time = fenster_time();
|
||||
if (time - now < 1000 / 60) {
|
||||
fenster_sleep(time - now);
|
||||
}
|
||||
now = time;
|
||||
}
|
||||
fenster_close(&f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* An example of using raster graphics with Fenster
|
||||
* - Line: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
|
||||
* - Circle: https://en.wikipedia.org/wiki/Midpoint_circle_algorithm
|
||||
* - Rectangle: well, it's obvious
|
||||
* - Flood fill: very inefficient, using recursion
|
||||
* - Text: small 5x3 bitmap font is used to render glyps
|
||||
* ============================================================ */
|
||||
static void fenster_line(struct fenster *f, int x0, int y0, int x1, int y1,
|
||||
uint32_t c) {
|
||||
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
|
||||
int dy = abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
|
||||
int err = (dx > dy ? dx : -dy) / 2, e2;
|
||||
for (;;) {
|
||||
fenster_pixel(f, x0, y0) = c;
|
||||
if (x0 == x1 && y0 == y1) {
|
||||
break;
|
||||
}
|
||||
e2 = err;
|
||||
if (e2 > -dx) {
|
||||
err -= dy;
|
||||
x0 += sx;
|
||||
}
|
||||
if (e2 < dy) {
|
||||
err += dx;
|
||||
y0 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fenster_rect(struct fenster *f, int x, int y, int w, int h,
|
||||
uint32_t c) {
|
||||
for (int row = 0; row < h; row++) {
|
||||
for (int col = 0; col < w; col++) {
|
||||
fenster_pixel(f, x + col, y + row) = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fenster_circle(struct fenster *f, int x, int y, int r, uint32_t c) {
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
if (dx * dx + dy * dy <= r * r) {
|
||||
fenster_pixel(f, x + dx, y + dy) = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fenster_fill(struct fenster *f, int x, int y, uint32_t old,
|
||||
uint32_t c) {
|
||||
if (x < 0 || y < 0 || x >= f->width || y >= f->height) {
|
||||
return;
|
||||
}
|
||||
if (fenster_pixel(f, x, y) == old) {
|
||||
fenster_pixel(f, x, y) = c;
|
||||
fenster_fill(f, x - 1, y, old, c);
|
||||
fenster_fill(f, x + 1, y, old, c);
|
||||
fenster_fill(f, x, y - 1, old, c);
|
||||
fenster_fill(f, x, y + 1, old, c);
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
static uint16_t font5x3[] = {0x0000,0x2092,0x002d,0x5f7d,0x279e,0x52a5,0x7ad6,0x0012,0x4494,0x1491,0x017a,0x05d0,0x1400,0x01c0,0x0400,0x12a4,0x2b6a,0x749a,0x752a,0x38a3,0x4f4a,0x38cf,0x3bce,0x12a7,0x3aae,0x49ae,0x0410,0x1410,0x4454,0x0e38,0x1511,0x10e3,0x73ee,0x5f7a,0x3beb,0x624e,0x3b6b,0x73cf,0x13cf,0x6b4e,0x5bed,0x7497,0x2b27,0x5add,0x7249,0x5b7d,0x5b6b,0x3b6e,0x12eb,0x4f6b,0x5aeb,0x388e,0x2497,0x6b6d,0x256d,0x5f6d,0x5aad,0x24ad,0x72a7,0x6496,0x4889,0x3493,0x002a,0xf000,0x0011,0x6b98,0x3b79,0x7270,0x7b74,0x6750,0x95d6,0xb9ee,0x5b59,0x6410,0xb482,0x56e8,0x6492,0x5be8,0x5b58,0x3b70,0x976a,0xcd6a,0x1370,0x38f0,0x64ba,0x3b68,0x2568,0x5f68,0x54a8,0xb9ad,0x73b8,0x64d6,0x2492,0x3593,0x03e0};
|
||||
// clang-format on
|
||||
static void fenster_text(struct fenster *f, int x, int y, char *s, int scale,
|
||||
uint32_t c) {
|
||||
while (*s) {
|
||||
char chr = *s++;
|
||||
if (chr > 32) {
|
||||
uint16_t bmp = font5x3[chr - 32];
|
||||
for (int dy = 0; dy < 5; dy++) {
|
||||
for (int dx = 0; dx < 3; dx++) {
|
||||
if (bmp >> (dy * 3 + dx) & 1) {
|
||||
fenster_rect(f, x + dx * scale, y + dy * scale, scale, scale, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
x = x + 4 * scale;
|
||||
}
|
||||
}
|
||||
|
||||
static int demo_raster_graphics() {
|
||||
uint32_t buf[W * H];
|
||||
struct fenster f = {
|
||||
.title = "hello",
|
||||
.width = W,
|
||||
.height = H,
|
||||
.buf = buf,
|
||||
};
|
||||
fenster_open(&f);
|
||||
uint32_t t = 0;
|
||||
int64_t now = fenster_time();
|
||||
while (fenster_loop(&f) == 0) {
|
||||
t++;
|
||||
fenster_rect(&f, 0, 0, W, H, 0x00333333);
|
||||
fenster_rect(&f, W / 4, H / 2, W / 2, H / 3, 0x00ff0000);
|
||||
fenster_rect(&f, W / 2, H / 2 + H / 12, W / 6, H / 3 - H / 12, 0x00ffffff);
|
||||
fenster_circle(&f, W / 2 - W / 8, H / 2 + H / 6, W / 20, 0x00ffffff);
|
||||
fenster_line(&f, W / 4 - 25, H / 2, W / 2, H / 4, 0x0000ffff);
|
||||
fenster_line(&f, W - W / 4 + 25, H / 2, W / 2, H / 4, 0x0000ffff);
|
||||
fenster_line(&f, W - W / 4 + 25, H / 2, W / 4 - 25, H / 2, 0x0000ffff);
|
||||
fenster_fill(&f, W / 2, H / 3, 0x00333333, 0x00ff00ff);
|
||||
fenster_text(&f, 10, 10, "House", 8, 0x00ffffff);
|
||||
int64_t time = fenster_time();
|
||||
if (time - now < 1000 / 60) {
|
||||
fenster_sleep(time - now);
|
||||
}
|
||||
now = time;
|
||||
}
|
||||
fenster_close(&f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Another small example demostrating keymaps/keycodes:
|
||||
* - On all platforms keys usually correspond to upper-case ASCII
|
||||
* - Enter code is 10, Tab is 9, Backspace is 8, Escape is 27
|
||||
* - Delete is 127, Space is 32
|
||||
* - Modifiers are: Ctrl=1, Shift=2, Ctrl+Shift=3
|
||||
*
|
||||
* This demo prints currently pressed keys with modifiers.
|
||||
* ============================================================ */
|
||||
static int demo_keys() {
|
||||
uint32_t buf[W * H] = {0};
|
||||
struct fenster f = {
|
||||
.title = "Press any key...",
|
||||
.width = W,
|
||||
.height = H,
|
||||
.buf = buf,
|
||||
};
|
||||
fenster_open(&f);
|
||||
int64_t now = fenster_time();
|
||||
while (fenster_loop(&f) == 0) {
|
||||
int has_keys = 0;
|
||||
char s[32];
|
||||
char *p = s;
|
||||
for (int i = 0; i < 128; i++) {
|
||||
if (f.keys[i]) {
|
||||
has_keys = 1;
|
||||
*p++ = i;
|
||||
}
|
||||
}
|
||||
*p = '\0';
|
||||
fenster_rect(&f, 8, 8, W, 100, 0);
|
||||
fenster_text(&f, 8, 8, s, 4, 0xffffff);
|
||||
if (has_keys) {
|
||||
if (f.mod & 1) {
|
||||
fenster_text(&f, 8, 40, "Ctrl", 4, 0xffffff);
|
||||
}
|
||||
if (f.mod & 2) {
|
||||
fenster_text(&f, 8, 80, "Shift", 4, 0xffffff);
|
||||
}
|
||||
}
|
||||
if (f.keys[27]) {
|
||||
break;
|
||||
}
|
||||
int64_t time = fenster_time();
|
||||
if (time - now < 1000 / 60) {
|
||||
fenster_sleep(time - now);
|
||||
}
|
||||
now = time;
|
||||
}
|
||||
fenster_close(&f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run() {
|
||||
(void) demo_minimal_framebuffer;
|
||||
(void) demo_raster_graphics;
|
||||
(void) demo_keys;
|
||||
|
||||
/*return demo_minimal_framebuffer();*/
|
||||
return demo_raster_graphics();
|
||||
/*return demo_keys();*/
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine,
|
||||
int nCmdShow) {
|
||||
(void)hInstance, (void)hPrevInstance, (void)pCmdLine, (void)nCmdShow;
|
||||
return run();
|
||||
}
|
||||
#else
|
||||
int main() { return run(); }
|
||||
#endif
|
||||
|
302
fenster.h
Normal file
302
fenster.h
Normal file
@ -0,0 +1,302 @@
|
||||
#ifndef FENSTER_H
|
||||
#define FENSTER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <AudioToolbox/AudioQueue.h>
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <objc/NSObjCRuntime.h>
|
||||
#include <objc/objc-runtime.h>
|
||||
#elif defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <X11/XKBlib.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
||||
struct fenster {
|
||||
const char *title;
|
||||
const int width;
|
||||
const int height;
|
||||
uint32_t *buf;
|
||||
int keys[256]; /* keys are mostly ASCII, but arrows are 17..20 */
|
||||
int mod; /* mod is 4 bits mask, ctrl=1, shift=2, alt=4, meta=8 */
|
||||
#if defined(__APPLE__)
|
||||
id wnd;
|
||||
#elif defined(_WIN32)
|
||||
HWND hwnd;
|
||||
#else
|
||||
Display *dpy;
|
||||
Window w;
|
||||
GC gc;
|
||||
XImage *img;
|
||||
#endif
|
||||
};
|
||||
|
||||
#define FENSTER_API static
|
||||
FENSTER_API int fenster_open(struct fenster *f);
|
||||
FENSTER_API int fenster_loop(struct fenster *f);
|
||||
FENSTER_API void fenster_close(struct fenster *f);
|
||||
FENSTER_API void fenster_sleep(int ms);
|
||||
FENSTER_API int64_t fenster_time();
|
||||
#define fenster_pixel(f, x, y) ((f)->buf[((y) * (f)->width) + (x)])
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#define msg(r, o, s) ((r(*)(id, SEL))objc_msgSend)(o, sel_getUid(s))
|
||||
#define msg1(r, o, s, A, a) \
|
||||
((r(*)(id, SEL, A))objc_msgSend)(o, sel_getUid(s), a)
|
||||
#define msg2(r, o, s, A, a, B, b) \
|
||||
((r(*)(id, SEL, A, B))objc_msgSend)(o, sel_getUid(s), a, b)
|
||||
#define msg3(r, o, s, A, a, B, b, C, c) \
|
||||
((r(*)(id, SEL, A, B, C))objc_msgSend)(o, sel_getUid(s), a, b, c)
|
||||
#define msg4(r, o, s, A, a, B, b, C, c, D, d) \
|
||||
((r(*)(id, SEL, A, B, C, D))objc_msgSend)(o, sel_getUid(s), a, b, c, d)
|
||||
|
||||
#define cls(x) ((id)objc_getClass(x))
|
||||
|
||||
extern id const NSDefaultRunLoopMode;
|
||||
extern id const NSApp;
|
||||
|
||||
static void fenster_draw_rect(id v, SEL s, CGRect r) {
|
||||
(void)r, (void)s;
|
||||
struct fenster *f = (struct fenster *)objc_getAssociatedObject(v, "fenster");
|
||||
CGContextRef context =
|
||||
msg(CGContextRef, msg(id, cls("NSGraphicsContext"), "currentContext"),
|
||||
"graphicsPort");
|
||||
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
|
||||
CGDataProviderRef provider = CGDataProviderCreateWithData(
|
||||
NULL, f->buf, f->width * f->height * 4, NULL);
|
||||
CGImageRef img =
|
||||
CGImageCreate(f->width, f->height, 8, 32, f->width * 4, space,
|
||||
kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little,
|
||||
provider, NULL, false, kCGRenderingIntentDefault);
|
||||
CGColorSpaceRelease(space);
|
||||
CGDataProviderRelease(provider);
|
||||
CGContextDrawImage(context, CGRectMake(0, 0, f->width, f->height), img);
|
||||
CGImageRelease(img);
|
||||
}
|
||||
|
||||
static BOOL fenster_should_close(id v, SEL s, id w) {
|
||||
(void)v, (void)s, (void)w;
|
||||
msg1(void, NSApp, "terminate:", id, NSApp);
|
||||
return YES;
|
||||
}
|
||||
|
||||
FENSTER_API int fenster_open(struct fenster *f) {
|
||||
msg(id, cls("NSApplication"), "sharedApplication");
|
||||
msg1(void, NSApp, "setActivationPolicy:", NSInteger, 0);
|
||||
f->wnd = msg4(id, msg(id, cls("NSWindow"), "alloc"),
|
||||
"initWithContentRect:styleMask:backing:defer:", CGRect,
|
||||
CGRectMake(0, 0, f->width, f->height), NSUInteger, 3,
|
||||
NSUInteger, 2, BOOL, NO);
|
||||
Class windelegate =
|
||||
objc_allocateClassPair((Class)cls("NSObject"), "FensterDelegate", 0);
|
||||
class_addMethod(windelegate, sel_getUid("windowShouldClose:"),
|
||||
(IMP)fenster_should_close, "c@:@");
|
||||
objc_registerClassPair(windelegate);
|
||||
msg1(void, f->wnd, "setDelegate:", id,
|
||||
msg(id, msg(id, (id)windelegate, "alloc"), "init"));
|
||||
Class c = objc_allocateClassPair((Class)cls("NSView"), "FensterView", 0);
|
||||
class_addMethod(c, sel_getUid("drawRect:"), (IMP)fenster_draw_rect, "i@:@@");
|
||||
objc_registerClassPair(c);
|
||||
|
||||
id v = msg(id, msg(id, (id)c, "alloc"), "init");
|
||||
msg1(void, f->wnd, "setContentView:", id, v);
|
||||
objc_setAssociatedObject(v, "fenster", (id)f, OBJC_ASSOCIATION_ASSIGN);
|
||||
|
||||
id title = msg1(id, cls("NSString"), "stringWithUTF8String:", const char *,
|
||||
f->title);
|
||||
msg1(void, f->wnd, "setTitle:", id, title);
|
||||
msg1(void, f->wnd, "makeKeyAndOrderFront:", id, nil);
|
||||
msg(void, f->wnd, "center");
|
||||
msg1(void, NSApp, "activateIgnoringOtherApps:", BOOL, YES);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FENSTER_API void fenster_close(struct fenster *f) {
|
||||
msg(void, f->wnd, "close");
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
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};
|
||||
// clang-format on
|
||||
FENSTER_API int fenster_loop(struct fenster *f) {
|
||||
msg1(void, msg(id, f->wnd, "contentView"), "setNeedsDisplay:", BOOL, YES);
|
||||
id ev = msg4(id, NSApp,
|
||||
"nextEventMatchingMask:untilDate:inMode:dequeue:", NSUInteger,
|
||||
NSUIntegerMax, id, NULL, id, NSDefaultRunLoopMode, BOOL, YES);
|
||||
if (!ev) {
|
||||
return 0;
|
||||
}
|
||||
NSUInteger evtype = msg(NSUInteger, ev, "type");
|
||||
switch (evtype) {
|
||||
case 10: /*NSEventTypeKeyDown*/
|
||||
case 11: /*NSEventTypeKeyUp:*/ {
|
||||
NSUInteger k = msg(NSUInteger, ev, "keyCode");
|
||||
f->keys[k < 127 ? FENSTER_KEYCODES[k] : 0] = evtype == 10;
|
||||
NSUInteger mod = msg(NSUInteger, ev, "modifierFlags") >> 17;
|
||||
f->mod = (mod & 0xc) | ((mod & 1) << 1) | ((mod >> 1) & 1);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
msg1(void, NSApp, "sendEvent:", id, ev);
|
||||
return 0;
|
||||
}
|
||||
#elif defined(_WIN32)
|
||||
// clang-format off
|
||||
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};
|
||||
// clang-format on
|
||||
static LRESULT CALLBACK fenster_wndproc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
struct fenster *f = (struct fenster *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
||||
switch (msg) {
|
||||
case WM_PAINT: {
|
||||
PAINTSTRUCT ps;
|
||||
HDC hdc = BeginPaint(hwnd, &ps);
|
||||
HDC memdc = CreateCompatibleDC(hdc);
|
||||
HBITMAP hbmp = CreateCompatibleBitmap(hdc, f->width, f->height);
|
||||
HBITMAP oldbmp = SelectObject(memdc, hbmp);
|
||||
BITMAPINFO bi = {{sizeof(bi), f->width, -f->height, 1, 32, BI_BITFIELDS}};
|
||||
bi.bmiColors[0].rgbRed = 0xff;
|
||||
bi.bmiColors[1].rgbGreen = 0xff;
|
||||
bi.bmiColors[2].rgbBlue = 0xff;
|
||||
SetDIBitsToDevice(memdc, 0, 0, f->width, f->height, 0, 0, 0, f->height,
|
||||
f->buf, (BITMAPINFO *)&bi, DIB_RGB_COLORS);
|
||||
BitBlt(hdc, 0, 0, f->width, f->height, memdc, 0, 0, SRCCOPY);
|
||||
SelectObject(memdc, oldbmp);
|
||||
DeleteObject(hbmp);
|
||||
DeleteDC(memdc);
|
||||
EndPaint(hwnd, &ps);
|
||||
} break;
|
||||
case WM_CLOSE:
|
||||
DestroyWindow(hwnd);
|
||||
break;
|
||||
case WM_KEYDOWN:
|
||||
case WM_KEYUP: {
|
||||
f->mod = ((GetKeyState(VK_CONTROL) & 0x8000) >> 15) |
|
||||
((GetKeyState(VK_SHIFT) & 0x8000) >> 14) |
|
||||
((GetKeyState(VK_MENU) & 0x8000) >> 13) |
|
||||
(((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & 0x8000) >> 12);
|
||||
f->keys[FENSTER_KEYCODES[HIWORD(lParam) & 0x1ff]] = !((lParam >> 31) & 1);
|
||||
} break;
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
break;
|
||||
/* TODO: process other events */
|
||||
default:
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
FENSTER_API int fenster_open(struct fenster *f) {
|
||||
HINSTANCE hInstance = GetModuleHandle(NULL);
|
||||
WNDCLASSEX wc = {0};
|
||||
wc.cbSize = sizeof(WNDCLASSEX);
|
||||
wc.style = CS_VREDRAW | CS_HREDRAW;
|
||||
wc.lpfnWndProc = fenster_wndproc;
|
||||
wc.hInstance = hInstance;
|
||||
wc.lpszClassName = f->title;
|
||||
RegisterClassEx(&wc);
|
||||
f->hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, f->title, f->title,
|
||||
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
f->width, f->height, NULL, NULL, hInstance, NULL);
|
||||
|
||||
if (f->hwnd == NULL) {
|
||||
return -1;
|
||||
}
|
||||
SetWindowLongPtr(f->hwnd, GWLP_USERDATA, (LONG_PTR)f);
|
||||
ShowWindow(f->hwnd, SW_NORMAL);
|
||||
UpdateWindow(f->hwnd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FENSTER_API void fenster_close(struct fenster *f) { (void)f; }
|
||||
|
||||
FENSTER_API int fenster_loop(struct fenster *f) {
|
||||
MSG msg;
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
if (msg.message == WM_QUIT) {
|
||||
return -1;
|
||||
}
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
InvalidateRect(f->hwnd, NULL, TRUE);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
// clang-format off
|
||||
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};
|
||||
// clang-format on
|
||||
FENSTER_API int fenster_open(struct fenster *f) {
|
||||
f->dpy = XOpenDisplay(NULL);
|
||||
int screen = DefaultScreen(f->dpy);
|
||||
f->w = XCreateSimpleWindow(f->dpy, RootWindow(f->dpy, screen), 0, 0, f->width,
|
||||
f->height, 0, BlackPixel(f->dpy, screen),
|
||||
WhitePixel(f->dpy, screen));
|
||||
f->gc = XCreateGC(f->dpy, f->w, 0, 0);
|
||||
XSelectInput(f->dpy, f->w, ExposureMask | KeyPressMask | KeyReleaseMask);
|
||||
XStoreName(f->dpy, f->w, f->title);
|
||||
XMapWindow(f->dpy, f->w);
|
||||
XSync(f->dpy, f->w);
|
||||
f->img = XCreateImage(f->dpy, DefaultVisual(f->dpy, 0), 24, ZPixmap, 0,
|
||||
(char *)f->buf, f->width, f->height, 32, 0);
|
||||
return 0;
|
||||
}
|
||||
FENSTER_API void fenster_close(struct fenster *f) { XCloseDisplay(f->dpy); }
|
||||
FENSTER_API int fenster_loop(struct fenster *f) {
|
||||
XEvent ev;
|
||||
XPutImage(f->dpy, f->w, f->gc, f->img, 0, 0, 0, 0, f->width, f->height);
|
||||
XFlush(f->dpy);
|
||||
while (XPending(f->dpy)) {
|
||||
XNextEvent(f->dpy, &ev);
|
||||
switch (ev.type) {
|
||||
case KeyPress:
|
||||
case KeyRelease: {
|
||||
int m = ev.xkey.state;
|
||||
int k = XkbKeycodeToKeysym(f->dpy, ev.xkey.keycode, 0, 0);
|
||||
for (unsigned int i = 0; i < 124; i += 2) {
|
||||
if (FENSTER_KEYCODES[i] == k) {
|
||||
f->keys[FENSTER_KEYCODES[i + 1]] = (ev.type == KeyPress);
|
||||
break;
|
||||
}
|
||||
}
|
||||
f->mod = !!(m & ControlMask) | (!!(m & ShiftMask) << 1) |
|
||||
(!!(m & Mod1Mask) << 2) | (!!(m & Mod4Mask) << 3);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
FENSTER_API void fenster_sleep(int ms) { Sleep(ms); }
|
||||
FENSTER_API int64_t fenster_time() {
|
||||
LARGE_INTEGER freq, count;
|
||||
QueryPerformanceFrequency(&freq);
|
||||
QueryPerformanceCounter(&count);
|
||||
return (int64_t)(count.QuadPart * 1000.0 / freq.QuadPart);
|
||||
}
|
||||
#else
|
||||
FENSTER_API void fenster_sleep(int ms) {
|
||||
struct timespec ts;
|
||||
ts.tv_sec = ms / 1000;
|
||||
ts.tv_nsec = (ms % 1000) * 1000000;
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
FENSTER_API int64_t fenster_time() {
|
||||
struct timespec time;
|
||||
clock_gettime(CLOCK_REALTIME, &time);
|
||||
return time.tv_sec * 1000 + (time.tv_nsec / 1000000);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* FENSTER_H */
|
Reference in New Issue
Block a user