diff options
Diffstat (limited to 'modules/TUI')
| -rw-r--r-- | modules/TUI/module.jai | 178 | ||||
| -rw-r--r-- | modules/TUI/palette_24b.jai | 2 |
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}; |
