From 26ac6871da1d8fa8d2a8ee953c38973f8f284252 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sun, 18 Apr 2021 12:40:48 -0700 Subject: [PATCH] Add TUI paneling example --- Makefile | 2 +- examples/examples.mk | 1 + examples/panels.c | 177 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 examples/panels.c diff --git a/Makefile b/Makefile index 7769dfc0..c87d438f 100644 --- a/Makefile +++ b/Makefile @@ -137,7 +137,6 @@ include libc/testlib/testlib.mk include tool/viz/lib/vizlib.mk include third_party/lua/lua.mk include third_party/quickjs/quickjs.mk -include examples/examples.mk include third_party/lz4cli/lz4cli.mk include tool/build/lib/buildlib.mk include third_party/chibicc/chibicc.mk @@ -145,6 +144,7 @@ include third_party/chibicc/test/test.mk include tool/build/emucrt/emucrt.mk include tool/build/emubin/emubin.mk include tool/build/build.mk +include examples/examples.mk include tool/decode/lib/decodelib.mk include tool/decode/decode.mk include tool/hash/hash.mk diff --git a/examples/examples.mk b/examples/examples.mk index ef6080ef..acf6ebfa 100644 --- a/examples/examples.mk +++ b/examples/examples.mk @@ -74,6 +74,7 @@ EXAMPLES_DIRECTDEPS = \ THIRD_PARTY_STB \ THIRD_PARTY_XED \ THIRD_PARTY_ZLIB \ + TOOL_BUILD_LIB \ TOOL_VIZ_LIB EXAMPLES_DEPS := \ diff --git a/examples/panels.c b/examples/panels.c new file mode 100644 index 00000000..d38bd5d4 --- /dev/null +++ b/examples/panels.c @@ -0,0 +1,177 @@ +#if 0 +/*─────────────────────────────────────────────────────────────────╗ +│ To the extent possible under law, Justine Tunney has waived │ +│ all copyright and related or neighboring rights to this file, │ +│ as it is written in the following disclaimers: │ +│ • http://unlicense.org/ │ +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ +╚─────────────────────────────────────────────────────────────────*/ +#endif +#include "libc/calls/calls.h" +#include "libc/calls/ioctl.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/termios.h" +#include "libc/calls/struct/winsize.h" +#include "libc/log/check.h" +#include "libc/log/gdb.h" +#include "libc/log/log.h" +#include "libc/macros.internal.h" +#include "libc/runtime/gc.internal.h" +#include "libc/runtime/runtime.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/sa.h" +#include "libc/sysv/consts/sig.h" +#include "libc/sysv/consts/termios.h" +#include "libc/x/x.h" +#include "tool/build/lib/panel.h" + +/** + * @fileoverview Cosmopolitan Paneling Demo. + * + * This is useful for creating terminal user interfaces. We take the + * simplest approach possible. The main thing we abstract is like, + * truncating the lines that overflow within a panel. In order to do + * that, we abstract the ANSI parsing and the implementation is able to + * tell how many cells wide each UNICODE character is. + * + * There are smarter ways for Cosmopolitan to do this. For example, it'd + * be great to have automatic flex boxes. It'd also be nice to be able + * to use dynamic programming for low bandwidth display updates, like + * Emacs does, but that's less of an issue these days and can actually + * make things slower, since for heavy workloads like printing videos, + * having ANSI codes bouncing around the display actually goes slower. + * + * Beyond basic paneling, a message box widget is also provided, which + * makes it easier to do modal dialogs. + */ + +struct Panels { + union { + struct { + struct Panel left; + struct Panel right; + }; + struct Panel p[2]; + }; +} pan; + +long tyn; +long txn; +char key[8]; +bool shutdown; +bool invalidated; +struct termios oldterm; + +void OnShutdown(int sig) { + shutdown = true; +} + +void OnInvalidate(int sig) { + invalidated = true; +} + +void GetTtySize(void) { + struct winsize wsize; + wsize.ws_row = tyn; + wsize.ws_col = txn; + getttysize(1, &wsize); + tyn = wsize.ws_row; + txn = wsize.ws_col; +} + +int Write(const char *s) { + return write(1, s, strlen(s)); +} + +void Setup(void) { + CHECK_NE(-1, ioctl(1, TCGETS, &oldterm)); +} + +void Enter(void) { + struct termios term; + memcpy(&term, &oldterm, sizeof(term)); + term.c_cc[VMIN] = 1; + term.c_cc[VTIME] = 1; + term.c_iflag &= ~(INPCK | ISTRIP | PARMRK | INLCR | IGNCR | ICRNL | IXON); + term.c_lflag &= ~(IEXTEN | ICANON | ECHO | ECHONL); + term.c_cflag &= ~(CSIZE | PARENB); + term.c_cflag |= CS8; + term.c_iflag |= IUTF8; + CHECK_NE(-1, ioctl(1, TCSETS, &term)); + Write("\e[?25l"); +} + +void Leave(void) { + Write(gc(xasprintf("\e[?25h\e[%d;%dH\e[S\r\n", tyn, txn))); + ioctl(1, TCSETS, &oldterm); +} + +void Clear(void) { + long i, j; + for (i = 0; i < ARRAYLEN(pan.p); ++i) { + for (j = 0; j < pan.p[i].n; ++j) { + free(pan.p[i].lines[j].p); + } + free(pan.p[i].lines); + pan.p[i].lines = 0; + pan.p[i].n = 0; + } +} + +void Layout(void) { + long i, j; + i = txn >> 1; + pan.left.top = 0; + pan.left.left = 0; + pan.left.bottom = tyn; + pan.left.right = i; + pan.right.top = 0; + pan.right.left = i + 1; + pan.right.bottom = tyn; + pan.right.right = txn; + pan.left.n = pan.left.bottom - pan.left.top; + pan.left.lines = xcalloc(pan.left.n, sizeof(*pan.left.lines)); + pan.right.n = pan.right.bottom - pan.right.top; + pan.right.lines = xcalloc(pan.right.n, sizeof(*pan.right.lines)); +} + +void Append(struct Panel *p, int i, const char *s) { + if (i >= p->n) return; + AppendStr(p->lines + i, s); +} + +void Draw(void) { + Append(&pan.left, 0, gc(xasprintf("you typed %`'s", key))); + Append(&pan.left, ((tyn + 1) >> 1) + 0, "hello left 1 𐌰𐌱𐌲𐌳𐌴𐌵𐌶𐌷"); + Append(&pan.left, ((tyn + 1) >> 1) + 1, "hello left 2 (╯°□°)╯"); + Append(&pan.right, ((tyn + 1) >> 1) + 0, "hello right 1"); + Append(&pan.right, ((tyn + 1) >> 1) + 1, "hello right 2"); + PrintPanels(1, ARRAYLEN(pan.p), pan.p, tyn, txn); +} + +int main(int argc, char *argv[]) { + struct sigaction sa[2] = { + {.sa_handler = OnShutdown}, + {.sa_handler = OnInvalidate, .sa_flags = SA_RESTART}, + }; + showcrashreports(); + Setup(); + Enter(); + GetTtySize(); + sigaction(SIGINT, &sa[0], 0); + sigaction(SIGCONT, &sa[1], 0); + sigaction(SIGWINCH, &sa[1], 0); + atexit(Leave); + do { + Clear(); + Layout(); + Draw(); + if (invalidated) { + Enter(); + GetTtySize(); + invalidated = false; + } else { + readansi(0, key, sizeof(key)); + } + } while (!shutdown); +}