code / tomo-btui

Lines1.1K C700 Tomo305 Markdown95
(271 lines)
1 # A tomo library for Terminal User Interfaces
2 use ./btui.c
4 enum Color(Normal, Black, Red, Green, Yellow, Blue, Magenta, Cyan, White, Color256(n:Byte), RGB(r,g,b:Byte))
5 func is_dark(c:Color -> Bool)
6 when c is Black
7 return yes
8 is Color256(n)
9 return n == 0 or n == 16 or n == 17 or (232 <= n and n <= 237)
10 is RGB(r,g,b)
11 return Int(r)+Int(g)+Int(b) < 127*3
12 else
13 return no
15 func is_light(c:Color -> Bool)
16 when c is White
17 return yes
18 is Color256, RGB
19 return not c.is_dark()
20 else
21 return no
23 struct ScreenVec2(x,y:Int)
24 func divided_by(v:ScreenVec2, divisor:Int -> ScreenVec2)
25 return ScreenVec2(v.x/divisor, v.y/divisor)
26 func scaled_by(v:ScreenVec2, scale:Int -> ScreenVec2)
27 return ScreenVec2(v.x*scale, v.y*scale)
28 func plus(a,b:ScreenVec2 -> ScreenVec2)
29 return ScreenVec2(a.x+b.x, a.y+b.y)
30 func minus(a,b:ScreenVec2 -> ScreenVec2)
31 return ScreenVec2(a.x-b.x, a.y-b.y)
33 enum BTUIMode(Disabled, Normal, TUI)
35 func set_mode(mode:BTUIMode)
36 if C_code:Bool`bt.mode == BTUI_MODE_UNINITIALIZED`
37 at_cleanup(disable)
39 when mode is Disabled
40 C_code `btui_set_mode(BTUI_MODE_DISABLED);`
41 is Normal
42 C_code `btui_set_mode(BTUI_MODE_NORMAL);`
43 is TUI
44 C_code `btui_set_mode(BTUI_MODE_TUI);`
46 func disable()
47 C_code `btui_disable();`
49 func force_close()
50 C_code `btui_force_close();`
52 enum ClearMode(Screen, Above, Below, Line, Left, Right)
54 func clear(mode:ClearMode=Screen, pos:ScreenVec2?=none)
55 if pos
56 move_cursor(pos)
58 when mode is Screen then C_code `btui_clear(BTUI_CLEAR_SCREEN);`
59 is Above then C_code `btui_clear(BTUI_CLEAR_ABOVE);`
60 is Below then C_code `btui_clear(BTUI_CLEAR_BELOW);`
61 is Line then C_code `btui_clear(BTUI_CLEAR_LINE);`
62 is Left then C_code `btui_clear(BTUI_CLEAR_LEFT);`
63 is Right then C_code `btui_clear(BTUI_CLEAR_RIGHT);`
65 enum TextAlign(Left, Center, Right)
67 func write(text:Text, pos:ScreenVec2?=none, align:TextAlign=Left)
68 if pos
69 when align is Left then pass
70 is Center then pos -= ScreenVec2(text.width()/2, 0)
71 is Right then pos -= ScreenVec2(text.width(), 0)
72 move_cursor(pos)
74 C_code `
75 btui_puts(@(text.as_c_string()));
76 btui_flush();
79 enum CursorMode(Default, BlinkingBlock, SteadyBlock, BlinkingUnderline, SteadyUnderline, BlinkingBar, SteadyBar)
81 func set_cursor(cursor:CursorMode)
82 when cursor is Default then C_code`btui_set_cursor(CURSOR_DEFAULT);`
83 is BlinkingBlock then C_code`btui_set_cursor(CURSOR_BLINKING_BLOCK);`
84 is SteadyBlock then C_code`btui_set_cursor(CURSOR_STEADY_BLOCK);`
85 is BlinkingUnderline then C_code`btui_set_cursor(CURSOR_BLINKING_UNDERLINE);`
86 is SteadyUnderline then C_code`btui_set_cursor(CURSOR_STEADY_UNDERLINE);`
87 is BlinkingBar then C_code`btui_set_cursor(CURSOR_BLINKING_BAR);`
88 is SteadyBar then C_code`btui_set_cursor(CURSOR_STEADY_BAR);`
90 func move_cursor(pos:ScreenVec2, relative=no)
91 if relative
92 C_code `btui_move_cursor_relative(@(Int32(pos.x)), @(Int32(pos.y)));`
93 else
94 C_code `btui_move_cursor(@(Int32(pos.x)), @(Int32(pos.y)));`
96 func hide_cursor()
97 C_code `btui_hide_cursor();`
99 func show_cursor()
100 C_code `btui_show_cursor();`
102 func get_size(->ScreenVec2)
103 return ScreenVec2(
104 Int(C_code:Int32`bt.width`),
105 Int(C_code:Int32`bt.height`),
108 func get_key(mouse_pos:&ScreenVec2?=none, timeout:Int?=none -> Text)
109 timeout_i32 := if timeout then Int32(timeout) else Int32(-1)
110 mouse_x : Int32
111 mouse_y : Int32
112 key := C_code:Text `
113 int key = btui_getkey(@timeout_i32, &@mouse_x, &@mouse_y);
114 char buf[256];
115 char *end = btui_keyname(key, buf);
116 Text$from_strn(buf, (int64_t)(end - buf));
118 if mouse_pos
119 mouse_pos[] = ScreenVec2(Int(mouse_x), Int(mouse_y))
121 return key
123 func flush()
124 C_code `btui_flush();`
126 func draw_linebox(pos:ScreenVec2, size:ScreenVec2)
127 C_code `btui_draw_linebox(@(Int32(pos.x)), @(Int32(pos.y)), @(Int32(size.x)), @(Int32(size.y)));`
129 func draw_shadow(pos:ScreenVec2, size:ScreenVec2)
130 C_code `btui_draw_shadow(@(Int32(pos.x)), @(Int32(pos.y)), @(Int32(size.x)), @(Int32(size.y)));`
133 func fill_box(pos:ScreenVec2, size:ScreenVec2)
134 C_code `btui_fill_box(@(Int32(pos.x)), @(Int32(pos.y)), @(Int32(size.x)), @(Int32(size.y)));`
136 func scroll(first_line:Int, last_line:Int, scroll_amount:Int)
137 C_code `btui_scroll(@(Int32(first_line)), @(Int32(last_line)), @(Int32(scroll_amount)));`
139 func style(
140 fg:Color?=none, bg:Color?=none,
141 normal:Bool?=none, bold:Bool?=none, faint:Bool?=none, italic:Bool?=none,
142 underline:Bool?=none, blink_slow:Bool?=none, blink_fast:Bool?=none,
143 reverse:Bool?=none, conceal:Bool?=none, strikethrough:Bool?=none,
144 fraktur:Bool?=none, double_underline:Bool?=none, framed:Bool?=none,
145 encircled:Bool?=none, overlined:Bool?=none,
147 if fg
148 when fg is RGB(r,g,b) then C_code `btui_set_fg(@r, @g, @b);`
149 is Color256(n) then C_code `btui_set_fg256(@n);`
150 is Normal then C_code `btui_set_attributes(BTUI_FG_NORMAL);`
151 is Black then C_code `btui_set_attributes(BTUI_FG_BLACK);`
152 is Red then C_code `btui_set_attributes(BTUI_FG_RED);`
153 is Green then C_code `btui_set_attributes(BTUI_FG_GREEN);`
154 is Yellow then C_code `btui_set_attributes(BTUI_FG_YELLOW);`
155 is Blue then C_code `btui_set_attributes(BTUI_FG_BLUE);`
156 is Magenta then C_code `btui_set_attributes(BTUI_FG_MAGENTA);`
157 is Cyan then C_code `btui_set_attributes(BTUI_FG_CYAN);`
158 is White then C_code `btui_set_attributes(BTUI_FG_WHITE);`
160 if bg
161 when bg is RGB(r,g,b) then C_code `btui_set_bg(@r, @g, @b);`
162 is Color256(n) then C_code `btui_set_bg256(@n);`
163 is Normal then C_code `btui_set_attributes(BTUI_BG_NORMAL);`
164 is Black then C_code `btui_set_attributes(BTUI_BG_BLACK);`
165 is Red then C_code `btui_set_attributes(BTUI_BG_RED);`
166 is Green then C_code `btui_set_attributes(BTUI_BG_GREEN);`
167 is Yellow then C_code `btui_set_attributes(BTUI_BG_YELLOW);`
168 is Blue then C_code `btui_set_attributes(BTUI_BG_BLUE);`
169 is Magenta then C_code `btui_set_attributes(BTUI_BG_MAGENTA);`
170 is Cyan then C_code `btui_set_attributes(BTUI_BG_CYAN);`
171 is White then C_code `btui_set_attributes(BTUI_BG_WHITE);`
173 C_code `uint64_t attr = 0;`
175 # Disable attributes
176 if bold == no or faint then C_code `attr |= BTUI_NO_BOLD_OR_FAINT;`
177 if italic == no or fraktur == no then C_code `attr |= BTUI_NO_ITALIC_OR_FRAKTUR;`
178 if underline == no then C_code `attr |= BTUI_NO_UNDERLINE;`
179 if blink_slow == no or blink_fast == no then C_code `attr |= BTUI_NO_BLINK;`
180 if reverse == no then C_code `attr |= BTUI_NO_REVERSE;`
181 if conceal == no then C_code `attr |= BTUI_NO_CONCEAL;`
182 if strikethrough == no then C_code `attr |= BTUI_NO_STRIKETHROUGH;`
183 if framed == no or encircled == no then C_code `attr |= BTUI_NO_FRAMED_OR_ENCIRCLED;`
184 if overlined == no then C_code `attr |= BTUI_NO_OVERLINED;`
186 # Turn on attributes
187 if normal == yes then C_code `attr |= BTUI_NORMAL;`
188 if bold == yes then C_code `attr |= BTUI_BOLD;`
189 if faint == yes then C_code `attr |= BTUI_FAINT;`
190 if italic == yes then C_code `attr |= BTUI_ITALIC;`
191 if underline == yes then C_code `attr |= BTUI_UNDERLINE;`
192 if blink_slow == yes then C_code `attr |= BTUI_BLINK_SLOW;`
193 if blink_fast == yes then C_code `attr |= BTUI_BLINK_FAST;`
194 if reverse == yes then C_code `attr |= BTUI_REVERSE;`
195 if conceal == yes then C_code `attr |= BTUI_CONCEAL;`
196 if strikethrough == yes then C_code `attr |= BTUI_STRIKETHROUGH;`
197 if fraktur == yes then C_code `attr |= BTUI_FRAKTUR;`
198 if double_underline == yes then C_code `attr |= BTUI_DOUBLE_UNDERLINE;`
199 if framed == yes then C_code `attr |= BTUI_FRAMED;`
200 if encircled == yes then C_code `attr |= BTUI_ENCIRCLED;`
201 if overlined == yes then C_code `attr |= BTUI_OVERLINED;`
203 C_code `if (attr) btui_set_attributes(attr);`
205 func get_bg(->Color?)
206 no_color : Color? = none
207 response := C_code:Text`
208 const char *query_bg = "\\x1b]11;?\\x07";
209 write(fileno(bt.out), query_bg, strlen(query_bg));
211 char buf[256] = {};
212 ssize_t n = read(fileno(bt.in), buf, sizeof(buf)-1);
213 if (n < 0) {
214 return @(no_color);
216 const char *response_prefix = "\\x1b]11;";
217 size_t prefix_len = strlen(response_prefix);
218 if ((size_t)n < prefix_len || strncmp(buf, response_prefix, prefix_len) != 0) {
219 return @(no_color);
222 Text$from_strn(buf + prefix_len, n - prefix_len)
224 value : Text
225 if response.starts_with("rgb:", &value) or response.starts_with("rgba:", &value)
226 rgb := value.split("/")
227 r := Byte.parse("0x"++rgb[1]!.to(2)) or return none
228 g := Byte.parse("0x"++rgb[2]!.to(2)) or return none
229 b := Byte.parse("0x"++rgb[3]!.to(2)) or return none
230 return Color.RGB(r,g,b)
231 else if response.starts_with("#", &value)
232 r := Byte.parse("0x"++value.from(1).to(2)) or return none
233 g := Byte.parse("0x"++value.from(3).to(4)) or return none
234 b := Byte.parse("0x"++value.from(5).to(6)) or return none
235 return Color.RGB(r, g, b)
236 else if response.starts_with("rgb(", &value) or response.starts_with("rgba(", &value)
237 value = value.without_suffix(")")
238 rgb := value.split(",")
239 r := Byte.parse(rgb[1] or return none) or return none
240 g := Byte.parse(rgb[2] or return none) or return none
241 b := Byte.parse(rgb[3] or return none) or return none
242 return Color.RGB(r,g,b)
243 else
244 return none
246 func suspend()
247 C_code `btui_suspend();`
249 func main()
250 set_mode(TUI)
251 size := get_size()
252 style(bold=yes)
253 write("Welcome to BTUI", size/2, Center)
254 style(bold=no, faint=yes)
255 write("Press 'q' or 'Ctrl-c' to quit", size/2 + ScreenVec2(0,1), Center)
256 bg := get_bg()
257 write("BG: $bg (dark=$((bg or Color.Normal).is_dark()))", size/2 + ScreenVec2(0,3), Center)
258 style(bold=no)
259 repeat
260 key := get_key()
261 pos := size/2 + ScreenVec2(0,2)
262 clear(Line, pos=pos)
263 style(Magenta)
264 write("Your input: $key", pos, Center)
265 when key is "q"
266 stop
267 is "Ctrl-c"
268 # This error message will print correctly after the cleanup code has run
269 fail("User interrupt")
271 disable()