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 finishfn = fn(w: *widget) void; export type widgetsize = (tty::ttysize | void); export type none = void; export type all = void; export type row = uint; export type col = uint; //export type damageitem = (row | col | coords); export type damageitem = row; export type damage = (none | all | []damageitem); export type widget = struct { state: *tui::tui, print: *printfn, resize: *resizefn, finish: *finishfn, buf: str, pos: coords, sz: widgetsize, style: (*style | void), damage: damage, }; 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"; def CLEAR: str = "\x1B[2J"; def CLEARROW: str = "\x1B[2K"; // 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); let iscolorcode = false; for (let r: rune => strings::next(&iter)) { if (r == '\x1B') { iscolorcode = true; }; const char = if (!iscolorcode) { yield strings::fromrunes([r, OVERLINE]); } else { yield strings::fromrunes([r]); }; defer free(char); memio::concat(&st, char)!; if (iscolorcode && r == 'm') iscolorcode = false; }; 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)); fn clearrow(row: uint) str = fmt::asprintf("\x1B[{}d{}", row, CLEARROW); export fn print(w: *widget) void = { const clear = match (w.damage) { case all => yield strings::dup(CLEAR); case none => yield strings::dup(""); case let dam: []damageitem => let st = memio::dynamic(); defer io::close(&st)!; for (let item: damageitem .. dam) { memio::concat(&st, clearrow(item))!; //match (item) { //case let r: row => // yield clearrow(r); ////case let c: col => ////case let co: coords => //}; }; yield strings::dup(memio::string(&st)!); }; defer free(clear); 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)!; w.state.clear = false; }; // 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)!; }; export fn finish(w: *widget) void = { match (w.damage) { case let dam: []damageitem => free(dam); case all => return; case all => return; }; }; export fn cleardamage(w: *widget) void = { let dam = if (w.damage is all) { return; } else { yield w.damage as []damageitem; }; for (let i = 0z; i < len(dam); i += 1) { delete(dam[i]); }; };