From 2e31cbeb70d93bb0f5512064f181990db189d1f6 Mon Sep 17 00:00:00 2001 From: Sebastiano Tronto Date: Sun, 4 Oct 2026 20:02:15 +0200 Subject: Initial commit --- .gitignore | 3 + .gitmodules | 3 + Makefile | 46 ++++ README.md | 87 +++++++ demo/colors16/color16.ha | 65 +++++ demo/fullcolor/fullcolor.ha | 59 +++++ demo/hjkl/hjkl.ha | 190 ++++++++++++++ demo/size/size.ha | 35 +++ termbox2/README | 41 +++ termbox2/attrw+attrw16.ha | 35 +++ termbox2/attrw+attrw32.ha | 35 +++ termbox2/attrw.ha | 39 +++ termbox2/egc+noegc.ha | 30 +++ termbox2/egc.ha | 31 +++ termbox2/termbox2.ha | 605 ++++++++++++++++++++++++++++++++++++++++++++ vendor/termbox2 | 1 + 16 files changed, 1305 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Makefile create mode 100644 README.md create mode 100644 demo/colors16/color16.ha create mode 100644 demo/fullcolor/fullcolor.ha create mode 100644 demo/hjkl/hjkl.ha create mode 100644 demo/size/size.ha create mode 100644 termbox2/README create mode 100644 termbox2/attrw+attrw16.ha create mode 100644 termbox2/attrw+attrw32.ha create mode 100644 termbox2/attrw.ha create mode 100644 termbox2/egc+noegc.ha create mode 100644 termbox2/egc.ha create mode 100644 termbox2/termbox2.ha create mode 160000 vendor/termbox2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..65c3d19 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +TODO +build +**/*.o diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6714657 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/termbox2"] + path = vendor/termbox2 + url = https://github.com/termbox/termbox2 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ef76fbd --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +HARE=hare +DESTDIR= +PREFIX=/usr/local +SRCDIR=$(PREFIX)/src +HARESRCDIR=$(SRCDIR)/hare +THIRDPARTYDIR=$(HARESRCDIR)/third-party + +TB_DIR=vendor/termbox2 +TB_OBJ=termbox2.o +BUILD_DIR=build +DEMO_DIR=demo + +all: demos + +install: + install -Dm644 termbox2/* $(DESTDIR)$(THIRDPARTYDIR)/termbox2/ + +uninstall: + rm -rf $(DESTDIR)$(THIRDPARTYDIR)/termbox2 + +demos: $(BUILD_DIR) + mkdir -p $(BUILD_DIR)/$(DEMO_DIR) + for d in $(DEMO_DIR)/*; \ + do $(HARE) build -ltermbox2 -o $(BUILD_DIR)/$$d $$d; \ + done + +local: $(BUILD_DIR) termbox2/$(TB_OBJ) + mkdir -p $(BUILD_DIR)/$(DEMO_DIR) + for d in $(DEMO_DIR)/*; \ + do $(HARE) build -lc -o $(BUILD_DIR)/$$d $$d; \ + done + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +termbox2/$(TB_OBJ): $(TB_DIR)/termbox2.o + cp $(TB_DIR)/termbox2.o termbox2/ + +$(TB_DIR)/termbox2.o: + cd $(TB_DIR) && make termbox2.o + +clean: + rm -rf $(BUILD_DIR) termbox2/$(TB_OBJ) + [ -d $(TB_DIR) ] && cd $(TB_DIR) && make clean + +.PHONY: all clean demos install local-demos uninstall diff --git a/README.md b/README.md new file mode 100644 index 0000000..7620429 --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# Hare bindings for termbox2 + +This repository contains [hare](https://harelang.org) bindings for the +terminal I/O library [termbox2](https://github.com/termbox/termbox2). + +There are some differences between the Hare bindings and the original +C code, for example the fact that the prefixes `tb_` and `TB_` have been +replaced by the namespace `termbox2::`. See the haredoc documentation +in `termbox2/README` for more information. + +## Using this library + +There are two ways to build termbox2 for this library. Both involve +building termbox2 as a standalone library or object file, rather than +including it as a header-only library as one could do in a C program. + +### Using the system's termbox2 (default) + +This method assumes that termbox2 is already installed as a library +system-wide, for example by using your OS's package manager or by +installing it from source using `make install_lib`. + +Once termbox2 is installed, you can run `make install`. This will +copy the hare bindings to `/usr/local/src/hare/third-party/` so that +you can include them in your Hare project as any other module. Do +not forget to add the `-ltermbox2` option to build any program that +uses this library. + +To uninstall the bindings, run `make uninstall`. + +You can also run `make demos` (or simply `make`) to build the demo +programs contained in `demo/`, and then you can run them from the +`build/` folder. For example, to run the `hjkl` demo you can run +`make && ./build/demo/hjkl`. + +### Using a local termbox2 + +This method is useful if you just want to experiment with termbox2 +programming in Hare, for example by running and modifying the demos +in `demo/`, without installing anything system-wide. + +To do this, the original library is provided as a +[git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) +in this repository. You can download it with: + +``` +git submodule init +``` + +(Alternative, you can place the source code for termbox2 in +`vendor/termbox2`). Then you can build termbox2 locally with + +``` +make local +``` + +This will directly build all the demo programs from `demo/`, placing +the resulting binaries in `build/demo`. + +Technical note: this will include the compiled termbox object file +`termbox.o` directly in the compiled binaries. + +To build the demo programs using this method, run `make local-demos`. + +## Notes on build options + +The original termbox2 library offers several build options via `TB_OPT_*` +constants. Two of them, `TB_OPT_ATTR_W` and `TB_OPT_EGC`, are relevant for +the API. + +* `TB_OPT_ATTR_W` (in the Hare bindings: `termbox2::OPT_ATTR_W`) can be +either `16`, `32` or `64`, and is used to enable more output attributer +(e.g. truecolor). The termbox2 sources claim that the default value is +`16`, but this is only true when including termbox2 as a header-only +library. When building it as an external library, the default is `64`. +Since these bindings are meant to be used with termbox2 compiled as an +external library, we use `64` as default. The hare build tags `attrw16` +and `attrw32` are offered if one wants to use a different value. + +* `TB_OPT_EGC` (in the Hare bindings: `termbox2::OPT_EGC`) can be set or +not set (in the bindings: it is a boolean value, `true` for set, `false` +for not set). Similarly to `TB_OPT_ATTR_W`, it is by default set when +building termbox2 as an external library, unlike when including it as a +header-only library. An `noegc` tag is available to disable this option. + +Note: the consistency of these two build options between the C library +and the Hare bindings is checked and enforced in `termbox2::init()`. diff --git a/demo/colors16/color16.ha b/demo/colors16/color16.ha new file mode 100644 index 0000000..3121b93 --- /dev/null +++ b/demo/colors16/color16.ha @@ -0,0 +1,65 @@ +use fmt; +use tb = termbox2; + +fn paint_cell(x: size, y: size, c: tb::uattr) void = + tb::set_cell(x: int, y: int, ' ', tb::COLOR.DEFAULT, c)!; + +fn show_colors() size = { + let colors = [ + tb::COLOR.BLACK, + tb::COLOR.RED, + tb::COLOR.GREEN, + tb::COLOR.YELLOW, + tb::COLOR.BLUE, + tb::COLOR.MAGENTA, + tb::COLOR.CYAN, + tb::COLOR.WHITE, + ]; + + tb::print(0, 0, tb::COLOR.DEFAULT, tb::COLOR.DEFAULT, + "8 regular colors:")!; + tb::print(0, 2, tb::COLOR.DEFAULT, tb::COLOR.DEFAULT, + "8 bright colors:")!; + for (let i: size = 0; i < len(colors); i += 1) { + // 8 regular colors. + paint_cell(i, 1, colors[i]); + + // 8 bright colors. + paint_cell(i, 3, colors[i] | tb::ATTRIBUTE.BRIGHT); + }; + + return 4; +}; + +export fn main() int = { + let e = tb::init(); + if (e is tb::err) + fmt::fatalf(tb::strerror(e as tb::err)); + defer tb::shutdown()!; + + let w = tb::width()!; + let h = tb::height()!; + tb::print(0, (h - 1): int, tb::COLOR.DEFAULT, tb::COLOR.DEFAULT, + "Press any key to exit")!; + + show_colors(); + + for (true) { + let e = tb::present(); + if (e is tb::err) + fmt::fatalf(tb::strerror(e as tb::err)); + match (tb::poll_event()) { + case let e: tb::err => { + if (e == tb::ERR.NO_EVENT) + continue + else + tb::print(0, h:int -1, + tb::COLOR.DEFAULT, tb::COLOR.DEFAULT, + "Unexpected error {}!", tb::strerror(e))!; + }; + case tb::key_event => break; + case (tb::mouse_event | tb::resize_event) => continue; + }; + }; + return 0; +}; diff --git a/demo/fullcolor/fullcolor.ha b/demo/fullcolor/fullcolor.ha new file mode 100644 index 0000000..5d46c7c --- /dev/null +++ b/demo/fullcolor/fullcolor.ha @@ -0,0 +1,59 @@ +use fmt; +use tb = termbox2; + +fn paint_cell(x: size, y: size, c: tb::uattr) void = + tb::set_cell(x: int, y: int, ' ', tb::COLOR.DEFAULT, c)!; + +fn show_colors(wmin: size, hmin: size, wmax: size, hmax: size) void = { + tb::set_output_mode(tb::OUTPUT.TRUECOLOR)!; + tb::print(wmin: int, hmin: int, tb::COLOR.DEFAULT, tb::COLOR.DEFAULT, + "Color palette sample:")!; + hmin = hmin + 1; + + let nsamples = (wmax - wmin +1) * (hmax - hmin + 1); + let step = 0xffffff / nsamples; + for (let y = hmin; y < hmax; y += 1) { + for (let x = wmin; x < wmax; x += 1) { + let c: tb::uattr = ((y - hmin) * wmax + (x - wmin)) * step; + paint_cell(x, y, c); + }; + }; +}; + +export fn main() int = { + let e = tb::init(); + if (e is tb::err) + fmt::fatalf(tb::strerror(e as tb::err)); + defer tb::shutdown()!; + + let w = tb::width()!; + let h = tb::height()!; + tb::print(0, (h - 1): int, tb::COLOR.DEFAULT, tb::COLOR.DEFAULT, + "Press any key to exit")!; + + switch (tb::OPT_ATTR_W) { + case 64 => show_colors(0, 0, w, h-1); + case => tb::print(0, ((h+1)/2): int, tb::COLOR.DEFAULT, tb::COLOR.DEFAULT, + "This demo is only available with truecolor support, here " + "tb::OPT_ATTR_W is {}", tb::OPT_ATTR_W)!; + }; + + for (true) { + let e = tb::present(); + if (e is tb::err) + fmt::fatalf(tb::strerror(e as tb::err)); + match (tb::poll_event()) { + case let e: tb::err => { + if (e == tb::ERR.NO_EVENT) + continue + else + tb::print(0, h:int -1, + tb::COLOR.DEFAULT, tb::COLOR.DEFAULT, + "Unexpected error {}!", tb::strerror(e))!; + }; + case tb::key_event => break; + case (tb::mouse_event | tb::resize_event) => continue; + }; + }; + return 0; +}; diff --git a/demo/hjkl/hjkl.ha b/demo/hjkl/hjkl.ha new file mode 100644 index 0000000..487ebd3 --- /dev/null +++ b/demo/hjkl/hjkl.ha @@ -0,0 +1,190 @@ +use fmt; +use tb = termbox2; + +def bg = tb::COLOR.DEFAULT; +def fg = tb::COLOR.DEFAULT | tb::ATTRIBUTE.BOLD; +def exit_help: str = "Use :q to exit (colon, q, enter). Sad to see you go :("; + +let wpos: (int, int) = (-1, -1); +let msg: str = "Use HJKL to move the widget around"; +let colon_mode: bool = false; +let exit_attempts: size = 0; + +type cmd = enum { + UP, + DOWN, + LEFT, + RIGHT, + COLON, + ARROW, + Q, + ENTER, + EXIT_ATTEMPT, + NONE, +}; + +fn draw_small_widget_thing(w: size, h: size) void = { + if (wpos.0 < 4) wpos.0 += 1 + else if (wpos.0 > w: int - 5) wpos.0 -= 1 + else if (wpos.1 < 2) wpos.1 += 1 + else if (wpos.1 > h: int - 3) wpos.1 -= 1; + + tb::print(wpos.0, wpos.1 - 1, fg, bg, "K")!; + tb::print(wpos.0, wpos.1 + 1, fg, bg, "J")!; + tb::print(wpos.0 - 2, wpos.1, fg, bg, "H + L")!; +}; + +fn paint(x: int, y: int, c: tb::uattr) void = + tb::set_cell(x, y, ' ', tb::COLOR.DEFAULT, c)!; + +fn paint_col3(x: int, y: int, c: tb::uattr) void = { + paint(x, y-1, c); + paint(x, y, c); + paint(x, y+1, c); +}; + +fn paint_row5(x: int, y: int, c: tb::uattr) void = { + paint(x-2, y, c); + paint(x-1, y, c); + paint(x, y, c); + paint(x+1, y, c); + paint(x+2, y, c); +}; + +fn paint_cell(x: int, y: int, c: tb::uattr, s: str) void = { + paint_col3(x-2, y, c); + paint_row5(x, y-1, c); + tb::print(x, y, fg, bg, s)!; + paint_row5(x, y+1, c); + paint_col3(x+2, y, c); +}; + +fn draw_large_widget_thing(w: size, h: size) void = { + if (wpos.0 < 10) wpos.0 += 1 + else if (wpos.0 > w: int - 11) wpos.0 -= 1 + else if (wpos.1 < 7) wpos.1 += 1 + else if (wpos.1 > h: int - 8) wpos.1 -= 1; + + let c = tb::COLOR.BLUE; + paint_cell(wpos.0-6, wpos.1, c, "H"); + paint_cell(wpos.0+6, wpos.1, c, "L"); + paint_cell(wpos.0, wpos.1-4, c, "K"); + paint_cell(wpos.0, wpos.1+4, c, "J"); + paint_col3(wpos.0, wpos.1, c); + paint_row5(wpos.0, wpos.1, c); +}; + +fn draw_widget_thing(w: size, h: size) void = { + if (w < 30 || h < 15) + draw_small_widget_thing(w, h) + else + draw_large_widget_thing(w, h); +}; + +fn draw_bottom_bar(w: size, h: size) void = { + tb::print(0, h: int - 1, fg, bg, msg)!; +}; + +fn handle_events_and_exit() bool = { + let e = tb::poll_event(); + if (e is tb::err) + return false; + + return match (e as tb::event) { + case tb::resize_event => yield false; + case tb::mouse_event => yield { + msg = "A mouse? Are you serious?"; + yield false; + }; + case let ke: tb::key_event => yield handle_key_event_and_exit(ke); + }; +}; + +fn is_arrow(k: tb::key) bool = + k == tb::KEY.ARROW_UP || k == tb::KEY.ARROW_DOWN || + k == tb::KEY.ARROW_LEFT || k == tb::KEY.ARROW_RIGHT; + +fn is_exit_attempt(k: tb::key) bool = + k == tb::KEY.CTRL_Q || k == tb::KEY.CTRL_C || k == tb::KEY.CTRL_X || + k == tb::KEY.CTRL_Z || k == tb::KEY.CTRL_D || + k == tb::KEY.DELETE || k == tb::KEY.BACKSPACE || k == tb::KEY.ESC; + +fn get_cmd(ke: tb::key_event) cmd = match (ke.key_or_ch) { + case let r: rune => yield + if (r == 'h') cmd::LEFT + else if (r == 'j') cmd::DOWN + else if (r == 'k') cmd::UP + else if (r == 'l') cmd::RIGHT + else if (r == ':') cmd::COLON + else if (r == 'q') cmd::Q + else if (r == 'Q') cmd::EXIT_ATTEMPT + else cmd::NONE; + case let k: tb::key => yield + if (k == tb::KEY.ENTER) cmd::ENTER + else if (is_arrow(k)) cmd::ARROW + else if (is_exit_attempt(k)) cmd::EXIT_ATTEMPT + else cmd::NONE; + }; + +fn exit_attempt() void = { + exit_attempts += 1; + msg = if (exit_attempts < 3) "Trying to get out? :3" + else if (exit_attempts < 6) "It's just like Vim!" + else if (exit_attempts < 8) "Uhm no, that won't work" + else if (exit_attempts < 10) "Ok, I'll tell you..." + else exit_help; +}; + +fn handle_key_event_and_exit(ke: tb::key_event) bool = { + let c = get_cmd(ke); + + if (c == cmd::COLON) + colon_mode = true; + + if (colon_mode) { + if (msg == ":q" && c == cmd::ENTER) + return true; + + msg = if (msg == ":") { + yield if (c == cmd::Q) ":q" else exit_help; + } else if (c == cmd::COLON) ":" else exit_help; + + return false; + }; + + switch (c) { + case cmd::UP => wpos.1 -= 1; + case cmd::DOWN => wpos.1 += 1; + case cmd::LEFT => wpos.0 -= 1; + case cmd::RIGHT => wpos.0 += 1; + case cmd::ARROW => msg = "No arrows allowed!"; + case cmd::COLON => colon_mode = true; + case cmd::EXIT_ATTEMPT => exit_attempt(); + case cmd::Q => exit_attempt(); + case => yield; + }; + + return false; +}; + +export fn main() void = { + let e = tb::init(); + if (e is tb::err) + fmt::fatalf(tb::strerror(e as tb::err)); + defer tb::shutdown()!; + + wpos = (tb::width()!: int / 2, tb::height()!: int / 2); + for (true) { + tb::clear()!; + + let w = tb::width()!; + let h = tb::height()!; + + draw_widget_thing(w, h); + draw_bottom_bar(w, h); + tb::present()!; + + if (handle_events_and_exit()) break; + }; + return; +}; diff --git a/demo/size/size.ha b/demo/size/size.ha new file mode 100644 index 0000000..55b6a3c --- /dev/null +++ b/demo/size/size.ha @@ -0,0 +1,35 @@ +use fmt; +use tb = termbox2; + +export fn main() void = { + let e = tb::init(); + if (e is tb::err) + fmt::fatalf(tb::strerror(e as tb::err)); + defer tb::shutdown()!; + + let w = tb::width()!; + let h = tb::height()!; + tb::print(0, 0, tb::COLOR.GREEN, tb::COLOR.BLACK, + "Terminal width: {}", w)!; + tb::print(0, 1, tb::COLOR.GREEN, tb::COLOR.BLACK, + "Terminal height: {}", h)!; + tb::print(0, 2, tb::COLOR.WHITE, tb::COLOR.BLACK, + "Press any key to exit")!; + for (true) { + let e = tb::present(); + if (e is tb::err) + fmt::fatalf(tb::strerror(e as tb::err)); + match (tb::poll_event()) { + case let e: tb::err => { + if (e == tb::ERR.NO_EVENT) + continue + else + tb::print(0, 4, tb::COLOR.WHITE, tb::COLOR.BLACK, + "Unexpected error {}!", tb::strerror(e))!; + }; + case tb::key_event => break; + case (tb::mouse_event | tb::resize_event) => continue; + }; + }; + return; +}; diff --git a/termbox2/README b/termbox2/README new file mode 100644 index 0000000..e28c58c --- /dev/null +++ b/termbox2/README @@ -0,0 +1,41 @@ +This module provides bindings for termbox2. + +Some changes to the API have been made to enable using this library in a +more Hare-idiomatic style. In particular: +- The tb_ and TB_ prefixes have been removed, as the namespace termbox2:: +is used instead. We recommend importing this library as tb, so that one +can write e.g. tb::init(), keeping it similar to the original tb_init(). +- Functions return a value that can be either a valid result (e.g. int, +void, str...) or an int-backed error [[termbox2::err]]. The error codes +are consistent with the original library and can be interpreted with +[[termbox2::strerror]]. Some additional codes for binding-related errors +are provided. +- Similarly to the previous point, some functions that use to return +multiple values by taking pointer parameters now simply return a tuple, +see for example [[termbox2::get_fds]]. +- The tb_event type has changed into a tagged union of 3 types, +see [[termbox2::event]]. +- The four tb_print* functions have been replaced by a single +[[termbox2::print]] function that uses Hare-style string formatting. +Similarly, tb_send and tb_sendf have replaced by only one function +[[termbox2::send]]. +- The constants provided by ther termbox2 API, such as those for key codes, +colors and attributes, have been split into various structures, such as +[[termbox2::KEY]], [[termbox2::COLOR]] and [[termbox2::ATTRIBUTE]]. + +Some functionality has been outright removed. If you are interested in +having any of the following available in these bindings, don't hesitate +to send a patch or a feature request to sebastiano@tronto.net: +- The tb_get_cell function is not available. This function makes it +possible to navigate through cells via pointer arithmetic, but since +the Hare [[termbox2::cell]] type does not have the same size as the +original tb_cell, this would not translate cleanly. +- The tb_iswprint, tb_wcwidth and tb_utf8_* have functions have been +removed. Use the Hare standard libary functions [[encoding::utf8::]] +and [[strings::]] instead. +- The tb_last_errno function has been removed. +- The TB_CAP_* code constants have been removed. + +Links: +- Main repository: https://git.tronto.net/hare-termbox2 +- termbox2: https://github.com/termbox/termbox2 diff --git a/termbox2/attrw+attrw16.ha b/termbox2/attrw+attrw16.ha new file mode 100644 index 0000000..69f457d --- /dev/null +++ b/termbox2/attrw+attrw16.ha @@ -0,0 +1,35 @@ +// Integer width of fg and bg attributes. Valid values (assuming system +// support) are 16, 32, and 64; see [[termbox2::uattr]]. 32 or 64 enables +// OUTPUT.TRUECOLOR, see [[termbox2::OUTPUT]] and +// [[termbox2::set_output_mode]]. 64 enables additional style attributes. +// Larger values consume more memory in exchange for more features. +// Defaults to 64. +export def OPT_ATTR_W: size = 16; + +// Unsigned integer type for cell attributes, see [[termbox2::ATTRIBUTE]]. +export type uattr = u16; + +// Colors (bitwise), see [[termbox2::cell]]. +export def COLOR = struct { + DEFAULT: uattr = 0x0000, + BLACK: uattr = 0x0001, + RED: uattr = 0x0002, + GREEN: uattr = 0x0003, + YELLOW: uattr = 0x0004, + BLUE: uattr = 0x0005, + MAGENTA: uattr = 0x0006, + CYAN: uattr = 0x0007, + WHITE: uattr = 0x0008, +}; + +// Style attributes (bitwise), see termbox2::cell. +export def ATTRIBUTE = struct { + BOLD: uattr = 0x0100, + UNDERLINE: uattr = 0x0200, + REVERSE: uattr = 0x0400, + ITALIC: uattr = 0x0800, + BLINK: uattr = 0x1000, + HI_BLACK: uattr = 0x2000, + BRIGHT: uattr = 0x4000, + DIM: uattr = 0x8000, +}; diff --git a/termbox2/attrw+attrw32.ha b/termbox2/attrw+attrw32.ha new file mode 100644 index 0000000..9f5f885 --- /dev/null +++ b/termbox2/attrw+attrw32.ha @@ -0,0 +1,35 @@ +// Integer width of fg and bg attributes. Valid values (assuming system +// support) are 16, 32, and 64; see [[termbox2::uattr]]. 32 or 64 enables +// OUTPUT.TRUECOLOR, see [[termbox2::OUTPUT]] and +// [[termbox2::set_output_mode]]. 64 enables additional style attributes. +// Larger values consume more memory in exchange for more features. +// Defaults to 64. +export def OPT_ATTR_W: size = 32; + +// Unsigned integer type for cell attributes, see [[termbox2::ATTRIBUTE]]. +export type uattr = u32; + +// Colors (bitwise), see [[termbox2::cell]]. +export def COLORS = struct { + DEFAULT: uattr = 0x0000, + BLACK: uattr = 0x0001, + RED: uattr = 0x0002, + GREEN: uattr = 0x0003, + YELLOW: uattr = 0x0004, + BLUE: uattr = 0x0005, + MAGENTA: uattr = 0x0006, + CYAN: uattr = 0x0007, + WHITE: uattr = 0x0008, +}; + +// Style attributes (bitwise), see [[termbox2::cell]]. +export def ATTRIBUTE = struct { + BOLD: uattr = 0x01000000, + UNDERLINE: uattr = 0x02000000, + REVERSE: uattr = 0x04000000, + ITALIC: uattr = 0x08000000, + BLINK: uattr = 0x10000000, + HI_BLACK: uattr = 0x20000000, + BRIGHT: uattr = 0x40000000, + DIM: uattr = 0x80000000, +}; diff --git a/termbox2/attrw.ha b/termbox2/attrw.ha new file mode 100644 index 0000000..d337b8f --- /dev/null +++ b/termbox2/attrw.ha @@ -0,0 +1,39 @@ +// Integer width of fg and bg attributes. Valid values (assuming system +// support) are 16, 32, and 64; see [[termbox2::uattr]]. 32 or 64 enables +// OUTPUT.TRUECOLOR, see [[termbox2::OUTPUT]] and +// [[termbox2::set_output_mode]]. 64 enables additional style attributes. +// Larger values consume more memory in exchange for more features. +// Defaults to 64. +export def OPT_ATTR_W: size = 64; + +// Unsigned integer type for cell attributes, see [[termbox2::ATTRIBUTE]]. +export type uattr = u64; + +// Colors (bitwise), see [[termbox2::cell]]. +export def COLOR = struct { + DEFAULT: uattr = 0x0000, + BLACK: uattr = 0x0001, + RED: uattr = 0x0002, + GREEN: uattr = 0x0003, + YELLOW: uattr = 0x0004, + BLUE: uattr = 0x0005, + MAGENTA: uattr = 0x0006, + CYAN: uattr = 0x0007, + WHITE: uattr = 0x0008, +}; + +// Style attributes (bitwise), see [[termbox2::cell]]. +export def ATTRIBUTE = struct { + BOLD: uattr = 0x01000000, + UNDERLINE: uattr = 0x02000000, + REVERSE: uattr = 0x04000000, + ITALIC: uattr = 0x08000000, + BLINK: uattr = 0x10000000, + HI_BLACK: uattr = 0x20000000, + BRIGHT: uattr = 0x40000000, + DIM: uattr = 0x80000000, + STRIKEOUT: uattr = 0x0000000100000000, + UNDERLINE_2: uattr = 0x0000000200000000, + OVERLINE: uattr = 0x0000000400000000, + INVISIBLE: uattr = 0x0000000800000000, +}; diff --git a/termbox2/egc+noegc.ha b/termbox2/egc+noegc.ha new file mode 100644 index 0000000..40d1b33 --- /dev/null +++ b/termbox2/egc+noegc.ha @@ -0,0 +1,30 @@ +// If set, enable extended grapheme cluster support (see +// [[termbox2::extend_cell]] and [[termbox2::set_cell_ex]]). Consumes more +// memory. Defaults on. +export def OPT_EGC: bool = false; + +// A cell in a 2d grid representing the terminal screen. +// +// The terminal screen is represented as a 2d array of cells. The structure is +// optimized for dealing with single-width (wcwidth==1) Unicode codepoints, +// however some support for grapheme clusters (e.g., combining diacritical +// marks) and wide codepoints (e.g., Hiragana) is provided through ech via +// [[termbox2::set_cell_ex]]. ech is only valid if it is non-empty, otherwise +// ch is used. +// +// For non-single-width codepoints, given W=wcwidth(ch || ech): +// +// - when W<=0: termbox forces a single-width cell. Callers should avoid this +// if aiming to render text accurately. Callers may use +// [[termbox2::set_cell_ex]] or [[termbox2::print]] or similar to render +// W==0 combining characters. +// - when W>=2: termbox zeroes out the following W-1 cells and skips +// sending them to the tty. So, e.g., if the caller sets x=0,y=0 to a +// W==2 codepoint, the caller's next set should be at x=2,y=0. Anything +// set at x=1,y=0 will be ignored. If there are not enough columns +// remaining on th eline to render W cells, spaces are sent instead. +export type cell = struct { + ch: u32, // a Unicode codepoint + fg: uattr, // bitwise foreground attributes + bg: uattr, // bitwise background attributes +}; diff --git a/termbox2/egc.ha b/termbox2/egc.ha new file mode 100644 index 0000000..a14e7ef --- /dev/null +++ b/termbox2/egc.ha @@ -0,0 +1,31 @@ +// If set, enable extended grapheme cluster support (see +// [[termbox2::extend_cell]] and [[termbox2::set_cell_ex]]). Consumes more +// memory. Defaults on. +export def OPT_EGC: bool = true; + +// A cell in a 2d grid representing the terminal screen. +// +// The terminal screen is represented as a 2d array of cells. The structure is +// optimized for dealing with single-width (wcwidth==1) Unicode codepoints, +// however some support for grapheme clusters (e.g., combining diacritical +// marks) and wide codepoints (e.g., Hiragana) is provided through ech via +// [[termbox2::set_cell_ex]]. ech is only valid if it is non-empty, otherwise +// ch is used. +// +// For non-single-width codepoints, given W=wcwidth(ch || ech): +// +// - when W<=0: termbox forces a single-width cell. Callers should avoid this +// if aiming to render text accurately. Callers may use +// [[termbox2::set_cell_ex]] or [[termbox2::print]] or similar to render +// W==0 combining characters. +// - when W>=2: termbox zeroes out the following W-1 cells and skips +// sending them to the tty. So, e.g., if the caller sets x=0,y=0 to a +// W==2 codepoint, the caller's next set should be at x=2,y=0. Anything +// set at x=1,y=0 will be ignored. If there are not enough columns +// remaining on th eline to render W cells, spaces are sent instead. +export type cell = struct { + ch: u32, // a Unicode codepoint + fg: uattr, // bitwise foreground attributes + bg: uattr, // bitwise background attributes + ech: []rune, // a grapheme cluster of Unicode codepoints +}; diff --git a/termbox2/termbox2.ha b/termbox2/termbox2.ha new file mode 100644 index 0000000..f8476ee --- /dev/null +++ b/termbox2/termbox2.ha @@ -0,0 +1,605 @@ +use encoding::utf8; +use fmt; +use types::c; + +// Type for key values, see [[termbox2::KEY]]. +export type key = u16; + +// ASCII constants, see [[termbox2::key_event]].key. +export def KEY = struct { + CTRL_TILDE: key = 0x00, + CTRL_2: key = 0x00, // clash with CTRL_TILDE + CTRL_A: key = 0x01, + CTRL_B: key = 0x02, + CTRL_C: key = 0x03, + CTRL_D: key = 0x04, + CTRL_E: key = 0x05, + CTRL_F: key = 0x06, + CTRL_G: key = 0x07, + BACKSPACE: key = 0x08, + CTRL_H: key = 0x08, // clash with CTRL_BACKSPACE + TAB: key = 0x09, + CTRL_I: key = 0x09, // clash with TAB + CTRL_J: key = 0x0a, + CTRL_K: key = 0x0b, + CTRL_L: key = 0x0c, + ENTER: key = 0x0d, + CTRL_M: key = 0x0d, // clash with ENTER + CTRL_N: key = 0x0e, + CTRL_O: key = 0x0f, + CTRL_P: key = 0x10, + CTRL_Q: key = 0x11, + CTRL_R: key = 0x12, + CTRL_S: key = 0x13, + CTRL_T: key = 0x14, + CTRL_U: key = 0x15, + CTRL_V: key = 0x16, + CTRL_W: key = 0x17, + CTRL_X: key = 0x18, + CTRL_Y: key = 0x19, + CTRL_Z: key = 0x1a, + ESC: key = 0x1b, + CTRL_LSQ_BRACKET: key = 0x1b, // clash with 'ESC' + CTRL_3: key = 0x1b, // clash with 'ESC' + CTRL_4: key = 0x1c, + CTRL_BACKSLASH: key = 0x1c, // clash with 'CTRL_4' + CTRL_5: key = 0x1d, + CTRL_RSQ_BRACKET: key = 0x1d, // clash with 'CTRL_5' + CTRL_6: key = 0x1e, + CTRL_7: key = 0x1f, + CTRL_SLASH: key = 0x1f, // clash with 'CTRL_7' + CTRL_UNDERSCORE: key = 0x1f, // clash with 'CTRL_7' + SPACE: key = 0x20, + BACKSPACE2: key = 0x7f, + CTRL_8: key = 0x7f, // clash with 'BACKSPACE2' + F1: key = (0xffff - 0), + F2: key = (0xffff - 1), + F3: key = (0xffff - 2), + F4: key = (0xffff - 3), + F5: key = (0xffff - 4), + F6: key = (0xffff - 5), + F7: key = (0xffff - 6), + F8: key = (0xffff - 7), + F9: key = (0xffff - 8), + F10: key = (0xffff - 9), + F11: key = (0xffff - 10), + F12: key = (0xffff - 11), + INSERT: key = (0xffff - 12), + DELETE: key = (0xffff - 13), + HOME: key = (0xffff - 14), + END: key = (0xffff - 15), + PGUP: key = (0xffff - 16), + PGDN: key = (0xffff - 17), + ARROW_UP: key = (0xffff - 18), + ARROW_DOWN: key = (0xffff - 19), + ARROW_LEFT: key = (0xffff - 20), + ARROW_RIGHT: key = (0xffff - 21), + BACK_TAB: key = (0xffff - 22), + MOUSE_LEFT: key = (0xffff - 23), + MOUSE_RIGHT: key = (0xffff - 24), + MOUSE_MIDDLE: key = (0xffff - 25), + MOUSE_RELEASE: key = (0xffff - 26), + MOUSE_WHEEL_UP: key = (0xffff - 27), + MOUSE_WHEEL_DOWN: key = (0xffff - 28), +}; + +// Type for key modifiers, see [[termbox2::MOD]]. +export type mod = u8; + +// Key modifiers (bitwise), see [[termbox2::event]].mod. +export def MOD = struct { + ALT: mod = 1, + CTRL: mod = 2, + SHIFT: mod = 4, + MOTION: mod = 8, +}; + +// Type for input modes, see [[termbox2::INPUT]]. +export type input_mode = int; + +// Input modes (bitwise), see [[termbox2::set_input_mode]]. +export def INPUT = struct { + CURRENT: input_mode = 0, + ESC: input_mode = 1, + ALT: input_mode = 2, + MOUSE: input_mode = 4, +}; + +// Type for output modes, see [[termbox2::OUTPUT]]. +export type output_mode = int; + +// Output modes, see [[termbox2::set_output_mode]]. +export def OUTPUT = struct { + CURRENT: output_mode = 0, + NORMAL: output_mode = 1, + _256: output_mode = 2, + _216: output_mode = 3, + GRAYSCALE: output_mode = 4, + TRUECOLOR: output_mode = 5, // only available if OPT_ATTR_W >= 32 +}; + +// Type for error return values, see [[termbox2::ERR]]. +export type err = !int; + +// Common error return values. +// +// Library behavior is undefined after receiving ERR.MEM. +// Callers may attempt reinitializing by freeing memory, invoking +// [[termbox2::shutdown]], then [[termbox2::init]]. +export def ERR = struct { + ERR: err = -1, + NEED_MORE: err = -2, + INIT_ALREADY: err = -3, + INIT_OPEN: err = -4, + MEM: err = -5, + NO_EVENT: err = -6, + NO_TERM: err = -7, + NOT_INIT: err = -8, + OUT_OF_BOUNDS: err = -9, + READ: err = -10, + RESIZE_IOCTL: err = -11, + RESIZE_PIPE: err = -12, + RESIZE_SIGACTION: err = -13, + POLL: err = -14, + SELECT: err = -14, + TCGETATTR: err = -15, + TCSETATTR: err = -16, + UNSUPPORTED_TERM: err = -17, + RESIZE_WRITE: err = -18, + RESIZE_POLL: err = -19, + RESIZE_SELECT: err = -19, + RESIZE_READ: err = -20, + RESIZE_SSCANF: err = -21, + CAP_COLLISION: err = -22, + HARE_BUILD: err = -9990, // Additional error type + HARE_INVALID_UTF8: err = -9991, // Additional error type + HARE_OTHER: err = -9992, // Additional error type +}; + +// An incoming key press event. Note that there is overlap between +// MOD.CTRL and KEY.CTRL_* keys. MOD.CTRL and MOD.SHIFT are only set +// as modifiers to KEY.ARROW_*. +export type key_event = struct { + mod: mod, // Modifier + key_or_ch: (key | rune), // A [[termbox2::KEY]] or a Unicode code point +}; + +// An incoming resize event. +export type resize_event = struct { + w: i32, // resize width + h: i32, // resize height +}; + +// An incoming mouse event. +export type mouse_event = struct { + key: key, // Mouse button, e.g. termbox2::KEY.MOUSE_LEFT + x: i32, + y: i32, +}; + +// The C data structure for an event, for compatibility. +type tb_event = struct { + t: u8, + mod: u8, + key: u16, + ch: u32, + w: i32, + h: i32, + x: i32, + y: i32, +}; + +def TB_EVENT_KEY: u8 = 1; +def TB_EVENT_RESIZE: u8 = 2; +def TB_EVENT_MOUSE: u8 = 3; +fn tb_event_to_event(e: tb_event) (event | err) = switch (e.t) { + case TB_EVENT_KEY => yield key_event { + mod = e.mod, + key_or_ch = if (e.key != 0) e.key: key else e.ch: rune, + }; + case TB_EVENT_RESIZE => yield resize_event { + w = e.w, + h = e.h, + }; + case TB_EVENT_MOUSE => yield mouse_event { + key = e.key, + x = e.x, + y = e.y, + }; + case => yield ERR.HARE_OTHER; + }; + +// Any incoming event from the tty. +export type event = (key_event | resize_event | mouse_event); + +fn build_opts_match() bool = + (has_egc() == OPT_EGC) && + (attr_width() == OPT_ATTR_W) && + (has_truecolor() == (OPT_ATTR_W == 32 || OPT_ATTR_W == 64)); + +fn void_or_err(r: int) (void | err) = if (r < 0) r: err; +fn int_or_err(r: int) (int | err) = if (r < 0) r: err else r; +fn size_or_err(r: int) (size | err) = if (r < 0) r: err else r: size; +fn str_or_err(cstr: *c::char) (str | err) = match(c::tostr(cstr)) { + case utf8::invalid => yield ERR.HARE_INVALID_UTF8; + case let s: str => yield s; +}; + +// Initialize the termbox library. This function should be called before +// any other functions. After successful initialization, the library must +// be finalized using [[termbox2::shutdown]]. +export fn init() (void | err) = + if (!build_opts_match()) + ERR.HARE_BUILD + else + void_or_err(tb_init()); +@symbol("tb_init") fn tb_init() int; + +// Same as [[termbox2::init]], but allows to specify a file path in place +// of the default /dev/tty. +export fn init_file(path: str) (void | err) = { + let c_path = c::fromstr(path)!; + defer free(c_path); + return if (!build_opts_match()) + ERR.HARE_BUILD + else + void_or_err(tb_init_file(c_path)); +}; +@symbol("tb_init_file") fn tb_init_file(cstr: *c::char) int; + +// Same as [[termbox2::init]], but allows to specify an output +// file descriptor. +export fn init_fd(ttyfd: int) (void | err) = + if (!build_opts_match()) + ERR.HARE_BUILD + else + void_or_err(tb_init_fd(ttyfd)); +@symbol("tb_init_fd") fn tb_init_fd(fd: int) int; + +// Same as [[termbox2::init]], but allows to specify input and output +// file descriptors. +export fn init_rwfd(rfd: int, wfd: int) (void | err) = + if (!build_opts_match()) + ERR.HARE_BUILD + else + void_or_err(tb_init_rwfd(rfd, wfd)); +@symbol("tb_init_rwfd") fn tb_init_rwfd(rfd: int, wfd: int) int; + +// Finalizes the library. +export fn shutdown() (void | err) = void_or_err(tb_shutdown()); +@symbol("tb_shutdown") fn tb_shutdown() int; + +// Return the width of the internal back buffer (which is the same as the +// terminal's window width in columns). The internal buffer can be resized +// after [[termbox2::clear]] or [[termbox2::present]] calls. Returns ERR.ERR +// when called before [[termbox2::init]] or after [[termbox2::shutdown]]. +export fn width() (size | err) = size_or_err(tb_width()); +@symbol("tb_width") fn tb_width() int; + +// Return the height of the internal back buffer (which is the same as the +// terminal's window width in rows). The internal buffer can be resized +// after [[termbox2::clear]] or [[termbox2::present]] calls. Returns ERR.ERR +// when called before [[termbox2::init]] or after [[termbox2::shutdown]]. +export fn height() (size | err) = size_or_err(tb_height()); +@symbol("tb_height") fn tb_height() int; + +// Clear the internal back buffer using [[termbox2::ATTRIBUTE]].DEFAULT. +export fn clear() (void | err) = void_or_err(tb_clear()); +@symbol("tb_clear") fn tb_clear() int; + +// Clear the internal back buffer using the specified attributes. +export fn set_clear_attrs(fg: uattr, bg: uattr) (void | err) = + void_or_err(tb_set_clear_attrs(fg, bg)); +@symbol("tb_set_clear_attrs") fn tb_set_clear_attrs(fg: uattr, bg: uattr) int; + +// Synchronize the internal back buffer with the terminal by +// writing to tty. +export fn present() (void | err) = void_or_err(tb_present()); +@symbol("tb_present") fn tb_present() int; + +// Clear the internal front buffer effectively forcing a complete re-render +// of the back buffer to the tty. It is not necessary to call this under +// normal circumstances. +export fn invalidate() (void | err) = void_or_err(tb_invalidate()); +@symbol("tb_invalidate") fn tb_invalidate() int; + +// Set the position of the cursor. Upper-left cell is (0, 0). +export fn set_cursor(cx: int, cy: int) (void | err) = + void_or_err(tb_set_cursor(cx, cy)); +@symbol("tb_set_cursor") fn tb_set_cursor(x: int, y: int) int; + +// Hide the cursor. +export fn hide_cursor() (void | err) = void_or_err(tb_hide_cursor()); +@symbol("tb_hide_cursor") fn tb_hide_cursor() int; + +// Set cell contents in the internal back buffer at the specified position. +// Non-printable runes are replaced with U+FFFD at render time. +export fn set_cell( + x: int, + y: int, + ch: rune, + fg: uattr, + bg: uattr, +) (void | err) = void_or_err(tb_set_cell(x, y, ch: u32, fg, bg)); +@symbol("tb_set_cell") fn tb_set_cell( + x: int, + y: int, + ch: u32, + fg: uattr, + bg: uattr, +) int; + +// Set extended cell contents. Only available when OPT_ATTR_W >= 32. +// See also [[termbox2::set_cell]] and [[termbox2::cell]]. +export fn set_cell_ex( + x: int, + y: int, + ch: []rune, + fg: uattr, + bg: uattr, +) (void | err) = + void_or_err(tb_set_cell_ex(x, y, &ch[0]: *u32, len(ch), fg, bg)); +@symbol("tb_set_cell_ex") fn tb_set_cell_ex( + x: int, + y: int, + ch: *u32, + n: size, + fg: uattr, + bg: uattr, +) int; + +// Add one character to the cell's ech. Only available when OPT_ATTR_W >= 32. +// See also [[termbox2::set_cell_ex]] and [[termbox2::cell]]. +export fn extend_cell(x: int, y: int, ch: rune) (void | err) = + void_or_err(tb_extend_cell(x, y, ch: u32)); +@symbol("tb_extend_cell") fn tb_extend_cell(x: int, y: int, ch: u32) int; + +// Set the input mode, see [[termbox2::INPUT]]. +// +// Termbox has two input modes: +// - INPUT.ESC When escape (\x1b) is in the buffer and there's no match +// for an escape sequence, a key event for [[termbox2::KEY]].ESC is returned. +// - INPUT.ALT When escape (\x1b) is in the buffer and there's no match +// for an an escape sequence, the next keyboard event is returned with a +// [[termbox2::MOD]].ALT modifier. +// +// You can also apply termbox2::INPUT.MOUSE via bitwise OR operation to either +// of the modes (e.g., INPUT.ALT | INPUT.MOUSE) to receive +// [[termbox2::mouse_event]] events. If none of the main two modes were +// set, but the mouse mode was, INPUT.ESC is used. If for some reason you've +// decided to use INPUT.ESC | INPUT.ALT, it will behave as if only INPUT.ESC +// was selected. +// +// If mode is INPUT.CURRENT, return the current input mode. +// +// The default input mode is INPUT.ESC. +export fn set_input_mode(mode: input_mode) (int | err) = + int_or_err(tb_set_input_mode(mode: int)); +@symbol("tb_set_input_mode") fn tb_set_input_mode(mode: int) int; + +// Set the output mode, see [[termbox2::OUTPUT]]. +// +// Termbox has multiple output modes: +// +// 1. OUTPUT.NORMAL => [0..8] +// +// This mode provides 8 different colors (see [[termbox2::COLOR]]), plus +// COLOR.DEFAULT which skips sending a color code (i.e., uses the terminal's +// default color). Colors may be bitwise OR'd with style attributes, see +// [[termbox2::ATTRIBUTE]]. Extra attributes are available if OPT_ATTR_W == 64. +// +// Some notes: ATTRIBUTE.REVERSE and ATTRIBUTE.BRIGHT can be applied as either +// fg or bg attributes for the same effect. The rest of the attributes apply to +// fg only and are ignored as bg attributes. +// +// Example usage: +// +// let fg = termbox2::COLOR.BLACK | termbox2::ATTRIBUTE.BOLD; +// let bg = termbox2::COLOR.RED; +// termbox2::set_cell(x, y, '@', fg, bg); +// +// 2. OUTPUT._256 => [0..255] + ATTRIBUTE.HI_BLACK +// +// In this mode you get 256 distinct colors (plus default): +// - 0x00 (1): ATTRIBUTE.DEFAULT +// - ATTRIBUTE.HI_BLACK (1): COLOR.BLACK in OUTPUT.NORMAL +// - 0x01..0x07 (7): the next 7 colors as in OUTPUT.NORMAL +// - 0x08..0x0f (8): bright versions of the above +// - 0x10..0xe7 (216): 216 different colors +// - 0xe8..0xff (24): 24 different shades of gray +// +// All style attributes except ATTRIBUTE.BRIGHT may be bitwise OR'd as in +// OUTPUT.NORMAL. +// +// Note COLOR.BLACK must be used for black, as 0x00 represents default. +// +// 3. OUTPUT._216 => [0..216] +// +// This mode supports the 216-color range of OUTPUT._256 only, but you +// don't need to provide an offset: +// - 0x00 (1): COLOR.DEFAULT +// - 0x01..0xd8 (216): 216 different colors +// +// 4. OUTPUT.GRAYSCALE => [0..24] +// +// This mode supports the 24-color range of OUTPUT._256 only, but you +// don't need to provide an offset: +// - 0x00 (1): COLOR.DEFAULT +// - 0x01..0x18 (24): 24 different shades of gray +// +// 5. OUTPUT.TRUECOLOR => [0x000000..0xffffff] + ATTRIBUTE.HI_BLACK +// +// This mode provides 24-bit color on supported terminals. +// The format is 0xRRGGBB. +// +// All style attributes except ATTRIBUTE.BRIGHT may be bitwise OR'd as in +// OUTPUT.NORMAL. +// +// Note ATTRIBUTE.HI_BLACK must be used for black, as 0x000000 represents +// default. +// +// To use the terminal default color (i.e., to not send an escape code), pass +// COLOR.DEFAULT. For convenience, the value 0 is interpreted as COLOR.DEFAULT +// in all modes. +// +// Note, cell attributes persist after switching output modes. Any translation +// between, for example, OUTPUT.NORMAL's COLOR.RED and OUTPUT.TRUECOLOR's +// 0xff0000 must be performed by the caller. Also note that cells previously +// rendered in one mode may persist unchanged until the front buffer is cleared +// (such as after a resize event) at which point it will be re-interpreted and +// flushed according to the current mode. Callers may invoke +// [[termbox2::invalidate]] if it is desirable to immediately re-interpret +// and flush the entire screen according to the current mode. +// +// Note, not all terminals support all output modes, especially beyond +// OUTPUT.NORMAL. There is also no very reliable way to determine color +// support dynamically. If portability is desired, callers are recommended to +// use OUTPUT.NORMAL or make output mode end-user configurable. The same +// advice applies to style attributes. +// +// If mode is OUTPUT.CURRENT, return the current output mode. +// +// The default output mode is OUTPUT.NORMAL. +export fn set_output_mode(mode: output_mode) (int | err) = + int_or_err(tb_set_output_mode(mode: int)); +@symbol("tb_set_output_mode") fn tb_set_output_mode(mode: int) int; + +// Wait for an event up to timeout_ms milliseconds and return it. If no event +// is available within the timeout period, ERR.NO_EVENT is returned. On a resize +// event, the underlying select(2) call may be interrupted, yielding a return +// code of ERR.POLL. +export fn peek_event(timeout_ms: size) (event | err) = { + let e = tb_event { + t = 0, + mod = 0, + key = 0, + ch = 0, + w = 0, + h = 0, + x = 0, + y = 0, + }; + let r = tb_peek_event(&e, timeout_ms: int); + return if (r < 0) r: err else tb_event_to_event(e); +}; +@symbol("tb_peek_event") fn tb_peek_event(ev: *tb_event, t: int) int; + +// Same as [[termbox2::peek_event]] except no timeout. +export fn poll_event() (event | err) = { + let e = tb_event { + t = 0, + mod = 0, + key = 0, + ch = 0, + w = 0, + h = 0, + x = 0, + y = 0, + }; + let r = tb_poll_event(&e); + return if (r < 0) r: err else tb_event_to_event(e); +}; +@symbol("tb_poll_event") fn tb_poll_event(ev: *tb_event) int; + +// Internal termbox fds that can be used with poll(2), select(2), etc. +// externally. Callers must invoke [[termbox2::poll_event]] or +// [[termbox2::peek_event]] if fds become readable. +export fn get_fds() ((int, int) | err) = { + let ttyfd: int = 0; + let resizefd: int = 0; + let r = tb_get_fds(&ttyfd, &resizefd); + return if (r < 0) r: err else (ttyfd, resizefd); +}; +@symbol("tb_get_fds") fn tb_get_fds(ttyfd: *int, resizefd: *int) int; + +// Print function. On success, it returns the width of the printed string. +// Strings are interpreted as UTF-8. +// +// No attempt is made to do proper grapheme cluster parsing. With +// [[termbox2::OPT_EGC]] enabled, as a very coarse approximation, codepoints +// of width 0 are tacked on to the previous codepoint via +// [[termbox2::extend_cell]]. +// +// Non-printable characters (iswprint(3)) and truncated UTF-8 byte sequences +// are replaced with U+FFFD. +// +// Newlines (\n) are supported with the caveat that the return value will be +// the width of the string as if it were on a single line. +// +// If the starting coordinate is out of bounds, ERR.OUT_OF_BOUNDS is returned. +// If the starting coordinates is in bounds, but goes out of bounds, then the +// out-of-bounds portions of the string are ignored. +// +// For finer control, use [[termbox2::set_cell]]. +export fn print( + x: int, + y: int, + fg: uattr, + bg: uattr, + fmt_str: str, + args: fmt::field... +) (size | err) = { + let s = fmt::asprintf(fmt_str, args...)!; + defer free(s); + let cstr = c::fromstr(s)!; + defer free(cstr); + let outw: size = 0; + let e = tb_print_ex(x, y, fg, bg, &outw, cstr); + return if (e < 0) e: err else outw; +}; +@symbol("tb_print_ex") fn tb_print_ex( + x: int, + y: int, + fg: uattr, + bg: uattr, + out_w: *size, + cstr: const *c::char, +) int; + +// Send raw bytes to the terminal. +export fn send(buf: []u8) (void | err) = + void_or_err(tb_send(&buf[0]: *c::char, len(buf))); +@symbol("tb_send") fn tb_send(buf: const *c::char, nbuf: size) int; + +// Returns a string explaining the given error code. The caller must free +// the return value. +export fn strerror(e: err) str = switch (e) { + case ERR.HARE_BUILD => + yield fmt::asprintf( + "Cannot initialize Termbox because of a mismatch " + "between the C and Hare build options.\n" + "Hare OPT_EGC is {} and C TB_OPT_EGC is {}\n" + "Hare OPT_ATTR_W {} and C TB_OPT_ATTR_W is {}", + OPT_EGC, has_egc(), OPT_ATTR_W, attr_width() + )!; + case ERR.HARE_INVALID_UTF8 => + yield fmt::asprintf("Invalid UTF8 sequence recieved from tb")!; + case ERR.HARE_OTHER => + yield fmt::asprintf("Unknown error in the Hare bindings")!; + case => yield match (str_or_err(tb_strerror(e: int))) { + case err => yield fmt::asprintf( + "Unexpected error while interpreting previous error" + )!; + case let estr: str => yield estr; + }; +}; +@symbol("tb_strerror") fn tb_strerror(e: int) const *c::char; + +// Returns true if the the C termbox2 library was compiled with truecolor +// support (i.e., if [[termbox2::OPT_ATTR_W]] is 32 or 64). +export fn has_truecolor() bool = tb_has_truecolor() != 0; +@symbol("tb_has_truecolor") fn tb_has_truecolor() int; + +// Returns true if the the C termbox2 library was compiled with EGC +// support (i.e., if [[termbox2::OPT_EGC]] is true). +export fn has_egc() bool = tb_has_egc() != 0; +@symbol("tb_has_egc") fn tb_has_egc() int; + +// Returns the value of the TB_OPT_ATTR_W compile time option of the +// C termbox2 library. +export fn attr_width() size = tb_attr_width(): size; +@symbol("tb_attr_width") fn tb_attr_width() int; + +// Returns the string version of the C termbox2 library. +export fn version() (str | err) = str_or_err(tb_version()); +@symbol("tb_version") fn tb_version() const *c::char; diff --git a/vendor/termbox2 b/vendor/termbox2 new file mode 160000 index 0000000..cdf62e9 --- /dev/null +++ b/vendor/termbox2 @@ -0,0 +1 @@ +Subproject commit cdf62e9990d8b200768780080fb10a4e2f680051 -- cgit v1.3