#if 0 /* #/ ================================================================ #/ #/ ui.c #/ #/ This is a reduced system layer. #/ It allows you to create a window, draw graphics in it, handle #/ input events. #/ #/ ---------------------------------------------------------------- #/ #/ DESIGN PRINCIPLES #/ #/ - Minimalistic feature set. For graphics, you have access to the #/ pixel buffer, and that's it. #/ #/ - No implicit control flow. No callbacks. You write your own #/ main and call everything explicitly. But the number of things #/ you have to call to do something is as little as possible. #/ #/ - Optimized to use in a single source file. #/ Installation process? Ctrl+C, Ctrl+V, done. #/ #/ If you have an idea how to reduce the feature set further, #/ let me know! #/ #/ ---------------------------------------------------------------- #/ #/ (C) 2024 Mitya Selivanov , MIT License #/ #/ ================================================================ #/ #/ Self-compilation shell script #/ SRC=${0##*./} BIN=${SRC%.*} gcc \ -Wall -Wextra -Werror -pedantic \ -Wno-old-style-declaration \ -Wno-missing-braces \ -Wno-unused-variable \ -Wno-unused-but-set-variable \ -Wno-unused-parameter \ -Wno-overlength-strings \ -O3 \ -fsanitize=undefined,address,leak -mshstk \ -lX11 -lm \ -o $BIN $SRC && \ ./$BIN $@ && rm $BIN exit $? # */ #endif // ================================================================ // // Basic declarations // // ================================================================ #define _GNU_SOURCE typedef signed char i8; typedef signed short i16; typedef signed i32; typedef signed long long i64; typedef unsigned char u8; typedef unsigned short u16; typedef unsigned u32; typedef unsigned long long u64; typedef char c8; typedef signed char b8; typedef float f32; typedef double f64; // ================================================================ // // PLATFORM API // // ================================================================ enum { MAX_NUM_PIXELS = 4 * 1024 * 1024, MAX_CLIPBOARD_SIZE = 0, MAX_NUM_AUDIO_SAMPLES = 0, MAX_NUM_SOCKETS = 0, AUDIO_NUM_CHANNELS = 2, AUDIO_SAMPLE_RATE = 44100, IPv4 = 1, IPv6 = 2, KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_LCTRL, KEY_RCTRL, KEY_LSHIFT, KEY_RSHIFT, KEY_LALT, KEY_RALT, KEY_ESCAPE, KEY_PRINTSCREEN, KEY_DELETE, KEY_PAUSE, KEY_INSERT, KEY_HOME, KEY_END, KEY_PAGEUP, KEY_PAGEDOWN, KEY_KP_ = 128, KEY_F_ = 256, BUTTON_LEFT = KEY_F_ + 64, BUTTON_MIDDLE, BUTTON_RIGHT, }; typedef struct { c8 *title; i32 frame_width; i32 frame_height; u32 *pixels; i64 clipboard_size; c8 *clipboard; // TODO b8 done; b8 has_focus; b8 has_cursor; i32 cursor_x; i32 cursor_y; i32 cursor_dx; i32 cursor_dy; i64 wheel_dy; b8 key_down[512]; b8 key_pressed[512]; } Platform; typedef struct { u16 type; u16 port; union { u8 v4_address[4]; u8 v6_address[16]; }; } IP_Address; typedef i64 (*Thread_Proc)(void *user_data); // Window void p_init(void); void p_cleanup(void); i32 p_handle_events(void); i32 p_wait_events(void); void p_render_frame(void); // Sound void p_handle_audio(i64 time_elapsed); void p_queue_sound(i64 delay, i64 num_samples, f32 *samples); // UDP sockets i64 p_recv(u16 slot, IP_Address address, i64 size, u8 *data, IP_Address *remote_address); i64 p_send(u16 slot, IP_Address address, i64 size, u8 *data); Platform platform = {0}; // ================================================================ // // UI // // ================================================================ #include #include #include #include #define CHAR_NUM_BITS_X 6 #define CHAR_NUM_BITS_Y 7 u64 bitfont[] = { 0x1041040000000000, 0x14500100, 0x38538429f28a7ca0, 0x621421f085084394, 0x10472450c614, 0x420410410420000, 0x233f312042082082, 0x411f10400001, 0x1f0000010800000, 0x4100040000000000, 0xc51451c004108208, 0x1c00070820830001, 0x71021070001c1084, 0x470001041c514000, 0x1c51c11800031070, 0x51c0002082107000, 0x31071470001c51c, 0x800000400010000, 0x102042100042000, 0x20400003c03c000, 0x20021050800421, 0xf24918e05955944e, 0x801c924724700924, 0x2492470062410491, 0xf00f0411c13c01c9, 0x4d04918004104704, 0x1c024924f2490062, 0x5104107007082082, 0x410092450c524008, 0x4557db4401c10410, 0x91804516554d1011, 0x411c924700624924, 0x2472062492491800, 0x62481811802491c9, 0x4924010410411f00, 0x10a4514510062492, 0x429100a7d5551440, 0x108429144045128, 0x410c03c108420f0, 0x41020820410c1041, 0x112840c20820820c, 0x3c00000000000000, 0xf206000000000081, 0x1c924704000624, 0x24e2000062412460, 0x604f246000389, 0x892460000821c218, 0x2491c10401883, 0x20c0080007083002, 0x4100514314500108, 0x55554f0000810410, 0x60002492491c0015, 0x472491c000624924, 0x14000838924e0000, 0x6206046000041043, 0x490000820821c000, 0x10a45144000e2492, 0xa44000a7d5451000, 0x108429100044a10, 0x821003c11883c00, 0x1041041041102081, 0x66604208408204, 0x0, 0x1, }; void print_text(u32 color, i32 x, i32 y, f64 width, f64 height, i64 num_chars, c8 *text) { assert(text != NULL); f64 dx = x; f64 dy = y; i64 w = (i64) floor(width + .5); for (i64 n = 0; n < num_chars; ++n) { if (text[n] == '\r') { dx = x; continue; } if (text[n] == '\n') { dx = x; dy += (height * (CHAR_NUM_BITS_Y + 1)) / CHAR_NUM_BITS_Y; continue; } if (text[n] <= ' ') { dx += width; continue; } i64 x0 = (i64) floor(dx + .5); i64 y0 = (i64) floor(dy + .5); i64 y1 = (i64) floor(dy + height + .5); i64 num_cols = 0; for (i64 i = x0; i < x0 + w; ++i, ++num_cols) { b8 empty_col = 1; i64 char_x = ((i - x0) * (CHAR_NUM_BITS_X + 1)) / w; if (char_x >= CHAR_NUM_BITS_X) break; for (i64 j = y0; j < y1; ++j) { i64 char_y = ((j - y0) * CHAR_NUM_BITS_Y) / (y1 - y0); i64 bit_index = (text[n] - 32) * CHAR_NUM_BITS_X * CHAR_NUM_BITS_Y + char_y * CHAR_NUM_BITS_X + char_x; i64 qword_index = bit_index / 64; if (qword_index >= (i64) (sizeof bitfont / sizeof *bitfont)) continue; u64 mask = 1ull << (bit_index % 64); if (!!(bitfont[qword_index] & mask)) { empty_col = 0; if (i < 0 || i >= platform.frame_width || j < 0 || j >= platform.frame_height) continue; platform.pixels[j * platform.frame_width + i] = color; } } if (empty_col && char_x + 1 < CHAR_NUM_BITS_X) for (i64 j = y; j < y1; ++j) { i64 char_y = ((j - y) * CHAR_NUM_BITS_Y) / (y1 - y); i64 bit_index = (text[n] - 32) * CHAR_NUM_BITS_X * CHAR_NUM_BITS_Y + char_y * CHAR_NUM_BITS_X + char_x + 1; i64 qword_index = bit_index / 64; if (qword_index >= (i64) (sizeof bitfont / sizeof *bitfont)) continue; u64 mask = 1ull << (bit_index % 64); if (!!(bitfont[qword_index] & mask)) empty_col = 0; } if (empty_col) break; } dx += num_cols + width / CHAR_NUM_BITS_X; } } i32 main(i32 argc, c8 **argv) { (void) argc; (void) argv; platform = (Platform) { .title = "UI", .frame_width = 960, .frame_height = 720, }; p_init(); while (!platform.done) { p_wait_events(); for (i32 j = 0; j < platform.frame_height; ++j) for (i32 i = 0; i < platform.frame_width; ++i) platform.pixels[j * platform.frame_width + i] = 0x302000; i64 color = 0xff7f7f; if (platform.cursor_x >= 80 && platform.cursor_x < 800 && platform.cursor_y >= 80 && platform.cursor_y < 180) color = 0xffffff; print_text(color, 80, 80, 60., 100., 14, "Hello, Sailor!"); c8 chars[] = "\"#$%&'()*+,-./\n0123456789:;<=>?@\nABCDEFGHIJKLMNOPQRSTUVWXYZ\n[\\]^_`\nabcdefghijklmnopqrstuvwxyz\n{|}~"; print_text(0x7fffff, 80, 200, 60., 100., sizeof chars - 1, chars); p_render_frame(); } p_cleanup(); return 0; } // ================================================================ // // PLATFORM IMPLEMENTATION // // ---------------------------------------------------------------- // // TODO // - Clipboard // - Sound // - Sockets // // X11 clipboard // https://handmade.network/forums/articles/t/8544-implementing_copy_paste_in_x11 // // ALSA // https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_min_8c-example.html // // ================================================================ #include #include // ================================================================ // // UDP sockets // // ================================================================ i64 p_recv(u16 slot, IP_Address address, i64 size, u8 *data, IP_Address *remote_address) { (void) slot; (void) address; (void) size; (void) data; (void) remote_address; assert(0); return 0; } i64 p_send(u16 slot, IP_Address address, i64 size, u8 *data) { (void) slot; (void) address; (void) size; (void) data; assert(0); return 0; } // ================================================================ // // X11 // // ================================================================ #ifdef __linux__ #include #include #include #include static i16 _key_table[512]; static u32 _buffer[MAX_NUM_PIXELS]; static XImage _image; static Display *_display; static GC _gc; static Window _window; static Atom _wm_delete_window; void p_init(void) { _display = XOpenDisplay(NULL); assert(_display != NULL); _key_table[XKeysymToKeycode(_display, XK_Left)] = KEY_LEFT; _key_table[XKeysymToKeycode(_display, XK_Right)] = KEY_RIGHT; _key_table[XKeysymToKeycode(_display, XK_Up)] = KEY_UP; _key_table[XKeysymToKeycode(_display, XK_Down)] = KEY_DOWN; _key_table[XKeysymToKeycode(_display, XK_1)] = '1'; _key_table[XKeysymToKeycode(_display, XK_2)] = '2'; _key_table[XKeysymToKeycode(_display, XK_3)] = '3'; _key_table[XKeysymToKeycode(_display, XK_4)] = '4'; _key_table[XKeysymToKeycode(_display, XK_5)] = '5'; _key_table[XKeysymToKeycode(_display, XK_6)] = '6'; _key_table[XKeysymToKeycode(_display, XK_7)] = '7'; _key_table[XKeysymToKeycode(_display, XK_8)] = '8'; _key_table[XKeysymToKeycode(_display, XK_9)] = '9'; _key_table[XKeysymToKeycode(_display, XK_0)] = '0'; _key_table[XKeysymToKeycode(_display, XK_A)] = 'a'; _key_table[XKeysymToKeycode(_display, XK_B)] = 'b'; _key_table[XKeysymToKeycode(_display, XK_C)] = 'c'; _key_table[XKeysymToKeycode(_display, XK_D)] = 'd'; _key_table[XKeysymToKeycode(_display, XK_E)] = 'e'; _key_table[XKeysymToKeycode(_display, XK_F)] = 'f'; _key_table[XKeysymToKeycode(_display, XK_G)] = 'g'; _key_table[XKeysymToKeycode(_display, XK_H)] = 'h'; _key_table[XKeysymToKeycode(_display, XK_I)] = 'i'; _key_table[XKeysymToKeycode(_display, XK_J)] = 'j'; _key_table[XKeysymToKeycode(_display, XK_K)] = 'k'; _key_table[XKeysymToKeycode(_display, XK_L)] = 'l'; _key_table[XKeysymToKeycode(_display, XK_M)] = 'm'; _key_table[XKeysymToKeycode(_display, XK_N)] = 'n'; _key_table[XKeysymToKeycode(_display, XK_O)] = 'o'; _key_table[XKeysymToKeycode(_display, XK_P)] = 'p'; _key_table[XKeysymToKeycode(_display, XK_Q)] = 'q'; _key_table[XKeysymToKeycode(_display, XK_R)] = 'r'; _key_table[XKeysymToKeycode(_display, XK_S)] = 's'; _key_table[XKeysymToKeycode(_display, XK_T)] = 't'; _key_table[XKeysymToKeycode(_display, XK_U)] = 'u'; _key_table[XKeysymToKeycode(_display, XK_V)] = 'v'; _key_table[XKeysymToKeycode(_display, XK_W)] = 'w'; _key_table[XKeysymToKeycode(_display, XK_X)] = 'x'; _key_table[XKeysymToKeycode(_display, XK_Y)] = 'y'; _key_table[XKeysymToKeycode(_display, XK_Z)] = 'z'; _key_table[XKeysymToKeycode(_display, XK_space)] = ' '; _key_table[XKeysymToKeycode(_display, XK_braceleft)] = '['; _key_table[XKeysymToKeycode(_display, XK_braceright)] = ']'; _key_table[XKeysymToKeycode(_display, XK_colon)] = ';'; _key_table[XKeysymToKeycode(_display, XK_quotedbl)] = '\''; _key_table[XKeysymToKeycode(_display, XK_asciitilde)] = '`'; _key_table[XKeysymToKeycode(_display, XK_backslash)] = '\\'; _key_table[XKeysymToKeycode(_display, XK_comma)] = ','; _key_table[XKeysymToKeycode(_display, XK_greater)] = '.'; _key_table[XKeysymToKeycode(_display, XK_question)] = '/'; _key_table[XKeysymToKeycode(_display, XK_F1)] = KEY_F_ + 1; _key_table[XKeysymToKeycode(_display, XK_F2)] = KEY_F_ + 2; _key_table[XKeysymToKeycode(_display, XK_F3)] = KEY_F_ + 3; _key_table[XKeysymToKeycode(_display, XK_F4)] = KEY_F_ + 4; _key_table[XKeysymToKeycode(_display, XK_F5)] = KEY_F_ + 5; _key_table[XKeysymToKeycode(_display, XK_F6)] = KEY_F_ + 6; _key_table[XKeysymToKeycode(_display, XK_F7)] = KEY_F_ + 7; _key_table[XKeysymToKeycode(_display, XK_F8)] = KEY_F_ + 8; _key_table[XKeysymToKeycode(_display, XK_F9)] = KEY_F_ + 9; _key_table[XKeysymToKeycode(_display, XK_F10)] = KEY_F_ + 10; _key_table[XKeysymToKeycode(_display, XK_F11)] = KEY_F_ + 11; _key_table[XKeysymToKeycode(_display, XK_F12)] = KEY_F_ + 12; _key_table[XKeysymToKeycode(_display, XK_Control_L)] = KEY_LCTRL; _key_table[XKeysymToKeycode(_display, XK_Control_R)] = KEY_RCTRL; _key_table[XKeysymToKeycode(_display, XK_Shift_L)] = KEY_LSHIFT; _key_table[XKeysymToKeycode(_display, XK_Shift_R)] = KEY_RSHIFT; _key_table[XKeysymToKeycode(_display, XK_Alt_L)] = KEY_LALT; _key_table[XKeysymToKeycode(_display, XK_Alt_R)] = KEY_RALT; _key_table[XKeysymToKeycode(_display, XK_Escape)] = KEY_ESCAPE; _key_table[XKeysymToKeycode(_display, XK_BackSpace)] = '\b'; _key_table[XKeysymToKeycode(_display, XK_Tab)] = '\t'; _key_table[XKeysymToKeycode(_display, XK_Return)] = '\n'; _key_table[XKeysymToKeycode(_display, XK_Print)] = KEY_PRINTSCREEN; _key_table[XKeysymToKeycode(_display, XK_Delete)] = KEY_DELETE; _key_table[XKeysymToKeycode(_display, XK_Pause)] = KEY_PAUSE; _key_table[XKeysymToKeycode(_display, XK_Insert)] = KEY_INSERT; _key_table[XKeysymToKeycode(_display, XK_Home)] = KEY_HOME; _key_table[XKeysymToKeycode(_display, XK_End)] = KEY_END; _key_table[XKeysymToKeycode(_display, XK_Page_Up)] = KEY_PAGEUP; _key_table[XKeysymToKeycode(_display, XK_Page_Down)] = KEY_PAGEDOWN; _key_table[XKeysymToKeycode(_display, XK_KP_0)] = KEY_KP_ + '0'; _key_table[XKeysymToKeycode(_display, XK_KP_1)] = KEY_KP_ + '1'; _key_table[XKeysymToKeycode(_display, XK_KP_2)] = KEY_KP_ + '2'; _key_table[XKeysymToKeycode(_display, XK_KP_3)] = KEY_KP_ + '3'; _key_table[XKeysymToKeycode(_display, XK_KP_4)] = KEY_KP_ + '4'; _key_table[XKeysymToKeycode(_display, XK_KP_5)] = KEY_KP_ + '5'; _key_table[XKeysymToKeycode(_display, XK_KP_6)] = KEY_KP_ + '6'; _key_table[XKeysymToKeycode(_display, XK_KP_7)] = KEY_KP_ + '7'; _key_table[XKeysymToKeycode(_display, XK_KP_8)] = KEY_KP_ + '8'; _key_table[XKeysymToKeycode(_display, XK_KP_9)] = KEY_KP_ + '9'; _key_table[XKeysymToKeycode(_display, XK_KP_Enter)] = KEY_KP_ + '\n'; _key_table[XKeysymToKeycode(_display, XK_KP_Divide)] = KEY_KP_ + '/'; _key_table[XKeysymToKeycode(_display, XK_KP_Multiply)] = KEY_KP_ + '*'; _key_table[XKeysymToKeycode(_display, XK_KP_Add)] = KEY_KP_ + '+'; _key_table[XKeysymToKeycode(_display, XK_KP_Subtract)] = KEY_KP_ + '-'; _key_table[XKeysymToKeycode(_display, XK_KP_Decimal)] = KEY_KP_ + '.'; _key_table[XKeysymToKeycode(_display, XK_KP_Separator)] = KEY_KP_ + ','; i32 screen = DefaultScreen(_display); i32 depth = DefaultDepth (_display, screen); Visual *visual = DefaultVisual(_display, screen); _gc = DefaultGC(_display, screen); XSetGraphicsExposures(_display, _gc, False); i32 _display_width = DisplayWidth (_display, screen); i32 _display_height = DisplayHeight(_display, screen); i32 x = (_display_width - platform.frame_width) / 2; i32 y = (_display_height - platform.frame_height) / 2; _window = XCreateWindow(_display, XDefaultRootWindow(_display), x, y, platform.frame_width, platform.frame_height, 0, depth, InputOutput, visual, CWEventMask, &(XSetWindowAttributes) { .event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | VisibilityChangeMask | FocusChangeMask | StructureNotifyMask | SubstructureNotifyMask, }); platform.pixels = _buffer; _image = (XImage) { .width = platform.frame_width, .height = platform.frame_height, .depth = depth, .xoffset = 0, .format = ZPixmap, .data = (c8 *) _buffer, .byte_order = LSBFirst, .bitmap_unit = 32, .bitmap_bit_order = LSBFirst, .bitmap_pad = 32, .bits_per_pixel = 32, .bytes_per_line = 4 * platform.frame_width, .red_mask = 0xff0000, .green_mask = 0x00ff00, .blue_mask = 0x0000ff, }; XInitImage(&_image); _wm_delete_window = XInternAtom(_display, "WM_DELETE_WINDOW", False); XSetWMProtocols(_display, _window, &_wm_delete_window, 1); XStoreName(_display, _window, platform.title); XMapWindow(_display, _window); } void p_cleanup(void) { XDestroyWindow(_display, _window); XCloseDisplay (_display); } i32 p_handle_events(void) { i32 num_events = XEventsQueued(_display, QueuedAlready); memset(platform.key_pressed, 0, sizeof platform.key_pressed); platform.cursor_dx = 0; platform.cursor_dy = 0; platform.wheel_dy = 0; XEvent ev; for (i32 i = 0; i < num_events; ++i) { XNextEvent(_display, &ev); switch (ev.type) { case DestroyNotify: platform.done = 1; break; case MotionNotify: platform.cursor_dx += ev.xmotion.x - platform.cursor_x; platform.cursor_dy += ev.xmotion.y - platform.cursor_y; platform.cursor_x = ev.xmotion.x; platform.cursor_y = ev.xmotion.y; break; case ButtonPress: platform.cursor_x = ev.xbutton.x; platform.cursor_y = ev.xbutton.y; switch (ev.xbutton.button) { case Button1: platform.key_down[BUTTON_LEFT] = 1; ++platform.key_pressed[BUTTON_LEFT]; break; case Button2: platform.key_down[BUTTON_MIDDLE] = 1; ++platform.key_pressed[BUTTON_MIDDLE]; break; case Button3: platform.key_down[BUTTON_RIGHT] = 1; ++platform.key_pressed[BUTTON_RIGHT]; break; case Button4: ++platform.wheel_dy; break; case Button5: --platform.wheel_dy; break; default:; } break; case ButtonRelease: platform.cursor_x = ev.xbutton.x; platform.cursor_y = ev.xbutton.y; switch (ev.xbutton.button) { case Button1: platform.key_down[BUTTON_LEFT] = 0; break; case Button2: platform.key_down[BUTTON_MIDDLE] = 0; break; case Button3: platform.key_down[BUTTON_RIGHT] = 0; break; default:; } break; case KeyPress: platform.cursor_x = ev.xkey.x; platform.cursor_y = ev.xkey.y; platform.key_down [_key_table[ev.xkey.keycode]] = 1; platform.key_pressed[_key_table[ev.xkey.keycode]]++; break; case KeyRelease: platform.cursor_x = ev.xkey.x; platform.cursor_y = ev.xkey.y; platform.key_down [_key_table[ev.xkey.keycode]] = 0; platform.key_pressed[_key_table[ev.xkey.keycode]]--; break; case EnterNotify: platform.has_cursor = 1; break; case LeaveNotify: platform.has_cursor = 0; break; case FocusIn: platform.has_focus = 1; break; case FocusOut: platform.has_focus = 0; break; case ClientMessage: if ((Atom) ev.xclient.data.l[0] == _wm_delete_window) platform.done = 1; break; default:; } } XWindowAttributes attrs; XGetWindowAttributes(_display, _window, &attrs); if ((platform.frame_width != attrs.width || platform.frame_height != attrs.height) && attrs.width * attrs.height * 4 <= (i32) sizeof _buffer) { if (attrs.width > 0 && attrs.height > 0) { _image.width = attrs.width; _image.height = attrs.height; _image.bytes_per_line = 4 * attrs.width; } platform.frame_width = attrs.width; platform.frame_height = attrs.height; } return num_events; } i32 p_wait_events(void) { i32 num_events = 0; do { num_events = p_handle_events(); sched_yield(); } while (num_events == 0); return num_events; } void p_render_frame(void) { if (platform.done) return; XPutImage(_display, _window, _gc, &_image, 0, 0, 0, 0, platform.frame_width, platform.frame_height); XFlush(_display); } #endif // ================================================================ // // ALSA // // ================================================================ #ifdef __linux__ void p_handle_audio(i64 time_elapsed) { (void) time_elapsed; assert(0); } void p_queue_sound(i64 delay, i64 num_samples, f32 *samples) { (void) delay; (void) num_samples; (void) samples; assert(0); } #endif