diff options
| author | Sebastiano Tronto <sebastiano@tronto.net> | 2026-10-04 20:02:15 +0200 |
|---|---|---|
| committer | Sebastiano Tronto <sebastiano@tronto.net> | 2026-10-04 20:02:15 +0200 |
| commit | 2e31cbeb70d93bb0f5512064f181990db189d1f6 (patch) | |
| tree | 049f137715b692bcac0fd402fcbf04ef703523f0 /termbox2/termbox2.ha | |
| download | hare-termbox2-2e31cbeb70d93bb0f5512064f181990db189d1f6.tar.gz hare-termbox2-2e31cbeb70d93bb0f5512064f181990db189d1f6.zip | |
Initial commit
Diffstat (limited to '')
| -rw-r--r-- | termbox2/termbox2.ha | 605 |
1 files changed, 605 insertions, 0 deletions
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 @@ | |||
| 1 | use encoding::utf8; | ||
| 2 | use fmt; | ||
| 3 | use types::c; | ||
| 4 | |||
| 5 | // Type for key values, see [[termbox2::KEY]]. | ||
| 6 | export type key = u16; | ||
| 7 | |||
| 8 | // ASCII constants, see [[termbox2::key_event]].key. | ||
| 9 | export def KEY = struct { | ||
| 10 | CTRL_TILDE: key = 0x00, | ||
| 11 | CTRL_2: key = 0x00, // clash with CTRL_TILDE | ||
| 12 | CTRL_A: key = 0x01, | ||
| 13 | CTRL_B: key = 0x02, | ||
| 14 | CTRL_C: key = 0x03, | ||
| 15 | CTRL_D: key = 0x04, | ||
| 16 | CTRL_E: key = 0x05, | ||
| 17 | CTRL_F: key = 0x06, | ||
| 18 | CTRL_G: key = 0x07, | ||
| 19 | BACKSPACE: key = 0x08, | ||
| 20 | CTRL_H: key = 0x08, // clash with CTRL_BACKSPACE | ||
| 21 | TAB: key = 0x09, | ||
| 22 | CTRL_I: key = 0x09, // clash with TAB | ||
| 23 | CTRL_J: key = 0x0a, | ||
| 24 | CTRL_K: key = 0x0b, | ||
| 25 | CTRL_L: key = 0x0c, | ||
| 26 | ENTER: key = 0x0d, | ||
| 27 | CTRL_M: key = 0x0d, // clash with ENTER | ||
| 28 | CTRL_N: key = 0x0e, | ||
| 29 | CTRL_O: key = 0x0f, | ||
| 30 | CTRL_P: key = 0x10, | ||
| 31 | CTRL_Q: key = 0x11, | ||
| 32 | CTRL_R: key = 0x12, | ||
| 33 | CTRL_S: key = 0x13, | ||
| 34 | CTRL_T: key = 0x14, | ||
| 35 | CTRL_U: key = 0x15, | ||
| 36 | CTRL_V: key = 0x16, | ||
| 37 | CTRL_W: key = 0x17, | ||
| 38 | CTRL_X: key = 0x18, | ||
| 39 | CTRL_Y: key = 0x19, | ||
| 40 | CTRL_Z: key = 0x1a, | ||
| 41 | ESC: key = 0x1b, | ||
| 42 | CTRL_LSQ_BRACKET: key = 0x1b, // clash with 'ESC' | ||
| 43 | CTRL_3: key = 0x1b, // clash with 'ESC' | ||
| 44 | CTRL_4: key = 0x1c, | ||
| 45 | CTRL_BACKSLASH: key = 0x1c, // clash with 'CTRL_4' | ||
| 46 | CTRL_5: key = 0x1d, | ||
| 47 | CTRL_RSQ_BRACKET: key = 0x1d, // clash with 'CTRL_5' | ||
| 48 | CTRL_6: key = 0x1e, | ||
| 49 | CTRL_7: key = 0x1f, | ||
| 50 | CTRL_SLASH: key = 0x1f, // clash with 'CTRL_7' | ||
| 51 | CTRL_UNDERSCORE: key = 0x1f, // clash with 'CTRL_7' | ||
| 52 | SPACE: key = 0x20, | ||
| 53 | BACKSPACE2: key = 0x7f, | ||
| 54 | CTRL_8: key = 0x7f, // clash with 'BACKSPACE2' | ||
| 55 | F1: key = (0xffff - 0), | ||
| 56 | F2: key = (0xffff - 1), | ||
| 57 | F3: key = (0xffff - 2), | ||
| 58 | F4: key = (0xffff - 3), | ||
| 59 | F5: key = (0xffff - 4), | ||
| 60 | F6: key = (0xffff - 5), | ||
| 61 | F7: key = (0xffff - 6), | ||
| 62 | F8: key = (0xffff - 7), | ||
| 63 | F9: key = (0xffff - 8), | ||
| 64 | F10: key = (0xffff - 9), | ||
| 65 | F11: key = (0xffff - 10), | ||
| 66 | F12: key = (0xffff - 11), | ||
| 67 | INSERT: key = (0xffff - 12), | ||
| 68 | DELETE: key = (0xffff - 13), | ||
| 69 | HOME: key = (0xffff - 14), | ||
| 70 | END: key = (0xffff - 15), | ||
| 71 | PGUP: key = (0xffff - 16), | ||
| 72 | PGDN: key = (0xffff - 17), | ||
| 73 | ARROW_UP: key = (0xffff - 18), | ||
| 74 | ARROW_DOWN: key = (0xffff - 19), | ||
| 75 | ARROW_LEFT: key = (0xffff - 20), | ||
| 76 | ARROW_RIGHT: key = (0xffff - 21), | ||
| 77 | BACK_TAB: key = (0xffff - 22), | ||
| 78 | MOUSE_LEFT: key = (0xffff - 23), | ||
| 79 | MOUSE_RIGHT: key = (0xffff - 24), | ||
| 80 | MOUSE_MIDDLE: key = (0xffff - 25), | ||
| 81 | MOUSE_RELEASE: key = (0xffff - 26), | ||
| 82 | MOUSE_WHEEL_UP: key = (0xffff - 27), | ||
| 83 | MOUSE_WHEEL_DOWN: key = (0xffff - 28), | ||
| 84 | }; | ||
| 85 | |||
| 86 | // Type for key modifiers, see [[termbox2::MOD]]. | ||
| 87 | export type mod = u8; | ||
| 88 | |||
| 89 | // Key modifiers (bitwise), see [[termbox2::event]].mod. | ||
| 90 | export def MOD = struct { | ||
| 91 | ALT: mod = 1, | ||
| 92 | CTRL: mod = 2, | ||
| 93 | SHIFT: mod = 4, | ||
| 94 | MOTION: mod = 8, | ||
| 95 | }; | ||
| 96 | |||
| 97 | // Type for input modes, see [[termbox2::INPUT]]. | ||
| 98 | export type input_mode = int; | ||
| 99 | |||
| 100 | // Input modes (bitwise), see [[termbox2::set_input_mode]]. | ||
| 101 | export def INPUT = struct { | ||
| 102 | CURRENT: input_mode = 0, | ||
| 103 | ESC: input_mode = 1, | ||
| 104 | ALT: input_mode = 2, | ||
| 105 | MOUSE: input_mode = 4, | ||
| 106 | }; | ||
| 107 | |||
| 108 | // Type for output modes, see [[termbox2::OUTPUT]]. | ||
| 109 | export type output_mode = int; | ||
| 110 | |||
| 111 | // Output modes, see [[termbox2::set_output_mode]]. | ||
| 112 | export def OUTPUT = struct { | ||
| 113 | CURRENT: output_mode = 0, | ||
| 114 | NORMAL: output_mode = 1, | ||
| 115 | _256: output_mode = 2, | ||
| 116 | _216: output_mode = 3, | ||
| 117 | GRAYSCALE: output_mode = 4, | ||
| 118 | TRUECOLOR: output_mode = 5, // only available if OPT_ATTR_W >= 32 | ||
| 119 | }; | ||
| 120 | |||
| 121 | // Type for error return values, see [[termbox2::ERR]]. | ||
| 122 | export type err = !int; | ||
| 123 | |||
| 124 | // Common error return values. | ||
| 125 | // | ||
| 126 | // Library behavior is undefined after receiving ERR.MEM. | ||
| 127 | // Callers may attempt reinitializing by freeing memory, invoking | ||
| 128 | // [[termbox2::shutdown]], then [[termbox2::init]]. | ||
| 129 | export def ERR = struct { | ||
| 130 | ERR: err = -1, | ||
| 131 | NEED_MORE: err = -2, | ||
| 132 | INIT_ALREADY: err = -3, | ||
| 133 | INIT_OPEN: err = -4, | ||
| 134 | MEM: err = -5, | ||
| 135 | NO_EVENT: err = -6, | ||
| 136 | NO_TERM: err = -7, | ||
| 137 | NOT_INIT: err = -8, | ||
| 138 | OUT_OF_BOUNDS: err = -9, | ||
| 139 | READ: err = -10, | ||
| 140 | RESIZE_IOCTL: err = -11, | ||
| 141 | RESIZE_PIPE: err = -12, | ||
| 142 | RESIZE_SIGACTION: err = -13, | ||
| 143 | POLL: err = -14, | ||
| 144 | SELECT: err = -14, | ||
| 145 | TCGETATTR: err = -15, | ||
| 146 | TCSETATTR: err = -16, | ||
| 147 | UNSUPPORTED_TERM: err = -17, | ||
| 148 | RESIZE_WRITE: err = -18, | ||
| 149 | RESIZE_POLL: err = -19, | ||
| 150 | RESIZE_SELECT: err = -19, | ||
| 151 | RESIZE_READ: err = -20, | ||
| 152 | RESIZE_SSCANF: err = -21, | ||
| 153 | CAP_COLLISION: err = -22, | ||
| 154 | HARE_BUILD: err = -9990, // Additional error type | ||
| 155 | HARE_INVALID_UTF8: err = -9991, // Additional error type | ||
| 156 | HARE_OTHER: err = -9992, // Additional error type | ||
| 157 | }; | ||
| 158 | |||
| 159 | // An incoming key press event. Note that there is overlap between | ||
| 160 | // MOD.CTRL and KEY.CTRL_* keys. MOD.CTRL and MOD.SHIFT are only set | ||
| 161 | // as modifiers to KEY.ARROW_*. | ||
| 162 | export type key_event = struct { | ||
| 163 | mod: mod, // Modifier | ||
| 164 | key_or_ch: (key | rune), // A [[termbox2::KEY]] or a Unicode code point | ||
| 165 | }; | ||
| 166 | |||
| 167 | // An incoming resize event. | ||
| 168 | export type resize_event = struct { | ||
| 169 | w: i32, // resize width | ||
| 170 | h: i32, // resize height | ||
| 171 | }; | ||
| 172 | |||
| 173 | // An incoming mouse event. | ||
| 174 | export type mouse_event = struct { | ||
| 175 | key: key, // Mouse button, e.g. termbox2::KEY.MOUSE_LEFT | ||
| 176 | x: i32, | ||
| 177 | y: i32, | ||
| 178 | }; | ||
| 179 | |||
| 180 | // The C data structure for an event, for compatibility. | ||
| 181 | type tb_event = struct { | ||
| 182 | t: u8, | ||
| 183 | mod: u8, | ||
| 184 | key: u16, | ||
| 185 | ch: u32, | ||
| 186 | w: i32, | ||
| 187 | h: i32, | ||
| 188 | x: i32, | ||
| 189 | y: i32, | ||
| 190 | }; | ||
| 191 | |||
| 192 | def TB_EVENT_KEY: u8 = 1; | ||
| 193 | def TB_EVENT_RESIZE: u8 = 2; | ||
| 194 | def TB_EVENT_MOUSE: u8 = 3; | ||
| 195 | fn tb_event_to_event(e: tb_event) (event | err) = switch (e.t) { | ||
| 196 | case TB_EVENT_KEY => yield key_event { | ||
| 197 | mod = e.mod, | ||
| 198 | key_or_ch = if (e.key != 0) e.key: key else e.ch: rune, | ||
| 199 | }; | ||
| 200 | case TB_EVENT_RESIZE => yield resize_event { | ||
| 201 | w = e.w, | ||
| 202 | h = e.h, | ||
| 203 | }; | ||
| 204 | case TB_EVENT_MOUSE => yield mouse_event { | ||
| 205 | key = e.key, | ||
| 206 | x = e.x, | ||
| 207 | y = e.y, | ||
| 208 | }; | ||
| 209 | case => yield ERR.HARE_OTHER; | ||
| 210 | }; | ||
| 211 | |||
| 212 | // Any incoming event from the tty. | ||
| 213 | export type event = (key_event | resize_event | mouse_event); | ||
| 214 | |||
| 215 | fn build_opts_match() bool = | ||
| 216 | (has_egc() == OPT_EGC) && | ||
| 217 | (attr_width() == OPT_ATTR_W) && | ||
| 218 | (has_truecolor() == (OPT_ATTR_W == 32 || OPT_ATTR_W == 64)); | ||
| 219 | |||
| 220 | fn void_or_err(r: int) (void | err) = if (r < 0) r: err; | ||
| 221 | fn int_or_err(r: int) (int | err) = if (r < 0) r: err else r; | ||
| 222 | fn size_or_err(r: int) (size | err) = if (r < 0) r: err else r: size; | ||
| 223 | fn str_or_err(cstr: *c::char) (str | err) = match(c::tostr(cstr)) { | ||
| 224 | case utf8::invalid => yield ERR.HARE_INVALID_UTF8; | ||
| 225 | case let s: str => yield s; | ||
| 226 | }; | ||
| 227 | |||
| 228 | // Initialize the termbox library. This function should be called before | ||
| 229 | // any other functions. After successful initialization, the library must | ||
| 230 | // be finalized using [[termbox2::shutdown]]. | ||
| 231 | export fn init() (void | err) = | ||
| 232 | if (!build_opts_match()) | ||
| 233 | ERR.HARE_BUILD | ||
| 234 | else | ||
| 235 | void_or_err(tb_init()); | ||
| 236 | @symbol("tb_init") fn tb_init() int; | ||
| 237 | |||
| 238 | // Same as [[termbox2::init]], but allows to specify a file path in place | ||
| 239 | // of the default /dev/tty. | ||
| 240 | export fn init_file(path: str) (void | err) = { | ||
| 241 | let c_path = c::fromstr(path)!; | ||
| 242 | defer free(c_path); | ||
| 243 | return if (!build_opts_match()) | ||
| 244 | ERR.HARE_BUILD | ||
| 245 | else | ||
| 246 | void_or_err(tb_init_file(c_path)); | ||
| 247 | }; | ||
| 248 | @symbol("tb_init_file") fn tb_init_file(cstr: *c::char) int; | ||
| 249 | |||
| 250 | // Same as [[termbox2::init]], but allows to specify an output | ||
| 251 | // file descriptor. | ||
| 252 | export fn init_fd(ttyfd: int) (void | err) = | ||
| 253 | if (!build_opts_match()) | ||
| 254 | ERR.HARE_BUILD | ||
| 255 | else | ||
| 256 | void_or_err(tb_init_fd(ttyfd)); | ||
| 257 | @symbol("tb_init_fd") fn tb_init_fd(fd: int) int; | ||
| 258 | |||
| 259 | // Same as [[termbox2::init]], but allows to specify input and output | ||
| 260 | // file descriptors. | ||
| 261 | export fn init_rwfd(rfd: int, wfd: int) (void | err) = | ||
| 262 | if (!build_opts_match()) | ||
| 263 | ERR.HARE_BUILD | ||
| 264 | else | ||
| 265 | void_or_err(tb_init_rwfd(rfd, wfd)); | ||
| 266 | @symbol("tb_init_rwfd") fn tb_init_rwfd(rfd: int, wfd: int) int; | ||
| 267 | |||
| 268 | // Finalizes the library. | ||
| 269 | export fn shutdown() (void | err) = void_or_err(tb_shutdown()); | ||
| 270 | @symbol("tb_shutdown") fn tb_shutdown() int; | ||
| 271 | |||
| 272 | // Return the width of the internal back buffer (which is the same as the | ||
| 273 | // terminal's window width in columns). The internal buffer can be resized | ||
| 274 | // after [[termbox2::clear]] or [[termbox2::present]] calls. Returns ERR.ERR | ||
| 275 | // when called before [[termbox2::init]] or after [[termbox2::shutdown]]. | ||
| 276 | export fn width() (size | err) = size_or_err(tb_width()); | ||
| 277 | @symbol("tb_width") fn tb_width() int; | ||
| 278 | |||
| 279 | // Return the height of the internal back buffer (which is the same as the | ||
| 280 | // terminal's window width in rows). The internal buffer can be resized | ||
| 281 | // after [[termbox2::clear]] or [[termbox2::present]] calls. Returns ERR.ERR | ||
| 282 | // when called before [[termbox2::init]] or after [[termbox2::shutdown]]. | ||
| 283 | export fn height() (size | err) = size_or_err(tb_height()); | ||
| 284 | @symbol("tb_height") fn tb_height() int; | ||
| 285 | |||
| 286 | // Clear the internal back buffer using [[termbox2::ATTRIBUTE]].DEFAULT. | ||
| 287 | export fn clear() (void | err) = void_or_err(tb_clear()); | ||
| 288 | @symbol("tb_clear") fn tb_clear() int; | ||
| 289 | |||
| 290 | // Clear the internal back buffer using the specified attributes. | ||
| 291 | export fn set_clear_attrs(fg: uattr, bg: uattr) (void | err) = | ||
| 292 | void_or_err(tb_set_clear_attrs(fg, bg)); | ||
| 293 | @symbol("tb_set_clear_attrs") fn tb_set_clear_attrs(fg: uattr, bg: uattr) int; | ||
| 294 | |||
| 295 | // Synchronize the internal back buffer with the terminal by | ||
| 296 | // writing to tty. | ||
| 297 | export fn present() (void | err) = void_or_err(tb_present()); | ||
| 298 | @symbol("tb_present") fn tb_present() int; | ||
| 299 | |||
| 300 | // Clear the internal front buffer effectively forcing a complete re-render | ||
| 301 | // of the back buffer to the tty. It is not necessary to call this under | ||
| 302 | // normal circumstances. | ||
| 303 | export fn invalidate() (void | err) = void_or_err(tb_invalidate()); | ||
| 304 | @symbol("tb_invalidate") fn tb_invalidate() int; | ||
| 305 | |||
| 306 | // Set the position of the cursor. Upper-left cell is (0, 0). | ||
| 307 | export fn set_cursor(cx: int, cy: int) (void | err) = | ||
| 308 | void_or_err(tb_set_cursor(cx, cy)); | ||
| 309 | @symbol("tb_set_cursor") fn tb_set_cursor(x: int, y: int) int; | ||
| 310 | |||
| 311 | // Hide the cursor. | ||
| 312 | export fn hide_cursor() (void | err) = void_or_err(tb_hide_cursor()); | ||
| 313 | @symbol("tb_hide_cursor") fn tb_hide_cursor() int; | ||
| 314 | |||
| 315 | // Set cell contents in the internal back buffer at the specified position. | ||
| 316 | // Non-printable runes are replaced with U+FFFD at render time. | ||
| 317 | export fn set_cell( | ||
| 318 | x: int, | ||
| 319 | y: int, | ||
| 320 | ch: rune, | ||
| 321 | fg: uattr, | ||
| 322 | bg: uattr, | ||
| 323 | ) (void | err) = void_or_err(tb_set_cell(x, y, ch: u32, fg, bg)); | ||
| 324 | @symbol("tb_set_cell") fn tb_set_cell( | ||
| 325 | x: int, | ||
| 326 | y: int, | ||
| 327 | ch: u32, | ||
| 328 | fg: uattr, | ||
| 329 | bg: uattr, | ||
| 330 | ) int; | ||
| 331 | |||
| 332 | // Set extended cell contents. Only available when OPT_ATTR_W >= 32. | ||
| 333 | // See also [[termbox2::set_cell]] and [[termbox2::cell]]. | ||
| 334 | export fn set_cell_ex( | ||
| 335 | x: int, | ||
| 336 | y: int, | ||
| 337 | ch: []rune, | ||
| 338 | fg: uattr, | ||
| 339 | bg: uattr, | ||
| 340 | ) (void | err) = | ||
| 341 | void_or_err(tb_set_cell_ex(x, y, &ch[0]: *u32, len(ch), fg, bg)); | ||
| 342 | @symbol("tb_set_cell_ex") fn tb_set_cell_ex( | ||
| 343 | x: int, | ||
| 344 | y: int, | ||
| 345 | ch: *u32, | ||
| 346 | n: size, | ||
| 347 | fg: uattr, | ||
| 348 | bg: uattr, | ||
| 349 | ) int; | ||
| 350 | |||
| 351 | // Add one character to the cell's ech. Only available when OPT_ATTR_W >= 32. | ||
| 352 | // See also [[termbox2::set_cell_ex]] and [[termbox2::cell]]. | ||
| 353 | export fn extend_cell(x: int, y: int, ch: rune) (void | err) = | ||
| 354 | void_or_err(tb_extend_cell(x, y, ch: u32)); | ||
| 355 | @symbol("tb_extend_cell") fn tb_extend_cell(x: int, y: int, ch: u32) int; | ||
| 356 | |||
| 357 | // Set the input mode, see [[termbox2::INPUT]]. | ||
| 358 | // | ||
| 359 | // Termbox has two input modes: | ||
| 360 | // - INPUT.ESC When escape (\x1b) is in the buffer and there's no match | ||
| 361 | // for an escape sequence, a key event for [[termbox2::KEY]].ESC is returned. | ||
| 362 | // - INPUT.ALT When escape (\x1b) is in the buffer and there's no match | ||
| 363 | // for an an escape sequence, the next keyboard event is returned with a | ||
| 364 | // [[termbox2::MOD]].ALT modifier. | ||
| 365 | // | ||
| 366 | // You can also apply termbox2::INPUT.MOUSE via bitwise OR operation to either | ||
| 367 | // of the modes (e.g., INPUT.ALT | INPUT.MOUSE) to receive | ||
| 368 | // [[termbox2::mouse_event]] events. If none of the main two modes were | ||
| 369 | // set, but the mouse mode was, INPUT.ESC is used. If for some reason you've | ||
| 370 | // decided to use INPUT.ESC | INPUT.ALT, it will behave as if only INPUT.ESC | ||
| 371 | // was selected. | ||
| 372 | // | ||
| 373 | // If mode is INPUT.CURRENT, return the current input mode. | ||
| 374 | // | ||
| 375 | // The default input mode is INPUT.ESC. | ||
| 376 | export fn set_input_mode(mode: input_mode) (int | err) = | ||
| 377 | int_or_err(tb_set_input_mode(mode: int)); | ||
| 378 | @symbol("tb_set_input_mode") fn tb_set_input_mode(mode: int) int; | ||
| 379 | |||
| 380 | // Set the output mode, see [[termbox2::OUTPUT]]. | ||
| 381 | // | ||
| 382 | // Termbox has multiple output modes: | ||
| 383 | // | ||
| 384 | // 1. OUTPUT.NORMAL => [0..8] | ||
| 385 | // | ||
| 386 | // This mode provides 8 different colors (see [[termbox2::COLOR]]), plus | ||
| 387 | // COLOR.DEFAULT which skips sending a color code (i.e., uses the terminal's | ||
| 388 | // default color). Colors may be bitwise OR'd with style attributes, see | ||
| 389 | // [[termbox2::ATTRIBUTE]]. Extra attributes are available if OPT_ATTR_W == 64. | ||
| 390 | // | ||
| 391 | // Some notes: ATTRIBUTE.REVERSE and ATTRIBUTE.BRIGHT can be applied as either | ||
| 392 | // fg or bg attributes for the same effect. The rest of the attributes apply to | ||
| 393 | // fg only and are ignored as bg attributes. | ||
| 394 | // | ||
| 395 | // Example usage: | ||
| 396 | // | ||
| 397 | // let fg = termbox2::COLOR.BLACK | termbox2::ATTRIBUTE.BOLD; | ||
| 398 | // let bg = termbox2::COLOR.RED; | ||
| 399 | // termbox2::set_cell(x, y, '@', fg, bg); | ||
| 400 | // | ||
| 401 | // 2. OUTPUT._256 => [0..255] + ATTRIBUTE.HI_BLACK | ||
| 402 | // | ||
| 403 | // In this mode you get 256 distinct colors (plus default): | ||
| 404 | // - 0x00 (1): ATTRIBUTE.DEFAULT | ||
| 405 | // - ATTRIBUTE.HI_BLACK (1): COLOR.BLACK in OUTPUT.NORMAL | ||
| 406 | // - 0x01..0x07 (7): the next 7 colors as in OUTPUT.NORMAL | ||
| 407 | // - 0x08..0x0f (8): bright versions of the above | ||
| 408 | // - 0x10..0xe7 (216): 216 different colors | ||
| 409 | // - 0xe8..0xff (24): 24 different shades of gray | ||
| 410 | // | ||
| 411 | // All style attributes except ATTRIBUTE.BRIGHT may be bitwise OR'd as in | ||
| 412 | // OUTPUT.NORMAL. | ||
| 413 | // | ||
| 414 | // Note COLOR.BLACK must be used for black, as 0x00 represents default. | ||
| 415 | // | ||
| 416 | // 3. OUTPUT._216 => [0..216] | ||
| 417 | // | ||
| 418 | // This mode supports the 216-color range of OUTPUT._256 only, but you | ||
| 419 | // don't need to provide an offset: | ||
| 420 | // - 0x00 (1): COLOR.DEFAULT | ||
| 421 | // - 0x01..0xd8 (216): 216 different colors | ||
| 422 | // | ||
| 423 | // 4. OUTPUT.GRAYSCALE => [0..24] | ||
| 424 | // | ||
| 425 | // This mode supports the 24-color range of OUTPUT._256 only, but you | ||
| 426 | // don't need to provide an offset: | ||
| 427 | // - 0x00 (1): COLOR.DEFAULT | ||
| 428 | // - 0x01..0x18 (24): 24 different shades of gray | ||
| 429 | // | ||
| 430 | // 5. OUTPUT.TRUECOLOR => [0x000000..0xffffff] + ATTRIBUTE.HI_BLACK | ||
| 431 | // | ||
| 432 | // This mode provides 24-bit color on supported terminals. | ||
| 433 | // The format is 0xRRGGBB. | ||
| 434 | // | ||
| 435 | // All style attributes except ATTRIBUTE.BRIGHT may be bitwise OR'd as in | ||
| 436 | // OUTPUT.NORMAL. | ||
| 437 | // | ||
| 438 | // Note ATTRIBUTE.HI_BLACK must be used for black, as 0x000000 represents | ||
| 439 | // default. | ||
| 440 | // | ||
| 441 | // To use the terminal default color (i.e., to not send an escape code), pass | ||
| 442 | // COLOR.DEFAULT. For convenience, the value 0 is interpreted as COLOR.DEFAULT | ||
| 443 | // in all modes. | ||
| 444 | // | ||
| 445 | // Note, cell attributes persist after switching output modes. Any translation | ||
| 446 | // between, for example, OUTPUT.NORMAL's COLOR.RED and OUTPUT.TRUECOLOR's | ||
| 447 | // 0xff0000 must be performed by the caller. Also note that cells previously | ||
| 448 | // rendered in one mode may persist unchanged until the front buffer is cleared | ||
| 449 | // (such as after a resize event) at which point it will be re-interpreted and | ||
| 450 | // flushed according to the current mode. Callers may invoke | ||
| 451 | // [[termbox2::invalidate]] if it is desirable to immediately re-interpret | ||
| 452 | // and flush the entire screen according to the current mode. | ||
| 453 | // | ||
| 454 | // Note, not all terminals support all output modes, especially beyond | ||
| 455 | // OUTPUT.NORMAL. There is also no very reliable way to determine color | ||
| 456 | // support dynamically. If portability is desired, callers are recommended to | ||
| 457 | // use OUTPUT.NORMAL or make output mode end-user configurable. The same | ||
| 458 | // advice applies to style attributes. | ||
| 459 | // | ||
| 460 | // If mode is OUTPUT.CURRENT, return the current output mode. | ||
| 461 | // | ||
| 462 | // The default output mode is OUTPUT.NORMAL. | ||
| 463 | export fn set_output_mode(mode: output_mode) (int | err) = | ||
| 464 | int_or_err(tb_set_output_mode(mode: int)); | ||
| 465 | @symbol("tb_set_output_mode") fn tb_set_output_mode(mode: int) int; | ||
| 466 | |||
| 467 | // Wait for an event up to timeout_ms milliseconds and return it. If no event | ||
| 468 | // is available within the timeout period, ERR.NO_EVENT is returned. On a resize | ||
| 469 | // event, the underlying select(2) call may be interrupted, yielding a return | ||
| 470 | // code of ERR.POLL. | ||
| 471 | export fn peek_event(timeout_ms: size) (event | err) = { | ||
| 472 | let e = tb_event { | ||
| 473 | t = 0, | ||
| 474 | mod = 0, | ||
| 475 | key = 0, | ||
| 476 | ch = 0, | ||
| 477 | w = 0, | ||
| 478 | h = 0, | ||
| 479 | x = 0, | ||
| 480 | y = 0, | ||
| 481 | }; | ||
| 482 | let r = tb_peek_event(&e, timeout_ms: int); | ||
| 483 | return if (r < 0) r: err else tb_event_to_event(e); | ||
| 484 | }; | ||
| 485 | @symbol("tb_peek_event") fn tb_peek_event(ev: *tb_event, t: int) int; | ||
| 486 | |||
| 487 | // Same as [[termbox2::peek_event]] except no timeout. | ||
| 488 | export fn poll_event() (event | err) = { | ||
| 489 | let e = tb_event { | ||
| 490 | t = 0, | ||
| 491 | mod = 0, | ||
| 492 | key = 0, | ||
| 493 | ch = 0, | ||
| 494 | w = 0, | ||
| 495 | h = 0, | ||
| 496 | x = 0, | ||
| 497 | y = 0, | ||
| 498 | }; | ||
| 499 | let r = tb_poll_event(&e); | ||
| 500 | return if (r < 0) r: err else tb_event_to_event(e); | ||
| 501 | }; | ||
| 502 | @symbol("tb_poll_event") fn tb_poll_event(ev: *tb_event) int; | ||
| 503 | |||
| 504 | // Internal termbox fds that can be used with poll(2), select(2), etc. | ||
| 505 | // externally. Callers must invoke [[termbox2::poll_event]] or | ||
| 506 | // [[termbox2::peek_event]] if fds become readable. | ||
| 507 | export fn get_fds() ((int, int) | err) = { | ||
| 508 | let ttyfd: int = 0; | ||
| 509 | let resizefd: int = 0; | ||
| 510 | let r = tb_get_fds(&ttyfd, &resizefd); | ||
| 511 | return if (r < 0) r: err else (ttyfd, resizefd); | ||
| 512 | }; | ||
| 513 | @symbol("tb_get_fds") fn tb_get_fds(ttyfd: *int, resizefd: *int) int; | ||
| 514 | |||
| 515 | // Print function. On success, it returns the width of the printed string. | ||
| 516 | // Strings are interpreted as UTF-8. | ||
| 517 | // | ||
| 518 | // No attempt is made to do proper grapheme cluster parsing. With | ||
| 519 | // [[termbox2::OPT_EGC]] enabled, as a very coarse approximation, codepoints | ||
| 520 | // of width 0 are tacked on to the previous codepoint via | ||
| 521 | // [[termbox2::extend_cell]]. | ||
| 522 | // | ||
| 523 | // Non-printable characters (iswprint(3)) and truncated UTF-8 byte sequences | ||
| 524 | // are replaced with U+FFFD. | ||
| 525 | // | ||
| 526 | // Newlines (\n) are supported with the caveat that the return value will be | ||
| 527 | // the width of the string as if it were on a single line. | ||
| 528 | // | ||
| 529 | // If the starting coordinate is out of bounds, ERR.OUT_OF_BOUNDS is returned. | ||
| 530 | // If the starting coordinates is in bounds, but goes out of bounds, then the | ||
| 531 | // out-of-bounds portions of the string are ignored. | ||
| 532 | // | ||
| 533 | // For finer control, use [[termbox2::set_cell]]. | ||
| 534 | export fn print( | ||
| 535 | x: int, | ||
| 536 | y: int, | ||
| 537 | fg: uattr, | ||
| 538 | bg: uattr, | ||
| 539 | fmt_str: str, | ||
| 540 | args: fmt::field... | ||
| 541 | ) (size | err) = { | ||
| 542 | let s = fmt::asprintf(fmt_str, args...)!; | ||
| 543 | defer free(s); | ||
| 544 | let cstr = c::fromstr(s)!; | ||
| 545 | defer free(cstr); | ||
| 546 | let outw: size = 0; | ||
| 547 | let e = tb_print_ex(x, y, fg, bg, &outw, cstr); | ||
| 548 | return if (e < 0) e: err else outw; | ||
| 549 | }; | ||
| 550 | @symbol("tb_print_ex") fn tb_print_ex( | ||
| 551 | x: int, | ||
| 552 | y: int, | ||
| 553 | fg: uattr, | ||
| 554 | bg: uattr, | ||
| 555 | out_w: *size, | ||
| 556 | cstr: const *c::char, | ||
| 557 | ) int; | ||
| 558 | |||
| 559 | // Send raw bytes to the terminal. | ||
| 560 | export fn send(buf: []u8) (void | err) = | ||
| 561 | void_or_err(tb_send(&buf[0]: *c::char, len(buf))); | ||
| 562 | @symbol("tb_send") fn tb_send(buf: const *c::char, nbuf: size) int; | ||
| 563 | |||
| 564 | // Returns a string explaining the given error code. The caller must free | ||
| 565 | // the return value. | ||
| 566 | export fn strerror(e: err) str = switch (e) { | ||
| 567 | case ERR.HARE_BUILD => | ||
| 568 | yield fmt::asprintf( | ||
| 569 | "Cannot initialize Termbox because of a mismatch " | ||
| 570 | "between the C and Hare build options.\n" | ||
| 571 | "Hare OPT_EGC is {} and C TB_OPT_EGC is {}\n" | ||
| 572 | "Hare OPT_ATTR_W {} and C TB_OPT_ATTR_W is {}", | ||
| 573 | OPT_EGC, has_egc(), OPT_ATTR_W, attr_width() | ||
| 574 | )!; | ||
| 575 | case ERR.HARE_INVALID_UTF8 => | ||
| 576 | yield fmt::asprintf("Invalid UTF8 sequence recieved from tb")!; | ||
| 577 | case ERR.HARE_OTHER => | ||
| 578 | yield fmt::asprintf("Unknown error in the Hare bindings")!; | ||
| 579 | case => yield match (str_or_err(tb_strerror(e: int))) { | ||
| 580 | case err => yield fmt::asprintf( | ||
| 581 | "Unexpected error while interpreting previous error" | ||
| 582 | )!; | ||
| 583 | case let estr: str => yield estr; | ||
| 584 | }; | ||
| 585 | }; | ||
| 586 | @symbol("tb_strerror") fn tb_strerror(e: int) const *c::char; | ||
| 587 | |||
| 588 | // Returns true if the the C termbox2 library was compiled with truecolor | ||
| 589 | // support (i.e., if [[termbox2::OPT_ATTR_W]] is 32 or 64). | ||
| 590 | export fn has_truecolor() bool = tb_has_truecolor() != 0; | ||
| 591 | @symbol("tb_has_truecolor") fn tb_has_truecolor() int; | ||
| 592 | |||
| 593 | // Returns true if the the C termbox2 library was compiled with EGC | ||
| 594 | // support (i.e., if [[termbox2::OPT_EGC]] is true). | ||
| 595 | export fn has_egc() bool = tb_has_egc() != 0; | ||
| 596 | @symbol("tb_has_egc") fn tb_has_egc() int; | ||
| 597 | |||
| 598 | // Returns the value of the TB_OPT_ATTR_W compile time option of the | ||
| 599 | // C termbox2 library. | ||
| 600 | export fn attr_width() size = tb_attr_width(): size; | ||
| 601 | @symbol("tb_attr_width") fn tb_attr_width() int; | ||
| 602 | |||
| 603 | // Returns the string version of the C termbox2 library. | ||
| 604 | export fn version() (str | err) = str_or_err(tb_version()); | ||
| 605 | @symbol("tb_version") fn tb_version() const *c::char; | ||
