aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordam <dam@gudinoff>2024-05-03 00:32:06 +0100
committerdam <dam@gudinoff>2024-05-03 00:32:06 +0100
commit8b312bad33617f9b718232141d2855d80cb8b912 (patch)
tree61880bef9e6b2c8200091cc365927b68878f9996
parent66dfee359cbe2c18ce8400edeb801a2373a771f4 (diff)
downloadtask-time-tracker-8b312bad33617f9b718232141d2855d80cb8b912.tar.zst
task-time-tracker-8b312bad33617f9b718232141d2855d80cb8b912.zip
WIP : Cleaning up TUI module.
-rw-r--r--modules/TUI/module.jai178
-rw-r--r--modules/TUI/palette_24b.jai2
-rw-r--r--snake.jai6
-rw-r--r--ttt.jai51
-rw-r--r--unused.jai15
5 files changed, 95 insertions, 157 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};
diff --git a/snake.jai b/snake.jai
index 465ae4d..b35c0fb 100644
--- a/snake.jai
+++ b/snake.jai
@@ -1,6 +1,6 @@
#import "Basic";
#import "Random";
-TUI :: #import "TUI"(COLOR_MODE = 8);
+TUI :: #import "TUI"(COLOR_MODE_BITS = 8);
Vec2D :: struct {
x: int;
@@ -140,7 +140,7 @@ main :: () {
seed: u64 = xx to_milliseconds(current_time_monotonic()) | 0x01; // Seed must be odd.
random_seed(seed);
- assert(TUI.start(), "Failed to start TUI.");
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
TUI.set_cursor_position(1, 1);
write_string("Please enter player name: ");
@@ -161,5 +161,5 @@ main :: () {
if TUI.get_key() == TUI.Keys.Escape then break;
}
- assert(TUI.stop(), "Failed to stop TUI.");
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
}
diff --git a/ttt.jai b/ttt.jai
index 50fa198..3512d20 100644
--- a/ttt.jai
+++ b/ttt.jai
@@ -17,7 +17,8 @@
// - release : jai ttt.jai -quiet -x64 -release
// - debug : jai ttt.jai -quiet -x64
-#import "Basic"()(MEMORY_DEBUGGER=true); // TODO Remove after final debug sessions. This takes up ~30MB of RAM.
+// #import "Basic"()(MEMORY_DEBUGGER=true); // TODO Remove after final debug sessions. This takes up ~30MB of RAM.
+#import "Basic";
#import "System";
#import "Sort";
#import "Math";
@@ -26,7 +27,7 @@
#import "String";
#import "Integer_Saturating_Arithmetic";
#import "UTF8";
-TUI :: #import "TUI"(COLOR_MODE=4);
+TUI :: #import "TUI"(COLOR_MODE_BITS=4);
// - fix/implement/finish TODO : use `dirty_bit_flag` to only update ehat has been changed
@@ -868,7 +869,7 @@ initialize_tui :: () {
}
}
- assert(TUI.start(), "Failed to start TUI.");
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
}
update_layout :: () {
@@ -1167,7 +1168,7 @@ main :: () {
print("- success\n", to_standard_error = true);
}
else {
- assert(TUI.stop(), "Failed to stop TUI.");
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
print("- ERROR: %", error_message, to_standard_error = true);
exit(1);
}
@@ -1180,19 +1181,19 @@ main :: () {
if perform_test && 1 {
print("TEST : set and get cursor position\n", to_standard_error = true);
- assert(TUI.start(), "Failed to start TUI.");
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
X :: 2;
Y :: 3;
TUI.set_cursor_position(X, Y);
x, y := TUI.get_cursor_position();
- assert(TUI.stop(), "Failed to stop TUI.");
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
assert_result(x == X && y == Y, "Failed set/get cursor position.\n");
}
if perform_test && 1 {
print("TEST : module logger\n", to_standard_error = true);
log("- log: before module start.");
- assert(TUI.start(), "Failed to start TUI.");
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
TUI.set_cursor_position(3, 3);
print("wait");
sleep_milliseconds(1000);
@@ -1200,14 +1201,14 @@ main :: () {
sleep_milliseconds(1000);
print(" a bit");
sleep_milliseconds(1000);
- assert(TUI.stop(), "Failed to stop TUI.");
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
log("- log: after module stop.");
}
if perform_test && 1 {
print("TEST : test key input\n", to_standard_error = true);
auto_release_temp();
- assert(TUI.start(), "Failed to start TUI.");
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
TUI.clear_terminal();
TUI.set_cursor_position(1, 1);
write_string("Press q to exit, other key to print it to screen, wait 1s to see animation.");
@@ -1226,28 +1227,28 @@ main :: () {
write_string(TUI.to_string(key));
}
}
- assert(TUI.stop(), "Failed to stop TUI.");
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
print("- success\n", to_standard_error = true);
}
if perform_test && 1 {
print("TEST : draw box\n", to_standard_error = true);
auto_release_temp();
- assert(TUI.start(), "Failed to start TUI.");
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
TUI.flush_input();
TUI.clear_terminal();
TUI.draw_box(1, 2, 5, 3);
TUI.set_cursor_position(1, 1);
print("Can you see the box below? (y/n)");
key := TUI.get_key();
- assert(TUI.stop(), "Failed to stop TUI.");
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
assert_result(key == #char "y", "Failed to draw box.\n");
}
if perform_test && 1 {
print("TEST : get terminal size\n", to_standard_error = true);
auto_release_temp();
- assert(TUI.start(), "Failed to start TUI.");
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
TUI.clear_terminal();
width, height := TUI.get_terminal_size();
TUI.set_cursor_position(1, 1);
@@ -1256,13 +1257,13 @@ main :: () {
while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) {
key = TUI.get_key();
}
- assert(TUI.stop(), "Failed to stop TUI.");
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
assert_result(key == #char "y", "Failed to get terminal size.\n");
}
if perform_test && 1 {
print("TEST : set terminal title\n", to_standard_error = true);
- assert(TUI.start(), "Failed to start TUI.");
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
title := "BAZINGA";
TUI.set_terminal_title(title);
TUI.set_cursor_position(1, 1);
@@ -1271,14 +1272,14 @@ main :: () {
while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) {
key = TUI.get_key();
}
- assert(TUI.stop(), "Failed to stop TUI.");
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
assert_result(key == #char "y", "Failed to set terminal title.\n");
}
if perform_test && 1 {
print("TEST : print keys and set terminal title\n", to_standard_error = true);
auto_release_temp();
- assert(TUI.start(), "Failed to start TUI.");
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
TUI.set_terminal_title("bazinga");
key: TUI.Key = #char "d";
last_none_char := "X";
@@ -1337,13 +1338,13 @@ main :: () {
// set_temporary_storage_mark(__mark);
}
print("- success");
- assert(TUI.stop(), "Failed to stop TUI.");
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
}
if perform_test && 1 {
print("TEST : user input\n", to_standard_error = true);
auto_release_temp();
- assert(TUI.start(), "Failed to start TUI.");
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
TUI.clear_terminal();
TUI.set_cursor_position(1, 1);
print("Enter some text (use Enter to finish, Esc to cancel, or resize to abort):");
@@ -1368,14 +1369,14 @@ main :: () {
}
}
answer := TUI.get_key();
- assert(TUI.stop(), "Failed to stop TUI.");
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
assert_result(answer == #char "y", error_message);
}
if perform_test && 1 {
print("TEST : hidden user input\n", to_standard_error = true);
auto_release_temp();
- assert(TUI.start(), "Failed to start TUI.");
+ assert(TUI.setup_terminal(), "Failed to setup TUI.");
TUI.clear_terminal();
TUI.set_cursor_position(1, 1);
print("Enter some secret (use Enter to finish, Esc to cancel, or resize to abort):");
@@ -1399,14 +1400,14 @@ main :: () {
}
}
answer := TUI.get_key();
- assert(TUI.stop(), "Failed to stop TUI.");
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
assert_result(answer == #char "y", error_message);
}
// -- -- -- Testing TUI -- STOP
- defer report_memory_leaks(); // TODO Remove after final debug sessions.
+ // defer report_memory_leaks(); // TODO Remove after final debug sessions.
defer free_memory();
@@ -1833,7 +1834,7 @@ main :: () {
if (active_task == null) continue;
select_task(db, db.active_idx);
- // Start/Stop
+ // Start and stop.
case TUI.Keys.Enter; #through;
case TUI.Keys.Space;
if (db != *database || selected_task == null) continue;
@@ -2021,7 +2022,7 @@ main :: () {
TUI.get_key();
}
- assert(TUI.stop(), "Failed to stop TUI.");
+ assert(TUI.reset_terminal(), "Failed to reset TUI.");
exit(xx ifx error_saving then 1 else 0);
}
diff --git a/unused.jai b/unused.jai
index 0425f8d..a8dddfc 100644
--- a/unused.jai
+++ b/unused.jai
@@ -40,6 +40,21 @@ print_database :: (db: Database) {
// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- //
+
+// Average cumulative calculation.
+average: float64 = 0;
+counter: float64 = 0;
+t0 := current_time_monotonic();
+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(">%us<", average);
+
+
+// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- //
+
// Implementation of tcsetattr, tcgetattr, and tcflush using only 'ioctl' which is provided by jai.
#if USE_LIBC {