diff options
| author | dam <dam@gudinoff> | 2024-05-03 00:32:06 +0100 |
|---|---|---|
| committer | dam <dam@gudinoff> | 2024-05-03 00:32:06 +0100 |
| commit | 8b312bad33617f9b718232141d2855d80cb8b912 (patch) | |
| tree | 61880bef9e6b2c8200091cc365927b68878f9996 | |
| parent | 66dfee359cbe2c18ce8400edeb801a2373a771f4 (diff) | |
| download | task-time-tracker-8b312bad33617f9b718232141d2855d80cb8b912.tar.zst task-time-tracker-8b312bad33617f9b718232141d2855d80cb8b912.zip | |
WIP : Cleaning up TUI module.
| -rw-r--r-- | modules/TUI/module.jai | 178 | ||||
| -rw-r--r-- | modules/TUI/palette_24b.jai | 2 | ||||
| -rw-r--r-- | snake.jai | 6 | ||||
| -rw-r--r-- | ttt.jai | 51 | ||||
| -rw-r--r-- | unused.jai | 15 |
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}; @@ -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."); } @@ -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); } @@ -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 { |
