[
  {
    "path": "Makefile",
    "content": "all: tuibox\n\nCC=cc\n\nLIBS=-lm\nCFLAGS=-O3 -pipe\nDEBUGCFLAGS=-Og -pipe -g\n\n.PHONY: tuibox\ntuibox:\n\t$(CC) demos/demo_basic.c -o demos/demo_basic $(LIBS) $(CFLAGS)\n\t$(CC) demos/demo_bounce.c -o demos/demo_bounce $(LIBS) $(CFLAGS)\n\t$(CC) demos/demo_drag.c -o demos/demo_drag $(LIBS) $(CFLAGS)\n"
  },
  {
    "path": "README.md",
    "content": "# tuibox\n\ntuibox (\"toybox\") is a single-header terminal UI library, capable of creating mouse-driven, interactive applications on the command line.\n\nIt is completely dependency-free, using nothing other than ANSI escape sequences to handle parsing input and rendering the screen.  Additionally, its individual parts work largely independently, meaning as much (or as little) of the library as is necessary can be used.\n\n## Demos\n\n[demo_basic.c](https://github.com/Cubified/tuibox/blob/main/demos/demo_basic.c):  Basic button demo w/ truecolor background\n- (Note:  Ugly color banding is from GIF compression)\n\n![demo_basic.gif](https://github.com/Cubified/tuibox/blob/main/demos/demo_basic.gif)\n\n[demo_bounce.c](https://github.com/Cubified/tuibox/blob/main/demos/demo_bounce.c):  Bouncing box using custom render loop\n\n![demo_bounce.gif](https://github.com/Cubified/tuibox/blob/main/demos/demo_bounce.gif)\n\n[demo_drag.c](https://github.com/Cubified/tuibox/blob/main/demos/demo_drag.c):  Click and drag\n\n![demo_drag.gif](https://github.com/Cubified/tuibox/blob/main/demos/demo_drag.gif)\n\n[bdfedit](https://github.com/Cubified/bdfedit), a bitmap font editor built using `tuibox`:\n\n![demo_bdfedit.gif](https://github.com/Cubified/tuibox/blob/main/demos/demo_bdfedit.gif)\n\n[colorslide](https://github.com/Cubified/colorslide), an RGBA/HSL/CMYK color picker built using `tuibox`:\n\n![demo_colorslide.gif](https://github.com/Cubified/tuibox/blob/main/demos/demo_colorslide.gif)\n\n[vt100utils](https://github.com/Cubified/vt100utils), an ANSI graphics escape sequence encoder/decoder that can couple with `tuibox` for content-aware text processing:\n\n![demo_vt100utils.gif](https://github.com/Cubified/tuibox/blob/main/demos/demo_vt100utils.gif)\n\n## Features\n\ntuibox currently contains the following:\n\n- Event-driven terminal render loop\n- Mouse click and hover events on individual UI elements\n- Keyboard events (including escape sequence events, such as arrow keys)\n- Render caching (controlled by a user-defined dirty bit)\n- Completely dependency-free, using pure ANSI escape sequences (no ncurses)\n- Incrementally-adoptable -- rendering, events, and loops can all be used independently\n\n## Design Overview\n\nThe basic hierarchy of a UI looks like this:\n\n- UI:  A collection of screens\n    - Screen:  A collection of boxes\n        - Box:  Any UI element\n            - X/Y, width/height\n            - Render function\n            - Click event function (optional)\n            - Hover event function (optional)\n            - Arbitrary user data (see below example)\n- Main loop:  Infinite `read()` loop gathering user input\n    - Update:  Parses user input and fires events (if applicable)\n\n## Example Code\n\nA complete example is as follows:\n\n```c\n/* Global UI struct */\nui_t u;\n\n/* Function that runs on box click */\nvoid click(ui_box_t *b, int x, int y){\n  b->data1 = \"you clicked me!\";\n  ui_draw(&u);\n}\n\n/* Function that runs on box hover */\nvoid hover(ui_box_t *b, int x, int y, int down){\n  b->data1 = \"you hovered me!\";\n  ui_draw(&u);\n}\n\nvoid stop(){\n  ui_free(&u);\n  exit(0);\n}\n\nint main(){\n  /* Initialize UI data structures */\n  ui_new(0, &u);\n\n  /* Add a new text box to screen 0 */\n  ui_text(\n    UI_CENTER_X, UI_CENTER_Y,\n    \"hello world!\",\n    0,\n    click, hover,\n    &u\n  );\n\n  /* Register an event on the q key */\n  ui_key(\"q\", stop, &u);\n\n  /* Render the screen */\n  ui_draw(&u);\n\n  ui_loop(&u){\n    /* Check for mouse/keyboard events */\n    ui_update(&u);\n  }\n\n  return 0;\n}\n```\n\n## Compiling and Running Demos\n\nTo compile all demos in one go:\n\n     $ make\n\n     $ ./demo_basic\n     $ ./demo_bounce\n     $ ./demo_drag\n\n## Libraries/See Also\n\n- [vec](https://github.com/rxi/vec):  Dynamic arrays used to store events and boxes\n- [bdfedit](https://github.com/Cubified/bdfedit):  Bitmap font editor written alongside `tuibox`\n"
  },
  {
    "path": "demos/demo_basic.c",
    "content": "/*\n * demo_basic.c: a simple tuibox.h demo\n */\n\n#include <math.h>\n\n#include \"../tuibox.h\"\n\n/* Global UI struct */\nui_t u;\n\n/* Functions that generate box contents */\nvoid text(ui_box_t *b, char *out){\n  sprintf(out, \"%s\", (char*)b->data1);\n}\n\nvoid draw(ui_box_t *b, char *out){\n  int x, y, len = 0, max = MAXCACHESIZE;\n\n  sprintf(out, \"\");\n  for(y=0;y<b->h;y++){\n    for(x=0;x<b->w;x++){\n      /* Truecolor string to generate gradient */\n      len += sprintf(out + len, \"\\x1b[48;2;%i;%i;%im \", (int)round(255 * ((double)x / (double)b->w)), (int)round(255 * ((double)y / (double)b->h)), (int)round(255 * ((double)x * (double)y / ((double)b->w * (double)b->h))));\n\n      if(len + 1024 > max){\n        out = realloc(out, (max *= 2));\n      }\n    }\n    strcat(out + len, \"\\x1b[0m\\n\");\n  }\n}\n\n/* Function that runs on box click */\nvoid click(ui_box_t *b, int x, int y){\n  b->data1 = \"\\x1b[0m                \\n  you clicked me!  \\n                \",\n  ui_draw_one(b, 1, &u);\n}\n\n/* Function that runs on box hover */\nvoid hover(ui_box_t *b, int x, int y, int down){\n  b->data1 = \"\\x1b[0m                \\n  you hovered me!  \\n                \",\n  ui_draw_one(b, 1, &u);\n}\n\nvoid stop(){\n  ui_free(&u);\n  exit(0);\n}\n\nint main(){\n  /* Initialize UI data structures */\n  ui_new(0, &u);\n\n  /* Add new UI elements to screen 0 */\n  ui_add(\n    1, 1,\n    u.ws.ws_col, u.ws.ws_row,\n    0,\n    NULL, 0,\n    draw,\n    NULL,\n    NULL,\n    NULL,\n    NULL,\n    &u\n  );\n\n  ui_text(\n    ui_center_x(19, &u), UI_CENTER_Y,\n    \"\\x1b[0m                   \\n    click on me!   \\n                   \",\n    0,\n    click, hover,\n    &u\n  );\n\n  /* Register an event on the q key */\n  ui_key(\"q\", stop, &u);\n\n  /* Render the screen */\n  ui_draw(&u);\n\n  ui_loop(&u){\n    /* Check for mouse/keyboard events */\n    ui_update(&u);\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "demos/demo_bounce.c",
    "content": "/*\n * demo_bounce.c: bouncing physics box\n */\n\n#include <math.h>\n#include <signal.h>\n\n#include \"../tuibox.h\"\n\nui_t u;\n\nvoid draw(ui_box_t *b, char *out){\n  int x, y;\n  char tmp[256];\n\n  sprintf(out, \"\\x1b[48;2;255;255;255m\");\n  for(y=0;y<b->h;y++){\n    for(x=0;x<b->w;x++){\n      strcat(out, \" \");\n    }\n    strcat(out, \"\\n\");\n  }\n}\n\nvoid stop(){\n  ui_free(&u);\n  exit(0);\n}\n\nint main(){\n  char watch = 0;\n  int id;\n  ui_box_t *b;\n\n  double vx = 2.0, vy = 0.0,\n         ax = 0.0, ay = 0.1;\n\n  ui_new(0, &u);\n\n  /* Add new UI elements to screen 0 */\n  id = ui_add(\n    10, 10,\n    20, 10,\n    0,\n    &watch, 1, /* Dirty bit: Run draw() once, and never again */\n    draw,\n    NULL, NULL,\n    NULL, NULL,\n    &u\n  );\n  b = ui_get(id, &u);\n\n  signal(SIGTERM, stop);\n  signal(SIGQUIT, stop);\n  signal(SIGINT,  stop);\n\n  for(;;){\n    b->x = round((double)b->x + vx);\n    b->y = round((double)b->y + vy);\n\n    vx += ax;\n    vy += ay;\n\n    if(b->x < 1){\n      b->x = 1;\n      vx *= -1.0;\n    } else if(b->x+b->w > u.ws.ws_col){\n      b->x = u.ws.ws_col - b->w;\n      vx *= -1.0;\n    }\n\n    if(b->y < 1){\n      b->y = 1;\n      vy *= -1.0;\n    } else if(b->y+b->h > u.ws.ws_row){\n      b->y = u.ws.ws_row - b->h;\n      vy *= -1.0;\n    }\n\n    ui_draw(&u);\n    usleep(10000);\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "demos/demo_drag.c",
    "content": "/*\n * demo_drag.c: click and drag\n */\n\n#include <math.h>\n\n#include \"../tuibox.h\"\n\n/* Global UI struct */\nui_t u;\n\nint sx = 15, sy = 7;\n\n/* Functions that generate box contents */\nvoid text(ui_box_t *b, char *out){\n  sprintf(out, \"%s\", (char*)b->data1);\n}\n\nvoid draw(ui_box_t *b, char *out){\n  int x, y;\n  char tmp[256];\n\n  sprintf(out, \"\");\n  for(y=0;y<b->h;y++){\n    for(x=0;x<b->w;x++){\n      /* Truecolor string to generate gradient */\n      sprintf(tmp, \"\\x1b[48;2;%i;%i;%im \", (int)round(255 * ((double)x * (double)y / ((double)b->w * (double)b->h))), (int)round(255 * ((double)y / (double)b->h)), (int)round(255 * ((double)x / (double)b->w)));\n      strcat(out, tmp);\n    }\n    strcat(out, \"\\x1b[0m\\n\");\n  }\n}\n\n/* Function that runs on box click */\nvoid click(ui_box_t *b, int x, int y){\n  sx = x-b->x;\n  sy = y-b->y;\n}\n\n/* Function that runs on box hover */\nvoid hover(ui_box_t *b, int x, int y, int down){\n  if(down){\n    b->x = x-sx;\n    b->y = y-sy;\n    ui_draw(&u);\n  }\n}\n\nvoid stop(){\n  ui_free(&u);\n  exit(0);\n}\n\nint main(){\n  char watch = 0;\n\n  /* Initialize UI data structures */\n  ui_new(0, &u);\n\n  /* Add new UI elements to screen 0 */\n  ui_add(\n    10, 10,\n    30, 15,\n    0,\n    &watch, 1,\n    draw,\n    click,\n    hover,\n    NULL,\n    NULL,\n    &u\n  );\n\n  /* Register an event on the q key */\n  ui_key(\"q\", stop, &u);\n\n  /* Render the screen */\n  ui_draw(&u);\n\n  ui_loop(&u){\n    /* Check for mouse/keyboard events */\n    ui_update(&u);\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "tuibox.h",
    "content": "/*\n * tuibox.h: simple tui library\n */\n\n#ifndef TUIBOX_H\n#define TUIBOX_H\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <string.h>\n#include <termios.h>\n#include <sys/ioctl.h>\n\n/** BEGIN vec.h **/\n\n/** \n * Copyright (c) 2014 rxi\n *\n * This library is free software; you can redistribute it and/or modify it\n * under the terms of the MIT license. See LICENSE for details.\n */\n\n#ifndef VEC_H\n#define VEC_H\n\n#include <stdlib.h>\n#include <string.h>\n\n#define VEC_VERSION \"0.2.1\"\n\n\n#define vec_unpack_(v)\\\n  (char**)&(v)->data, &(v)->length, &(v)->capacity, sizeof(*(v)->data)\n\n\n#define vec_t(T)\\\n  struct { T *data; int length, capacity; }\n\n\n#define vec_init(v)\\\n  memset((v), 0, sizeof(*(v)))\n\n\n#define vec_deinit(v)\\\n  ( free((v)->data),\\\n    vec_init(v) ) \n\n\n#define vec_push(v, val)\\\n  ( vec_expand_(vec_unpack_(v)) ? -1 :\\\n    ((v)->data[(v)->length++] = (val), 0), 0 )\n\n\n#define vec_pop(v)\\\n  (v)->data[--(v)->length]\n\n\n#define vec_splice(v, start, count)\\\n  ( vec_splice_(vec_unpack_(v), start, count),\\\n    (v)->length -= (count) )\n\n\n#define vec_swapsplice(v, start, count)\\\n  ( vec_swapsplice_(vec_unpack_(v), start, count),\\\n    (v)->length -= (count) )\n\n\n#define vec_insert(v, idx, val)\\\n  ( vec_insert_(vec_unpack_(v), idx) ? -1 :\\\n    ((v)->data[idx] = (val), 0), (v)->length++, 0 )\n    \n\n#define vec_sort(v, fn)\\\n  qsort((v)->data, (v)->length, sizeof(*(v)->data), fn)\n\n\n#define vec_swap(v, idx1, idx2)\\\n  vec_swap_(vec_unpack_(v), idx1, idx2)\n\n\n#define vec_truncate(v, len)\\\n  ((v)->length = (len) < (v)->length ? (len) : (v)->length)\n\n\n#define vec_clear(v)\\\n  ((v)->length = 0)\n\n\n#define vec_first(v)\\\n  (v)->data[0]\n\n\n#define vec_last(v)\\\n  (v)->data[(v)->length - 1]\n\n\n#define vec_reserve(v, n)\\\n  vec_reserve_(vec_unpack_(v), n)\n\n \n#define vec_compact(v)\\\n  vec_compact_(vec_unpack_(v))\n\n\n#define vec_pusharr(v, arr, count)\\\n  do {\\\n    int i__, n__ = (count);\\\n    if (vec_reserve_po2_(vec_unpack_(v), (v)->length + n__) != 0) break;\\\n    for (i__ = 0; i__ < n__; i__++) {\\\n      (v)->data[(v)->length++] = (arr)[i__];\\\n    }\\\n  } while (0)\n\n\n#define vec_extend(v, v2)\\\n  vec_pusharr((v), (v2)->data, (v2)->length)\n\n\n#define vec_find(v, val, idx)\\\n  do {\\\n    for ((idx) = 0; (idx) < (v)->length; (idx)++) {\\\n      if ((v)->data[(idx)] == (val)) break;\\\n    }\\\n    if ((idx) == (v)->length) (idx) = -1;\\\n  } while (0)\n\n\n#define vec_remove(v, val)\\\n  do {\\\n    int idx__;\\\n    vec_find(v, val, idx__);\\\n    if (idx__ != -1) vec_splice(v, idx__, 1);\\\n  } while (0)\n\n\n#define vec_reverse(v)\\\n  do {\\\n    int i__ = (v)->length / 2;\\\n    while (i__--) {\\\n      vec_swap((v), i__, (v)->length - (i__ + 1));\\\n    }\\\n  } while (0)\n\n\n#define vec_foreach(v, var, iter)\\\n  if  ( (v)->length > 0 )\\\n  for ( (iter) = 0;\\\n        (iter) < (v)->length && (((var) = (v)->data[(iter)]), 1);\\\n        ++(iter))\n\n\n#define vec_foreach_rev(v, var, iter)\\\n  if  ( (v)->length > 0 )\\\n  for ( (iter) = (v)->length - 1;\\\n        (iter) >= 0 && (((var) = (v)->data[(iter)]), 1);\\\n        --(iter))\n\n\n#define vec_foreach_ptr(v, var, iter)\\\n  if  ( (v)->length > 0 )\\\n  for ( (iter) = 0;\\\n        (iter) < (v)->length && (((var) = &(v)->data[(iter)]), 1);\\\n        ++(iter))\n\n\n#define vec_foreach_ptr_rev(v, var, iter)\\\n  if  ( (v)->length > 0 )\\\n  for ( (iter) = (v)->length - 1;\\\n        (iter) >= 0 && (((var) = &(v)->data[(iter)]), 1);\\\n        --(iter))\n\n\n\nint vec_expand_(char **data, int *length, int *capacity, int memsz);\nint vec_reserve_(char **data, int *length, int *capacity, int memsz, int n);\nint vec_reserve_po2_(char **data, int *length, int *capacity, int memsz,\n                     int n);\nint vec_compact_(char **data, int *length, int *capacity, int memsz);\nint vec_insert_(char **data, int *length, int *capacity, int memsz,\n                int idx);\nvoid vec_splice_(char **data, int *length, int *capacity, int memsz,\n                 int start, int count);\nvoid vec_swapsplice_(char **data, int *length, int *capacity, int memsz,\n                     int start, int count);\nvoid vec_swap_(char **data, int *length, int *capacity, int memsz,\n               int idx1, int idx2);\n\n\ntypedef vec_t(void*) vec_void_t;\ntypedef vec_t(char*) vec_str_t;\ntypedef vec_t(int) vec_int_t;\ntypedef vec_t(char) vec_char_t;\ntypedef vec_t(float) vec_float_t;\ntypedef vec_t(double) vec_double_t;\n\nint vec_expand_(char **data, int *length, int *capacity, int memsz) {\n  if (*length + 1 > *capacity) {\n    void *ptr;\n    int n = (*capacity == 0) ? 1 : *capacity << 1;\n    ptr = realloc(*data, n * memsz);\n    if (ptr == NULL) return -1;\n    *data = ptr;\n    *capacity = n;\n  }\n  return 0;\n}\n\n\nint vec_reserve_(char **data, int *length, int *capacity, int memsz, int n) {\n  (void) length;\n  if (n > *capacity) {\n    void *ptr = realloc(*data, n * memsz);\n    if (ptr == NULL) return -1;\n    *data = ptr;\n    *capacity = n;\n  }\n  return 0;\n}\n\n\nint vec_reserve_po2_(\n  char **data, int *length, int *capacity, int memsz, int n\n) {\n  int n2 = 1;\n  if (n == 0) return 0;\n  while (n2 < n) n2 <<= 1;\n  return vec_reserve_(data, length, capacity, memsz, n2);\n}\n\n\nint vec_compact_(char **data, int *length, int *capacity, int memsz) {\n  if (*length == 0) {\n    free(*data);\n    *data = NULL;\n    *capacity = 0;\n    return 0;\n  } else {\n    void *ptr;\n    int n = *length;\n    ptr = realloc(*data, n * memsz);\n    if (ptr == NULL) return -1;\n    *capacity = n;\n    *data = ptr;\n  }\n  return 0;\n}\n\n\nint vec_insert_(char **data, int *length, int *capacity, int memsz,\n                 int idx\n) {\n  int err = vec_expand_(data, length, capacity, memsz);\n  if (err) return err;\n  memmove(*data + (idx + 1) * memsz,\n          *data + idx * memsz,\n          (*length - idx) * memsz);\n  return 0;\n}\n\n\nvoid vec_splice_(char **data, int *length, int *capacity, int memsz,\n                 int start, int count\n) {\n  (void) capacity;\n  memmove(*data + start * memsz,\n          *data + (start + count) * memsz,\n          (*length - start - count) * memsz);\n}\n\n\nvoid vec_swapsplice_(char **data, int *length, int *capacity, int memsz,\n                     int start, int count\n) {\n  (void) capacity;\n  memmove(*data + start * memsz,\n          *data + (*length - count) * memsz,\n          count * memsz);\n}\n\n\nvoid vec_swap_(char **data, int *length, int *capacity, int memsz,\n               int idx1, int idx2 \n) {\n  unsigned char *a, *b, tmp;\n  int count;\n  (void) length;\n  (void) capacity;\n  if (idx1 == idx2) return;\n  a = (unsigned char*) *data + idx1 * memsz;\n  b = (unsigned char*) *data + idx2 * memsz;\n  count = memsz;\n  while (count--) {\n    tmp = *a;\n    *a = *b;\n    *b = tmp;\n    a++, b++;\n  }\n}\n\n#endif\n\n/** END vec.h **/\n\n/*\n * PREPROCESSOR\n */\n#define MAXCACHESIZE 65535\n\n#define CURSOR_Y(b) (b->y+(n+1)+(u->canscroll ? u->scroll : 0))\n\n#define box_contains(x, y, b) (x >= b->x && x <= b->x + b->w && y >= b->y && y <= b->y + b->h)\n\n#define ui_screen(s, u) u->screen = s;u->force = 1\n\n#define ui_center_x(w, u) (((u)->ws.ws_col - w) / 2)\n#define ui_center_y(h, u) (((u)->ws.ws_row - h) / 2)\n\n#define UI_CENTER_X -1\n#define UI_CENTER_Y -1\n\n/* The argument isn't actually necessary here, but it helps with design consistency */\n#define ui_loop(u) char buf[64];int n;while((n=read(STDIN_FILENO, buf, sizeof(buf))) > 0)\n\n#define ui_update(u) _ui_update(buf, n, u)\n\n#define ui_get(id, u) ((u)->b.data[id])\n\n#define COORDINATE_DECODE() \\\n  tok = strtok(NULL, \";\"); \\\n  x = atoi(tok); \\\n  tok = strtok(NULL, \";\"); \\\n  y = strtol(tok, NULL, 10) - (u->canscroll ? u->scroll : 0)\n\n#define CLICK_COMPARATOR(x, y, tmp) \\\n  (u->click == tmp || \\\n   (box_contains(x, y, tmp) && u->click == NULL))\n\n#define HOVER_COMPARATOR(x, y, tmp) \\\n  (box_contains(x, y, tmp))\n\n#define LOOP_AND_EXECUTE(f, c) \\\n  do { \\\n    vec_foreach(&(u->b), tmp, ind){ \\\n      if(tmp->screen == u->screen && \\\n         f != NULL && \\\n         (c ? CLICK_COMPARATOR(x, y, tmp) : HOVER_COMPARATOR(x, y, tmp)) \\\n      ){ \\\n        f(tmp, x, y, u->mouse); \\\n        if(c){ \\\n          u->click = tmp; \\\n        } \\\n      } \\\n    } \\\n  } while(0)\n\n/*\n * TYPES\n */\ntypedef void (*func)();\n\ntypedef struct ui_box_t {\n  int id;\n  int x, y;\n  int w, h;\n  int screen;\n  char *cache;\n  char *watch;\n  char last;\n  func draw;\n  func onclick;\n  func onhover;\n  void *data1;\n  void *data2;\n} ui_box_t;\n\ntypedef struct ui_evt_t {\n  char *c;\n  func f;\n} ui_evt_t;\n\ntypedef vec_t(ui_box_t*) vec_box_t;\ntypedef vec_t(ui_evt_t*) vec_evt_t;\n\ntypedef struct ui_t {\n  struct termios tio;\n  struct winsize ws;\n  vec_box_t b;\n  vec_evt_t e;\n  ui_box_t *click;\n  int mouse, screen,\n      scroll, canscroll,\n      id, force;\n} ui_t;\n\n/* =========================== */\n\n/*\n * Initializes a new UI struct,\n *   puts the terminal into raw\n *   mode, and prints out the\n *   necessary escape codes\n *   for mouse support.\n */\nvoid ui_new(int s, ui_t *u){\n  struct termios raw;\n\n  ioctl(STDOUT_FILENO, TIOCGWINSZ, &(u->ws));\n\n  tcgetattr(STDIN_FILENO, &(u->tio));\n  raw = u->tio;\n  raw.c_lflag &= ~(ECHO | ICANON);\n  tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);\n\n  vec_init(&(u->b));\n  vec_init(&(u->e));\n\n  u->click = NULL;\n\n  printf(\"\\x1b[?1049h\\x1b[0m\\x1b[2J\\x1b[?1003h\\x1b[?1015h\\x1b[?1006h\\x1b[?25l\");\n\n  u->mouse = 0;\n\n  u->screen = s;\n  u->scroll = 0;\n  u->canscroll = 1;\n  \n  u->id = 0;\n\n  u->force = 0;\n}\n\n/*\n * Frees the given UI struct,\n *   and takes the terminal\n *   out of raw mode.\n */\nvoid ui_free(ui_t *u){\n  ui_box_t *val;\n  ui_evt_t *evt;\n  int i;\n  char *term;\n\n  printf(\"\\x1b[0m\\x1b[2J\\x1b[?1049l\\x1b[?1003l\\x1b[?1015l\\x1b[?1006l\\x1b[?25h\");\n  tcsetattr(STDIN_FILENO, TCSAFLUSH, &(u->tio));\n\n  vec_foreach(&(u->b), val, i){\n    free(val->cache);\n    free(val);\n  }\n  vec_deinit(&(u->b));\n\n  vec_foreach(&(u->e), evt, i){\n    free(evt);\n  }\n  vec_deinit(&(u->e));\n\n  term = getenv(\"TERM\");\n  if(strncmp(term, \"screen\", 6) == 0 ||\n     strncmp(term, \"tmux\", 4) == 0){\n    printf(\"Note: Terminal multiplexer detected.\\n  For best performance (i.e. reduced flickering), running natively inside\\n  a GPU-accelerated terminal such as alacritty or kitty is recommended.\\n\");\n  }\n}\n\n/*\n * Adds a new box to the UI.\n *\n * This function is very simple in\n *   nature, but the variety of\n *   properties associated with\n *   an individual box makes it\n *   intimidating to look at.\n * TODO: Find some way to\n *   strip this down.\n */\nint ui_add(\n  int x, int y, int w, int h, int screen,\n  char *watch, char initial,\n  func draw, func onclick, func onhover,\n  void *data1, void *data2,\n  ui_t *u\n){\n  ui_box_t *b = malloc(sizeof(ui_box_t));\n\n  b->id = u->id++;\n\n  b->x = (x == UI_CENTER_X ? ui_center_x(w, u) : x);\n  b->y = (y == UI_CENTER_Y ? ui_center_y(h, u) : y);\n  b->w = w;\n  b->h = h;\n\n  b->screen = u->screen;\n\n  b->watch = watch;\n  b->last = initial;\n\n  b->draw = draw;\n  b->onclick = onclick;\n  b->onhover = onhover;\n\n  b->data1 = data1;\n  b->data2 = data2;\n\n  b->cache = malloc(MAXCACHESIZE);\n  draw(b, b->cache);\n  b->cache = realloc(b->cache, strlen(b->cache) * 2);\n\n  vec_push(&(u->b), b);\n\n  return b->id;\n}\n\n/*\n * Adds a new key event listener\n *   to the UI.\n */\nvoid ui_key(char *c, func f, ui_t *u){\n  ui_evt_t *e = malloc(sizeof(ui_evt_t));\n  e->c = c;\n  e->f = f;\n\n  vec_push(&(u->e), e);\n}\n\n/*\n * Clears all elements from\n *   the UI.\n */\nvoid ui_clear(ui_t *u){\n  int tmp = u->screen;\n\n  ui_free(u);\n  ui_new(tmp, u);\n}\n\n/*\n * Draws a single box to the\n *   screen.\n */\nvoid ui_draw_one(ui_box_t *tmp, int flush, ui_t *u){\n  char *buf, *tok;\n  int n = -1;\n\n  if(tmp->screen != u->screen) return;\n  \n  buf = calloc(1, strlen(tmp->cache) * 2);\n  if(u->force ||\n     tmp->watch == NULL ||\n     *(tmp->watch) != tmp->last\n  ){\n    tmp->draw(tmp, buf);\n    if(tmp->watch != NULL) tmp->last = *(tmp->watch);\n    strcpy(tmp->cache, buf);\n  } else {\n    /* buf is allocated proportionally to tmp->cache, so strcpy is safe */\n    strcpy(buf, tmp->cache);\n  }\n  tok = strtok(buf, \"\\n\");\n  while(tok != NULL){\n    if(tmp->x > 0 &&\n       tmp->x < u->ws.ws_col &&\n       CURSOR_Y(tmp) > 0 &&\n       CURSOR_Y(tmp) < u->ws.ws_row){\n      printf(\"\\x1b[%i;%iH%s\", CURSOR_Y(tmp), tmp->x, tok);\n      n++;\n    }\n    tok = strtok(NULL, \"\\n\");\n  }\n  free(buf);\n\n  if(flush) fflush(stdout);\n}\n\n/*\n * Draws all boxes to the screen.\n */\nvoid ui_draw(ui_t *u){\n  ui_box_t *tmp;\n  int i;\n\n  printf(\"\\x1b[0m\\x1b[2J\");\n\n  vec_foreach(&(u->b), tmp, i){\n    ui_draw_one(tmp, 0, u);\n  }\n  fflush(stdout);\n  u->force = 0;\n}\n\n/*\n * Forces a redraw of the screen,\n *   updating all boxes' caches.\n */\nvoid ui_redraw(ui_t *u){\n  u->force = 1;\n  ui_draw(u);\n}\n\n/*\n * Handles mouse and keyboard\n *   events, given a read()\n *   buffer.\n *\n * This is prefixed with an underscore\n *   to ensure consistency with the\n *   ui_loop macro, ensuring that the\n *   variables buf and n remain\n *   opaque to the user.\n */\nvoid _ui_update(char *c, int n, ui_t *u){\n  ui_box_t *tmp;\n  ui_evt_t *evt;\n  int ind, x, y;\n  char cpy[n], *tok;\n\n  if(n >= 4 &&\n     c[0] == '\\x1b' &&\n     c[1] == '[' &&\n     c[2] == '<'){\n    strncpy(cpy, c, n);\n    tok = strtok(cpy+3, \";\");\n    \n    switch(tok[0]){\n      case '0':\n        u->mouse = (strchr(c, 'm') == NULL);\n        COORDINATE_DECODE();\n        LOOP_AND_EXECUTE(tmp->onclick, 1);\n        if(!u->mouse){\n          u->click = NULL;\n        }\n        break;\n      case '3':\n        u->mouse = (strcmp(tok, \"32\") == 0);\n        COORDINATE_DECODE();\n        LOOP_AND_EXECUTE(tmp->onhover, u->mouse);\n        break;\n      case '6':\n        if(u->canscroll){\n          u->scroll += (4 * (tok[1] == '4')) - 2;\n          printf(\"\\x1b[0m\\x1b[2J\");\n          ui_draw(u);\n        }\n        break;\n    }\n  }\n\n  vec_foreach(&(u->e), evt, ind){\n    if(strncmp(c, evt->c, strlen(evt->c)) == 0) evt->f();\n  }\n}\n\n/*\n * HELPERS\n */\nvoid _ui_text(ui_box_t *b, char *out){\n  sprintf(out, \"%s\", (char*)b->data1);\n}\n\nint ui_text(\n  int x, int y, char *str,\n  int screen,\n  func click, func hover,\n  ui_t *u\n){\n  return ui_add(\n    x, y,\n    strlen(str), 1,\n    screen,\n    NULL, 0,\n    _ui_text,\n    click,\n    hover,\n    str,\n    NULL,\n    u\n  );\n}\n\n#endif\n"
  }
]