From e55266b83db116e1610b562bf186dbda94c549b4 Mon Sep 17 00:00:00 2001 From: Julian Hurst Date: Sat, 22 Mar 2025 18:38:36 +0100 Subject: Fix off by one in subwidth --- tui/width.ha | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tui/width.ha b/tui/width.ha index cb4464a..b7ac993 100644 --- a/tui/width.ha +++ b/tui/width.ha @@ -77,7 +77,7 @@ export fn subwidth(s: str, end: (size | strings::end)) str = { const r = runes[i]; sum += runewidth(r); if (sum > end: uint) { - return strings::sub(s, 0, i - 1); + return strings::sub(s, 0, i); }; }; return s; -- cgit v1.2.3 From 4c3c071e7b8f34d3b77a0232ef907350a992a49e Mon Sep 17 00:00:00 2001 From: Julian Hurst Date: Sat, 22 Mar 2025 18:39:17 +0100 Subject: Refactor styles to be widget specific (except border) --- cmd/il.ha | 63 ++++++++++++++++++++++++++++++++++++++++++- cmd/list_strictsz.ha | 15 ++++++----- cmd/text.ha | 22 +++++++-------- tui/widget/list/scrolllist.ha | 60 ++++++++++++++++++++++++++++++++++------- tui/widget/text/text.ha | 25 +++++++++++++++-- tui/widget/widget.ha | 35 +++++++++++++----------- 6 files changed, 173 insertions(+), 47 deletions(-) diff --git a/cmd/il.ha b/cmd/il.ha index 2101a65..e0b5f3a 100644 --- a/cmd/il.ha +++ b/cmd/il.ha @@ -1,4 +1,5 @@ use tui; +use tui::widget; use tui::widget::list; use tui::layout; use bufio; @@ -6,8 +7,55 @@ use os; use strings; use fmt; use io; +use getopt; + +fn strtocolor(s: str, df: widget::color) widget::color = { + return switch (s) { + case "red" => + yield widget::color::REDFG; + case "blue" => + yield widget::color::BLUEFG; + case "green" => + yield widget::color::GREENFG; + case "brown" => + yield widget::color::BROWNFG; + case "cyan" => + yield widget::color::CYANFG; + case "black" => + yield widget::color::BLACKFG; + case "white" => + yield widget::color::WHITEFG; + case "magenta" => + yield widget::color::MAGENTAFG; + case => + yield widget::color::DEFAULTFG; + }; +}; export fn main() void = { + const cmd = getopt::parse(os::args, + "interactive list", + ('n', "colour", "normal colour"), + ('m', "colour", "marked colour"), + ('b', "draw border"), + ); + defer getopt::finish(&cmd); + + let normalst = list::DEFAULTSTYLE.normal; + let markedst = list::DEFAULTSTYLE.marked; + let border = false; + for (let opt .. cmd.opts) { + switch (opt.0) { + case 'n' => + normalst = strtocolor(opt.1, normalst); + case 'm' => + markedst = strtocolor(opt.1, markedst) + 10; + case 'b' => + border = true; + case => abort(); + }; + }; + const scanner = bufio::newscanner(os::stdin); defer bufio::finish(&scanner); @@ -32,11 +80,24 @@ export fn main() void = { const state = tui::init()!; defer tui::finish(&state); + //let li = list::newscrolllist( + // &state, + // (1, 1), + // void, + // &list::DEFAULTSTYLE, + // items... + //)!; let li = list::newscrolllist( &state, (1, 1), void, - void, + &list::liststyle { + style = &widget::style { + border = border, + }, + normal = normalst, + marked = markedst, + }, items... )!; let vl = layout::newvlayout(&li); diff --git a/cmd/list_strictsz.ha b/cmd/list_strictsz.ha index e0a371d..6802387 100644 --- a/cmd/list_strictsz.ha +++ b/cmd/list_strictsz.ha @@ -10,14 +10,17 @@ use time; export fn main() void = { const state = tui::init()!; defer tui::finish(&state); - let li = list::newlist(&state, (1, 1), tty::ttysize { + let li = list::newscrolllist(&state, (1, 1), tty::ttysize { rows = 3, columns = 4, - }, &widget::style { - border = true, - colorfg = widget::color::BLUEFG, - colorbg = widget::color::BLUEBG, - }, "hello", "world", "bye", "world")!; + }, &list::liststyle { + style = &widget::style { + border = true, + }, + normal = widget::color::DEFAULTFG, + marked = widget::color::DEFAULTBG, + }, + "hel🎉", "world", "bye", "world")!; let l = layout::newvlayout(&li); l.layout.print(&l); }; diff --git a/cmd/text.ha b/cmd/text.ha index 7c569a8..f8696ee 100644 --- a/cmd/text.ha +++ b/cmd/text.ha @@ -9,55 +9,51 @@ use time; export fn main() void = { const state = tui::init()!; defer tui::finish(&state); - let txt = text::newtext(&state, "hello world", (50, 20), &widget::style { - border = true, - colorfg = widget::color::REDFG, - colorbg = widget::color::REDBG, - }); + let txt = text::newtext(&state, "hello world", (50, 20), &text::DEFAULTSTYLE); let l = layout::newvlayout(&txt); l.layout.print(&l); //tui::clear(&state); time::sleep(1 * time::SECOND); - let st = txt.widget.style as *widget::style; - st.colorfg = widget::color::GREENFG; + let st = txt.style; + st.normal = widget::color::GREENFG; text::settext(&txt, "bye world"); l.layout.print(&l); //tui::clear(&state); time::sleep(1 * time::SECOND); - st.colorfg = widget::color::BROWNFG; + st.normal = widget::color::BROWNFG; l.layout.print(&l); //tui::clear(&state); time::sleep(1 * time::SECOND); - st.colorfg = widget::color::BLUEFG; + st.normal = widget::color::BLUEFG; l.layout.print(&l); //tui::clear(&state); time::sleep(1 * time::SECOND); - st.colorfg = widget::color::MAGENTAFG; + st.normal = widget::color::MAGENTAFG; l.layout.print(&l); //tui::clear(&state); time::sleep(1 * time::SECOND); - st.colorfg = widget::color::CYANFG; + st.normal = widget::color::CYANFG; l.layout.print(&l); //tui::clear(&state); time::sleep(1 * time::SECOND); - st.colorfg = widget::color::WHITEFG; + st.normal = widget::color::WHITEFG; l.layout.print(&l); //tui::clear(&state); time::sleep(1 * time::SECOND); - st.colorfg = widget::color::DEFAULTFG; + st.normal = widget::color::DEFAULTFG; l.layout.print(&l); }; diff --git a/tui/widget/list/scrolllist.ha b/tui/widget/list/scrolllist.ha index ab1e7bc..95fa4e0 100644 --- a/tui/widget/list/scrolllist.ha +++ b/tui/widget/list/scrolllist.ha @@ -5,6 +5,13 @@ use unix::tty; use memio; use strings; use fmt; +use strconv; + +export def DEFAULTSTYLE = liststyle { + style = void, + normal = widget::color::DEFAULTFG, + marked = widget::color::BLUEBG, +}; export type scrolllist = struct { widget: widget::widget, @@ -12,13 +19,22 @@ export type scrolllist = struct { frame: frame, cursor: int, marked: []int, + style: *liststyle, +}; + +export type liststyle = struct { + style: (void | *widget::style), + normal: widget::color, + marked: widget::color, + //normal: (void | widget::color), + //marked: (void | widget::color), }; // Return an instance of list. out is the tty file, pos the starting position, // 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 newscrolllist(state: *tui::tui, pos: widget::coords, sz: widget::widgetsize, -style: (*widget::style | void), items: str...) (scrolllist | tty::error) = { +style: *liststyle, items: str...) (scrolllist | tty::error) = { const tsz = tty::winsize(state.out)?; let end = match (sz) { @@ -42,7 +58,7 @@ style: (*widget::style | void), items: str...) (scrolllist | tty::error) = { finish = &finishscrolllist, pos = pos, sz = sz, - style = style, + style = style.style, damage = widget::damageall, ... }, @@ -53,6 +69,7 @@ style: (*widget::style | void), items: str...) (scrolllist | tty::error) = { }, cursor = 0, marked = [], + style = style, }; }; @@ -153,22 +170,47 @@ fn stylesscrolllist(widget: *widget::widget, txt: str, idx: size) str = { const idx = idx: int + list.frame.start; let st = memio::dynamic(); defer io::close(&st)!; + + const normalst = colorordefault(list.style.normal, widget::color::DEFAULTFG); + const markst = colorordefault(list.style.marked, widget::color::BLUEBG); + + //memio::concat(&st, widget::color_to_str(normalst))!; + const normalsts = widget::color_to_str(normalst); + defer free(normalsts); + memio::concat(&st, normalsts)!; if (idx == list.cursor) { memio::concat(&st, "\x1B[7m")!; }; if (ismarked(*list, idx) is size) { - memio::concat(&st, "\x1B[44m")!; + const s = widget::color_to_str(markst); + defer free(s); + memio::concat(&st, s)!; }; memio::concat(&st, txt)!; - if (idx == list.cursor) { - memio::concat(&st, "\x1B[27m")!; - }; - if (ismarked(*list, idx) is size) { - memio::concat(&st, "\x1B[49m")!; - }; + memio::concat(&st, "\x1B[0m")!; + //if (idx == list.cursor) { + // memio::concat(&st, "\x1B[27m")!; + //} else if (ismarked(*list, idx) is size) { + //}; + //if (idx == list.cursor) { + // memio::concat(&st, "\x1B[27m")!; + //}; + //if (ismarked(*list, idx) is size) { + // memio::concat(&st, "\x1B[49m")!; + //}; return strings::dup(memio::string(&st)!); }; +fn colorordefault(col: (void | widget::color), d: widget::color) widget::color = { + const c = match (col) { + case let c: widget::color => + yield c; + case void => + yield d; + }; + return c; +}; + fn ismarked(li: scrolllist, j: int) (size | void) = { for (let i = 0z; i < len(li.marked); i += 1) { const idx = li.marked[i]; diff --git a/tui/widget/text/text.ha b/tui/widget/text/text.ha index 40b3f58..0055d96 100644 --- a/tui/widget/text/text.ha +++ b/tui/widget/text/text.ha @@ -2,13 +2,25 @@ use io; use unix::tty; use tui; use tui::widget; +use strings; export type text = struct { widget: widget::widget, txt: str, + style: *style, }; -export fn newtext(state: *tui::tui, txt: str, pos: widget::coords, style: (*widget::style | void)) text = { +export def DEFAULTSTYLE = style { + style = void, + normal = widget::color::DEFAULTFG, +}; + +export type style = struct { + style: (void | *widget::style), + normal: widget::color, +}; + +export fn newtext(state: *tui::tui, txt: str, pos: widget::coords, style: *style) text = { return text { widget = widget::widget { state = state, @@ -17,11 +29,12 @@ export fn newtext(state: *tui::tui, txt: str, pos: widget::coords, style: (*widg finish = &finishtext, pos = pos, sz = void, - style = style, + style = style.style, damage = widget::damageall, ... }, txt = txt, + style = style, }; }; @@ -29,6 +42,7 @@ fn printtext(widget: *widget::widget) void = { const widget = widget: *text; widget.widget.buf = widget::linesbuf { lines = [widget.txt], + styles = &styles, ... }; widget::print(widget); @@ -45,3 +59,10 @@ export fn settext(text: *text, txt: str) void = { fn finishtext(widget: *widget::widget) void = { widget::finish(widget); }; + +fn styles(widget: *widget::widget, txt: str, idx: size) str = { + const txtw = widget: *text; + const s = widget::color_to_str(txtw.style.normal); + defer free(s); + return strings::concat(s, txt, "\x1B[0m"); +}; diff --git a/tui/widget/widget.ha b/tui/widget/widget.ha index 4212eae..93f3a3e 100644 --- a/tui/widget/widget.ha +++ b/tui/widget/widget.ha @@ -72,14 +72,10 @@ export type color = enum uint { 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 def NEWLINE: str = "\r\n"; @@ -133,7 +129,14 @@ fn minrows(out: io::file, x: u16, y: widgetsize) (u16 | tty::error) = { }; // Must free return string -fn color_to_str(color: color) str = fmt::asprintf("\x1B[{}m", strconv::utos(color)); +export fn color_to_str(color: color) str = fmt::asprintf("\x1B[{}m", strconv::utos(color)); +//{ +// const cu = match (color) { +// case let c: colorfg => yield c: uint; +// case let c: colorbg => yield c: uint; +// }; +// return +//}; fn clearrow(row: uint) str = fmt::asprintf("\x1B[{}d{}", row, CLEARROW); @@ -208,17 +211,17 @@ fn applystyles(st: (*style | void), s: []str) []str = { yield strings::dupall(s); }; //defer strings::freeall(sborder); - const scolor = color_to_str(st.colorfg); - defer free(scolor); - const defcolor = color_to_str(color::DEFAULTFG); - defer free(defcolor); - const sb = strings::concat(scolor, sborder[0]); - free(sborder[0]); - sborder[0] = sb; - const endidx = len(sborder) - 1; - const sb = strings::concat(sborder[endidx], defcolor); - free(sborder[endidx]); - sborder[endidx] = sb; + //const scolor = color_to_str(st.colorfg); + //defer free(scolor); + //const defcolor = color_to_str(colorfg::DEFAULTFG); + //defer free(defcolor); + //const sb = strings::concat(scolor, sborder[0]); + //free(sborder[0]); + //sborder[0] = sb; + //const endidx = len(sborder) - 1; + //const sb = strings::concat(sborder[endidx], defcolor); + //free(sborder[endidx]); + //sborder[endidx] = sb; yield sborder; case void => yield strings::dupall(s); -- cgit v1.2.3 From d531bc786a3da309d10f3bdf62ebebda345e5205 Mon Sep 17 00:00:00 2001 From: Julian Hurst Date: Sat, 22 Mar 2025 18:47:17 +0100 Subject: Remove list.ha and refactor scrolllist --- cmd/il.ha | 12 +-- cmd/list.ha | 10 +- cmd/list_nostyle.ha | 3 +- cmd/list_strictsz.ha | 5 +- tui/widget/list/list.ha | 180 ++++++++++++++++++++++++++++++-- tui/widget/list/scrolllist.ha | 234 ------------------------------------------ 6 files changed, 188 insertions(+), 256 deletions(-) delete mode 100644 tui/widget/list/scrolllist.ha diff --git a/cmd/il.ha b/cmd/il.ha index e0b5f3a..4e611da 100644 --- a/cmd/il.ha +++ b/cmd/il.ha @@ -80,18 +80,18 @@ export fn main() void = { const state = tui::init()!; defer tui::finish(&state); - //let li = list::newscrolllist( + //let li = list::newlist( // &state, // (1, 1), // void, // &list::DEFAULTSTYLE, // items... //)!; - let li = list::newscrolllist( + let li = list::newlist( &state, (1, 1), void, - &list::liststyle { + &list::style { style = &widget::style { border = border, }, @@ -168,7 +168,7 @@ export fn main() void = { }; }; -fn search(state: *tui::tui, li: *list::scrolllist, prefix: (str | rune) = '/') (str | void) = { +fn search(state: *tui::tui, li: *list::list, prefix: (str | rune) = '/') (str | void) = { tui::unraw(state); defer tui::raw(state)!; fmt::fprint(state.out, prefix)!; @@ -182,7 +182,7 @@ fn search(state: *tui::tui, li: *list::scrolllist, prefix: (str | rune) = '/') ( return strings::dup(strings::fromutf8(uline)!); }; -fn nextsearch(li: *list::scrolllist, term: (str | void)) void = { +fn nextsearch(li: *list::list, term: (str | void)) void = { const term = match (term) { case let term: str => yield term; @@ -197,7 +197,7 @@ fn nextsearch(li: *list::scrolllist, term: (str | void)) void = { }; }; -fn prevsearch(li: *list::scrolllist, term: (str | void)) void = { +fn prevsearch(li: *list::list, term: (str | void)) void = { const term = match (term) { case let term: str => yield term; diff --git a/cmd/list.ha b/cmd/list.ha index 348083e..eacd69c 100644 --- a/cmd/list.ha +++ b/cmd/list.ha @@ -10,10 +10,12 @@ use time; export fn main() void = { const state = tui::init()!; defer tui::finish(&state); - let li = list::newlist(&state, (1, 1), void, &widget::style { - border = true, - colorfg = widget::color::REDFG, - colorbg = widget::color::REDBG, + let li = list::newlist(&state, (1, 1), void, &list::style { + style = &widget::style { + border = true, + }, + normal = widget::color::REDFG, + marked = widget::color::BLUEBG, },"hello", "world", "bye", "world")!; let l = layout::newvlayout(&li); diff --git a/cmd/list_nostyle.ha b/cmd/list_nostyle.ha index 4454220..133d137 100644 --- a/cmd/list_nostyle.ha +++ b/cmd/list_nostyle.ha @@ -4,13 +4,12 @@ use tui::widget; use tui::widget::list; use unix::tty; use io; -use fmt; use time; export fn main() void = { const state = tui::init()!; defer tui::finish(&state); - let li = list::newlist(&state, (1, 1), void, void,"hello", "world", "bye", "world")!; + let li = list::newlist(&state, (1, 1), void, &list::DEFAULTSTYLE,"hello", "world", "bye", "world")!; let l = layout::newvlayout(&li); l.layout.print(&l); }; diff --git a/cmd/list_strictsz.ha b/cmd/list_strictsz.ha index 6802387..2396e3c 100644 --- a/cmd/list_strictsz.ha +++ b/cmd/list_strictsz.ha @@ -4,16 +4,15 @@ use tui::widget; use tui::widget::list; use unix::tty; use io; -use fmt; use time; export fn main() void = { const state = tui::init()!; defer tui::finish(&state); - let li = list::newscrolllist(&state, (1, 1), tty::ttysize { + let li = list::newlist(&state, (1, 1), tty::ttysize { rows = 3, columns = 4, - }, &list::liststyle { + }, &list::style { style = &widget::style { border = true, }, diff --git a/tui/widget/list/list.ha b/tui/widget/list/list.ha index cbf83fa..05300ba 100644 --- a/tui/widget/list/list.ha +++ b/tui/widget/list/list.ha @@ -4,6 +4,14 @@ use io; use unix::tty; use memio; use strings; +use fmt; +use strconv; + +export def DEFAULTSTYLE = style { + style = void, + normal = widget::color::DEFAULTFG, + marked = widget::color::BLUEBG, +}; export type frame = struct { start: int, @@ -14,18 +22,30 @@ export type list = struct { widget: widget::widget, items: []str, frame: frame, + cursor: int, + marked: []int, + style: *style, +}; + +export type style = struct { + style: (void | *widget::style), + normal: widget::color, + marked: widget::color, + //normal: (void | widget::color), + //marked: (void | widget::color), }; // Return an instance of list. out is the tty file, pos the starting position, // 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(state: *tui::tui, pos: widget::coords, sz: widget::widgetsize, -style: (*widget::style | void), items: str...) (list | tty::error) = { +style: *style, items: str...) (list | tty::error) = { const tsz = tty::winsize(state.out)?; let end = match (sz) { case let sz: tty::ttysize => - yield if (tsz.rows < sz.rows) tsz.rows - 1 else sz.rows; + const rows = if (tsz.rows < sz.rows) tsz.rows - 1 else sz.rows; + yield rows; case void => yield tsz.rows - 1; }; @@ -43,31 +63,177 @@ style: (*widget::style | void), items: str...) (list | tty::error) = { finish = &finishlist, pos = pos, sz = sz, - style = style, + style = style.style, damage = widget::damageall, ... }, items = items, frame = frame { start = 0, - end = end: int, + end = end, }, + cursor = 0, + marked = [], + style = style, }; }; -export fn printlist(widget: *widget::widget) void = { +fn printlist(widget: *widget::widget) void = { const list = widget: *list; + assert(list.frame.start >= 0); + assert(list.frame.end <= len(list.items): int); list.widget.buf = widget::linesbuf { lines = list.items[list.frame.start..list.frame.end], - styles = null, + styles = &styleslist, }; widget::print(list); }; -export fn resizelist(widget: *widget::widget, ttysize: tty::ttysize) void = { +fn resizelist(widget: *widget::widget, ttysize: tty::ttysize) void = { return; }; fn finishlist(widget: *widget::widget) void = { + const list = widget: *list; widget::finish(widget); + free(list.marked); +}; + +export fn down(li: *list) void = { + if (li.cursor == li.frame.end - 1) { + framedown(li); + }; + if (li.cursor < len(li.items): int - 1) { + li.cursor += 1; + }; +}; + +export fn up(li: *list) void = { + if (li.cursor == li.frame.start) { + frameup(li); + }; + if (li.cursor > 0) { + li.cursor -= 1; + }; +}; + +export fn frameup(li: *list) void = { + if (li.frame.start > 0) { + li.frame.start -= 1; + li.frame.end -= 1; + }; +}; + +export fn framedown(li: *list) void = { + if (li.frame.end < len(li.items): int) { + li.frame.start += 1; + li.frame.end += 1; + }; +}; + +export fn top(li: *list) void = { + const sz = li.frame.end - li.frame.start; + li.cursor = 0; + li.frame.end = sz; + li.frame.start = 0; +}; + +export fn bottom(li: *list) void = { + const sz = li.frame.end - li.frame.start; + li.cursor = len(li.items): int - 1; + li.frame.end = len(li.items): int; + li.frame.start = len(li.items): int - sz; +}; + +export fn setcursor(li: *list, newpos: int) void = { + if (newpos < 0) { + li.cursor = 0; + } else if (newpos > len(li.items): int) { + li.cursor = len(li.items): int - 1; + } else { + li.cursor = newpos; + }; + reframe(li); +}; + +fn reframe(li: *list) void = { + if (li.cursor >= li.frame.end) { + const diff = li.frame.end - li.frame.start; + li.frame.end = li.cursor + 1; + li.frame.start = li.frame.end - diff; + } else if (li.cursor < li.frame.start) { + const diff = li.frame.end - li.frame.start; + li.frame.start = li.cursor; + li.frame.end = li.frame.start + diff; + }; + assert(li.frame.start >= 0); + assert(li.frame.end <= len(li.items): int); +}; + +fn styleslist(widget: *widget::widget, txt: str, idx: size) str = { + const list = widget: *list; + const idx = idx: int + list.frame.start; + let st = memio::dynamic(); + defer io::close(&st)!; + + const normalst = colorordefault(list.style.normal, widget::color::DEFAULTFG); + const markst = colorordefault(list.style.marked, widget::color::BLUEBG); + + //memio::concat(&st, widget::color_to_str(normalst))!; + const normalsts = widget::color_to_str(normalst); + defer free(normalsts); + memio::concat(&st, normalsts)!; + if (idx == list.cursor) { + memio::concat(&st, "\x1B[7m")!; + }; + if (ismarked(*list, idx) is size) { + const s = widget::color_to_str(markst); + defer free(s); + memio::concat(&st, s)!; + }; + memio::concat(&st, txt)!; + memio::concat(&st, "\x1B[0m")!; + //if (idx == list.cursor) { + // memio::concat(&st, "\x1B[27m")!; + //} else if (ismarked(*list, idx) is size) { + //}; + //if (idx == list.cursor) { + // memio::concat(&st, "\x1B[27m")!; + //}; + //if (ismarked(*list, idx) is size) { + // memio::concat(&st, "\x1B[49m")!; + //}; + return strings::dup(memio::string(&st)!); +}; + +fn colorordefault(col: (void | widget::color), d: widget::color) widget::color = { + const c = match (col) { + case let c: widget::color => + yield c; + case void => + yield d; + }; + return c; +}; + +fn ismarked(li: list, j: int) (size | void) = { + for (let i = 0z; i < len(li.marked); i += 1) { + const idx = li.marked[i]; + if (idx == j) { + return i; + }; + }; + return; +}; + +export fn mark(li: *list) bool = { + defer down(li); + match (ismarked(*li, li.cursor)) { + case let s: size => + delete(li.marked[s]); + return false; + case void => + append(li.marked, li.cursor); + return true; + }; }; diff --git a/tui/widget/list/scrolllist.ha b/tui/widget/list/scrolllist.ha deleted file mode 100644 index 95fa4e0..0000000 --- a/tui/widget/list/scrolllist.ha +++ /dev/null @@ -1,234 +0,0 @@ -use tui; -use tui::widget; -use io; -use unix::tty; -use memio; -use strings; -use fmt; -use strconv; - -export def DEFAULTSTYLE = liststyle { - style = void, - normal = widget::color::DEFAULTFG, - marked = widget::color::BLUEBG, -}; - -export type scrolllist = struct { - widget: widget::widget, - items: []str, - frame: frame, - cursor: int, - marked: []int, - style: *liststyle, -}; - -export type liststyle = struct { - style: (void | *widget::style), - normal: widget::color, - marked: widget::color, - //normal: (void | widget::color), - //marked: (void | widget::color), -}; - -// Return an instance of list. out is the tty file, pos the starting position, -// 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 newscrolllist(state: *tui::tui, pos: widget::coords, sz: widget::widgetsize, -style: *liststyle, items: str...) (scrolllist | tty::error) = { - const tsz = tty::winsize(state.out)?; - - let end = match (sz) { - case let sz: tty::ttysize => - const rows = if (tsz.rows < sz.rows) tsz.rows - 1 else sz.rows; - yield rows; - case void => - yield tsz.rows - 1; - }; - let end = end: int; - - if (end > len(items): int) { - end = len(items): int; - }; - - return scrolllist { - widget = widget::widget { - state = state, - print = &printscrolllist, - resize = &resizescrolllist, - finish = &finishscrolllist, - pos = pos, - sz = sz, - style = style.style, - damage = widget::damageall, - ... - }, - items = items, - frame = frame { - start = 0, - end = end, - }, - cursor = 0, - marked = [], - style = style, - }; -}; - -fn printscrolllist(widget: *widget::widget) void = { - const list = widget: *scrolllist; - assert(list.frame.start >= 0); - assert(list.frame.end <= len(list.items): int); - list.widget.buf = widget::linesbuf { - lines = list.items[list.frame.start..list.frame.end], - styles = &stylesscrolllist, - }; - widget::print(list); -}; - -fn resizescrolllist(widget: *widget::widget, ttysize: tty::ttysize) void = { - return; -}; - -fn finishscrolllist(widget: *widget::widget) void = { - const list = widget: *scrolllist; - widget::finish(widget); - free(list.marked); -}; - -export fn down(li: *scrolllist) void = { - if (li.cursor == li.frame.end - 1) { - framedown(li); - }; - if (li.cursor < len(li.items): int - 1) { - li.cursor += 1; - }; -}; - -export fn up(li: *scrolllist) void = { - if (li.cursor == li.frame.start) { - frameup(li); - }; - if (li.cursor > 0) { - li.cursor -= 1; - }; -}; - -export fn frameup(li: *scrolllist) void = { - if (li.frame.start > 0) { - li.frame.start -= 1; - li.frame.end -= 1; - }; -}; - -export fn framedown(li: *scrolllist) void = { - if (li.frame.end < len(li.items): int) { - li.frame.start += 1; - li.frame.end += 1; - }; -}; - -export fn top(li: *scrolllist) void = { - const sz = li.frame.end - li.frame.start; - li.cursor = 0; - li.frame.end = sz; - li.frame.start = 0; -}; - -export fn bottom(li: *scrolllist) void = { - const sz = li.frame.end - li.frame.start; - li.cursor = len(li.items): int - 1; - li.frame.end = len(li.items): int; - li.frame.start = len(li.items): int - sz; -}; - -export fn setcursor(li: *scrolllist, newpos: int) void = { - if (newpos < 0) { - li.cursor = 0; - } else if (newpos > len(li.items): int) { - li.cursor = len(li.items): int - 1; - } else { - li.cursor = newpos; - }; - reframe(li); -}; - -fn reframe(li: *scrolllist) void = { - if (li.cursor >= li.frame.end) { - const diff = li.frame.end - li.frame.start; - li.frame.end = li.cursor + 1; - li.frame.start = li.frame.end - diff; - } else if (li.cursor < li.frame.start) { - const diff = li.frame.end - li.frame.start; - li.frame.start = li.cursor; - li.frame.end = li.frame.start + diff; - }; - assert(li.frame.start >= 0); - assert(li.frame.end <= len(li.items): int); -}; - -fn stylesscrolllist(widget: *widget::widget, txt: str, idx: size) str = { - const list = widget: *scrolllist; - const idx = idx: int + list.frame.start; - let st = memio::dynamic(); - defer io::close(&st)!; - - const normalst = colorordefault(list.style.normal, widget::color::DEFAULTFG); - const markst = colorordefault(list.style.marked, widget::color::BLUEBG); - - //memio::concat(&st, widget::color_to_str(normalst))!; - const normalsts = widget::color_to_str(normalst); - defer free(normalsts); - memio::concat(&st, normalsts)!; - if (idx == list.cursor) { - memio::concat(&st, "\x1B[7m")!; - }; - if (ismarked(*list, idx) is size) { - const s = widget::color_to_str(markst); - defer free(s); - memio::concat(&st, s)!; - }; - memio::concat(&st, txt)!; - memio::concat(&st, "\x1B[0m")!; - //if (idx == list.cursor) { - // memio::concat(&st, "\x1B[27m")!; - //} else if (ismarked(*list, idx) is size) { - //}; - //if (idx == list.cursor) { - // memio::concat(&st, "\x1B[27m")!; - //}; - //if (ismarked(*list, idx) is size) { - // memio::concat(&st, "\x1B[49m")!; - //}; - return strings::dup(memio::string(&st)!); -}; - -fn colorordefault(col: (void | widget::color), d: widget::color) widget::color = { - const c = match (col) { - case let c: widget::color => - yield c; - case void => - yield d; - }; - return c; -}; - -fn ismarked(li: scrolllist, j: int) (size | void) = { - for (let i = 0z; i < len(li.marked); i += 1) { - const idx = li.marked[i]; - if (idx == j) { - return i; - }; - }; - return; -}; - -export fn mark(li: *scrolllist) bool = { - defer down(li); - match (ismarked(*li, li.cursor)) { - case let s: size => - delete(li.marked[s]); - return false; - case void => - append(li.marked, li.cursor); - return true; - }; -}; -- cgit v1.2.3