Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 56 additions & 4 deletions termbox2.h
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,9 @@ extern "C" {
#define TB_UNDERLINE_2 0x0000000200000000
#define TB_OVERLINE 0x0000000400000000
#define TB_INVISIBLE 0x0000000800000000
#define TB_UATTR 0x8000000000000000 // See `tb_set_uattr_func`
#define TB_UATTR_START 0x0000000100000000
#define TB_UATTR_MASK 0xffffffff00000000
#endif

/* Event types (`tb_event.type`) */
Expand Down Expand Up @@ -572,7 +575,8 @@ int tb_set_input_mode(int mode);
*
* The following style attributes are also available if compiled with
* `TB_OPT_ATTR_W` set to 64:
* `TB_STRIKEOUT`, `TB_UNDERLINE_2`, `TB_OVERLINE`, `TB_INVISIBLE`
* `TB_STRIKEOUT`, `TB_UNDERLINE_2`, `TB_OVERLINE`, `TB_INVISIBLE`,
* `TB_UATTR`
*
* As in all modes, the value 0 is interpreted as `TB_DEFAULT` for
* convenience.
Expand Down Expand Up @@ -704,6 +708,32 @@ int tb_sendf(const char *fmt, ...);
*/
int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *));

/**
* Set `TB_UATTR` callback.
*
* If a cell's `fg` or `bg` style attribute has the `TB_UATTR` bit set, termbox
* will ignore all the attributes set in the upper 32 bits of `fg` and `bg`
* (above and including `TB_STRIKEOUT`) and instead invoke the specified
* callback to get the escape codes to emit for cell. This allows for 31 bits of
* user-defined style attributes (minus 1 bit for `TB_UATTR` itself) for both
* `fg` and `bg`.
*
* For convenience, `TB_UATTR` may be combined with other attributes that occupy
* the lower 32 such as `TB_BOLD` and colors for each of the output modes.
*
* The callback should set `out` to a string of escape codes according to `fg`
* and `bg`, set `out_len` to the length of this string, and return `TB_OK`. Any
* other return value will cause future `tb_present` calls to error. The
* callback should be deterministic. That is, it should return the same output
* given the same input. Pass NULL to disable the callback, which causes
* `TB_UATTR` to have no effect.
*
* Note this functionality only works when termbox is compiled with 64-bit style
* attributes (`TB_OPT_ATTR_W == 64`).
*/
int tb_set_uattr_func(int (*fn)(uintattr_t fg, uintattr_t bg, char **out,
size_t *out_len));

/* Return byte length of codepoint given first byte of UTF-8 sequence (1-6). */
int tb_utf8_char_length(char c);

Expand Down Expand Up @@ -838,6 +868,7 @@ struct tb_global {
int has_orig_tios;
int last_errno;
int initialized;
int (*fn_uattr)(uintattr_t fg, uintattr_t bg, char **out, size_t *out_len);
int (*fn_extract_esc_pre)(struct tb_event *, size_t *);
int (*fn_extract_esc_post)(struct tb_event *, size_t *);
char errbuf[1024];
Expand Down Expand Up @@ -2732,6 +2763,12 @@ int tb_set_func(int fn_type, int (*fn)(struct tb_event *, size_t *)) {
return TB_ERR;
}

int tb_set_uattr_func(int (*fn)(uintattr_t fg, uintattr_t bg, char **out,
size_t *out_len)) {
global.fn_uattr = fn;
return TB_OK;
}

struct tb_cell *tb_cell_buffer(void) {
if (!global.initialized) return NULL;
return global.back.cells;
Expand Down Expand Up @@ -3774,15 +3811,30 @@ static void handle_resize(int sig) {
errno = errno_copy;
}

static int send_attr(uintattr_t fg, uintattr_t bg) {
static int send_attr(uintattr_t ofg, uintattr_t obg) {
int rv;

uintattr_t fg = ofg;
uintattr_t bg = obg;

if (fg == global.last_fg && bg == global.last_bg) {
return TB_OK;
}

if_err_return(rv, bytebuf_puts(&global.out, global.caps[TB_CAP_SGR0]));

#if TB_OPT_ATTR_W == 64
if (global.fn_uattr && ((fg & TB_UATTR) || (bg & TB_UATTR))) {
// Apply uattr
char *uattr = NULL;
size_t nuattr = 0;
if_err_return(rv, (global.fn_uattr)(fg, bg, &uattr, &nuattr));
if_err_return(rv, bytebuf_nputs(&global.out, uattr, nuattr));
fg &= ~TB_UATTR_MASK;
bg &= ~TB_UATTR_MASK;
}
#endif

uint32_t cfg, cbg;
switch (global.output_mode) {
default:
Expand Down Expand Up @@ -3881,8 +3933,8 @@ static int send_attr(uintattr_t fg, uintattr_t bg) {

if_err_return(rv, send_sgr(cfg, cbg, fg_is_default, bg_is_default));

global.last_fg = fg;
global.last_bg = bg;
global.last_fg = ofg;
global.last_bg = obg;

return TB_OK;
}
Expand Down
24 changes: 24 additions & 0 deletions tests/test_uattr/expected.ansi
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#5red and bold
#5curly and normal
#5dotted and red
#5dashed and bold




















39 changes: 39 additions & 0 deletions tests/test_uattr/test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);

if ($test->ffi->tb_attr_width() !== 64) {
$test->skip();
}

$test->ffi->tb_init();

$uattr = $test->defines['TB_UATTR'];
$red = $test->defines['TB_RED'];
$bold = $test->defines['TB_BOLD'];
$uattr_start = $test->defines['TB_UATTR_START'];
$curly = $uattr_start << 0;
$dotted = $uattr_start << 1;
$dashed = $uattr_start << 2;

$test->ffi->tb_set_uattr_func(function ($fg, $bg, $out, $nout) use ($test, $curly, $dotted, $dashed) {
$code = '';
if ($fg & $curly) $code = "\x1b[4:3m";
else if ($fg & $dotted) $code = "\x1b[4:4m";
else if ($fg & $dashed) $code = "\x1b[4:5m";
$code_len = strlen($code);
$buf = $test->ffi->new('char[64]', false);
FFI::memcpy($buf, $code, $code_len);
$out[0] = $buf;
$nout[0] = $code_len;
return $test->defines['TB_OK'];
});

$y = 0;
$test->ffi->tb_print(0, $y++, $red | $bold, 0, 'red and bold');
$test->ffi->tb_print(0, $y++, $test->defines['TB_UATTR'] | $curly, 0, 'curly and normal');
$test->ffi->tb_print(0, $y++, $test->defines['TB_UATTR'] | $dotted | $red, 0, 'dotted and red');
$test->ffi->tb_print(0, $y++, $test->defines['TB_UATTR'] | $dashed | $bold, 0, 'dashed and bold');

$test->ffi->tb_present();

$test->screencap();