summaryrefslogtreecommitdiff
path: root/tui
diff options
context:
space:
mode:
authorJulian Hurst <julian.hurst@digdash.com>2025-03-12 15:07:24 +0100
committerJulian Hurst <julian.hurst@digdash.com>2025-03-12 15:07:24 +0100
commita8d7b95b05d586f3b65fbf9757fab39c8098aee9 (patch)
treebd34e7539f2068f49a13c6c742b47f2db6c13b3e /tui
parent48c5f4a17840b8418cb64f6e1667da86d804ba02 (diff)
downloadhare-tui-a8d7b95b05d586f3b65fbf9757fab39c8098aee9.tar.gz
Support styling
Diffstat (limited to 'tui')
-rw-r--r--tui/widget/list/list.ha27
-rw-r--r--tui/widget/text/text.ha11
-rw-r--r--tui/widget/widget.ha151
3 files changed, 171 insertions, 18 deletions
diff --git a/tui/widget/list/list.ha b/tui/widget/list/list.ha
index 7e60299..50fb8df 100644
--- a/tui/widget/list/list.ha
+++ b/tui/widget/list/list.ha
@@ -16,15 +16,16 @@ export type list = struct {
};
// Return an instance of list. out is the tty file, pos the starting position,
-// sz is the size of the widget (if [[widget::nosize]] is used, the maximum possible
+// sz is the size of the widget (if void is used, the maximum possible
// size is used), items is the slice of items of the list.
-export fn newlist(out: io::file, pos: widget::coords, sz: widget::widgetsize, items: str...) (list | tty::error) = {
+export fn newlist(out: io::file, pos: widget::coords, sz: widget::widgetsize,
+style: (*widget::style | void), items: str...) (list | tty::error) = {
const tsz = tty::winsize(out)?;
let end = match (sz) {
case let sz: tty::ttysize =>
yield if (tsz.rows < sz.rows) tsz.rows else sz.rows;
- case widget::nosize =>
+ case void =>
yield tsz.rows;
};
@@ -39,6 +40,8 @@ export fn newlist(out: io::file, pos: widget::coords, sz: widget::widgetsize, it
resize = &resizelist,
pos = pos,
sz = sz,
+ style = style,
+ ...
},
items = items,
frame = frame {
@@ -53,18 +56,20 @@ export fn printlist(widget: *widget::widget) void = {
let st = memio::dynamic();
defer io::close(&st)!;
for (let i = list.frame.start; i < list.frame.end; i += 1) {
- let item = match (list.widget.sz) {
- case let sz: tty::ttysize =>
- yield strings::sub(list.items[i], 0z, sz.columns);
- case widget::nosize =>
- yield list.items[i];
- };
- memio::concat(&st, item)!;
+ //let item = match (list.widget.sz) {
+ //case let sz: tty::ttysize =>
+ // yield strings::sub(list.items[i], 0z, sz.columns);
+ //case widget::nosize =>
+ // yield list.items[i];
+ //};
+ //memio::concat(&st, item)!;
+ memio::concat(&st, list.items[i])!;
if (i != list.frame.end - 1) {
memio::concat(&st, "\n")!;
};
};
- widget::print(list.widget.out, memio::string(&st)!, (1, 1));
+ list.widget.buf = memio::string(&st)!;
+ widget::print(list);
};
export fn resizelist(widget: *widget::widget, ttysize: tty::ttysize) void = {
diff --git a/tui/widget/text/text.ha b/tui/widget/text/text.ha
index 5cd8da7..ecb5c2c 100644
--- a/tui/widget/text/text.ha
+++ b/tui/widget/text/text.ha
@@ -1,20 +1,23 @@
use io;
use unix::tty;
use tui::widget;
+use strings;
export type text = struct {
widget: widget::widget,
txt: str,
};
-export fn newtext(out: io::file, txt: str, pos: widget::coords) text = {
+export fn newtext(out: io::file, txt: str, pos: widget::coords, style: (*widget::style | void)) text = {
return text {
widget = widget::widget {
out = out,
print = &printtext,
resize = &resizetext,
pos = pos,
- sz = widget::nosize,
+ sz = void,
+ style = style,
+ ...
},
txt = txt,
};
@@ -22,7 +25,9 @@ export fn newtext(out: io::file, txt: str, pos: widget::coords) text = {
fn printtext(widget: *widget::widget) void = {
const widget = widget: *text;
- widget::print(widget.widget.out, widget.txt, widget.widget.pos);
+ widget.widget.buf = strings::dup(widget.txt);
+ defer free(widget.widget.buf);
+ widget::print(widget);
};
fn resizetext(widget: *widget::widget, ttysize: tty::ttysize) void = {
diff --git a/tui/widget/widget.ha b/tui/widget/widget.ha
index 77b6f88..dbbfa11 100644
--- a/tui/widget/widget.ha
+++ b/tui/widget/widget.ha
@@ -1,35 +1,178 @@
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 nosize = void;
-export type widgetsize = (tty::ttysize | nosize);
+export type widgetsize = (tty::ttysize | 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,
};
export type widget = struct {
out: io::file,
print: *printfn,
resize: *resizefn,
+ buf: str,
pos: coords,
sz: widgetsize,
- style: style,
+ style: (*style | void),
};
def gotoroot: str = "\x1B[1;1H";
-export fn print(out: io::file, s: str, pos: coords) void = {
+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 = {
+ fmt::fprintf(w.out, "\x1B[{};{}H", w.pos.0, w.pos.1)!;
+
+ let s = truncate_to_size(w);
+
+ let sstyle = match (w.style) {
+ case let st: *style =>
+ defer free(s);
+ 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 s;
+ };
+
+ defer free(sstyle);
+
+ fmt::fprint(w.out, sstyle)!;
+};
+
+// 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.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)!);
+};
+
+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)!;
};