aboutsummaryrefslogtreecommitdiff
path: root/modules/TUI
diff options
context:
space:
mode:
Diffstat (limited to 'modules/TUI')
-rw-r--r--modules/TUI/module.jai178
-rw-r--r--modules/TUI/palette_24b.jai2
2 files changed, 51 insertions, 129 deletions
diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai
index 991dc74..d44edab 100644
--- a/modules/TUI/module.jai
+++ b/modules/TUI/module.jai
@@ -1,9 +1,7 @@
-#module_parameters(COLOR_MODE := 24);
+#module_parameters(COLOR_MODE_BITS := 24);
#scope_file
-// - fix/implement/finish `TODO` on `TUI\module` (use some sort of buffet to reduce io/print calls)
-
#if OS == {
case .LINUX;
#load "unix.jai";
@@ -22,14 +20,14 @@
#load "key_map.jai";
#run {
- assert(COLOR_MODE == 4 || COLOR_MODE == 8 || COLOR_MODE == 24, "Invalid COLOR_MODE. Valid values are 4, 8, or 24 (default).");
+ assert(COLOR_MODE_BITS == 4 || COLOR_MODE_BITS == 8 || COLOR_MODE_BITS == 24, "Invalid COLOR_MODE_BITS. Valid values are 4, 8, or 24 (default).");
assert(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire Key, which should be able to hold an UTF8 code (4 bytes) or a terminal escape code (6 bytes).");
}
#scope_export;
-// Special Graphics Characters
-Drawings :: struct {
+// Special Graphics Characters.
+Drawings :: struct #type_info_none {
Blank :: "\x5F";
Diamond :: "\x60";
Checkerboard :: "\x61";
@@ -64,7 +62,9 @@ Drawings :: struct {
CenteredDot :: "\x7E";
}
-Commands :: struct {
+// Terminal Escape Codes.
+Commands :: struct #type_info_none {
+
// Screen buffers
AlternateScreenBuffer :: "\e[?1049h";
MainScreenBuffer :: "\e[?1049l";
@@ -118,7 +118,7 @@ Commands :: struct {
CursorNormalMode :: "\e[?1l";
}
-#if COLOR_MODE == 4 {
+#if COLOR_MODE_BITS == 4 {
#load "palette_4b.jai";
set_colors :: inline (foreground: Palette, background: Palette) {
@@ -127,7 +127,7 @@ Commands :: struct {
cast(u8)foreground + 30, cast(u8)background + 40);
}
}
-else #if COLOR_MODE == 8 {
+else #if COLOR_MODE_BITS == 8 {
#load "palette_8b.jai";
set_colors :: inline (foreground: Palette, background: Palette) {
@@ -153,19 +153,21 @@ else {
}
}
-#add_context tui_style: Style;
-
Style :: struct {
- #if COLOR_MODE == 4 || COLOR_MODE == 8 {
+ #if COLOR_MODE_BITS == 4 || COLOR_MODE_BITS == 8 {
background: Palette;
foreground: Palette;
} else {
background: Color_24b;
foreground: Color_24b;
}
-
+
background = Palette.BLACK;
foreground = Palette.WHITE;
+
+ // TODO Make this work...
+ use_default_background_color := false;
+ use_default_foreground_color := false;
bold: bool;
underline: bool;
@@ -198,9 +200,6 @@ using_style :: (style: Style) #expand {
`defer set_style(__style);
}
-// TODO Maybe make the OS_* procedures as inline?!
-// TODO Terminal action codes are encoded with values incompatible with UTF-8 to avoid collisions.
-
/*
We wanted the Key type to represent either UTF-8 encoded characters and also keyboard keys.
The UTF-8 only requires up to 4 bytes, but some keyboard keys return up to 6 bytes.
@@ -238,7 +237,7 @@ to_string :: inline (key: Key) -> string { // TODO FIXME TEMPORARY MEMORY
return str;
}
-is_escape_code :: inline (key: Key) -> bool {
+is_escape_code :: (key: Key) -> bool {
beginsWithEscape := ((key & 0xFF) ^ #char "#") == 0;
hasSomethingElse := (key & (~0xFF)) != 0;
return beginsWithEscape && hasSomethingElse;
@@ -281,6 +280,8 @@ Keys :: struct #type_info_none {
F12 : Key : #run to_key("#f12");
}
+#add_context tui_style: Style;
+
active := false;
input_buffer : [1024] u8;
@@ -296,7 +297,7 @@ module_logger :: (message: string, data: *void, info: Log_Info) {
}
assert_is_active :: inline () {
- assert(active, "TUI is not ready. Please call TUI.start() before."); // TODO Improve error message... and maybe use the logger and return success=false flag
+ assert(active, "Please call TUI.setup_terminal() before using this procedure."); // TODO Improve error message... and maybe use the logger and return success=false flag
}
////////////////////////////////////////////////////////////////////////////////
@@ -376,12 +377,15 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key {
return to_key(to_parse);
}
-// TODO Review me!
-read_input :: (count_limit: int = -1, terminators: .. u8) -> string {
+// TODO Review me and add some comments.
+read_input :: (count_limit: int = -1, terminators: .. u8) -> string, success := true {
assert_is_active();
-
- assert(count_limit >= 0 || terminators.count > 0, "Infinite loop detected, aborting."); // TODO Maybe just return!? Or simply return success=false with #must
-
+
+ if count_limit < 0 && terminators.count <= 0 {
+ log_error("Invalid arguments passed to read_input(): (%).\n", count_limit); // TODO Improve error message.
+ return "", false;
+ }
+
if count_limit < 0 {
builder: String_Builder;
init_string_builder(*builder);
@@ -436,7 +440,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key {
Escape discards the input returning an empty string and a Escape key.
Resize discards the input returning an empty string and a Resize key.
*/
-
+ assert_is_active();
assert(count_limit >= 0, "Invalid value on count_limit parameter."); // TODO Too agressive
str := alloc_string(count_limit);
@@ -452,7 +456,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key {
chars_count := count_characters(str);
- // Preview input.
+ // Preview input. TODO Optimize using temp_builder
if is_visible {
set_cursor_position(x, y);
write_string(str);
@@ -524,13 +528,8 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key {
return result, key;
}
-start :: () -> success := true #must {
+setup_terminal :: () -> success := true #must {
if active == true return;
-
- if inited == false {
- init_string_builder(*temp_builder,, temp); // TODO Testing
- inited = true;
- }
input_string.data = input_buffer.data;
input_string.count = 0;
@@ -557,7 +556,7 @@ start :: () -> success := true #must {
return;
}
-stop :: () -> success := true #must {
+reset_terminal :: () -> success := true #must {
if active == false return;
active = false;
@@ -578,138 +577,61 @@ stop :: () -> success := true #must {
}
flush_input :: () {
+ assert_is_active();
OS_flush_input();
input_string.data = input_buffer.data;
input_string.count = 0;
}
-
-
-// Using String_Builder improves:
-// - procedure time is now ~12x faster;
-// - reduces CPU usage by ~
-average: float64 = 0;
-counter: float64 = 0;
-inited:=false;
-temp_builder: String_Builder;
-
-#if false {
+// TODO What if we return success and fail when input arguments are invalid?
draw_box :: (x: int, y: int, width: int, height: int) {
- t0 := current_time_monotonic();
- assert_is_active();
-
- // TODO Check if using a String_Builder improves performance (measure it)!
- // TODO Validate input parameters against the terminal size.
- assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments.");
-
- auto_release_temp();
-
- tmp_string: string;
-
- tmp_string = tprint(Commands.SetCursorPosition, y, x);
- write_strings(
- Commands.DrawingMode,
- tmp_string,
- Drawings.CornerTL
- );
+ assert_is_active(); //TODO NOT NEEDED
- for 1..width-2 {
- write_string(Drawings.LineH);
- }
- write_string(Drawings.CornerTR);
-
- for idx: y+1..y+height-2 {
- tmpL := tprint(Commands.SetCursorPosition, idx, x);
- tmpR := tprint(Commands.SetCursorPosition, idx, x+width-1);
- write_strings(
- tmpL,
- Drawings.LineV,
- tmpR,
- Drawings.LineV);
- }
-
- tmpBL := tprint(Commands.SetCursorPosition, y+height-1, x);
- write_strings(
- tmpBL,
- Drawings.CornerBL);
- for 1..width-2 {
- write_string(Drawings.LineH);
+ if x <= 0 || y <= 0 || width <= 1 || height <= 1 {
+ log_error("Invalid arguments passed to draw_box(): (%, %, %, %).\n", x, y, width, height); // TODO Improve error message.
+ return;
}
- write_string(Drawings.CornerBR);
- write_string(Commands.TextMode);
-
- t1 := current_time_monotonic();
- set_cursor_position(2, 47);
- sample := cast(float64)to_nanoseconds(t1-t0)/1000;
- average = (sample + counter * average) / (counter + 1);
- counter += 1;
- print(">%<", average);
-}
-
-} else {
-
-
-// TODO Not sure how... but this seems to be leaking memory... maybe it's the String_Builder!?
-draw_box :: (x: int, y: int, width: int, height: int) {
- t0 := current_time_monotonic();
-
- assert_is_active();
-
- // TODO Check if using a String_Builder improves performance (measure it)!
- // TODO Validate input parameters against the terminal size.
- assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments.");
-
auto_release_temp();
- builder := temp_builder;
- // init_string_builder(*builder);
-
+ builder := String_Builder.{ allocator = temporary_allocator };
+
append(*builder, Commands.DrawingMode);
+
+ // Draw top line
append(*builder, tprint(Commands.SetCursorPosition, y, x));
append(*builder, Drawings.CornerTL);
-
for 1..width-2 {
append(*builder, Drawings.LineH);
}
append(*builder, Drawings.CornerTR);
+ // Draw left and right sides.
for idx: y+1..y+height-2 {
- tmpL := tprint(Commands.SetCursorPosition, idx, x);
- tmpR := tprint(Commands.SetCursorPosition, idx, x+width-1);
- append(*builder, tmpL);
+ append(*builder, tprint(Commands.SetCursorPosition, idx, x));
append(*builder, Drawings.LineV);
- append(*builder, tmpR);
+ append(*builder, tprint(Commands.SetCursorPosition, idx, x+width-1));
append(*builder, Drawings.LineV);
}
+ // Draw bottom line.
append(*builder, tprint(Commands.SetCursorPosition, y+height-1, x));
append(*builder, Drawings.CornerBL);
for 1..width-2 {
append(*builder, Drawings.LineH);
}
append(*builder, Drawings.CornerBR);
-
+
append(*builder, Commands.TextMode);
write_string(builder_to_string(*builder));
-
- t1 := current_time_monotonic();
- set_cursor_position(2, 47);
- sample := cast(float64)to_nanoseconds(t1-t0)/1000;
- average = (sample + counter * average) / (counter + 1);
- counter += 1;
- print(">%<", average);
-}
}
-// TODO Maybe rename to "clear()"
clear_terminal :: inline () {
assert_is_active();
write_string(Commands.ClearScreen);
}
-// TODO Maybe rename to "get_size()"
get_terminal_size :: () -> width: int, height: int {
assert_is_active();
@@ -735,7 +657,7 @@ get_terminal_size :: () -> width: int, height: int {
while input.count >= 3 && input[input.count-1] != FORMAT[FORMAT.count-1] {
input.count -= 1;
}
-
+
assert(input.count >= 3 &&
input[0] == FORMAT[0] && input[1] == FORMAT[1] && input[2] == FORMAT[2] && input[input.count-1] == FORMAT[FORMAT.count-1],
"Query window size in chars returned invalid response.");
@@ -757,7 +679,7 @@ get_terminal_size :: () -> width: int, height: int {
return columns, rows;
}
-set_cursor_position :: (x: int, y: int) {
+set_cursor_position :: inline (x: int, y: int) {
assert_is_active();
print(Commands.SetCursorPosition, y, x);
}
@@ -784,7 +706,7 @@ get_cursor_position :: () -> x: int, y: int {
while input.count >= 2 && input[input.count-1] != FORMAT[FORMAT.count-1] {
input.count -= 1;
}
-
+
assert(input.count >= 2 &&
input[0] == FORMAT[0] && input[1] == FORMAT[1] && input[input.count-1] == FORMAT[FORMAT.count-1],
"Query cursor position returned invalid response.");
@@ -796,7 +718,7 @@ get_cursor_position :: () -> x: int, y: int {
return column, row;
}
-set_terminal_title :: (title: string) {
+set_terminal_title :: inline (title: string) {
assert_is_active();
print(Commands.SetWindowTitle, title);
}
diff --git a/modules/TUI/palette_24b.jai b/modules/TUI/palette_24b.jai
index 7a263a0..ea0191c 100644
--- a/modules/TUI/palette_24b.jai
+++ b/modules/TUI/palette_24b.jai
@@ -1,5 +1,5 @@
// https://www.ditig.com/publications/256-colors-cheat-sheet
-Palette :: struct {
+Palette :: struct #type_info_none {
BLACK :: Color_24b.{0x00, 0x00, 0x00};
MAROON :: Color_24b.{0x80, 0x00, 0x00};
GREEN :: Color_24b.{0x00, 0x80, 0x00};