From 3f75902aa0d222a59f765055ba803c48ec0e1563 Mon Sep 17 00:00:00 2001 From: Timerix Date: Fri, 9 Jan 2026 05:04:35 +0500 Subject: [PATCH] moved code from header to source files --- example/ask.c | 20 +- example/hello.c | 10 +- example/snek.c | 56 +-- include/tim.h | 1229 +++++++---------------------------------------- makefile | 59 ++- readme.md | 6 +- src/drawing.c | 85 ++++ src/edit.c | 104 ++++ src/event.c | 19 + src/loop.c | 51 ++ src/render.c | 114 +++++ src/scope.c | 50 ++ src/string.c | 108 +++++ src/unix.c | 165 +++++++ src/widgets.c | 71 +++ src/windows.c | 170 +++++++ test/color.c | 16 +- test/string.c | 104 ++-- test/test.c | 52 +- test/width.c | 22 +- 20 files changed, 1304 insertions(+), 1207 deletions(-) create mode 100755 src/drawing.c create mode 100755 src/edit.c create mode 100755 src/event.c create mode 100755 src/loop.c create mode 100755 src/render.c create mode 100755 src/scope.c create mode 100755 src/string.c create mode 100755 src/unix.c create mode 100755 src/widgets.c create mode 100755 src/windows.c diff --git a/example/ask.c b/example/ask.c index 39a4977..371776a 100644 --- a/example/ask.c +++ b/example/ask.c @@ -9,40 +9,40 @@ #define CYES 0xa000f // yes green, black, white #define CNO 0x9000f // no red, black white -int main(int argc, char** argv) { +i32 main(i32 argc, char** argv) { if (argc < 2 || strcmp(argv[1], "-h") == 0) { printf("syntax: %s message\n", argv[0]); exit(1); } // get text properties - TimText_t msg = scan_str(argv[1]); + TimText msg = tim_scan_str(argv[1]); while (tim_run(0)) { // calculate size of message box - int w = MAX(msg.width + 4, 24); - int h = MAX(msg.lines + 6, 7); + i32 w = MAX(msg.width + 4, 28); + i32 h = MAX(msg.lines + 6, 7); - scope (A, A, w, h) { + tim_scope(A, A, w, h) { // draw frame around entire scope - frame(0, 0, ~0, ~0, CFR); + tim_frame(0, 0, ~0, ~0, CFR); // draw message label(argv[1], A, 1, msg.width, msg.lines, CTXT); // draw 'yes' button, return 0 when clicked - if (button("Yes", 2, ~1, A, A, CYES)) { + if (button("[y] Yes", 2, ~1, A, A, CYES) || tim_is_key_press('y')) { exit(0); } // draw 'no' button, return 1 when clicked - if (button("No ", ~2, ~1, A, A, CNO)) { + if (button("[n] No", ~2, ~1, A, A, CNO) || tim_is_key_press('n')) { exit(1); } // return with 1 when q or esc is pressed - if (is_key_press('q') || is_key_press(TimKey_Escape)) { - exit(1); + if (tim_is_key_press('q') || tim_is_key_press(TimKey_Escape)) { + exit(2); } } } diff --git a/example/hello.c b/example/hello.c index 2edd963..6e39c11 100644 --- a/example/hello.c +++ b/example/hello.c @@ -1,14 +1,14 @@ #include "tim.h" -int main(void) { +i32 main(void) { while (tim_run(0)) { // event loop - scope (A, A, 24, 8) { // centered 24x8 scope - uint64_t c = 0x0a060f; // three colors - frame(0, 0, ~0, ~0, c); // draw frame for scope + tim_scope(A, A, 24, 8) { // centered 24x8 scope + u64 c = 0x0a060f; // three colors + tim_frame(0, 0, ~0, ~0, c); // draw frame for scope label("Greetings!", A, 2, A, A, c); // label in top center if (button("OK", A, ~1, 8, A, c)) // button in bottom center return 0; // exit on button click - if (is_key_press('q')) // ctrl-c is masked + if (tim_is_key_press('q')) // ctrl-c is masked return 0; // exit on 'q' press } } //TODO: remove atexit cleanup diff --git a/example/snek.c b/example/snek.c index ba16035..f059ddb 100644 --- a/example/snek.c +++ b/example/snek.c @@ -13,16 +13,16 @@ typedef union { struct { - int32_t x; - int32_t y; + i32 x; + i32 y; }; - int64_t xy; + i64 xy; } point; static struct { - int state; // game state (NEW RUN PAUSE OVER) - int64_t tick; // updates every 10 ms - int len; // snake length + i32 state; // game state (NEW RUN PAUSE OVER) + i64 tick; // updates every 10 ms + i32 len; // snake length point body[200]; // snake body point food; // food position point look; // active direction @@ -38,7 +38,7 @@ static void start(void) { static void game(void) { // update game state about every 10 ms - int64_t tick = time_us() / 100000; + i64 tick = tim_time_usec() / 100000; if (snek.tick != tick) { snek.tick = tick; // move one unit @@ -47,7 +47,7 @@ static void game(void) { snek.body[0].y = snek.body[1].y + snek.look.y; // self crash bool crash = false; - for (int i = 1; i < snek.len; i++) { + for (i32 i = 1; i < snek.len; i++) { crash |= snek.body[0].xy == snek.body[i].xy; } // border crash @@ -56,7 +56,7 @@ static void game(void) { snek.state = crash ? OVER : snek.state; // food if (snek.food.xy == snek.body[0].xy) { - snek.len = MIN(snek.len + 2, (int)ARRAY_SIZE(snek.body)); + snek.len = MIN(snek.len + 2, (i32)ARRAY_SIZE(snek.body)); snek.food.x = rand() % (tim.w / 2 - 2) + 1; snek.food.y = rand() % (tim.h - 2) + 1; } @@ -65,22 +65,22 @@ static void game(void) { // draw if (tim.event.type == TimEvent_Draw) { // food - draw_chr(cell(" ", 0, 0xc5), snek.food.x * 2 + 0, snek.food.y); - draw_chr(cell(" ", 0, 0xc5), snek.food.x * 2 + 1, snek.food.y); + tim_draw_chr(tim_cell(" ", 0, 0xc5), snek.food.x * 2 + 0, snek.food.y); + tim_draw_chr(tim_cell(" ", 0, 0xc5), snek.food.x * 2 + 1, snek.food.y); // snek - TimCell_t s = cell(" ", 0, 0); - for (int i = 0; i < snek.len; i++) { + TimCell s = tim_cell(" ", 0, 0); + for (i32 i = 0; i < snek.len; i++) { s.bg = (i / 2) % 2 ? 0xe3 : 0xea; - int x = snek.body[i].x * 2; - int y = snek.body[i].y; - draw_chr(s, x + 0, y); - draw_chr(s, x + 1, y); + i32 x = snek.body[i].x * 2; + i32 y = snek.body[i].y; + tim_draw_chr(s, x + 0, y); + tim_draw_chr(s, x + 1, y); } } // user input if (tim.event.type == KEY_EVENT) { - int key = tim.event.key; + i32 key = tim.event.key; if ((key == TimKey_Right || key == 'd') && snek.look.x != -1) { snek.look = (point){{1, 0}}; } else if ((key == TimKey_Left || key == 'a') && snek.look.x != 1) { @@ -90,31 +90,34 @@ static void game(void) { } else if ((key == TimKey_Up || key == 'w') && snek.look.y != 1) { snek.look = (point){{0, -1}}; } + else if (key == 'q' || key == TimKey_Escape){ + snek.state = PAUSE; + } } } static void menu(void) { - scope(A, A, 20, 13) { + tim_scope(A, A, 20, 13) { char* lbl = snek.state == OVER ? "GAME OVER" : "SNEK - THE GAME"; char* btn = snek.state == PAUSE ? "Resume" : "Play"; label(lbl, A, 0, A, A, BTN); - if (button(btn, A, 2, 20, 5, BTN) || is_key_press(TimKey_Enter)) { + if (button(btn, A, 2, 20, 5, BTN) || tim_is_key_press(TimKey_Enter)) { if (snek.state != PAUSE) { start(); } snek.state = RUN; } - if (button("Exit", A, 8, 20, 5, BTN) || is_key_press(TimKey_Escape)) { + if (button("Exit", A, 8, 20, 5, BTN) || tim_is_key_press('q') || tim_is_key_press(TimKey_Escape)) { exit(0); } } } -int main(void) { +i32 main(void) { // draw every 10 ms - while (tim_run(10)) { - TimCell_t bg = cell(" ", 0, BG); - draw_lot(bg, 0, 0, tim.w, tim.h); + while (tim_run(60)) { + TimCell bg = tim_cell(" ", 0, BG); + tim_draw_lot(bg, 0, 0, tim.w, tim.h); if (snek.state == RUN) { game(); @@ -122,8 +125,5 @@ int main(void) { menu(); } - if (is_key_press(TimKey_Escape)) { - snek.state = PAUSE; - } } } diff --git a/include/tim.h b/include/tim.h index 5035a74..cb30f77 100644 --- a/include/tim.h +++ b/include/tim.h @@ -55,7 +55,7 @@ typedef const char* cstr; extern "C" { #endif -#pragma endregion include +#pragma endregion #pragma region constants @@ -92,82 +92,83 @@ enum { }; typedef i32 TimKey; -#pragma endregion constants +#pragma endregion #pragma region types -typedef struct TimCell_t { +typedef struct TimCell { u8 fg; // foreground color u8 bg; // background color u8 wide; // wide or following wide character u8 n; // number of bytes in buf u8 buf[4]; // utf8 code point -} TimCell_t; +} TimCell; -typedef struct TimRect_t { - int x; // x coordinate (left = 0) - int y; // y coordinate (top = 0) - int w; // width - int h; // height -} TimRect_t; +typedef struct TimRect { + i32 x; // x coordinate (left = 0) + i32 y; // y coordinate (top = 0) + i32 w; // width + i32 h; // height +} TimRect; -typedef struct TimText_t { - int size; // size in bytes without terminator - int width; // widest line - int lines; // number of lines -} TimText_t; +typedef struct TimText { + i32 size; // size in bytes without terminator + i32 width; // widest line + i32 lines; // number of lines +} TimText; -typedef struct TimLine_t { - const char* s; // input and parse state - const char* line; // line strings, not terminated - int size; // line size in bytes - int width; // line width in glyph -} TimLine_t; +typedef struct TimLine { + cstr s; // input and parse state + cstr line; // line strings, not terminated + i32 size; // line size in bytes + i32 width; // line width in glyph +} TimLine; -typedef struct TimEvent_t { +typedef struct TimEvent { TimEventType type; - TimKey key; // used by TimEvent_Key and TimEvent_Mouse - i32 x; // used by TimEvent_Mouse - i32 y; // used by TimEvent_Mouse - char s[32]; // string representation of key, used by TimEvent_Key -} TimEvent_t; + TimKey key; // used by TimEvent_Key and TimEvent_Mouse + i32 x; // used by TimEvent_Mouse + i32 y; // used by TimEvent_Mouse + char s[32]; // string representation of key, used by TimEvent_Key +} TimEvent; -typedef struct TimEdit_t { - int cursor; // cursor position (utf8) - int length; // string length (utf8) - int capacity; // buffer size - char* s; // zero terminated buffer -} TimEdit_t; +typedef struct TimEditState { + i32 cursor; // cursor position (utf8) + i32 length; // string length (utf8) + i32 capacity; // buffer size + char* s; // zero terminated buffer +} TimEditState; -typedef struct TimState_t { - int w; // screen width - int h; // screen height - int frame; // frame counter - TimEvent_t event; // current event - void* focus; // focused element - int loop_stage; // loop stage - bool resized; // screen was resized - int scope; // current scope - TimRect_t scopes[TIM_MAX_SCOPE]; // scope stack - TimCell_t* cells; // screen buffer - char* buf; // final output buffer - int buf_size; // position in write buffer - i64 start_us; // render start time - int render_us; // elapsed render time -#ifdef TIM_UNIX // - struct termios attr; // initial attributes - int signal_pipe[2]; // signal fifo pipe -#endif // -#ifdef TIM_WINDOWS // +typedef struct TimState { + i32 w; // screen width + i32 h; // screen height + i32 frame; // frame counter + TimEvent event; // current event + void* focus; // focused element + i32 loop_stage; // loop stage + bool resized; // screen was resized + i32 scope; // current scope + TimRect scopes[TIM_MAX_SCOPE]; // scope stack + TimCell* cells; // current screen buffer (first or second half of cells_double_buf) + TimCell* cells_double_buf; // pointer to double buffer + char* buf; // final output buffer + i32 buf_size; // position in write buffer + i64 start_us; // render start time + i32 render_us; // elapsed render time +#ifdef TIM_UNIX + struct termios attr; // initial attributes + i32 signal_pipe[2]; // signal fifo pipe +#endif +#ifdef TIM_WINDOWS SMALL_RECT window; // screen buffer window size DWORD mode_in; // initial input mode DWORD mode_out; // initial output mode UINT cp_in; // initial input code page UINT cp_out; // initial output code page #endif -} TimState_t; +} TimState; -#pragma endregion types +#pragma endregion #pragma region macros @@ -176,1049 +177,177 @@ typedef struct TimState_t { #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) // number of items in array #define S(s) ("" s), (sizeof(s) - 1) // expand to s, sizeof(s) - 1 -#pragma endregion macros +#pragma endregion +#pragma region general // TODO: remove global variables +extern TimState tim; -// These buffers were part of tim struct but caused the linker to produce very -// large binaries. -static TimCell_t tim_cells[TIM_MAX_CELLS << TIM_ENABLE_DBUF]; // screen buffer -static char tim_buf[TIM_MAX_BUF]; // output buffer +bool tim_run(f32 fps); -TimState_t tim = { - .cells = tim_cells, - .buf = tim_buf, -}; +void tim_reset_terminal(void); +i64 tim_time_usec(void); -#pragma region string +#pragma endregion -// like strlen, returns 0 on NULL or int overflow -static inline int ztrlen(const char* s) { - if(s == NULL) - return 0; - int n = strlen(s); - if(n < 0) - n = 0; - return n; -} - -// bit scan reverse, count leading zeros -static inline int bsr8(u8 x) { -#if defined __GNUC__ || defined __clang__ - unsigned int b = x; - b <<= sizeof(b) * CHAR_BIT - 8; - b |= 1 << (sizeof(b) * CHAR_BIT - 9); - return __builtin_clz(b); -#elif defined _MSC_VER - unsigned long n = 0; - unsigned long b = x; - b <<= sizeof(b) * CHAR_BIT - 8; - b |= 1 << (sizeof(b) * CHAR_BIT - 9); - _BitScanReverse(&n, b); - return n; -#else - int n = 0; - for (; n < 8 && !(x & 128); n++, x <<= 1) {} - return n; -#endif -} - -// decode one utf8 code point -static i32 utfchr(const char* s) { - s = s ? s : ""; - // use bit magic to mask out leading utf8 1s - u32 c = s[0] & ((1 << (8 - bsr8(~s[0]))) - 1); - for (int i = 1; s[0] && s[i] && i < 4; i++) { - c = (c << 6) | (s[i] & 63); - } - return (i32)c; -} - -// number of utf8 code points -static int utflen(const char* s) { - int n = 0; - for (int i = 0; s && s[i]; i++) { - n += (s[i] & 192) != 128; - } - return n; -} - -// index of utf8 code point at pos -static int utfpos(const char* s, int pos) { - int i = 0; - for (int n = 0; pos >= 0 && s && s[i]; i++) { - n += (s[i] & 192) != 128; - if (n == pos + 1) { - return i; - } - } - return i; -} - -// scan string for width and lines -static TimText_t scan_str(const char* s) { - if(s == NULL) - s = ""; - TimText_t t = { - .width = 0, - .lines = (s[0] != 0), - }; - int width = 0; - for (t.size = 0; s[t.size]; t.size++) { - char ch = s[t.size]; - int newline = (ch == '\n'); - width = newline ? 0 : width; - width += (ch & 192) != 128 && (u8)ch > 31; - t.lines += newline; - t.width = MAX(t.width, width); - } - return t; -} - -// iterate through lines, false when end is reached -static bool next_line(TimLine_t* l) { - if (!l->s || !l->s[0]) { - return false; - } - l->line = l->s; - l->size = 0; - l->width = 0; - for (const char* s = l->s; s[0] && s[0] != '\n'; s++) { - l->size += 1; - l->width += (s[0] & 192) != 128 && (u8)s[0] > 31; - } - l->s += l->size + !!l->s[l->size]; - return true; -} - -// true if utf8 code point could be wide -static bool is_wide_perhaps(const u8* s, int n) { - // Character width depends on character, terminal and font. There is no - // reliable method, however most frequently used characters are narrow. - // Zero with characters are ignored, and hope that user input is benign. - if (n < 3 || s[0] < 225) { - // u+0000 - u+1000, basic latin - tibetan - return false; - } else if (s[0] == 226 && s[1] >= 148 && s[1] < 152) { - // u+2500 - u+2600 box drawing, block elements, geometric shapes - return false; - } - return true; -} - -#pragma endregion string - -#pragma region unix - -// Unix-like terminal IO. Osx is missing ppoll and __unix__. Come on, fix it! - -#ifdef TIM_UNIX - -static void write_str(const char* s, int size) { - ssize_t _ = write(STDOUT_FILENO, s, size); - (void)_; // remove unused-result warning -} - -static void signal_handler(int signal) { - // signals are written into a fifo pipe and read by event loop - ssize_t _ = write(tim.signal_pipe[1], &signal, sizeof(signal)); - (void)_; // remove unused-result warning -} - -static void update_screen_size(void) { - struct winsize ws = {0}; - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != 0) { - printf("ERROR: can't get console buffer size\n"); - exit(1); - } - int w = ws.ws_col; - int h = ws.ws_row; - tim.resized = (unsigned)(w * h) <= TIM_MAX_CELLS && (w != tim.w || h != tim.h); - if (tim.resized) { - tim.w = tim.scopes[0].w = w; - tim.h = tim.scopes[0].h = h; - } -} - -static void init_terminal(void) { - tcgetattr(STDOUT_FILENO, &tim.attr); // store attributes - struct termios attr = tim.attr; // - cfmakeraw(&attr); // configure raw mode - tcsetattr(STDOUT_FILENO, TCSADRAIN, &attr); // set new attributes - write_str(S("\33[?2004l")); // reset bracketed paste mode - write_str(S("\33[?1049h")); // use alternate buffer - write_str(S("\33[?25l")); // hide cursor - write_str(S("\33[?1000h")); // enable mouse - write_str(S("\33[?1002h")); // enable button events - write_str(S("\33[?1006h")); // use mouse sgr protocol - update_screen_size(); // get terminal size - int err = pipe(tim.signal_pipe); // create signal pipe - if (!err) { // - signal(SIGWINCH, signal_handler); // terminal size changed - } -} - -static void reset_terminal(void) { - tcsetattr(STDOUT_FILENO, TCSADRAIN, &tim.attr); // restore attributes - write_str(S("\33[?1000l")); // disable mouse - write_str(S("\33[?1002l")); // disable mouse - write_str(S("\33[m")); // reset colors - write_str(S("\33[?25h")); // show cursor - write_str(S("\33[?1049l")); // exit alternate buffer -} - -// parse input stored in e->s -static bool parse_input(event* restrict e, int n) { - char* s = e->s; - - if (n == 1 || s[0] != 27) { - // regular key press - e->type = TimEvent_Key; - e->key = s[0] == 127 ? TimKey_Backspace : utfchr(s); - return true; - } - - if (n >= 9 && !memcmp(s, S("\33[<"))) { - // sgr mouse sequence - e->type = TimEvent_Mouse; - int btn = strtol(s + 3, &s, 10); - e->x = strtol(s + 1, &s, 10) - 1; - e->y = strtol(s + 1, &s, 10) - 1; - if (btn == 0 && s[0] == 'M') { - // left button pressed - e->key = TimKey_MouseButtonLeft; - return true; - } - return false; - } - - static struct {char s[4]; int k;} key_table[] = { - {"[A" , TimKey_Up}, // - {"[B" , TimKey_Down}, // - {"[C" , TimKey_Right}, // - {"[D" , TimKey_Left}, // - {"[2~", TimKey_Insert}, // - {"[4h", TimKey_Insert}, // st - {"[3~", TimKey_Delete}, // - {"[P" , TimKey_Delete}, // st - {"[H" , TimKey_Home}, // - {"[1~", TimKey_Home}, // rxvt, lxterm, putty, tmux, screen - {"[7~", TimKey_Home}, // rxvt - {"[F" , TimKey_End}, // - {"[4~", TimKey_End}, // rxvt, lxterm, putty, tmux, screen, st - {"[8~", TimKey_End}, // rxvt - {"[5~", TimKey_PageUp}, // - {"[6~", TimKey_PageDown}, // - }; - - if ((n == 3 || n == 4) && s[0] == 27) { - // key sequence - for (int i = 0; i < (int)ARRAY_SIZE(key_table); i++) { - if (!memcmp(s + 1, key_table[i].s, n - 1)) { - e->type = TimEvent_Key; - e->key = key_table[i].k; - return true; - } - } - } - - return false; -} - -static void read_event(int timeout_ms) { - event* e = &tim.event; - - struct pollfd pfd[2] = { - {.fd = tim.signal_pipe[0], .events = POLLIN}, - {.fd = STDIN_FILENO, .events = POLLIN}, - }; - - while (true) { - memset(e, 0, sizeof(*e)); - - int r = poll(pfd, 2, timeout_ms > 0 ? timeout_ms : -1); - if (r < 0) { - // poll error, EINTR or EAGAIN - continue; - } else if (r == 0) { - // poll timeout - e->type = TimEvent_Draw; - return; - } - - if (pfd[0].revents & POLLIN) { - // received signal - int sig = 0; - int n = read(tim.signal_pipe[0], &sig, sizeof(sig)); - if (n > 0 && sig == SIGWINCH) { - // screen size changed - e->type = TimEvent_Draw; - update_screen_size(); - return; - } - } - - if (pfd[1].revents & POLLIN) { - // received input - int n = read(STDIN_FILENO, e->s, sizeof(e->s) - 1); - if (parse_input(e, n)) { - return; - } - } - } // while -} - -static inline i64 time_us(void) { - struct timespec ts = {0}; - clock_gettime(CLOCK_MONOTONIC, &ts); - return ts.tv_sec * 1000000 + ts.tv_nsec / 1000; -} - -#endif // TIM_UNIX - -#pragma endregion unix - -#pragma region windows - -// Windows terminal IO. Win32 is actually not that horrible as many say. Quirky -// but well documented. - -#ifdef TIM_WINDOWS - -static void write_str(const char* s, int size) { - HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); - WriteFile(h, s, size, NULL, NULL); - FlushFileBuffers(h); -} - -static void update_screen_size(void) { - HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); - CONSOLE_SCREEN_BUFFER_INFO csbi = {0}; - if (GetConsoleScreenBufferInfo(hout, &csbi) == 0) { - printf("ERROR: can't get console buffer size\n"); - exit(1); - } - int w = csbi.srWindow.Right - csbi.srWindow.Left + 1; - int h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; - tim.resized = (unsigned)(w * h) <= TIM_MAX_CELLS && (w != tim.w || h != tim.h); - if (tim.resized) { - tim.w = tim.scopes[0].w = w; - tim.h = tim.scopes[0].h = h; - tim.window = csbi.srWindow; - } -} - -static void init_terminal(void) { - DWORD mode = 0; - HANDLE hin = GetStdHandle(STD_INPUT_HANDLE); - GetConsoleMode(hin, &tim.mode_in); // get current input mode - mode = tim.mode_in; // - mode &= ~ENABLE_ECHO_INPUT; // disable echo - mode &= ~ENABLE_LINE_INPUT; // disable line buffer - // TODO: enable ctrl-c again - mode &= ~ENABLE_PROCESSED_INPUT; // disable ctrl-c - mode |= ENABLE_WINDOW_INPUT; // enable resize event - mode |= ENABLE_MOUSE_INPUT; // enable mouse event - mode |= ENABLE_EXTENDED_FLAGS; // for ENABLE_QUICK_EDIT - mode &= ~ENABLE_QUICK_EDIT_MODE; // disable select mode - SetConsoleMode(hin, mode); // set input mode - // - HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); // - GetConsoleMode(hout, &tim.mode_out); // get current output mode - mode = tim.mode_out; // - mode |= ENABLE_PROCESSED_OUTPUT; // enable ascii sequences - mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; // enable vt sequences - SetConsoleMode(hout, mode); // set output mode - // - tim.cp_in = GetConsoleCP(); // get current code page - tim.cp_out = GetConsoleOutputCP(); // - SetConsoleCP(CP_UTF8); // set utf8 in/out code page - SetConsoleOutputCP(CP_UTF8); // - write_str(S("\33[?1049h")); // use alternate buffer - update_screen_size(); // -} - -static void reset_terminal(void) { - write_str(S("\33[m")); // reset colors - write_str(S("\33[?25h")); // show cursor - write_str(S("\33[?1049l")); // exit alternate buffer - HANDLE hin = GetStdHandle(STD_INPUT_HANDLE); // - HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); // - SetConsoleMode(hin, tim.mode_in); // set original mode - SetConsoleMode(hout, tim.mode_out); // - SetConsoleCP(tim.cp_in); // set original code page - SetConsoleOutputCP(tim.cp_out); // -} - -static void read_event(int timeout_ms) { - TimEvent_t* e = &tim.event; - HANDLE h = GetStdHandle(STD_INPUT_HANDLE); - - static const i8 key_table[256] = { - [VK_BACK] = TimKey_Backspace, - [VK_TAB] = TimKey_Tab, - [VK_RETURN] = TimKey_Enter, - [VK_ESCAPE] = TimKey_Escape, - [VK_PRIOR] = TimKey_PageUp, - [VK_NEXT] = TimKey_PageDown, - [VK_END] = TimKey_End, - [VK_HOME] = TimKey_Home, - [VK_LEFT] = TimKey_Left, - [VK_UP] = TimKey_Up, - [VK_RIGHT] = TimKey_Right, - [VK_DOWN] = TimKey_Down, - [VK_INSERT] = TimKey_Insert, - [VK_DELETE] = TimKey_Delete, - }; - - while (true) { - memset(e, 0, sizeof(*e)); - // In cmd.exe the cursor somtimes reappears. This reliably hides it. - write_str(S("\33[?25l")); - - DWORD r = WaitForSingleObject(h, timeout_ms); - if (r == WAIT_TIMEOUT) { - e->type = TimEvent_Draw; - update_screen_size(); // workaround, see WINDOW_BUFFER_SIZE_EVENT - return; - } else if (r != WAIT_OBJECT_0) { - continue; - } - - // received input - INPUT_RECORD rec = {0}; - DWORD n = 0; - ReadConsoleInput(h, &rec, 1, &n); - - switch (rec.EventType) { - case KEY_EVENT: { - if (!rec.Event.KeyEvent.bKeyDown) { - // only interested in key press - continue; - } - int key = key_table[(u8)rec.Event.KeyEvent.wVirtualKeyCode]; - WCHAR chr = rec.Event.KeyEvent.uChar.UnicodeChar; - if (!key && chr < ' ') { - // non printable key - continue; - } - update_screen_size(); // workaround, see WINDOW_BUFFER_SIZE_EVENT - e->type = TimEvent_Key; - if (key) { - e->key = key; - return; - } - e->key = chr; - WideCharToMultiByte(CP_UTF8, 0, &chr, 1, e->s, sizeof(e->s), - NULL, NULL); - return; - } - - case MOUSE_EVENT: { - bool move = rec.Event.MouseEvent.dwEventFlags & ~DOUBLE_CLICK; - bool left = rec.Event.MouseEvent.dwButtonState & - FROM_LEFT_1ST_BUTTON_PRESSED; - if (move || !left) { - // ignore move events and buttons other than left - continue; - } - update_screen_size(); // workaround, see WINDOW_BUFFER_SIZE_EVENT - e->type = TimEvent_Mouse; - e->key = TimKey_MouseButtonLeft; - e->x = rec.Event.MouseEvent.dwMousePosition.X - tim.window.Left; - e->y = rec.Event.MouseEvent.dwMousePosition.Y - tim.window.Top; - return; - } - - case WINDOW_BUFFER_SIZE_EVENT: - e->type = TimEvent_Draw; - // cmd.exe screen buffer and window size are separate, making this - // event a bit unreliable. Effectively it is only emitted when the - // terminal width changes and not for the height. As a workaround - // the screen size is updated every time an event is emitted. - update_screen_size(); - return; - } - } // while -} - -static inline i64 time_us(void) { - LARGE_INTEGER ticks = {0}; - LARGE_INTEGER freq = {0}; - QueryPerformanceCounter(&ticks); - QueryPerformanceFrequency(&freq); - return 1000000 * ticks.QuadPart / freq.QuadPart; -} - -#endif // TIM_WINDOWS - -#pragma endregion windows - -#pragma region events - -// returns true if event was of type and key -static inline bool is_event_key(TimEventType type, TimKey key) { - return tim.event.type == type && tim.event.key == key; -} - -// returns true if event was press of key -static inline bool is_key_press(TimKey key) { - return is_event_key(TimEvent_Key, key); -} - -// returns true if mouse event was over r -static inline bool is_mouse_over(TimRect_t r) { - int x = tim.event.x; - int y = tim.event.y; - return x >= r.x && x < r.x + r.w && y >= r.y && y < r.y + r.h; -} - -// returns true if event is mouse left-down and over r -static inline bool is_click_over(TimRect_t r) { - return is_event_key(TimEvent_Mouse, TimKey_MouseButtonLeft) && is_mouse_over(r); -} - -#pragma endregion events - -#pragma region drawing - -// create cell from utf8 code point with fg and bg colors -static inline TimCell_t cell(const char* s, u8 fg, u8 bg) { - TimCell_t c = {.fg = fg, .bg = bg, .n = 1, .buf = {s[0]}}; - while ((s[c.n] & 192) == 128 && c.n < sizeof(c.buf)) { - c.buf[c.n] = s[c.n]; - c.n += 1; - } - return c; -} - -// clear cell buffer -static void clear_cells(void) { - size_t size = sizeof(tim.cells[0]) * tim.w * tim.h; - memset(tim.cells, 0, size); -} - -// draw cell at position -static void draw_chr(TimCell_t cell, int x, int y) { - if (x >= 0 && x < tim.w && y >= 0 && y < tim.h) { - tim.cells[x + y * tim.w] = cell; - } -} - -// draw row of cells -static void draw_row(TimCell_t cell, int x, int y, int w) { - if (y >= 0 && y < tim.h && w > 0) { - for (int i = MAX(x, 0); i < MIN(x + w, tim.w); i++) { - tim.cells[i + y * tim.w] = cell; - } - } -} - -// draw column of cells -static void draw_col(TimCell_t cell, int x, int y, int h) { - if (x >= 0 && x < tim.w && h > 0) { - for (int i = MAX(y, 0); i < MIN(y + h, tim.h); i++) { - tim.cells[x + i * tim.w] = cell; - } - } -} - -// fill lot (area) of cells -static void draw_lot(TimCell_t cell, int x, int y, int w, int h) { - if (w > 0 && h > 0) { - for (int iy = MAX(y, 0); iy < MIN(y + h, tim.h); iy++) { - for (int ix = MAX(x, 0); ix < MIN(x + w, tim.w); ix++) { - tim.cells[ix + iy * tim.w] = cell; - } - } - } -} - -// draw string to line, tags potential wide characters -static void draw_str(const char* s, int x, int y, int w, - u8 fg, u8 bg) { - if (s && y >= 0 && x < tim.w && y < tim.h ) { - int end = MIN(x + w, tim.w); - bool wide = false; - for (int i = 0; s[i] && x < end; x++) { - TimCell_t c = cell(&s[i], fg, bg); - wide = wide || is_wide_perhaps(c.buf, c.n); - if (x >= 0) { - c.wide = wide; - tim.cells[x + y * tim.w] = c; - } - i += c.n; - } - } -} - -// draw box of ascii cell characters -static void draw_box(int x, int y, int w, int h, u8 fg, u8 bg) { - draw_chr(cell("┌", fg, bg), x , y); - draw_chr(cell("┐", fg, bg), x + w - 1, y); - draw_chr(cell("└", fg, bg), x , y + h - 1); - draw_chr(cell("┘", fg, bg), x + w - 1, y + h - 1); - draw_row(cell("─", fg, bg), x + 1 , y , w - 2); - draw_row(cell("─", fg, bg), x + 1 , y + h - 1, w - 2); - draw_col(cell("│", fg, bg), x , y + 1 , h - 2); - draw_col(cell("│", fg, bg), x + w - 1, y + 1 , h - 2); - draw_lot(cell(" ", fg, bg), x + 1 , y + 1 , w - 2, h - 2); -} - -// invert fg and bg colors of line of cells -static void draw_invert(int x, int y, int w) { - if (y >= 0 && y < tim.h && w > 0) { - for (int i = MAX(x, 0); i < MIN(x + w, tim.w); i++) { - TimCell_t c = tim.cells[i + y * tim.w]; - tim.cells[i + y * tim.w].fg = c.bg; - tim.cells[i + y * tim.w].bg = c.fg; - } - } -} - -#pragma endregion drawing #pragma region scope // enter layout scope -#define scope(x, y, w, h) \ - for (int _i = enter_scope((x), (y), (w), (h)); _i; _i = exit_scope()) +#define tim_scope(x, y, w, h) \ + for (i32 _i = tim_enter_scope((x), (y), (w), (h)); _i; _i = tim_exit_scope()) // convert relative (scoped) to absolute (screen) coordinates -static TimRect_t abs_xywh(int x, int y, int w, int h) { - TimRect_t p = tim.scopes[tim.scope]; // parent scope - - x = (x == A && w == A) ? 0 : x; // special cases - y = (y == A && h == A) ? 0 : y; // - w = (w == A) ? ~0 : w; // - h = (h == A) ? ~0 : h; // - // - if (w < 0) { // - w += p.w - x + 1; // get w from parent - } // - if (h < 0) { // - h += p.h - y + 1; // get h from parent - } // - if (x == A) { // - x = p.x + (p.w - w) / 2; // center x on parent - } else { // - if (x < 0) { // - x += p.w - w + 1; // anchor x to right - } // - x += p.x; // anchor x to left - } // - if (y == A) { // - y = p.y + (p.h - h) / 2; // center y on parent - } else { // - if (y < 0) { // - y += p.h - h + 1; // anchor y to bottom - } // - y += p.y; // anchor y to top - } - - return (TimRect_t){x, y, w, h}; -} +TimRect tim_scope_rect_to_absolute(i32 x, i32 y, i32 w, i32 h); // enter scope and push coordinates on stack -static inline int enter_scope(int x, int y, int w, int h) { - if (tim.scope + 1 >= TIM_MAX_SCOPE) { - return 0; - } - TimRect_t r = abs_xywh(x, y, w, h); - tim.scope += 1; - tim.scopes[tim.scope] = r; - return 1; -} +i32 tim_enter_scope(i32 x, i32 y, i32 w, i32 h); // exit scope and pop stack -static inline int exit_scope(void) { - tim.scope -= (tim.scope > 0); - return 0; -} +i32 tim_exit_scope(void); -#pragma endregion scope +#pragma endregion -#pragma region frame + +#pragma region widgets + +// TODO: create enum TimColor and struct TimStyle // frame // color: background, frame -static inline void frame(int x, int y, int w, int h, u64 color) { - if (tim.event.type == TimEvent_Draw) { - TimRect_t r = abs_xywh(x, y, w, h); - draw_box(r.x, r.y, r.w, r.h, color, color >> 8); - } -} - -#pragma endregion frame - -#pragma region label +void tim_frame(i32 x, i32 y, i32 w, i32 h, u64 color); // text label // str : text - supports multiple lines // color: background, text -static inline void label(const char* s, int x, int y, int w, int h, - u64 color) { - if (tim.event.type == TimEvent_Draw) { - TimText_t t = scan_str(s); - w = (w == A) ? t.width : w; - h = (h == A) ? t.lines : h; - TimRect_t r = abs_xywh(x, y, w, h); - TimCell_t c = cell(" ", color, color >> 8); - draw_lot(c, r.x, r.y, r.w, r.h); - TimLine_t l = {.s = s, .line = ""}; - for (int i = 0; next_line(&l); i++) { - draw_str(l.line, r.x, r.y + i, l.width, c.fg, c.bg); - } - } -} - -#pragma endregion label - -#pragma region button +void label(cstr s, i32 x, i32 y, i32 w, i32 h, u64 color); // button - returns true on click // color: frame, background, text -static inline bool button(const char* txt, int x, int y, int w, int h, - u64 color) { - int tw = utflen(txt); - w = (w == A) ? (tw + 4) : w; - h = (h == A) ? 3 : h; - TimRect_t r = abs_xywh(x, y, w, h); - - if (tim.event.type == TimEvent_Draw) { - draw_box(r.x, r.y, r.w, r.h, color >> 16, color >> 8); - draw_str(txt, r.x + (w - tw) / 2, r.y + h / 2, w, color, color >> 8); - } - return is_click_over(r); -} - -#pragma endregion button - -#pragma region edit - -static inline void edit_init(TimEdit_t* e, int capacity, const char* initial_content){ - e->length = utflen(initial_content); - e->cursor = utflen(initial_content); - e->capacity = capacity; - e->s = (char*)malloc(capacity + 1); - int byte_len = strlen(initial_content); - memcpy(e->s, initial_content, byte_len); - e->s[byte_len] = 0; -} - -static void edit_insert(TimEdit_t* e, const char* s) { - int dst_size = ztrlen(e->s); - int src_size = ztrlen(s); - if (dst_size + src_size < e->capacity) { - int len = utflen(s); // usually 1, except when smashing keys - int cur = utfpos(e->s, e->cursor); - memmove(e->s + cur + src_size, e->s + cur, dst_size - cur); - memmove(e->s + cur, s, src_size); - e->s[dst_size + src_size] = 0; - e->length += len; - e->cursor += len; - } -} - -static void edit_delete(TimEdit_t* e) { - int size = ztrlen(e->s); - int cur = utfpos(e->s, e->cursor); - int len = utfpos(e->s + cur, 1); - if (size - cur > 0) { - memmove(e->s + cur, e->s + cur + len, size - cur); - e->length -= 1; - } -} - -/// @return key id or 0 -static int edit_event(TimEdit_t* e, TimRect_t r) { - if (is_click_over(r)) { - // take focus - tim.focus = e; - return 0; - } - - if (tim.focus != e || tim.event.type != TimEvent_Key) { - // not focused or no key press - return 0; - } - tim.event.type = TimEvent_Void; // consume event - - switch (tim.event.key) { - case TimKey_Escape: - case TimKey_Enter: - tim.focus = 0; // release focus - break; - case TimKey_Delete: - edit_delete(e); - break; - case TimKey_Backspace: - if (e->cursor > 0) { - e->cursor -= 1; - edit_delete(e); - } - break; - case TimKey_Left: - e->cursor -= (e->cursor > 0); - break; - case TimKey_Right: - e->cursor += (e->cursor < e->length); - break; - case TimKey_Home: - e->cursor = 0; - break; - case TimKey_End: - e->cursor = e->length; - break; - default: - if (tim.event.key >= ' ') { - edit_insert(e, tim.event.s); - } - break; - } - - return tim.event.key; -} - -/// text edit - value in state -/// @param e persistent edit state, use edit_init() to create new state -/// @param color frame, background, text -/// @return key id or 0 -static inline int edit(TimEdit_t* e, int x, int y, int w, u64 color) { - TimRect_t r = abs_xywh(x, y, w, 3); - - if (tim.event.type == TimEvent_Draw) { - draw_box(r.x, r.y, r.w, r.h, color >> 16, color >> 8); - if (tim.focus == e) { - char* s = e->s + utfpos(e->s, e->cursor - r.w + 4); - int cur = MIN(r.w - 4, e->cursor); - draw_str(s, r.x + 2, r.y + 1, r.w - 3, color, color >> 8); - draw_invert(r.x + cur + 2, r.y + 1, 1); - } else { - draw_str(e->s, r.x + 2, r.y + 1, r.w - 3, color, color >> 8); - } - } - - return edit_event(e, r); -} - -#pragma endregion - -#pragma region check +bool button(cstr txt, i32 x, i32 y, i32 w, i32 h, u64 color); // check box - returns true when clicked // txt : text label // state: persistent state, 0 unchecked, -1 semi checked, !0: checked // color: check, background, text -static inline bool check(const char* txt, int* state, int x, int y, int w, - u64 color) { - w = (w == A) ? utflen(txt) + 4 : w; - TimRect_t r = abs_xywh(x, y, w, 1); - - if (tim.event.type == TimEvent_Draw) { - const char* st = *state == -1 ? "-" : *state ? "x" : " "; - draw_str("[ ] ", r.x, r.y, 4, color, color >> 8); - draw_str(st, r.x + 1, r.y, 1, color >> 16, color >> 8); - draw_str(txt, r.x + 4, r.y, r.w - 4, color, color >> 8); - } - - bool click = is_click_over(r); - *state = click ? !*state : *state; - return click; -} - -#pragma endregion - -#pragma region radio +bool check(cstr txt, i32* state, i32 x, i32 y, i32 w, u64 color); // radio button - return true when clicked // txt : text label // state: persistent state, selected if *state == v // v : value // color: radio, background, text -static inline bool radio(const char* txt, int* state, int v, int x, int y, - int w, u64 color) { - w = (w == A) ? utflen(txt) + 4 : w; - TimRect_t r = abs_xywh(x, y, w, 1); +bool radio(cstr txt, i32* state, i32 v, i32 x, i32 y, i32 w, u64 color); - if (tim.event.type == TimEvent_Draw) { - const char* st = *state == v ? "o" : " "; - draw_str("( ) ", r.x, r.y, 4, color, color >> 8); - draw_str(st, r.x + 1, r.y, 1, color >> 16, color >> 8); - draw_str(txt, r.x + 4, r.y, r.w - 4, color, color >> 8); - } +/// text edit - value in state +/// @param e persistent edit state, use TimEditState_init() to create new state +/// @param color frame, background, text +/// @return key id or 0 +i32 edit(TimEditState* e, i32 x, i32 y, i32 w, u64 color); - bool click = is_click_over(r); - *state = click ? v : *state; - return click; -} +void TimEditState_init(TimEditState* e, i32 capacity, cstr initial_content); -#pragma endregion +void edit_insert(TimEditState* e, cstr s); -#pragma region rendering - -// write character to output buffer -static inline void put_chr(char c) { - if (tim.buf_size + 1 < TIM_MAX_BUF) { - tim.buf[tim.buf_size] = c; - tim.buf_size += 1; - } -} - -// write string to output buffer -static inline void put_str(const char* s, int size) { - if (size > 0 && tim.buf_size + size < TIM_MAX_BUF) { - memmove(&tim.buf[tim.buf_size], s, size); - tim.buf_size += size; - } -} - -// write integer as decimal string to output buffer -static inline void put_int(int i) { - // optimized for small positive values, reduces load by a third - char* buf = &tim.buf[tim.buf_size]; - if (tim.buf_size + 11 >= TIM_MAX_BUF) { - // not enough space for 32 bit integer - } else if ((unsigned)i < 10) { - buf[0] = '0' + i; - tim.buf_size += 1; - } else if ((unsigned)i < 100) { - buf[0] = '0' + i / 10; - buf[1] = '0' + i % 10; - tim.buf_size += 2; - } else if ((unsigned)i < 1000) { - buf[0] = '0' + i / 100; - buf[1] = '0' + (i / 10) % 10; - buf[2] = '0' + i % 10; - tim.buf_size += 3; - } else { - tim.buf_size += sprintf(buf, "%d", i); - } -} - -static void render(void) { - int fg = -1; - int bg = -1; - bool wide = false; - bool skip = false; - - // screen buffers - TimCell_t* new_cells = tim_cells; - TimCell_t* old_cells = tim_cells; -#if TIM_ENABLE_DBUF - new_cells += (tim.frame & 1) ? TIM_MAX_CELLS : 0; - old_cells += (tim.frame & 1) ? 0 : TIM_MAX_CELLS; -#endif - tim.buf_size = 0; - - for (int i = 0; i < tim.w * tim.h; i++) { - TimCell_t c = new_cells[i]; -#if TIM_ENABLE_DBUF - // do nothing if cells in look-ahead are identical - const int la = 8; // look-ahead - if (!tim.resized && !(i % la) && (i + la < TIM_MAX_CELLS) && - !memcmp(new_cells + i, old_cells + i, la * sizeof(c))) { - skip = true; - i = i + la - 1; - continue; - } -#endif - // Set cursor position after a new line, after a string containing wide - // characters or after skipping identical cells. - bool new_line = i % tim.w == 0; - bool wide_spill = wide && (c.n == 0 || c.buf[0] == ' '); - bool wide_flank = wide && !wide_spill && !c.wide; - if (new_line || wide_flank || skip) { - put_str(S("\33[")); - put_int((i / tim.w) + 1); - put_chr(';'); - put_int((i % tim.w) + 1); - put_chr('H'); - } - wide = c.wide || wide_spill; - skip = false; - - // change foreground color - if (c.fg != fg) { - fg = c.fg; - put_str(S("\33[38;5;")); - put_int(fg); - put_chr('m'); - } - - // change background color - if (c.bg != bg) { - bg = c.bg; - put_str(S("\33[48;5;")); - put_int(bg); - put_chr('m'); - } - - // write character - if (c.n) { - put_str((char*)c.buf, c.n); - } else { - put_chr(' '); - } - } - - // duration depends on connection and terminal rendering speed - write_str(tim.buf, tim.buf_size); - - tim.resized = false; - tim.frame += 1; // frame counter - tim.cells = old_cells; // swap buffer -} - -#pragma endregion - -#pragma region event loop - -static bool tim_run(f32 fps) { - int timeout = (fps > 0) ? (int)(1000 / fps) : 0; - - while (true) { - switch (tim.loop_stage) { - case 0: - // runs only once - init_terminal(); - atexit(reset_terminal); - // fallthru - case 1: - // process input event - tim.start_us = time_us(); - if (tim.event.type != TimEvent_Draw) { - // reset focus on mouse click - if (is_event_key(TimEvent_Mouse, TimKey_MouseButtonLeft)) { - tim.focus = 0; - } - tim.loop_stage = 2; - return true; - } - // fallthru - case 2: - // process draw event - clear_cells(); - tim.event.type = TimEvent_Draw; - tim.loop_stage = 3; - return true; - case 3: - // render screen and wait for next event - render(); - tim.render_us = time_us() - tim.start_us; - read_event(timeout); // blocks - // fallthru - default: - tim.loop_stage = 1; - break; - } - } // while -} +void edit_delete(TimEditState* e); #pragma endregion + +#pragma region event + +// returns true if event was of type and key +bool tim_is_event_key(TimEventType type, TimKey key); + +// returns true if event was press of key +bool tim_is_key_press(TimKey key); +// returns true if mouse event was over r +bool tim_is_mouse_over(TimRect r); + +// returns true if event is mouse left-down and over r +bool tim_is_mouse_click_over(TimRect r); + +#pragma endregion + + +#pragma region drawing + +// create cell from utf8 code point with fg and bg colors +TimCell tim_cell(cstr s, u8 fg, u8 bg); + +// clear cell buffer +void tim_clear_cells(void); + +// draw cell at position +void tim_draw_chr(TimCell cell, i32 x, i32 y); + +// draw row of cells +void tim_draw_row(TimCell cell, i32 x, i32 y, i32 w); + +// draw column of cells +void tim_draw_col(TimCell cell, i32 x, i32 y, i32 h); + +// fill lot (area) of cells +void tim_draw_lot(TimCell cell, i32 x, i32 y, i32 w, i32 h); + +// draw string to line, tags potential wide characters +void tim_draw_str(cstr s, i32 x, i32 y, i32 w, u8 fg, u8 bg); + +// draw box of ascii cell characters +void tim_draw_box(i32 x, i32 y, i32 w, i32 h, u8 fg, u8 bg); + +// invert fg and bg colors of line of cells +void tim_draw_invert(i32 x, i32 y, i32 w); + +#pragma endregion + + +#pragma region string + +// like strlen, returns 0 on NULL or i32 overflow +i32 tim_ztrlen(cstr s); + +// decode one utf8 code point +i32 tim_utf8_to_i32(cstr s); + +// number of utf8 code points +i32 tim_utf8_len(cstr s); + +// index of utf8 code point at pos +i32 tim_utf8_pos(cstr s, i32 pos); + +// true if utf8 code point could be wide +bool tim_utf8_is_wide_perhaps(const u8* s, i32 n); + +// bit scan reverse, count leading zeros +i32 tim_bsr8(u8 x); + +// scan string for width and lines +TimText tim_scan_str(cstr s); + +// iterate through lines, false when end is reached +bool tim_next_line(TimLine* l); + +#pragma endregion + + +#pragma region internal + +void tim_write_str(cstr s, i32 size); + +void tim_update_screen_size(void); + +void tim_init_terminal(void); + +void tim_read_event(i32 timeout_ms); + +void tim_render(void); + +#pragma endregion + + #ifdef __cplusplus } #endif diff --git a/makefile b/makefile index e8b0c31..8d565a5 100644 --- a/makefile +++ b/makefile @@ -1,22 +1,53 @@ -CFLAGS+=-Iinclude -Isrc -g +CFLAGS+=-Iinclude -Isrc -O2 all: bin/test bin/string bin/color bin/hello bin/ask bin/snek -bin/test: test/test.c bin - $(CC) $< -Wall $(CFLAGS) -o $@ -bin/string: test/string.c bin - $(CC) $< -Wall $(CFLAGS) -o $@ -bin/color: test/color.c bin - $(CC) $< -Wall $(CFLAGS) -o $@ -bin/hello: example/hello.c bin - $(CC) $< -Wall $(CFLAGS) -o $@ -bin/ask: example/ask.c bin - $(CC) $< -Wall $(CFLAGS) -o $@ -bin/snek: example/snek.c bin - $(CC) $< -Wall $(CFLAGS) -o $@ +bin/test: test/test.c bin/tim.a + $(CC) $< -Wall $(CFLAGS) bin/tim.a -o $@ +bin/string: test/string.c bin/tim.a + $(CC) $< -Wall $(CFLAGS) bin/tim.a -o $@ +bin/color: test/color.c bin/tim.a + $(CC) $< -Wall $(CFLAGS) bin/tim.a -o $@ +bin/hello: example/hello.c bin/tim.a + $(CC) $< -Wall $(CFLAGS) bin/tim.a -o $@ +bin/ask: example/ask.c bin/tim.a + $(CC) $< -Wall $(CFLAGS) bin/tim.a -o $@ +bin/snek: example/snek.c bin/tim.a + $(CC) $< -Wall $(CFLAGS) bin/tim.a -o $@ + +bin/tim.a: bin \ + obj/drawing.c.o obj/edit.c.o obj/event.c.o obj/loop.c.o obj/render.c.o \ + obj/scope.c.o obj/string.c.o obj/unix.c.o obj/widgets.c.o obj/windows.c.o + ar rcs bin/tim.a \ + obj/drawing.c.o obj/edit.c.o obj/event.c.o obj/loop.c.o obj/render.c.o \ + obj/scope.c.o obj/string.c.o obj/unix.c.o obj/widgets.c.o obj/windows.c.o + +obj/drawing.c.o: src/drawing.c obj + $(CC) $< -Wall $(CFLAGS) -c -o $@ +obj/edit.c.o: src/edit.c obj + $(CC) $< -Wall $(CFLAGS) -c -o $@ +obj/event.c.o: src/event.c obj + $(CC) $< -Wall $(CFLAGS) -c -o $@ +obj/loop.c.o: src/loop.c obj + $(CC) $< -Wall $(CFLAGS) -c -o $@ +obj/render.c.o: src/render.c obj + $(CC) $< -Wall $(CFLAGS) -c -o $@ +obj/scope.c.o: src/scope.c obj + $(CC) $< -Wall $(CFLAGS) -c -o $@ +obj/string.c.o: src/string.c obj + $(CC) $< -Wall $(CFLAGS) -c -o $@ +obj/unix.c.o: src/unix.c obj + $(CC) $< -Wall $(CFLAGS) -c -o $@ +obj/widgets.c.o: src/widgets.c obj + $(CC) $< -Wall $(CFLAGS) -c -o $@ +obj/windows.c.o: src/windows.c obj + $(CC) $< -Wall $(CFLAGS) -c -o $@ bin: mkdir -p bin +obj: + mkdir -p obj + clean: - rm -rf bin + rm -rf bin obj diff --git a/readme.md b/readme.md index e0b2f70..83c8bb7 100644 --- a/readme.md +++ b/readme.md @@ -149,18 +149,18 @@ tim_run (fps) -> bool The Ctrl-C interrupt is masked, so make sure to put an exit condition like this at the end of the main loop: - if (is_key_press(ESCAPE_KEY)) + if (tim_is_key_press(ESCAPE_KEY)) exit(0); fps frames per second -is_key_press (key) -> bool +tim_is_key_press (key) -> bool Returns true if key was pressed. key char literal or one of the KEY constants, see constants -time_us () -> int64 +tim_time_usec () -> int64 Returns monotonic clock value in microseconds. Not affected by summer time or leap seconds. diff --git a/src/drawing.c b/src/drawing.c new file mode 100755 index 0000000..cdd3e9f --- /dev/null +++ b/src/drawing.c @@ -0,0 +1,85 @@ +#include "tim.h" + +TimCell tim_cell(cstr s, u8 fg, u8 bg) { + TimCell c = {.fg = fg, .bg = bg, .n = 1, .buf = {s[0]}}; + while ((s[c.n] & 192) == 128 && c.n < sizeof(c.buf)) { + c.buf[c.n] = s[c.n]; + c.n += 1; + } + return c; +} + +void tim_clear_cells(void) { + size_t size = sizeof(tim.cells[0]) * tim.w * tim.h; + memset(tim.cells, 0, size); +} + +void tim_draw_chr(TimCell cell, i32 x, i32 y) { + if (x >= 0 && x < tim.w && y >= 0 && y < tim.h) { + tim.cells[x + y * tim.w] = cell; + } +} + +void tim_draw_row(TimCell cell, i32 x, i32 y, i32 w) { + if (y >= 0 && y < tim.h && w > 0) { + for (i32 i = MAX(x, 0); i < MIN(x + w, tim.w); i++) { + tim.cells[i + y * tim.w] = cell; + } + } +} + +void tim_draw_col(TimCell cell, i32 x, i32 y, i32 h) { + if (x >= 0 && x < tim.w && h > 0) { + for (i32 i = MAX(y, 0); i < MIN(y + h, tim.h); i++) { + tim.cells[x + i * tim.w] = cell; + } + } +} + +void tim_draw_lot(TimCell cell, i32 x, i32 y, i32 w, i32 h) { + if (w > 0 && h > 0) { + for (i32 iy = MAX(y, 0); iy < MIN(y + h, tim.h); iy++) { + for (i32 ix = MAX(x, 0); ix < MIN(x + w, tim.w); ix++) { + tim.cells[ix + iy * tim.w] = cell; + } + } + } +} + +void tim_draw_str(cstr s, i32 x, i32 y, i32 w, u8 fg, u8 bg) { + if (s && y >= 0 && x < tim.w && y < tim.h ) { + i32 end = MIN(x + w, tim.w); + bool wide = false; + for (i32 i = 0; s[i] && x < end; x++) { + TimCell c = tim_cell(&s[i], fg, bg); + wide = wide || tim_utf8_is_wide_perhaps(c.buf, c.n); + if (x >= 0) { + c.wide = wide; + tim.cells[x + y * tim.w] = c; + } + i += c.n; + } + } +} + +void tim_draw_box(i32 x, i32 y, i32 w, i32 h, u8 fg, u8 bg) { + tim_draw_chr(tim_cell("┌", fg, bg), x , y); + tim_draw_chr(tim_cell("┐", fg, bg), x + w - 1, y); + tim_draw_chr(tim_cell("└", fg, bg), x , y + h - 1); + tim_draw_chr(tim_cell("┘", fg, bg), x + w - 1, y + h - 1); + tim_draw_row(tim_cell("─", fg, bg), x + 1 , y , w - 2); + tim_draw_row(tim_cell("─", fg, bg), x + 1 , y + h - 1, w - 2); + tim_draw_col(tim_cell("│", fg, bg), x , y + 1 , h - 2); + tim_draw_col(tim_cell("│", fg, bg), x + w - 1, y + 1 , h - 2); + tim_draw_lot(tim_cell(" ", fg, bg), x + 1 , y + 1 , w - 2, h - 2); +} + +void tim_draw_invert(i32 x, i32 y, i32 w) { + if (y >= 0 && y < tim.h && w > 0) { + for (i32 i = MAX(x, 0); i < MIN(x + w, tim.w); i++) { + TimCell c = tim.cells[i + y * tim.w]; + tim.cells[i + y * tim.w].fg = c.bg; + tim.cells[i + y * tim.w].bg = c.fg; + } + } +} diff --git a/src/edit.c b/src/edit.c new file mode 100755 index 0000000..a4bd62e --- /dev/null +++ b/src/edit.c @@ -0,0 +1,104 @@ +#include "tim.h" + + +void TimEditState_init(TimEditState* e, i32 capacity, cstr initial_content){ + e->length = tim_utf8_len(initial_content); + e->cursor = tim_utf8_len(initial_content); + e->capacity = capacity; + e->s = (char*)malloc(capacity + 1); + i32 byte_len = strlen(initial_content); + memcpy(e->s, initial_content, byte_len); + e->s[byte_len] = 0; +} + +void edit_insert(TimEditState* e, cstr s) { + i32 dst_size = tim_ztrlen(e->s); + i32 src_size = tim_ztrlen(s); + if (dst_size + src_size < e->capacity) { + i32 len = tim_utf8_len(s); // usually 1, except when smashing keys + i32 cur = tim_utf8_pos(e->s, e->cursor); + memmove(e->s + cur + src_size, e->s + cur, dst_size - cur); + memmove(e->s + cur, s, src_size); + e->s[dst_size + src_size] = 0; + e->length += len; + e->cursor += len; + } +} + +void edit_delete(TimEditState* e) { + i32 size = tim_ztrlen(e->s); + i32 cur = tim_utf8_pos(e->s, e->cursor); + i32 len = tim_utf8_pos(e->s + cur, 1); + if (size - cur > 0) { + memmove(e->s + cur, e->s + cur + len, size - cur); + e->length -= 1; + } +} + +/// @return key id or 0 +static i32 edit_event(TimEditState* e, TimRect r) { + if (tim_is_mouse_click_over(r)) { + // take focus + tim.focus = e; + return 0; + } + + if (tim.focus != e || tim.event.type != TimEvent_Key) { + // not focused or no key press + return 0; + } + tim.event.type = TimEvent_Void; // consume event + + switch (tim.event.key) { + case TimKey_Escape: + case TimKey_Enter: + tim.focus = 0; // release focus + break; + case TimKey_Delete: + edit_delete(e); + break; + case TimKey_Backspace: + if (e->cursor > 0) { + e->cursor -= 1; + edit_delete(e); + } + break; + case TimKey_Left: + e->cursor -= (e->cursor > 0); + break; + case TimKey_Right: + e->cursor += (e->cursor < e->length); + break; + case TimKey_Home: + e->cursor = 0; + break; + case TimKey_End: + e->cursor = e->length; + break; + default: + if (tim.event.key >= ' ') { + edit_insert(e, tim.event.s); + } + break; + } + + return tim.event.key; +} + +i32 edit(TimEditState* e, i32 x, i32 y, i32 w, u64 color) { + TimRect r = tim_scope_rect_to_absolute(x, y, w, 3); + + if (tim.event.type == TimEvent_Draw) { + tim_draw_box(r.x, r.y, r.w, r.h, color >> 16, color >> 8); + if (tim.focus == e) { + char* s = e->s + tim_utf8_pos(e->s, e->cursor - r.w + 4); + i32 cur = MIN(r.w - 4, e->cursor); + tim_draw_str(s, r.x + 2, r.y + 1, r.w - 3, color, color >> 8); + tim_draw_invert(r.x + cur + 2, r.y + 1, 1); + } else { + tim_draw_str(e->s, r.x + 2, r.y + 1, r.w - 3, color, color >> 8); + } + } + + return edit_event(e, r); +} diff --git a/src/event.c b/src/event.c new file mode 100755 index 0000000..dae1506 --- /dev/null +++ b/src/event.c @@ -0,0 +1,19 @@ +#include "tim.h" + +bool tim_is_event_key(TimEventType type, TimKey key) { + return tim.event.type == type && tim.event.key == key; +} + +bool tim_is_key_press(TimKey key) { + return tim_is_event_key(TimEvent_Key, key); +} + +bool tim_is_mouse_over(TimRect r) { + i32 x = tim.event.x; + i32 y = tim.event.y; + return x >= r.x && x < r.x + r.w && y >= r.y && y < r.y + r.h; +} + +bool tim_is_mouse_click_over(TimRect r) { + return tim_is_event_key(TimEvent_Mouse, TimKey_MouseButtonLeft) && tim_is_mouse_over(r); +} diff --git a/src/loop.c b/src/loop.c new file mode 100755 index 0000000..3ea04e3 --- /dev/null +++ b/src/loop.c @@ -0,0 +1,51 @@ +#include "tim.h" + +// TODO: remove global variables +static TimCell _tim_cells[TIM_MAX_CELLS << TIM_ENABLE_DBUF]; +static char _tim_buf[TIM_MAX_BUF]; +TimState tim = { + .cells = _tim_cells, + .cells_double_buf = _tim_cells, + .buf = _tim_buf, +}; + +bool tim_run(f32 fps) { + i32 timeout = (fps > 0) ? (i32)(1000 / fps) : 0; + + while (true) { + switch (tim.loop_stage) { + case 0: + // runs only once + tim_init_terminal(); + atexit(tim_reset_terminal); + // fallthru + case 1: + // process input event + tim.start_us = tim_time_usec(); + if (tim.event.type != TimEvent_Draw) { + // reset focus on mouse click + if (tim_is_event_key(TimEvent_Mouse, TimKey_MouseButtonLeft)) { + tim.focus = 0; + } + tim.loop_stage = 2; + return true; + } + // fallthru + case 2: + // process draw event + tim_clear_cells(); + tim.event.type = TimEvent_Draw; + tim.loop_stage = 3; + return true; + case 3: + // render screen and wait for next event + tim_render(); + tim.render_us = tim_time_usec() - tim.start_us; + tim_read_event(timeout); // blocks + // fallthru + default: + tim.loop_stage = 1; + break; + } + } // while +} diff --git a/src/render.c b/src/render.c new file mode 100755 index 0000000..0f34fd9 --- /dev/null +++ b/src/render.c @@ -0,0 +1,114 @@ +#include "tim.h" + +// write character to output buffer +static void tim_put_chr(char c) { + if (tim.buf_size + 1 < TIM_MAX_BUF) { + tim.buf[tim.buf_size] = c; + tim.buf_size += 1; + } +} + +// write string to output buffer +static void tim_put_str(cstr s, i32 size) { + if (size > 0 && tim.buf_size + size < TIM_MAX_BUF) { + memmove(&tim.buf[tim.buf_size], s, size); + tim.buf_size += size; + } +} + +// write integer as decimal string to output buffer +static void tim_put_int(i32 i) { + // optimized for small positive values, reduces load by a third + char* buf = &tim.buf[tim.buf_size]; + if (tim.buf_size + 11 >= TIM_MAX_BUF) { + // not enough space for 32 bit integer + } else if ((u32)i < 10) { + buf[0] = '0' + i; + tim.buf_size += 1; + } else if ((u32)i < 100) { + buf[0] = '0' + i / 10; + buf[1] = '0' + i % 10; + tim.buf_size += 2; + } else if ((u32)i < 1000) { + buf[0] = '0' + i / 100; + buf[1] = '0' + (i / 10) % 10; + buf[2] = '0' + i % 10; + tim.buf_size += 3; + } else { + tim.buf_size += sprintf(buf, "%d", i); + } +} + +void tim_render(void) { + i32 fg = -1; + i32 bg = -1; + bool wide = false; + bool skip = false; + + // screen buffers + TimCell* new_cells = tim.cells_double_buf; + TimCell* old_cells = tim.cells_double_buf; +#if TIM_ENABLE_DBUF + new_cells += (tim.frame & 1) ? TIM_MAX_CELLS : 0; + old_cells += (tim.frame & 1) ? 0 : TIM_MAX_CELLS; +#endif + tim.buf_size = 0; + + for (i32 i = 0; i < tim.w * tim.h; i++) { + TimCell c = new_cells[i]; +#if TIM_ENABLE_DBUF + // do nothing if cells in look-ahead are identical + const i32 la = 8; // look-ahead + if (!tim.resized && !(i % la) && (i + la < TIM_MAX_CELLS) && + !memcmp(new_cells + i, old_cells + i, la * sizeof(c))) { + skip = true; + i = i + la - 1; + continue; + } +#endif + // Set cursor position after a new line, after a string containing wide + // characters or after skipping identical cells. + bool new_line = i % tim.w == 0; + bool wide_spill = wide && (c.n == 0 || c.buf[0] == ' '); + bool wide_flank = wide && !wide_spill && !c.wide; + if (new_line || wide_flank || skip) { + tim_put_str(S("\33[")); + tim_put_int((i / tim.w) + 1); + tim_put_chr(';'); + tim_put_int((i % tim.w) + 1); + tim_put_chr('H'); + } + wide = c.wide || wide_spill; + skip = false; + + // change foreground color + if (c.fg != fg) { + fg = c.fg; + tim_put_str(S("\33[38;5;")); + tim_put_int(fg); + tim_put_chr('m'); + } + + // change background color + if (c.bg != bg) { + bg = c.bg; + tim_put_str(S("\33[48;5;")); + tim_put_int(bg); + tim_put_chr('m'); + } + + // write character + if (c.n) { + tim_put_str((char*)c.buf, c.n); + } else { + tim_put_chr(' '); + } + } + + // duration depends on connection and terminal rendering speed + tim_write_str(tim.buf, tim.buf_size); + + tim.resized = false; + tim.frame += 1; // frame counter + tim.cells = old_cells; // swap buffer +} diff --git a/src/scope.c b/src/scope.c new file mode 100755 index 0000000..c954991 --- /dev/null +++ b/src/scope.c @@ -0,0 +1,50 @@ +#include "tim.h" + +TimRect tim_scope_rect_to_absolute(i32 x, i32 y, i32 w, i32 h) { + TimRect p = tim.scopes[tim.scope]; // parent scope + + x = (x == A && w == A) ? 0 : x; // special cases + y = (y == A && h == A) ? 0 : y; // + w = (w == A) ? ~0 : w; // + h = (h == A) ? ~0 : h; // + // + if (w < 0) { // + w += p.w - x + 1; // get w from parent + } // + if (h < 0) { // + h += p.h - y + 1; // get h from parent + } // + if (x == A) { // + x = p.x + (p.w - w) / 2; // center x on parent + } else { // + if (x < 0) { // + x += p.w - w + 1; // anchor x to right + } // + x += p.x; // anchor x to left + } // + if (y == A) { // + y = p.y + (p.h - h) / 2; // center y on parent + } else { // + if (y < 0) { // + y += p.h - h + 1; // anchor y to bottom + } // + y += p.y; // anchor y to top + } + + return (TimRect){x, y, w, h}; +} + +i32 tim_enter_scope(i32 x, i32 y, i32 w, i32 h) { + if (tim.scope + 1 >= TIM_MAX_SCOPE) { + return 0; + } + TimRect r = tim_scope_rect_to_absolute(x, y, w, h); + tim.scope += 1; + tim.scopes[tim.scope] = r; + return 1; +} + +i32 tim_exit_scope(void) { + tim.scope -= (tim.scope > 0); + return 0; +} diff --git a/src/string.c b/src/string.c new file mode 100755 index 0000000..08d35c1 --- /dev/null +++ b/src/string.c @@ -0,0 +1,108 @@ +#include "tim.h" + +i32 tim_ztrlen(cstr s) { + if(s == NULL) + return 0; + i32 n = strlen(s); + if(n < 0) + n = 0; + return n; +} + +i32 tim_bsr8(u8 x) { +#if defined __GNUC__ || defined __clang__ + unsigned int b = x; + b <<= sizeof(b) * CHAR_BIT - 8; + b |= 1 << (sizeof(b) * CHAR_BIT - 9); + return __builtin_clz(b); +#elif defined _MSC_VER + unsigned long n = 0; + unsigned long b = x; + b <<= sizeof(b) * CHAR_BIT - 8; + b |= 1 << (sizeof(b) * CHAR_BIT - 9); + _BitScanReverse(&n, b); + return n; +#else + i32 n = 0; + for (; n < 8 && !(x & 128); n++, x <<= 1) {} + return n; +#endif +} + + +i32 tim_utf8_to_i32(cstr s) { + s = s ? s : ""; + // use bit magic to mask out leading utf8 1s + u32 c = s[0] & ((1 << (8 - tim_bsr8(~s[0]))) - 1); + for (i32 i = 1; s[0] && s[i] && i < 4; i++) { + c = (c << 6) | (s[i] & 63); + } + return (i32)c; +} + +i32 tim_utf8_len(cstr s) { + i32 n = 0; + for (i32 i = 0; s && s[i]; i++) { + n += (s[i] & 192) != 128; + } + return n; +} + +i32 tim_utf8_pos(cstr s, i32 pos) { + i32 i = 0; + for (i32 n = 0; pos >= 0 && s && s[i]; i++) { + n += (s[i] & 192) != 128; + if (n == pos + 1) { + return i; + } + } + return i; +} + +bool tim_utf8_is_wide_perhaps(const u8* s, i32 n) { + // Character width depends on character, terminal and font. There is no + // reliable method, however most frequently used characters are narrow. + // Zero with characters are ignored, and hope that user input is benign. + if (n < 3 || s[0] < 225) { + // u+0000 - u+1000, basic latin - tibetan + return false; + } else if (s[0] == 226 && s[1] >= 148 && s[1] < 152) { + // u+2500 - u+2600 box drawing, block elements, geometric shapes + return false; + } + return true; +} + +TimText tim_scan_str(cstr s) { + if(s == NULL) + s = ""; + TimText t = { + .width = 0, + .lines = (s[0] != 0), + }; + i32 width = 0; + for (t.size = 0; s[t.size]; t.size++) { + char ch = s[t.size]; + i32 newline = (ch == '\n'); + width = newline ? 0 : width; + width += (ch & 192) != 128 && (u8)ch > 31; + t.lines += newline; + t.width = MAX(t.width, width); + } + return t; +} + +bool tim_next_line(TimLine* l) { + if (!l->s || !l->s[0]) { + return false; + } + l->line = l->s; + l->size = 0; + l->width = 0; + for (cstr s = l->s; s[0] && s[0] != '\n'; s++) { + l->size += 1; + l->width += (s[0] & 192) != 128 && (u8)s[0] > 31; + } + l->s += l->size + !!l->s[l->size]; + return true; +} diff --git a/src/unix.c b/src/unix.c new file mode 100755 index 0000000..1e0ccc3 --- /dev/null +++ b/src/unix.c @@ -0,0 +1,165 @@ +#include "tim.h" + +#ifdef TIM_UNIX + +i64 tim_time_usec(void) { + struct timespec ts = {0}; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000000 + ts.tv_nsec / 1000; +} + +void tim_write_str(cstr s, i32 size) { + ssize_t _ = write(STDOUT_FILENO, s, size); + (void)_; // remove unused-result warning +} + +void signal_handler(i32 signal) { + // signals are written into a fifo pipe and read by event loop + ssize_t _ = write(tim.signal_pipe[1], &signal, sizeof(signal)); + (void)_; // remove unused-result warning +} + +void tim_update_screen_size(void) { + struct winsize ws = {0}; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != 0) { + printf("ERROR: can't get console buffer size\n"); + exit(1); + } + i32 w = ws.ws_col; + i32 h = ws.ws_row; + tim.resized = (u32)(w * h) <= TIM_MAX_CELLS && (w != tim.w || h != tim.h); + if (tim.resized) { + tim.w = tim.scopes[0].w = w; + tim.h = tim.scopes[0].h = h; + } +} + +void tim_init_terminal(void) { + tcgetattr(STDOUT_FILENO, &tim.attr); // store attributes + struct termios attr = tim.attr; // + cfmakeraw(&attr); // configure raw mode + tcsetattr(STDOUT_FILENO, TCSADRAIN, &attr); // set new attributes + tim_write_str(S("\33[?2004l")); // reset bracketed paste mode + tim_write_str(S("\33[?1049h")); // use alternate buffer + tim_write_str(S("\33[?25l")); // hide cursor + tim_write_str(S("\33[?1000h")); // enable mouse + tim_write_str(S("\33[?1002h")); // enable button events + tim_write_str(S("\33[?1006h")); // use mouse sgr protocol + tim_update_screen_size(); // get terminal size + i32 err = pipe(tim.signal_pipe); // create signal pipe + if (!err) { // + signal(SIGWINCH, signal_handler); // terminal size changed + } +} + +void tim_reset_terminal(void) { + tcsetattr(STDOUT_FILENO, TCSADRAIN, &tim.attr); // restore attributes + tim_write_str(S("\33[?1000l")); // disable mouse + tim_write_str(S("\33[?1002l")); // disable mouse + tim_write_str(S("\33[m")); // reset colors + tim_write_str(S("\33[?25h")); // show cursor + tim_write_str(S("\33[?1049l")); // exit alternate buffer +} + +// parse input stored in e->s +bool parse_input(event* restrict e, i32 n) { + char* s = e->s; + + if (n == 1 || s[0] != 27) { + // regular key press + e->type = TimEvent_Key; + e->key = s[0] == 127 ? TimKey_Backspace : tim_utf8_to_i32(s); + return true; + } + + if (n >= 9 && !memcmp(s, S("\33[<"))) { + // sgr mouse sequence + e->type = TimEvent_Mouse; + i32 btn = strtol(s + 3, &s, 10); + e->x = strtol(s + 1, &s, 10) - 1; + e->y = strtol(s + 1, &s, 10) - 1; + if (btn == 0 && s[0] == 'M') { + // left button pressed + e->key = TimKey_MouseButtonLeft; + return true; + } + return false; + } + + struct {char s[4]; i32 k;} key_table[] = { + {"[A" , TimKey_Up}, // + {"[B" , TimKey_Down}, // + {"[C" , TimKey_Right}, // + {"[D" , TimKey_Left}, // + {"[2~", TimKey_Insert}, // + {"[4h", TimKey_Insert}, // st + {"[3~", TimKey_Delete}, // + {"[P" , TimKey_Delete}, // st + {"[H" , TimKey_Home}, // + {"[1~", TimKey_Home}, // rxvt, lxterm, putty, tmux, screen + {"[7~", TimKey_Home}, // rxvt + {"[F" , TimKey_End}, // + {"[4~", TimKey_End}, // rxvt, lxterm, putty, tmux, screen, st + {"[8~", TimKey_End}, // rxvt + {"[5~", TimKey_PageUp}, // + {"[6~", TimKey_PageDown}, // + }; + + if ((n == 3 || n == 4) && s[0] == 27) { + // key sequence + for (i32 i = 0; i < (i32)ARRAY_SIZE(key_table); i++) { + if (!memcmp(s + 1, key_table[i].s, n - 1)) { + e->type = TimEvent_Key; + e->key = key_table[i].k; + return true; + } + } + } + + return false; +} + +void tim_read_event(i32 timeout_ms) { + event* e = &tim.event; + + struct pollfd pfd[2] = { + {.fd = tim.signal_pipe[0], .events = POLLIN}, + {.fd = STDIN_FILENO, .events = POLLIN}, + }; + + while (true) { + memset(e, 0, sizeof(*e)); + + i32 r = poll(pfd, 2, timeout_ms > 0 ? timeout_ms : -1); + if (r < 0) { + // poll error, EINTR or EAGAIN + continue; + } else if (r == 0) { + // poll timeout + e->type = TimEvent_Draw; + return; + } + + if (pfd[0].revents & POLLIN) { + // received signal + i32 sig = 0; + i32 n = read(tim.signal_pipe[0], &sig, sizeof(sig)); + if (n > 0 && sig == SIGWINCH) { + // screen size changed + e->type = TimEvent_Draw; + tim_update_screen_size(); + return; + } + } + + if (pfd[1].revents & POLLIN) { + // received input + i32 n = read(STDIN_FILENO, e->s, sizeof(e->s) - 1); + if (parse_input(e, n)) { + return; + } + } + } // while +} + +#endif // TIM_UNIX diff --git a/src/widgets.c b/src/widgets.c new file mode 100755 index 0000000..d114b86 --- /dev/null +++ b/src/widgets.c @@ -0,0 +1,71 @@ +#include "tim.h" + +void tim_frame(i32 x, i32 y, i32 w, i32 h, u64 color) { + if (tim.event.type == TimEvent_Draw) { + TimRect r = tim_scope_rect_to_absolute(x, y, w, h); + tim_draw_box(r.x, r.y, r.w, r.h, color, color >> 8); + } +} + +void label(cstr s, i32 x, i32 y, i32 w, i32 h, u64 color) { + if (tim.event.type == TimEvent_Draw) { + TimText t = tim_scan_str(s); + w = (w == A) ? t.width : w; + h = (h == A) ? t.lines : h; + TimRect r = tim_scope_rect_to_absolute(x, y, w, h); + TimCell c = tim_cell(" ", color, color >> 8); + tim_draw_lot(c, r.x, r.y, r.w, r.h); + TimLine l = {.s = s, .line = ""}; + for (i32 i = 0; tim_next_line(&l); i++) { + tim_draw_str(l.line, r.x, r.y + i, l.width, c.fg, c.bg); + } + } +} + +bool button(cstr txt, i32 x, i32 y, i32 w, i32 h, u64 color) { + i32 tw = tim_utf8_len(txt); + w = (w == A) ? (tw + 4) : w; + h = (h == A) ? 3 : h; + TimRect r = tim_scope_rect_to_absolute(x, y, w, h); + + if (tim.event.type == TimEvent_Draw) { + tim_draw_box(r.x, r.y, r.w, r.h, color >> 16, color >> 8); + tim_draw_str(txt, r.x + (w - tw) / 2, r.y + h / 2, w, color, color >> 8); + } + return tim_is_mouse_click_over(r); +} + +bool check(cstr txt, i32* state, i32 x, i32 y, i32 w, u64 color) { + w = (w == A) ? tim_utf8_len(txt) + 4 : w; + TimRect r = tim_scope_rect_to_absolute(x, y, w, 1); + + if (tim.event.type == TimEvent_Draw) { + cstr st = *state == -1 ? "-" : *state ? "x" : " "; + tim_draw_str("[ ] ", r.x, r.y, 4, color, color >> 8); + tim_draw_str(st, r.x + 1, r.y, 1, color >> 16, color >> 8); + tim_draw_str(txt, r.x + 4, r.y, r.w - 4, color, color >> 8); + } + + bool click = tim_is_mouse_click_over(r); + *state = click ? !*state : *state; + return click; +} + + +bool radio(cstr txt, i32* state, i32 v, i32 x, i32 y, i32 w, u64 color) { + w = (w == A) ? tim_utf8_len(txt) + 4 : w; + TimRect r = tim_scope_rect_to_absolute(x, y, w, 1); + + if (tim.event.type == TimEvent_Draw) { + cstr st = *state == v ? "o" : " "; + tim_draw_str("( ) ", r.x, r.y, 4, color, color >> 8); + tim_draw_str(st, r.x + 1, r.y, 1, color >> 16, color >> 8); + tim_draw_str(txt, r.x + 4, r.y, r.w - 4, color, color >> 8); + } + + bool click = tim_is_mouse_click_over(r); + *state = click ? v : *state; + return click; +} + + diff --git a/src/windows.c b/src/windows.c new file mode 100755 index 0000000..2d4ad91 --- /dev/null +++ b/src/windows.c @@ -0,0 +1,170 @@ +#include "tim.h" + +#ifdef TIM_WINDOWS + +i64 tim_time_usec(void) { + LARGE_INTEGER ticks = {0}; + LARGE_INTEGER freq = {0}; + QueryPerformanceCounter(&ticks); + QueryPerformanceFrequency(&freq); + return 1000000 * ticks.QuadPart / freq.QuadPart; +} + +void tim_write_str(cstr s, i32 size) { + HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); + WriteFile(h, s, size, NULL, NULL); + FlushFileBuffers(h); +} + +void tim_update_screen_size(void) { + HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbi = {0}; + if (GetConsoleScreenBufferInfo(hout, &csbi) == 0) { + printf("ERROR: can't get console buffer size\n"); + exit(1); + } + i32 w = csbi.srWindow.Right - csbi.srWindow.Left + 1; + i32 h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + tim.resized = (u32)(w * h) <= TIM_MAX_CELLS && (w != tim.w || h != tim.h); + if (tim.resized) { + tim.w = tim.scopes[0].w = w; + tim.h = tim.scopes[0].h = h; + tim.window = csbi.srWindow; + } +} + +void tim_init_terminal(void) { + DWORD mode = 0; + HANDLE hin = GetStdHandle(STD_INPUT_HANDLE); + GetConsoleMode(hin, &tim.mode_in); // get current input mode + mode = tim.mode_in; // + mode &= ~ENABLE_ECHO_INPUT; // disable echo + mode &= ~ENABLE_LINE_INPUT; // disable line buffer + // TODO: enable ctrl-c again + mode &= ~ENABLE_PROCESSED_INPUT; // disable ctrl-c + mode |= ENABLE_WINDOW_INPUT; // enable resize event + mode |= ENABLE_MOUSE_INPUT; // enable mouse event + mode |= ENABLE_EXTENDED_FLAGS; // for ENABLE_QUICK_EDIT + mode &= ~ENABLE_QUICK_EDIT_MODE; // disable select mode + SetConsoleMode(hin, mode); // set input mode + // + HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); // + GetConsoleMode(hout, &tim.mode_out); // get current output mode + mode = tim.mode_out; // + mode |= ENABLE_PROCESSED_OUTPUT; // enable ascii sequences + mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; // enable vt sequences + SetConsoleMode(hout, mode); // set output mode + // + tim.cp_in = GetConsoleCP(); // get current code page + tim.cp_out = GetConsoleOutputCP(); // + SetConsoleCP(CP_UTF8); // set utf8 in/out code page + SetConsoleOutputCP(CP_UTF8); // + tim_write_str(S("\33[?1049h")); // use alternate buffer + tim_update_screen_size(); // +} + +void tim_reset_terminal(void) { + tim_write_str(S("\33[m")); // reset colors + tim_write_str(S("\33[?25h")); // show cursor + tim_write_str(S("\33[?1049l")); // exit alternate buffer + HANDLE hin = GetStdHandle(STD_INPUT_HANDLE); // + HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); // + SetConsoleMode(hin, tim.mode_in); // set original mode + SetConsoleMode(hout, tim.mode_out); // + SetConsoleCP(tim.cp_in); // set original code page + SetConsoleOutputCP(tim.cp_out); // +} + +void tim_read_event(i32 timeout_ms) { + TimEvent* e = &tim.event; + HANDLE h = GetStdHandle(STD_INPUT_HANDLE); + + const i8 key_table[256] = { + [VK_BACK] = TimKey_Backspace, + [VK_TAB] = TimKey_Tab, + [VK_RETURN] = TimKey_Enter, + [VK_ESCAPE] = TimKey_Escape, + [VK_PRIOR] = TimKey_PageUp, + [VK_NEXT] = TimKey_PageDown, + [VK_END] = TimKey_End, + [VK_HOME] = TimKey_Home, + [VK_LEFT] = TimKey_Left, + [VK_UP] = TimKey_Up, + [VK_RIGHT] = TimKey_Right, + [VK_DOWN] = TimKey_Down, + [VK_INSERT] = TimKey_Insert, + [VK_DELETE] = TimKey_Delete, + }; + + while (true) { + memset(e, 0, sizeof(*e)); + // In cmd.exe the cursor somtimes reappears. This reliably hides it. + tim_write_str(S("\33[?25l")); + + DWORD r = WaitForSingleObject(h, timeout_ms); + if (r == WAIT_TIMEOUT) { + e->type = TimEvent_Draw; + tim_update_screen_size(); // workaround, see WINDOW_BUFFER_SIZE_EVENT + return; + } else if (r != WAIT_OBJECT_0) { + continue; + } + + // received input + INPUT_RECORD rec = {0}; + DWORD n = 0; + ReadConsoleInput(h, &rec, 1, &n); + + switch (rec.EventType) { + case KEY_EVENT: { + if (!rec.Event.KeyEvent.bKeyDown) { + // only interested in key press + continue; + } + i32 key = key_table[(u8)rec.Event.KeyEvent.wVirtualKeyCode]; + WCHAR chr = rec.Event.KeyEvent.uChar.UnicodeChar; + if (!key && chr < ' ') { + // non printable key + continue; + } + tim_update_screen_size(); // workaround, see WINDOW_BUFFER_SIZE_EVENT + e->type = TimEvent_Key; + if (key) { + e->key = key; + return; + } + e->key = chr; + WideCharToMultiByte(CP_UTF8, 0, &chr, 1, e->s, sizeof(e->s), + NULL, NULL); + return; + } + + case MOUSE_EVENT: { + bool move = rec.Event.MouseEvent.dwEventFlags & ~DOUBLE_CLICK; + bool left = rec.Event.MouseEvent.dwButtonState & + FROM_LEFT_1ST_BUTTON_PRESSED; + if (move || !left) { + // ignore move events and buttons other than left + continue; + } + tim_update_screen_size(); // workaround, see WINDOW_BUFFER_SIZE_EVENT + e->type = TimEvent_Mouse; + e->key = TimKey_MouseButtonLeft; + e->x = rec.Event.MouseEvent.dwMousePosition.X - tim.window.Left; + e->y = rec.Event.MouseEvent.dwMousePosition.Y - tim.window.Top; + return; + } + + case WINDOW_BUFFER_SIZE_EVENT: + e->type = TimEvent_Draw; + // cmd.exe screen buffer and window size are separate, making this + // event a bit unreliable. Effectively it is only emitted when the + // terminal width changes and not for the height. As a workaround + // the screen size is updated every time an event is emitted. + tim_update_screen_size(); + return; + } + } // while +} + +#endif // TIM_WINDOWS diff --git a/test/color.c b/test/color.c index 95b7061..17f8a66 100644 --- a/test/color.c +++ b/test/color.c @@ -2,27 +2,27 @@ #include "tim.h" -static void foo(int x, int y, int c) { +static void foo(i32 x, i32 y, i32 c) { char buf[16] = {0}; sprintf(buf, " %02x ", c); - draw_str(buf, x * 4, y, 4, 0, c); + tim_draw_str(buf, x * 4, y, 4, 0, c); } -int main(void) { +i32 main(void) { while (tim_run(0)) { - for (int i = 0; i < 16; i++) { + for (i32 i = 0; i < 16; i++) { foo(i % 8, i / 8, i); } - for (int i = 0; i < 108; i++) { + for (i32 i = 0; i < 108; i++) { foo(i % 6, i / 6 + 3, i + 16); } - for (int i = 0; i < 108; i++) { + for (i32 i = 0; i < 108; i++) { foo(i % 6 + 7, i / 6 + 3, i + 124); } - for (int i = 0; i < 24; i++) { + for (i32 i = 0; i < 24; i++) { foo(i % 12, i / 12 + 22, i + 232); } - if (is_key_press('q') || is_key_press(TimKey_Escape)) { + if (tim_is_key_press('q') || tim_is_key_press(TimKey_Escape)) { exit(1); } } diff --git a/test/string.c b/test/string.c index fdf9e83..6a94b25 100644 --- a/test/string.c +++ b/test/string.c @@ -2,69 +2,69 @@ #include "tim.h" -#define U(s) (uint8_t*)(""s), (sizeof(s) - 1) +#define U(s) (u8*)(""s), (sizeof(s) - 1) #define TEST(t) printf("\33[3%s\33[0m %s\n", (t) ? "2mpass" : "1mfail", #t) -int main(void) { +i32 main(void) { (void)tim_run; - TEST(ztrlen(NULL) == 0); - TEST(ztrlen("") == 0); - TEST(ztrlen("$") == 1); - TEST(ztrlen("£") == 2); - TEST(ztrlen("€") == 3); - TEST(ztrlen("𐍈") == 4); + TEST(tim_ztrlen(NULL) == 0); + TEST(tim_ztrlen("") == 0); + TEST(tim_ztrlen("$") == 1); + TEST(tim_ztrlen("£") == 2); + TEST(tim_ztrlen("€") == 3); + TEST(tim_ztrlen("𐍈") == 4); - TEST(bsr8(128) == 0); - TEST(bsr8(64) == 1); - TEST(bsr8(1) == 7); - TEST(bsr8(0) == 8); + TEST(tim_bsr8(128) == 0); + TEST(tim_bsr8(64) == 1); + TEST(tim_bsr8(1) == 7); + TEST(tim_bsr8(0) == 8); - TEST(utfchr(NULL) == 0); - TEST(utfchr("") == 0); - TEST(utfchr("$") == 0x24); - TEST(utfchr("£") == 0xA3); - TEST(utfchr("И") == 0x418); - TEST(utfchr("ह") == 0x939); - TEST(utfchr("€") == 0x20AC); - TEST(utfchr("한") == 0xD55C); - TEST(utfchr("𐍈") == 0x10348); + TEST(tim_utf8_to_i32(NULL) == 0); + TEST(tim_utf8_to_i32("") == 0); + TEST(tim_utf8_to_i32("$") == 0x24); + TEST(tim_utf8_to_i32("£") == 0xA3); + TEST(tim_utf8_to_i32("И") == 0x418); + TEST(tim_utf8_to_i32("ह") == 0x939); + TEST(tim_utf8_to_i32("€") == 0x20AC); + TEST(tim_utf8_to_i32("한") == 0xD55C); + TEST(tim_utf8_to_i32("𐍈") == 0x10348); - TEST(utflen(NULL) == 0); - TEST(utflen("") == 0); - TEST(utflen("$") == 1); - TEST(utflen("$$") == 2); - TEST(utflen("$£") == 2); - TEST(utflen("$€𐍈") == 3); + TEST(tim_utf8_len(NULL) == 0); + TEST(tim_utf8_len("") == 0); + TEST(tim_utf8_len("$") == 1); + TEST(tim_utf8_len("$$") == 2); + TEST(tim_utf8_len("$£") == 2); + TEST(tim_utf8_len("$€𐍈") == 3); - TEST(utfpos(NULL, 0) == 0); - TEST(utfpos("ab", -1) == 0); - TEST(utfpos("äbc", 0) == 0); - TEST(utfpos("äbc", 1) == 2); - TEST(utfpos("äbc", 2) == 3); - TEST(utfpos("äbc", 9) == 4); + TEST(tim_utf8_pos(NULL, 0) == 0); + TEST(tim_utf8_pos("ab", -1) == 0); + TEST(tim_utf8_pos("äbc", 0) == 0); + TEST(tim_utf8_pos("äbc", 1) == 2); + TEST(tim_utf8_pos("äbc", 2) == 3); + TEST(tim_utf8_pos("äbc", 9) == 4); - TEST(scan_str(NULL).lines == 0); - TEST(scan_str("").lines == 0); - TEST(scan_str("abc").lines == 1); - TEST(scan_str("a\no").lines == 2); - TEST(scan_str("a").width == 1); - TEST(scan_str("äß\no").width == 2); + TEST(tim_scan_str(NULL).lines == 0); + TEST(tim_scan_str("").lines == 0); + TEST(tim_scan_str("abc").lines == 1); + TEST(tim_scan_str("a\no").lines == 2); + TEST(tim_scan_str("a").width == 1); + TEST(tim_scan_str("äß\no").width == 2); - TimLine_t ln = {.s = "foo\nbar"}; - TEST(next_line(&ln) == true); + TimLine ln = {.s = "foo\nbar"}; + TEST(tim_next_line(&ln) == true); TEST(!memcmp(ln.line, "foo", ln.size)); - TEST(next_line(&ln) == true); + TEST(tim_next_line(&ln) == true); TEST(!memcmp(ln.line, "bar", ln.size)); - TEST(next_line(&ln) == false); + TEST(tim_next_line(&ln) == false); - TEST(is_wide_perhaps(NULL, 0) == false); - TEST(is_wide_perhaps(U("")) == false); - TEST(is_wide_perhaps(U("$")) == false); - TEST(is_wide_perhaps(U("£")) == false); - TEST(is_wide_perhaps(U("ह")) == false); - TEST(is_wide_perhaps(U("€")) == true); - TEST(is_wide_perhaps(U("┌")) == false); - TEST(is_wide_perhaps(U("한")) == true); - TEST(is_wide_perhaps(U("𐍈")) == true); + TEST(tim_utf8_is_wide_perhaps(NULL, 0) == false); + TEST(tim_utf8_is_wide_perhaps(U("")) == false); + TEST(tim_utf8_is_wide_perhaps(U("$")) == false); + TEST(tim_utf8_is_wide_perhaps(U("£")) == false); + TEST(tim_utf8_is_wide_perhaps(U("ह")) == false); + TEST(tim_utf8_is_wide_perhaps(U("€")) == true); + TEST(tim_utf8_is_wide_perhaps(U("┌")) == false); + TEST(tim_utf8_is_wide_perhaps(U("한")) == true); + TEST(tim_utf8_is_wide_perhaps(U("𐍈")) == true); } diff --git a/test/test.c b/test/test.c index 6ae9ff8..d6bc636 100644 --- a/test/test.c +++ b/test/test.c @@ -1,9 +1,9 @@ #include "tim.h" -static inline void test_screen(TimEvent_t* e) { - static TimEvent_t me; - static TimEvent_t ke; - static int render_us; +static inline void test_screen(TimEvent* e) { + static TimEvent me; + static TimEvent ke; + static i32 render_us; char buf[64]; ke = (e->type == KEY_EVENT) ? *e : ke; @@ -46,8 +46,8 @@ static inline void test_screen(TimEvent_t* e) { label("multi\nliñe\nlabël", 24, 1, A, A, 0xf); // colors - scope (1, 5, 16, 5) { - frame(0, 0, ~0, ~0, 0xf); + tim_scope(1, 5, 16, 5) { + tim_frame(0, 0, ~0, ~0, 0xf); label(" Red ", 1, 1, 7, A, 0x0900); label(" ", 8, 1, 7, A, 0xc400); label(" Green ", 1, 2, 7, A, 0x0a00); @@ -57,16 +57,16 @@ static inline void test_screen(TimEvent_t* e) { } // button - static uint64_t bc = 0x100; + static u64 bc = 0x100; if (button("Click Me", 17, 5, 16, 5, bc)) { bc = (bc + 0x100) & 0xff00; } // edit - static TimEdit_t ed1; - static TimEdit_t ed2; - edit_init(&ed1, 32, "Edit 1"); - edit_init(&ed2, 32, ""); + static TimEditState ed1; + static TimEditState ed2; + TimEditState_init(&ed1, 32, "Edit 1"); + TimEditState_init(&ed2, 32, ""); edit(&ed1, 1, 10, 32, 0xff00ff); sprintf(buf, "cursor: %d length: %d", ed1.cursor, ed1.length); label(buf, 2, 13, A, A, 0xf); @@ -74,46 +74,46 @@ static inline void test_screen(TimEvent_t* e) { label(ed2.s, 2, 17, A, A, 0xf); // checkbox - static int chk[2] = {-1, 1}; + static i32 chk[2] = {-1, 1}; check("Check 1", &chk[0], 1, 18, A, 0xa000f); check("Check 2", &chk[1], 14, 18, A, 0xa000f); // radiobox - static int rad = 0; + static i32 rad = 0; radio("Radio 1", &rad, 1, 1, 19, A, 0xa000f); radio("Radio 2", &rad, 2, 14, 19, A, 0xa000f); radio("Radio 3", &rad, 3, 1, 20, A, 0xa000f); radio("Radio 4", &rad, 4, 14, 20, A, 0xa000f); // scope nesting - scope(~1, 1, 20, 10) { - scope(0, 0, 10, 5) { - frame(0, 0, ~0, ~0, 0x9); + tim_scope(~1, 1, 20, 10) { + tim_scope(0, 0, 10, 5) { + tim_frame(0, 0, ~0, ~0, 0x9); } - scope(~0, 0, 10, 5) { - frame(0, 0, ~0, ~0, 0xa); + tim_scope(~0, 0, 10, 5) { + tim_frame(0, 0, ~0, ~0, 0xa); } - scope(~0, ~0, 10, 5) { - frame(0, 0, ~0, ~0, 0xb); + tim_scope(~0, ~0, 10, 5) { + tim_frame(0, 0, ~0, ~0, 0xb); } - scope(0, ~0, 10, 5) { - frame(0, 0, ~0, ~0, 0xc); + tim_scope(0, ~0, 10, 5) { + tim_frame(0, 0, ~0, ~0, 0xc); } } // funny characters - scope (~1, ~3, 11, 5) { - frame(0, 0, ~0, ~0, 0xf); + tim_scope(~1, ~3, 11, 5) { + tim_frame(0, 0, ~0, ~0, 0xf); label("123456789", 1, 1, 9, A, 0x0f05); label("$£ह€𐍈6789", 1, 2, A, A, 0x0f05); label("圍棋56789", 1, 3, A, A, 0x0f05); } } -int main(void) { +i32 main(void) { while (tim_run(1.5)) { test_screen(&tim.event); - if (is_key_press('q') || is_key_press(TimKey_Escape)) { + if (tim_is_key_press('q') || tim_is_key_press(TimKey_Escape)) { break; } } diff --git a/test/width.c b/test/width.c index 493a09d..2a89ebc 100644 --- a/test/width.c +++ b/test/width.c @@ -3,7 +3,7 @@ #include #include "tim.h" -static int cp_to_utf8(int32_t cp, char* s) { +static i32 cp_to_utf8(i32 cp, char* s) { assert(cp > 0 && cp < 0x110000); if (cp < 0x80) { @@ -29,33 +29,33 @@ static int cp_to_utf8(int32_t cp, char* s) { return -1; } -static int cursor_pos() { +static i32 cursor_pos() { write(STDOUT_FILENO, S("\33[6n")); char buf[64] = {0}; - int n = read(STDIN_FILENO, buf, 64); + i32 n = read(STDIN_FILENO, buf, 64); if (n < 6 || buf[0] != '\33' || buf[n - 1] != 'R') { return -1; } - int r = atoi(buf + 2); - int c = atoi(buf + 4 + (r > 9)); + i32 r = atoi(buf + 2); + i32 c = atoi(buf + 4 + (r > 9)); return c; } -int main(int argc, char** argv) { +i32 main(i32 argc, char** argv) { assert(argc == 2); (void)tim_run; FILE* f = fopen(argv[1], "w"); assert(f); - init_terminal(); + tim_init_terminal(); - for (int i = 32; i < 0x110000; i++) { + for (i32 i = 32; i < 0x110000; i++) { write(STDOUT_FILENO, S("\33[0;0H")); char buf[5] = {0}; - int n = cp_to_utf8(i, buf); + i32 n = cp_to_utf8(i, buf); write(STDOUT_FILENO, buf, n); - int w = cursor_pos() - 1; + i32 w = cursor_pos() - 1; if (w) { fprintf(f, "u+%06x %d %s\n", i, w, buf); } else { @@ -63,7 +63,7 @@ int main(int argc, char** argv) { } } - reset_terminal(); + tim_reset_terminal(); fclose(f); }