use tui; use fmt; use unix::tty; use io; use strings; use memio; use os; use strconv; export type coords = (u16, u16); export type printfn = fn(w: *widget) void; export type resizefn = fn(w: *widget, ttysize: tty::ttysize) void; export type widgetsize = (tty::ttysize | void); export type widget = struct { state: *tui::tui, print: *printfn, resize: *resizefn, buf: str, pos: coords, sz: widgetsize, style: (*style | void), }; export type color = enum uint { BLACKFG = 30, REDFG = 31, GREENFG = 32, BROWNFG = 33, BLUEFG = 34, MAGENTAFG = 35, CYANFG = 36, WHITEFG = 37, DEFAULTFG = 39, BLACKBG = 40, REDBG = 41, GREENBG = 42, BROWNBG = 43, BLUEBG = 44, MAGENTABG = 45, CYANBG = 46, WHITEBG = 47, DEFAULTBG = 49, }; export type style = struct { border: bool, colorfg: color, colorbg: color, }; export def DEFAULT_STYLE: style = style { border = false, colorfg = color::DEFAULTFG, colorbg = color::DEFAULTBG, }; def gotoroot: str = "\x1B[1;1H"; def UNDERLINE: str = "\x1B[4m"; def NOUNDERLINE: str = "\x1B[0m"; def OVERLINE: rune = '\u0305'; def NOOVERLINE: str = "\x1B[0m"; // Must free return value fn underline(s: str) str = { return strings::concat(UNDERLINE, s, NOUNDERLINE); }; // Must free return value fn overline(s: str) str = { const st = memio::dynamic(); defer io::close(&st)!; let iter = strings::iter(s); for (let r: rune => strings::next(&iter)) { const char = strings::fromrunes([r, OVERLINE]); defer free(char); memio::concat(&st, char)!; }; return strings::dup(memio::string(&st)!); }; fn minrows(out: io::file, x: u16, y: widgetsize) (u16 | tty::error) = { const y = match (y) { case let y: tty::ttysize => yield y; case void => yield tty::winsize(out)?; }; return if (x < y.rows) x else y.rows; }; // Must free return string fn color_to_str(color: color) str = fmt::asprintf("\x1B[{}m", strconv::utos(color)); export fn print(w: *widget) void = { let s = truncate_to_size(w); defer free(s); let sstyle = applystyles(w.style, s); defer free(sstyle); const clear = if (w.state.clear) "\x1B[2J" else ""; let seekpos = fmt::asprintf("{}\x1B[{};{}H", clear, w.pos.0, w.pos.1); defer free(seekpos); const sout = strings::concat(seekpos, sstyle); defer free(sout); fmt::fprint(w.state.out, sout)!; }; // Applies styling (style) to the given string fn applystyles(st: (*style | void), s: str) str = { return match (st) { case let st: *style => let sborder = if (st.border) { yield border(s); } else { yield strings::dup(s); }; defer free(sborder); const scolor = color_to_str(st.colorfg); defer free(scolor); const defcolor = color_to_str(color::DEFAULTFG); defer free(defcolor); yield strings::concat(scolor, sborder, defcolor); case void => yield strings::dup(s); }; }; // Truncates the text of the widget to the widget's size (rows and columns) and // returns the resulting string. fn truncate_to_size(w: *widget) str = { let spl = strings::split(w.buf, "\n"); const st = memio::dynamic(); defer io::close(&st)!; for (let i = 0z; i < minrows(w.state.out, len(spl): u16, w.sz)!; i += 1) { const line = spl[i]; let item = match (w.sz) { case let sz: tty::ttysize => const s = if (len(line) > sz.columns) strings::sub(line, 0z, sz.columns) else line; yield strings::rpad(s, ' ', sz.columns); case void => yield strings::dup(line); }; defer free(item); memio::concat(&st, item)!; if (i < len(spl) - 1) { memio::concat(&st, "\n")!; }; }; return strings::dup(memio::string(&st)!); }; // Add a border around a string fn border(s: str) str = { let st = memio::dynamic(); defer io::close(&st)!; let spl = strings::split(s, "\n"); for (let i = 0z; i < len(spl); i += 1) { const s = strings::concat("|", spl[i], "|"); //const s = strings::dup(spl[i]); defer free(s); if (i == 0) { s = overline(s); }; if (i == len(spl) - 1) { s = underline(s); }; //memio::concat(&st, "|", s, "|")!; memio::concat(&st, s)!; if (i < len(spl) - 1) { memio::concat(&st, "\n")!; }; }; return strings::dup(memio::string(&st)!); }; export fn prints(out: io::file, s: str, pos: coords) void = { fmt::fprintf(out, "\x1B[{};{}H", pos.0, pos.1)!; fmt::fprint(out, s)!; };