From d828e742d7702c7e2698db4371012a49dfeb95d5 Mon Sep 17 00:00:00 2001 From: dam Date: Wed, 28 Feb 2024 00:03:27 +0000 Subject: Moved custom modules to newly supported local modules folder. --- modules/TUI/module.jai | 823 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 823 insertions(+) create mode 100644 modules/TUI/module.jai (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai new file mode 100644 index 0000000..d986eaf --- /dev/null +++ b/modules/TUI/module.jai @@ -0,0 +1,823 @@ +// TODO Move TUI into ./modules/TUI so we can stop calling --import_dir on compile. +#if OS == { + case .LINUX; + #load "unix.jai"; + case .MACOS; + #load "unix.jai"; + case .WINDOWS; + #load "windows.jai"; + case; + #assert(false, "Unsupported OS."); +} + +#import "Basic"; +#import "String"; +#import "Thread"; + +// Special Graphics Characters +Drawings :: struct { + Blank :: "\x5F"; + Diamond :: "\x60"; + Checkerboard :: "\x61"; + HorizontalTab :: "\x62"; + FormFeed :: "\x63"; + CarriageReturn :: "\x64"; + LineFeed :: "\x65"; + DegreeSymbol :: "\x66"; + PlusMinus :: "\x67"; + NewLine :: "\x68"; + VerticalTab :: "\x69"; + CornerBR :: "\x6A"; + CornerTR :: "\x6B"; + CornerTL :: "\x6C"; + CornerBL :: "\x6D"; + Cross :: "\x6E"; + LineHT :: "\x6F"; + LineHt :: "\x70"; + LineH :: "\x71"; + LineHb :: "\x72"; + LineHB :: "\x73"; + TeeL :: "\x74"; + TeeR :: "\x75"; + TeeB :: "\x76"; + TeeT :: "\x77"; + LineV :: "\x78"; + LessThanOrEqual :: "\x79"; + GreaterThanOrEqual :: "\x7A"; + Pi :: "\x7B"; + NotEqual :: "\x7C"; + PoundSign :: "\x7D"; + CenteredDot :: "\x7E"; +} + +Commands :: struct { + AlternateScreenBuffer :: "\e[?1049h"; + MainScreenBuffer :: "\e[?1049l"; + + DrawingMode :: "\e(0"; + TextMode :: "\e(B"; + ClearScreen :: "\e[2J"; + ClearLine :: "\e[2K"; + ClearScrollBack :: "\e[3J"; + + Bell :: "\x07"; + + SetWindowTitle :: "\e]0;%\e\\"; + + RefreshWindow :: "\e[7t"; // TODO Not yet tested. + + SetIEC2022 :: "\e%@"; + SetUTF8 :: "\e%G"; + + SetGraphicsRendition :: "\e[%m"; + + // Cursor Position + SetCursorPosition :: "\e[%;%H"; + + // Cursor Visibility + ShowCursor :: "\e[?25h"; + HideCursor :: "\e[?25l"; + StartBlinking :: "\e[?25h"; + StopBlinking :: "\e[?25l"; + SaveCursorPosition :: "\e7"; + RestoreCursorPosition :: "\e8"; + + // Cursor Shape + DefaultShape :: "\e[0 q"; + BlinkingBlockShape :: "\e[1 q"; + SteadyBlockShape :: "\e[2 q"; + BlinkingUnderlineShape :: "\e[3 q"; + SteadyUnderlineShape :: "\e[4 q"; + BlinkingBarShape :: "\e[5 q"; + SteadyBarShape :: "\e[6 q"; + + // Input Mode + KeypadAppMode :: "\e="; + KeypadNumMode :: "\e>"; + CursorAppMode :: "\e[?1h"; + CursorNormalMode :: "\e[?1l"; + + // Query State + QueryCursorPosition :: "\e[6n"; // Emits the cursor position as: "ESC [ ; R" Where = row and = column. + QueryDeviceAttributes :: "\e[0c"; + QueryWindowSizeInChars :: "\e[18t"; // Emits the window size as: "ESC [ 8 ; t" Where = row and = column. TODO Does not work on windows. +} + +clear_style :: () { + write_string(#run sprint(Commands.SetGraphicsRendition, "0")); +} + +Colors8b :: struct { + Black :: 0; + Maroon :: 1; + Green :: 2; + Olive :: 3; + Navy :: 4; + Purple :: 5; + Teal :: 6; + Silver :: 7; + Gray :: 8; + Red :: 9; + Lime :: 10; + Yellow :: 11; + Blue :: 12; + Magenta :: 13; + Cyan :: 14; + White :: 15; +} + +// TODO Maybe rename. +set_style_colors :: (foreground: u8, background: u8) { + print( + #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), + foreground, background); +} + +// set_colors_24b :: (foreground_r: u255) // TODO https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit + +set_style :: (bold: bool, underline: bool = false, strike_through: bool = false, negative: bool = false) { + print( + #run sprint(Commands.SetGraphicsRendition, "%;%;%;%"), + ifx bold then 1 else 22, + ifx underline then 4 else 24, + ifx strike_through then 9 else 29, + ifx negative then 7 else 27); +} + +// 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. + Therefore, we rounded it up to 8 bytes to support all this and more (if needed). + + This has to be compatible with: (#char "a" == key) ... so "a" must be stored in the LSB of key + |-|-|-|-|-| + string |a|b|c|0|0| + key/u64 |0|0|c|b|a| -> that in memory lays as (BE:|0|0|c|b|a|) and (LE:|a|b|c|0|0|) +*/ + +Key :: u64; // Terminal key-codes have 1 to 6 bytes so we'll use 8 bytes. + +KEY_SIZE :: #run type_info(Key).runtime_size; + +Keys :: struct #type_info_none { + None : Key : #run to_key("#NONE"); + Resize : Key : #run to_key("#RESIZE"); + + Space : Key : #char " "; + Enter : Key : #char "\r"; + Tab : Key : #char "\t"; + + Up : Key : #run to_key("\e[A"); + Down : Key : #run to_key("\e[B"); + Right : Key : #run to_key("\e[C"); + Left : Key : #run to_key("\e[D"); + + MetaUp : Key : #run to_key("\e[1;1A"); + MetaDown : Key : #run to_key("\e[1;1B"); + MetaRight : Key : #run to_key("\e[1;1C"); + MetaLeft : Key : #run to_key("\e[1;1D"); + + ShiftUp : Key : #run to_key("\e[1;2A"); + ShiftDown : Key : #run to_key("\e[1;2B"); + ShiftRight : Key : #run to_key("\e[1;2C"); + ShiftLeft : Key : #run to_key("\e[1;2D"); + + AltUp : Key : #run to_key("\e[1;3A"); + AltDown : Key : #run to_key("\e[1;3B"); + AltRight : Key : #run to_key("\e[1;3C"); + AltLeft : Key : #run to_key("\e[1;3D"); + + AltShiftUp : Key : #run to_key("\e[1;4A"); + AltShiftDown : Key : #run to_key("\e[1;4B"); + AltShiftRight : Key : #run to_key("\e[1;4C"); + AltShiftLeft : Key : #run to_key("\e[1;4D"); + + CtrlUp : Key : #run to_key("\e[1;5A"); + CtrlDown : Key : #run to_key("\e[1;5B"); + CtrlRight : Key : #run to_key("\e[1;5C"); + CtrlLeft : Key : #run to_key("\e[1;5D"); + + CtrlShiftUp : Key : #run to_key("\e[1;6A"); + CtrlShiftDown : Key : #run to_key("\e[1;6B"); + CtrlShiftRight : Key : #run to_key("\e[1;6C"); + CtrlShiftLeft : Key : #run to_key("\e[1;6D"); + + CtrlAltUp : Key : #run to_key("\e[1;7A"); + CtrlAltDown : Key : #run to_key("\e[1;7B"); + CtrlAltRight : Key : #run to_key("\e[1;7C"); + CtrlAltLeft : Key : #run to_key("\e[1;7D"); + + CtrlAltShiftUp : Key : #run to_key("\e[1;7A"); + CtrlAltShiftDown : Key : #run to_key("\e[1;7B"); + CtrlAltShiftRight : Key : #run to_key("\e[1;7C"); + CtrlAltShiftLeft : Key : #run to_key("\e[1;7D"); + + Home : Key : #run to_key("\e[H"); + End : Key : #run to_key("\e[F"); + + Escape : Key : 0x00000000_0000001B; + Backspace : Key : 0x00000000_0000007F; + Pause : Key : 0x00000000_0000001A; + Insert : Key : #run to_key("\e[2~"); + Delete : Key : #run to_key("\e[3~"); + PgUp : Key : #run to_key("\e[5~"); + PgDown : Key : #run to_key("\e[6~"); + + /* TODO On get_key, convert F1 to F4 into the format "\e[1X~" + so that: + F1 : 1b 4f 50 : ^OP : -> \e[11~ + Shift+ F1 : 1b 4f 32 50 : ^O2P : -> \e[11;2~ + F2 : 1b 4f 51 : ^OQ : -> \e[12~ + Shift+ F2 : 1b 4f 32 51 : ^O2Q : -> \e[12;2~ + F3 : 1b 4f 52 : ^OR : -> \e[13~ + Shift+ F3 : 1b 4f 32 52 : ^O2R : -> \e[13;2~ + F4 : 1b 4f 53 : ^OS : -> \e[14~ + Meta+ Fx : 1b 4f 31 53 : ^O1S : -> \e[14;1~ + Shift+ F4 : 1b 4f 32 53 : ^O2S : -> \e[14;2~ + Alt+ F4 : 1b 4f 33 53 : ^O3S : -> \e[14;3~ + S+A F4 : 1b 4f 34 53 : ^O4S : -> \e[14;4~ + Ctrl+ F4 : 1b 4f 35 53 : ^O4S : -> \e[14;5~ + Ctrl+ F4 : 1b 4f 35 53 : ^O4S : -> \e[14;5~ + ... + */ + + // WIP HERE + + F1 : Key : #run to_key("\eOP"); + F2 : Key : #run to_key("\eOQ"); + F3 : Key : #run to_key("\eOR"); + F4 : Key : #run to_key("\eOS"); + F5 : Key : #run to_key("\e[15~"); + F6 : Key : #run to_key("\e[17~"); + F7 : Key : #run to_key("\e[18~"); + F8 : Key : #run to_key("\e[19~"); + F9 : Key : #run to_key("\e[20~"); + F10 : Key : #run to_key("\e[21~"); + F11 : Key : #run to_key("\e[23~"); + F12 : Key : #run to_key("\e[24~"); + + MetaF1 : Key : #run to_key("\e[1;1P"); + MetaF2 : Key : #run to_key("\e[1;1Q"); + MetaF3 : Key : #run to_key("\e[1;1R"); + MetaF4 : Key : #run to_key("\e[1;1S"); + MetaF5 : Key : #run to_key("\e[15;1~"); + MetaF6 : Key : #run to_key("\e[17;1~"); + MetaF7 : Key : #run to_key("\e[18;1~"); + MetaF8 : Key : #run to_key("\e[19;1~"); + MetaF9 : Key : #run to_key("\e[20;1~"); + MetaF10 : Key : #run to_key("\e[21;1~"); + MetaF11 : Key : #run to_key("\e[23;1~"); + MetaF12 : Key : #run to_key("\e[24;1~"); + + ShiftF1 : Key : #run to_key("\e[1;2P"); + ShiftF2 : Key : #run to_key("\e[1;2Q"); + ShiftF3 : Key : #run to_key("\e[1;2R"); + ShiftF4 : Key : #run to_key("\e[1;2S"); + ShiftF5 : Key : #run to_key("\e[15;2~"); + ShiftF6 : Key : #run to_key("\e[17;2~"); + ShiftF7 : Key : #run to_key("\e[18;2~"); + ShiftF8 : Key : #run to_key("\e[19;2~"); + ShiftF9 : Key : #run to_key("\e[20;2~"); + ShiftF10 : Key : #run to_key("\e[21;2~"); + ShiftF11 : Key : #run to_key("\e[23;2~"); + ShiftF12 : Key : #run to_key("\e[24;2~"); + + AltF1 : Key : #run to_key("\e[1;3P"); + AltF2 : Key : #run to_key("\e[1;3Q"); + AltF3 : Key : #run to_key("\e[1;3R"); + AltF4 : Key : #run to_key("\e[1;3S"); + AltF5 : Key : #run to_key("\e[15;3~"); + AltF6 : Key : #run to_key("\e[17;3~"); + AltF7 : Key : #run to_key("\e[18;3~"); + AltF8 : Key : #run to_key("\e[19;3~"); + AltF9 : Key : #run to_key("\e[20;3~"); + AltF10 : Key : #run to_key("\e[21;3~"); + AltF11 : Key : #run to_key("\e[23;3~"); + AltF12 : Key : #run to_key("\e[24;3~"); + + AltShiftF1 : Key : #run to_key("\e[1;4P"); + AltShiftF2 : Key : #run to_key("\e[1;4Q"); + AltShiftF3 : Key : #run to_key("\e[1;4R"); + AltShiftF4 : Key : #run to_key("\e[1;4S"); + AltShiftF5 : Key : #run to_key("\e[15;4~"); + AltShiftF6 : Key : #run to_key("\e[17;4~"); + AltShiftF7 : Key : #run to_key("\e[18;4~"); + AltShiftF8 : Key : #run to_key("\e[19;4~"); + AltShiftF9 : Key : #run to_key("\e[20;4~"); + AltShiftF10 : Key : #run to_key("\e[21;4~"); + AltShiftF11 : Key : #run to_key("\e[23;4~"); + AltShiftF12 : Key : #run to_key("\e[24;4~"); + + CtrlF1 : Key : #run to_key("\e[1;5P"); + CtrlF2 : Key : #run to_key("\e[1;5Q"); + CtrlF3 : Key : #run to_key("\e[1;5R"); + CtrlF4 : Key : #run to_key("\e[1;5S"); + CtrlF5 : Key : #run to_key("\e[15;5~"); + CtrlF6 : Key : #run to_key("\e[17;5~"); + CtrlF7 : Key : #run to_key("\e[18;5~"); + CtrlF8 : Key : #run to_key("\e[19;5~"); + CtrlF9 : Key : #run to_key("\e[20;5~"); + CtrlF10 : Key : #run to_key("\e[21;5~"); + CtrlF11 : Key : #run to_key("\e[23;5~"); + CtrlF12 : Key : #run to_key("\e[24;5~"); + + CtrlShiftF1 : Key : #run to_key("\e[1;6P"); + CtrlShiftF2 : Key : #run to_key("\e[1;6Q"); + CtrlShiftF3 : Key : #run to_key("\e[1;6R"); + CtrlShiftF4 : Key : #run to_key("\e[1;6S"); + CtrlShiftF5 : Key : #run to_key("\e[15;6~"); + CtrlShiftF6 : Key : #run to_key("\e[17;6~"); + CtrlShiftF7 : Key : #run to_key("\e[18;6~"); + CtrlShiftF8 : Key : #run to_key("\e[19;6~"); + CtrlShiftF9 : Key : #run to_key("\e[20;6~"); + CtrlShiftF10 : Key : #run to_key("\e[21;6~"); + CtrlShiftF11 : Key : #run to_key("\e[23;6~"); + CtrlShiftF12 : Key : #run to_key("\e[24;6~"); + + CtrlAltF1 : Key : #run to_key("\e[1;7P"); + CtrlAltF2 : Key : #run to_key("\e[1;7Q"); + CtrlAltF3 : Key : #run to_key("\e[1;7R"); + CtrlAltF4 : Key : #run to_key("\e[1;7S"); + CtrlAltF5 : Key : #run to_key("\e[15;7~"); + CtrlAltF6 : Key : #run to_key("\e[17;7~"); + CtrlAltF7 : Key : #run to_key("\e[18;7~"); + CtrlAltF8 : Key : #run to_key("\e[19;7~"); + CtrlAltF9 : Key : #run to_key("\e[20;7~"); + CtrlAltF10 : Key : #run to_key("\e[21;7~"); + CtrlAltF11 : Key : #run to_key("\e[23;7~"); + CtrlAltF12 : Key : #run to_key("\e[24;7~"); + + CtrlAltShiftF1 : Key : #run to_key("\e[1;8P"); + CtrlAltShiftF2 : Key : #run to_key("\e[1;8Q"); + CtrlAltShiftF3 : Key : #run to_key("\e[1;8R"); + CtrlAltShiftF4 : Key : #run to_key("\e[1;8S"); + CtrlAltShiftF5 : Key : #run to_key("\e[15;8~"); + CtrlAltShiftF6 : Key : #run to_key("\e[17;8~"); + CtrlAltShiftF7 : Key : #run to_key("\e[18;8~"); + CtrlAltShiftF8 : Key : #run to_key("\e[19;8~"); + CtrlAltShiftF9 : Key : #run to_key("\e[20;8~"); + CtrlAltShiftF10 : Key : #run to_key("\e[21;8~"); + CtrlAltShiftF11 : Key : #run to_key("\e[23;8~"); + CtrlAltShiftF12 : Key : #run to_key("\e[24;8~"); +} + +to_key :: inline (str: $T) -> Key #modify { return T == ([]u8) || T == string; } { + k: Key; + // #if DEBUG { + // assert(str.count <= 8); // TODO Add DEBUG to module parameters. + // } + for 0..str.count-1 #no_abc { + k |= ((cast(u64)str[it]) << (it*8)); + } + return k; +} + +to_string :: inline (key: Key) -> string { // TODO FIXME TEMPORARY MEMORY + str := talloc_string(KEY_SIZE); + str.count = 0; + while key != 0 #no_abc { + str[str.count] = xx key & 0xFF; + key >>= 8; + str.count += 1; + } + return str; +} + +is_escape_code :: inline (key: Key) -> bool { + result := false; + while key != 0 { + key >>= 8; + result |= ((key ^ Keys.Escape) == 0); + } + return result; +} + +initialized := false; + +//input_buffer : [64] u8; // TODO FIXME Input buffer is too small!!! +input_buffer : [8] u8; // TODO FIXME Input buffer is too small!!! +input_string : string; +input_override : Key; + +#run { + // TODO FIXME DEBUG HACK or maybe... let it be?! + // Some tests. + assert(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire terminal (6 bytes) or UTF8 (4 bytes) code."); +} + +assert_is_initialized :: inline () { + assert(initialized, "TUI is not ready."); +} + +set_next_key :: (key: Key) { + assert_is_initialized(); + input_override = key; +} + +get_key :: (timeout_milliseconds: s32 = -1) -> Key { + assert_is_initialized(); + /* + TODO + get_key already deals with utf8 codes, but we don't know when we're receiving ANSI escape codes. If initial key is escape and other keys are awaiting in the input buffer, we need to parse them as escaped sequences. See wikiedia* for help on that. Lets use the escape sequences used on windows amd forget all others. Those should be the most used ones; at least they are the cross-platform compatible ones :P + * https://en.m.wikipedia.org/wiki/ANSI_escape_code + + Check this + https://devmemo.io/cheatsheets/terminal_escape_code/ + */ + + + // BBBB BBBB & 1100 0000 == 10XX XXXX -> is continuation byte + is_utf8_continuation_byte :: inline (byte: u8) -> bool { + return (byte & 0xC0) == 0x80; + } + + // BBBB BBBB & 1110 0000 == 110X XXXX -> 1 initial + 1 continuation byte + // BBBB BBBB & 1111 0000 == 1110 XXXX -> 1 initial + 2 continuation byte + // BBBB BBBB & 1111 1000 == 1111 0XXX -> 1 initial + 3 continuation byte + count_utf8_bytes :: inline (byte: u8) -> int { + if (byte & 0xE0) == 0xC0 return 1+1; + if (byte & 0xF0) == 0xE0 return 1+2; + if (byte & 0xF8) == 0xF0 return 1+3; + return 1; + } + + if input_override != xx Keys.None { + defer input_override = xx Keys.None; + return input_override; + } + + if OS_was_terminal_resized() return xx Keys.Resize; + + should_read_input := false; + is_input_available := false; + + if input_string.count == 0 { + should_read_input = true; + is_input_available = OS_wait_for_input(timeout_milliseconds); + } + else if input_string.count < KEY_SIZE { + should_read_input = true; + is_input_available = OS_wait_for_input(0); + } + + if OS_was_terminal_resized() return xx Keys.Resize; + + if should_read_input && is_input_available { + // Copy buffered bytes to the start, and read the remaining ones from input. + for 0..input_string.count-1 { + input_buffer[it] = input_string[it]; + } + + // Read input into remaining part of buffer. + bytes_read := OS_read_input(input_buffer.data + input_string.count, input_buffer.count - input_string.count); + input_string.data = input_buffer.data; + input_string.count += bytes_read; + } + + if input_string.count > 0 + { + utf8_bytes := count_utf8_bytes(input_string[0]); + to_parse := input_string; + to_parse.count = utf8_bytes; + + // Must be a terminal escape sequence. + if utf8_bytes == 1 && input_string[0] == #char "\e" { + assert(input_string.count <= KEY_SIZE, "Received oversized terminal sequence."); // TODO + to_parse.count = ifx input_string.count > KEY_SIZE then KEY_SIZE else input_string.count; // TODO We should look into the input_string and search for the following escape sequence or somehting!? + } + + key := to_key(to_parse); + advance(*input_string, to_parse.count); + return key; + } + + // TODO try_parse_escape_code + // { + // assert(false, "TODO try_parse_escape_code"); + // } + + return xx Keys.None; +} + +// TODO Review me! +read_input :: (count_limit: int = -1, terminators: .. u8) -> string { + assert_is_initialized(); + + assert(count_limit >= 0 || terminators.count > 0, "Infinite loop detected, aborting."); // TODO Maybe just return!? + + if count_limit < 0 { + builder: String_Builder; + init_string_builder(*builder); + + while read_loop := true { + buffer := get_current_buffer(*builder); + buffer_data := get_buffer_data(buffer); + + previous_count := buffer.count; + buffer.count += OS_read_input(buffer_data + buffer.count, buffer.allocated - buffer.count); + + for previous_count..buffer.count-1 { + for t: terminators { + if buffer_data[it] == t then break read_loop; + } + } + + if buffer.count == buffer.allocated then expand(*builder); + OS_wait_for_input(); + } + return builder_to_string(*builder); + } + else { + buffer := alloc_string(count_limit); + buffer.count = 0; + + while read_loop := true { + + previous_count := buffer.count; + buffer.count += OS_read_input(buffer.data + buffer.count, count_limit - buffer.count); + + if buffer.count == count_limit then break; + + for previous_count..buffer.count-1 { + for t: terminators { + if buffer[it] == t then break read_loop; + } + } + + OS_wait_for_input(); + } + + return buffer; + } +} + +// TODO UNTESTED +read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { + assert(count_limit >= 0, "Invalid value on count_limit parameter."); + str := alloc_string(count_limit); + + // TODO Show blinking cursor + write_strings(Commands.StartBlinking, Commands.BlinkingUnderlineShape); + + row, col := get_cursor_position(); + idx := 0; + + key := Keys.None; + + // TODO Some of these may be nice to have: + // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line + + while key != Keys.Resize && key != Keys.Escape { + // TODO How to alloc/release temporary memory here? + key = get_key(); + + if key == { + case Keys.Enter; + break; + + case Keys.Left; + if idx == 0 continue; + idx -= 1; + + case Keys.Right; + if idx == str.count-1 continue; + idx += 1; + + case Keys.Delete; + if idx == str.count-1 continue; + for idx..str.count-2 { + str.data[it] = str.data[it+1]; + } + + case Keys.Backspace; + if idx == 0 continue; + idx -= 1; + for idx..str.count-2 { + str.data[it] = str.data[it+1]; + } + + case; + if is_escape_code(key) continue; // TODO NOT WORKING FIX THIS + key_str := to_string(key); + str.data[idx] = key_str.data[0]; + idx += 1; + } + + + // append(*builder, str); + // set_cursor_position(row, col); + // write_builder(*builder, false); + set_cursor_position(row, col+idx); + for idx..count_limit print_character(#char " "); + set_cursor_position(row, col); + write_string(str); + // print(">%<", builder_to_string(*builder,, temporary_allocator)); + set_cursor_position(row, col+idx); + } + + write_strings(Commands.StopBlinking, Commands.DefaultShape); + /* + Use the get_key to read user input and show it on screen... should allow to move the cursor left and right and to delete/backspace. + Enter should end the input, returning the input string and the Enter key. + Escape should discard the input returning an empty string and a None key. + Resize should discard the input returning an empty string and a Resize key. + */ + // result := ifx key == Keys.Enter then builder_to_string(*builder) else ""; + result := ifx key == Keys.Enter then str else ""; + return result, key; +} + +start :: () { + if initialized == true return; + + input_string.data = input_buffer.data; + input_string.count = 0; + input_override = xx Keys.None; + + write_strings( + Commands.HideCursor, + Commands.SaveCursorPosition, + Commands.AlternateScreenBuffer, + Commands.SetUTF8, + Commands.CursorNormalMode, + Commands.KeypadNumMode); + + OS_prepare_terminal(); + + initialized = true; +} + +stop :: () { + if initialized == false return; + initialized = false; + + OS_reset_terminal(); + write_strings(Commands.MainScreenBuffer, Commands.RestoreCursorPosition, Commands.ShowCursor); +} + +flush_input :: () { + OS_flush_input(); + input_string.data = input_buffer.data; + input_string.count = 0; +} + +// TODO move style related procedures here! +// set_style :: () { + // print("", Commands.) -- +// } + +draw_box :: (x: int, y: int, width: int, height: int) { + assert_is_initialized(); + + // 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 + ); + + 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); + } + write_string(Drawings.CornerBR); + + write_string(Commands.TextMode); +} + +// TODO Maybe rename to "clear()" +clear_terminal :: inline () { + assert_is_initialized(); + write_string(Commands.ClearScreen); +} + +// TODO Maybe rename to "get_size()" +get_terminal_size :: () -> rows: int, columns: int { + assert_is_initialized(); + + auto_release_temp(); + + flush_input(); + write_string(Commands.QueryWindowSizeInChars); + + rows, columns: int = ---; + if OS_wait_for_input(0) { + + // Expected response format: \e[8;;t + // where is the number of rows and of columns. + FORMAT :: "\e[8;;t"; + input := read_input(64, #char "t",, temporary_allocator); + + // Discard head noise. + while input.count >= 3 && (input[0] != FORMAT[0] || input[1] != FORMAT[1] || input[2] != FORMAT[2]) { + advance(*input); + } + + // Discard tail noise. + 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."); + + parts := split(input, ";",, temporary_allocator); + rows = parse_int(*parts[1]); + columns = parse_int(*parts[2]); + } + // Some systems don't allow to query the terminal size directly. + // In such cases, measure it indirectly by the maximum possible cursor position. + else { + flush_input(); + cursor_row, cursor_column := get_cursor_position(); + defer set_cursor_position(cursor_row, cursor_column); + + set_cursor_position(0xFFFF, 0xFFFF); + rows, columns = get_cursor_position(); + } + + return rows, columns; +} + +set_cursor_position :: (row: int, column: int) { + assert_is_initialized(); + auto_release_temp(); + print(Commands.SetCursorPosition, row, column); +} + +get_cursor_position :: () -> row: int, column: int { + assert_is_initialized(); + + auto_release_temp(); + + flush_input(); + write_string(Commands.QueryCursorPosition); + + // Expected response format: \e[;R + // where is the number of rows and of columns. + FORMAT :: "\e[;R"; + input := read_input(64, #char "R"); + + // Discard head noise. + while input.count >= 2 && (input[0] != FORMAT[0] || input[1] != FORMAT[1]) { + advance(*input); + } + + // Discard tail noise. + 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."); + + advance(*input, 2); + parts := split(input, ";",, temporary_allocator); + row := parse_int(*parts[0]); + column := parse_int(*parts[1]); + return row, column; +} + +set_terminal_title :: (title: string) { + assert_is_initialized(); + print(Commands.SetWindowTitle, title); +} + + +#if OS == .WINDOWS { + // Prototyping zone... keep clear! +} +else #if OS == .LINUX || OS == .MACOS { + // Prototyping zone... keep clear! +} -- cgit v1.2.3 From c181df188164853845441c7798e8aa45f843ee65 Mon Sep 17 00:00:00 2001 From: dam Date: Wed, 28 Feb 2024 09:21:18 +0000 Subject: WIP --- modules/TUI/module.jai | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index d986eaf..2f32752 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -234,17 +234,17 @@ Keys :: struct #type_info_none { Shift+ F2 : 1b 4f 32 51 : ^O2Q : -> \e[12;2~ F3 : 1b 4f 52 : ^OR : -> \e[13~ Shift+ F3 : 1b 4f 32 52 : ^O2R : -> \e[13;2~ + F4 : 1b 4f 53 : ^OS : -> \e[14~ - Meta+ Fx : 1b 4f 31 53 : ^O1S : -> \e[14;1~ + Meta+ F4 : 1b 4f 31 53 : ^O1S : -> \e[14;1~ Shift+ F4 : 1b 4f 32 53 : ^O2S : -> \e[14;2~ Alt+ F4 : 1b 4f 33 53 : ^O3S : -> \e[14;3~ S+A F4 : 1b 4f 34 53 : ^O4S : -> \e[14;4~ Ctrl+ F4 : 1b 4f 35 53 : ^O4S : -> \e[14;5~ - Ctrl+ F4 : 1b 4f 35 53 : ^O4S : -> \e[14;5~ ... */ - // WIP HERE + WIP HERE F1 : Key : #run to_key("\eOP"); F2 : Key : #run to_key("\eOQ"); -- cgit v1.2.3 From ce40906a177708e54ea1067ca157a308a185a164 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 29 Feb 2024 01:08:40 +0000 Subject: WIP : Improving get_key to deal with escape codes. --- modules/TUI/module.jai | 56 ++++++++++++++++++++++++++++++++------------------ ttt.jai | 4 ++-- 2 files changed, 38 insertions(+), 22 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 2f32752..3008d23 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -244,8 +244,6 @@ Keys :: struct #type_info_none { ... */ - WIP HERE - F1 : Key : #run to_key("\eOP"); F2 : Key : #run to_key("\eOQ"); F3 : Key : #run to_key("\eOR"); @@ -477,29 +475,47 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { input_string.count += bytes_read; } - if input_string.count > 0 - { - utf8_bytes := count_utf8_bytes(input_string[0]); - to_parse := input_string; - to_parse.count = utf8_bytes; + if input_string.count == 0 return Keys.None; - // Must be a terminal escape sequence. - if utf8_bytes == 1 && input_string[0] == #char "\e" { - assert(input_string.count <= KEY_SIZE, "Received oversized terminal sequence."); // TODO - to_parse.count = ifx input_string.count > KEY_SIZE then KEY_SIZE else input_string.count; // TODO We should look into the input_string and search for the following escape sequence or somehting!? - } + // Assume we're parsing just a single char. + to_parse := input_string; + to_parse.count = 1; - key := to_key(to_parse); - advance(*input_string, to_parse.count); - return key; + // Try to parse UTF8 character. + if is_utf8_continuation_byte(input_string[0]) { + to_parse.count = count_utf8_bytes(input_string[0]); } - // TODO try_parse_escape_code - // { - // assert(false, "TODO try_parse_escape_code"); - // } + // Try to parse escape code. + if input_string[0] == #char "\e" && input_string.count > 1 { + assert(input_string.count <= KEY_SIZE, "Received oversized terminal sequence."); // TODO + to_parse.count = ifx input_string.count > KEY_SIZE then KEY_SIZE else input_string.count; // TODO We should look into the input_string and search for the following escape sequence or somehting!? + + WIP HERE + // A possible way to solve this is to create a LUT, and then, grow the to_parse.count from 2 to KEY_SIZE and return as soon + // as we ding a match on the LUT. + // If the LUT is too big... maybe use a hash-table. + + if compare(to_parse, "\e[A") == 0 { + advance(*input_string, to_parse.count); + return to_key("#UP"); + } + else if compare(to_parse, "\e[B") == 0 { + advance(*input_string, to_parse.count); + return to_key("#DOWN"); + } + else if compare(to_parse, "\e[C") == 0 { + advance(*input_string, to_parse.count); + return to_key("#RIGHT"); + } + else if compare(to_parse, "\e[D") == 0 { + advance(*input_string, to_parse.count); + return to_key("#LEFT"); + } + } - return xx Keys.None; + advance(*input_string, to_parse.count); + return to_key(to_parse); } // TODO Review me! diff --git a/ttt.jai b/ttt.jai index 7c21a1f..194bd06 100644 --- a/ttt.jai +++ b/ttt.jai @@ -14,8 +14,8 @@ // this program. If not, see . // Compilation commands: -// - release : jai ttt.jai -import_dir . -quiet -x64 -release -// - debug : jai ttt.jai -import_dir . -quiet -x64 +// - 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 "System"; -- cgit v1.2.3 From 45e56e387b713cebd78c3789ed7c234e588fbe48 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 1 Mar 2024 02:08:36 +0000 Subject: WIP : First attempt to use hash table on escape codes. --- modules/TUI/module.jai | 57 +++++++++++++++++++++++++++++++++++++++++++++++++- ttt.jai | 4 ++-- 2 files changed, 58 insertions(+), 3 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 3008d23..ed3f517 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -13,6 +13,7 @@ #import "Basic"; #import "String"; #import "Thread"; +#import "Hash_Table"; // Special Graphics Characters Drawings :: struct { @@ -362,6 +363,52 @@ Keys :: struct #type_info_none { CtrlAltShiftF12 : Key : #run to_key("\e[24;8~"); } +key_map: Table(string, Key); + +setup_key_map :: () { + + table_set(*key_map, "\e[A", to_key("#UP")); + table_set(*key_map, "\e[A", to_key("#UP")); + + table_set(*key_map, "\eOQ", to_key("#F2")); + table_set(*key_map, "\e[12~", to_key("#F2")); + + table_set(*key_map, "\eO2Q", to_key("#SF2")); + table_set(*key_map, "\e[1;2Q", to_key("#SF2")); + table_set(*key_map, "\e[12;2~", to_key("#SF2")); + + table_set(*key_map, "\e\e[12~", to_key("#AF2")); + table_set(*key_map, "\e\e[24~", to_key("#AF12")); + + table_set(*key_map, "\e[12^", to_key("#CF2")); + + // A good example + table_set(*key_map, "\e[21~", to_key("#F10")); + table_set(*key_map, "\e[21;1~", to_key("#MF10")); + table_set(*key_map, "\e[21;2~", to_key("#SF10")); + table_set(*key_map, "\e[21;3~", to_key("#AF10")); + table_set(*key_map, "\e[21;4~", to_key("#SAF10")); + table_set(*key_map, "\e[21;5~", to_key("#CF10")); + table_set(*key_map, "\e[21;6~", to_key("#SCF10")); + table_set(*key_map, "\e[21;7~", to_key("#ACF10")); + table_set(*key_map, "\e[21;8~", to_key("#SACF10")); + + table_set(*key_map, "\e[24~", to_key("#F12")); + table_set(*key_map, "\e[24;2~", to_key("#SF12")); + table_set(*key_map, "\e[24;3~", to_key("#AF12")); + + // TODO Try with: + // - konsole + // - xterm + // - rxvt-unicode + // - kittyterminal + // - termux + + // F2 : Key : #run to_key("\eOQ"); + // F2 : 1b 4f 51 : ^OQ : -> \e[12~ + // Shift+ F2 : 1b 4f 32 51 : ^O2Q : -> \e[12;2~ +} + to_key :: inline (str: $T) -> Key #modify { return T == ([]u8) || T == string; } { k: Key; // #if DEBUG { @@ -491,10 +538,16 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { assert(input_string.count <= KEY_SIZE, "Received oversized terminal sequence."); // TODO to_parse.count = ifx input_string.count > KEY_SIZE then KEY_SIZE else input_string.count; // TODO We should look into the input_string and search for the following escape sequence or somehting!? - WIP HERE + // WIP HERE // A possible way to solve this is to create a LUT, and then, grow the to_parse.count from 2 to KEY_SIZE and return as soon // as we ding a match on the LUT. // If the LUT is too big... maybe use a hash-table. + + key, success := table_find(*key_map, to_parse); + if success { + advance(*input_string, to_parse.count); + return key; + } if compare(to_parse, "\e[A") == 0 { advance(*input_string, to_parse.count); @@ -649,6 +702,8 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { start :: () { if initialized == true return; + setup_key_map(); // TODO This is being called multiple times... please fix me! + input_string.data = input_buffer.data; input_string.count = 0; input_override = xx Keys.None; diff --git a/ttt.jai b/ttt.jai index 194bd06..94e868c 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1367,11 +1367,11 @@ main :: () { print(": % : ", string_to_print); for 0..str.count-1 { if str[it] == #char "\e" { - str[it] = #char "^"; + str[it] = #char "#"; } } write_string(str); - write_string(" : ~DAM"); + write_string(" :"); drop_down += 1; } } -- cgit v1.2.3 From 76eab65ffe14674a7b7fdece67c2a25c2a044dc7 Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 2 Mar 2024 03:58:33 +0000 Subject: Setup mappings for several different terminals. --- modules/TUI/module.jai | 663 +++++++++++++++++++++++++++++-------------------- ttt.jai | 8 +- 2 files changed, 400 insertions(+), 271 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index ed3f517..a5f5837 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -163,252 +163,6 @@ Key :: u64; // Terminal key-codes have 1 to 6 bytes so we'll use 8 bytes. KEY_SIZE :: #run type_info(Key).runtime_size; -Keys :: struct #type_info_none { - None : Key : #run to_key("#NONE"); - Resize : Key : #run to_key("#RESIZE"); - - Space : Key : #char " "; - Enter : Key : #char "\r"; - Tab : Key : #char "\t"; - - Up : Key : #run to_key("\e[A"); - Down : Key : #run to_key("\e[B"); - Right : Key : #run to_key("\e[C"); - Left : Key : #run to_key("\e[D"); - - MetaUp : Key : #run to_key("\e[1;1A"); - MetaDown : Key : #run to_key("\e[1;1B"); - MetaRight : Key : #run to_key("\e[1;1C"); - MetaLeft : Key : #run to_key("\e[1;1D"); - - ShiftUp : Key : #run to_key("\e[1;2A"); - ShiftDown : Key : #run to_key("\e[1;2B"); - ShiftRight : Key : #run to_key("\e[1;2C"); - ShiftLeft : Key : #run to_key("\e[1;2D"); - - AltUp : Key : #run to_key("\e[1;3A"); - AltDown : Key : #run to_key("\e[1;3B"); - AltRight : Key : #run to_key("\e[1;3C"); - AltLeft : Key : #run to_key("\e[1;3D"); - - AltShiftUp : Key : #run to_key("\e[1;4A"); - AltShiftDown : Key : #run to_key("\e[1;4B"); - AltShiftRight : Key : #run to_key("\e[1;4C"); - AltShiftLeft : Key : #run to_key("\e[1;4D"); - - CtrlUp : Key : #run to_key("\e[1;5A"); - CtrlDown : Key : #run to_key("\e[1;5B"); - CtrlRight : Key : #run to_key("\e[1;5C"); - CtrlLeft : Key : #run to_key("\e[1;5D"); - - CtrlShiftUp : Key : #run to_key("\e[1;6A"); - CtrlShiftDown : Key : #run to_key("\e[1;6B"); - CtrlShiftRight : Key : #run to_key("\e[1;6C"); - CtrlShiftLeft : Key : #run to_key("\e[1;6D"); - - CtrlAltUp : Key : #run to_key("\e[1;7A"); - CtrlAltDown : Key : #run to_key("\e[1;7B"); - CtrlAltRight : Key : #run to_key("\e[1;7C"); - CtrlAltLeft : Key : #run to_key("\e[1;7D"); - - CtrlAltShiftUp : Key : #run to_key("\e[1;7A"); - CtrlAltShiftDown : Key : #run to_key("\e[1;7B"); - CtrlAltShiftRight : Key : #run to_key("\e[1;7C"); - CtrlAltShiftLeft : Key : #run to_key("\e[1;7D"); - - Home : Key : #run to_key("\e[H"); - End : Key : #run to_key("\e[F"); - - Escape : Key : 0x00000000_0000001B; - Backspace : Key : 0x00000000_0000007F; - Pause : Key : 0x00000000_0000001A; - Insert : Key : #run to_key("\e[2~"); - Delete : Key : #run to_key("\e[3~"); - PgUp : Key : #run to_key("\e[5~"); - PgDown : Key : #run to_key("\e[6~"); - - /* TODO On get_key, convert F1 to F4 into the format "\e[1X~" - so that: - F1 : 1b 4f 50 : ^OP : -> \e[11~ - Shift+ F1 : 1b 4f 32 50 : ^O2P : -> \e[11;2~ - F2 : 1b 4f 51 : ^OQ : -> \e[12~ - Shift+ F2 : 1b 4f 32 51 : ^O2Q : -> \e[12;2~ - F3 : 1b 4f 52 : ^OR : -> \e[13~ - Shift+ F3 : 1b 4f 32 52 : ^O2R : -> \e[13;2~ - - F4 : 1b 4f 53 : ^OS : -> \e[14~ - Meta+ F4 : 1b 4f 31 53 : ^O1S : -> \e[14;1~ - Shift+ F4 : 1b 4f 32 53 : ^O2S : -> \e[14;2~ - Alt+ F4 : 1b 4f 33 53 : ^O3S : -> \e[14;3~ - S+A F4 : 1b 4f 34 53 : ^O4S : -> \e[14;4~ - Ctrl+ F4 : 1b 4f 35 53 : ^O4S : -> \e[14;5~ - ... - */ - - F1 : Key : #run to_key("\eOP"); - F2 : Key : #run to_key("\eOQ"); - F3 : Key : #run to_key("\eOR"); - F4 : Key : #run to_key("\eOS"); - F5 : Key : #run to_key("\e[15~"); - F6 : Key : #run to_key("\e[17~"); - F7 : Key : #run to_key("\e[18~"); - F8 : Key : #run to_key("\e[19~"); - F9 : Key : #run to_key("\e[20~"); - F10 : Key : #run to_key("\e[21~"); - F11 : Key : #run to_key("\e[23~"); - F12 : Key : #run to_key("\e[24~"); - - MetaF1 : Key : #run to_key("\e[1;1P"); - MetaF2 : Key : #run to_key("\e[1;1Q"); - MetaF3 : Key : #run to_key("\e[1;1R"); - MetaF4 : Key : #run to_key("\e[1;1S"); - MetaF5 : Key : #run to_key("\e[15;1~"); - MetaF6 : Key : #run to_key("\e[17;1~"); - MetaF7 : Key : #run to_key("\e[18;1~"); - MetaF8 : Key : #run to_key("\e[19;1~"); - MetaF9 : Key : #run to_key("\e[20;1~"); - MetaF10 : Key : #run to_key("\e[21;1~"); - MetaF11 : Key : #run to_key("\e[23;1~"); - MetaF12 : Key : #run to_key("\e[24;1~"); - - ShiftF1 : Key : #run to_key("\e[1;2P"); - ShiftF2 : Key : #run to_key("\e[1;2Q"); - ShiftF3 : Key : #run to_key("\e[1;2R"); - ShiftF4 : Key : #run to_key("\e[1;2S"); - ShiftF5 : Key : #run to_key("\e[15;2~"); - ShiftF6 : Key : #run to_key("\e[17;2~"); - ShiftF7 : Key : #run to_key("\e[18;2~"); - ShiftF8 : Key : #run to_key("\e[19;2~"); - ShiftF9 : Key : #run to_key("\e[20;2~"); - ShiftF10 : Key : #run to_key("\e[21;2~"); - ShiftF11 : Key : #run to_key("\e[23;2~"); - ShiftF12 : Key : #run to_key("\e[24;2~"); - - AltF1 : Key : #run to_key("\e[1;3P"); - AltF2 : Key : #run to_key("\e[1;3Q"); - AltF3 : Key : #run to_key("\e[1;3R"); - AltF4 : Key : #run to_key("\e[1;3S"); - AltF5 : Key : #run to_key("\e[15;3~"); - AltF6 : Key : #run to_key("\e[17;3~"); - AltF7 : Key : #run to_key("\e[18;3~"); - AltF8 : Key : #run to_key("\e[19;3~"); - AltF9 : Key : #run to_key("\e[20;3~"); - AltF10 : Key : #run to_key("\e[21;3~"); - AltF11 : Key : #run to_key("\e[23;3~"); - AltF12 : Key : #run to_key("\e[24;3~"); - - AltShiftF1 : Key : #run to_key("\e[1;4P"); - AltShiftF2 : Key : #run to_key("\e[1;4Q"); - AltShiftF3 : Key : #run to_key("\e[1;4R"); - AltShiftF4 : Key : #run to_key("\e[1;4S"); - AltShiftF5 : Key : #run to_key("\e[15;4~"); - AltShiftF6 : Key : #run to_key("\e[17;4~"); - AltShiftF7 : Key : #run to_key("\e[18;4~"); - AltShiftF8 : Key : #run to_key("\e[19;4~"); - AltShiftF9 : Key : #run to_key("\e[20;4~"); - AltShiftF10 : Key : #run to_key("\e[21;4~"); - AltShiftF11 : Key : #run to_key("\e[23;4~"); - AltShiftF12 : Key : #run to_key("\e[24;4~"); - - CtrlF1 : Key : #run to_key("\e[1;5P"); - CtrlF2 : Key : #run to_key("\e[1;5Q"); - CtrlF3 : Key : #run to_key("\e[1;5R"); - CtrlF4 : Key : #run to_key("\e[1;5S"); - CtrlF5 : Key : #run to_key("\e[15;5~"); - CtrlF6 : Key : #run to_key("\e[17;5~"); - CtrlF7 : Key : #run to_key("\e[18;5~"); - CtrlF8 : Key : #run to_key("\e[19;5~"); - CtrlF9 : Key : #run to_key("\e[20;5~"); - CtrlF10 : Key : #run to_key("\e[21;5~"); - CtrlF11 : Key : #run to_key("\e[23;5~"); - CtrlF12 : Key : #run to_key("\e[24;5~"); - - CtrlShiftF1 : Key : #run to_key("\e[1;6P"); - CtrlShiftF2 : Key : #run to_key("\e[1;6Q"); - CtrlShiftF3 : Key : #run to_key("\e[1;6R"); - CtrlShiftF4 : Key : #run to_key("\e[1;6S"); - CtrlShiftF5 : Key : #run to_key("\e[15;6~"); - CtrlShiftF6 : Key : #run to_key("\e[17;6~"); - CtrlShiftF7 : Key : #run to_key("\e[18;6~"); - CtrlShiftF8 : Key : #run to_key("\e[19;6~"); - CtrlShiftF9 : Key : #run to_key("\e[20;6~"); - CtrlShiftF10 : Key : #run to_key("\e[21;6~"); - CtrlShiftF11 : Key : #run to_key("\e[23;6~"); - CtrlShiftF12 : Key : #run to_key("\e[24;6~"); - - CtrlAltF1 : Key : #run to_key("\e[1;7P"); - CtrlAltF2 : Key : #run to_key("\e[1;7Q"); - CtrlAltF3 : Key : #run to_key("\e[1;7R"); - CtrlAltF4 : Key : #run to_key("\e[1;7S"); - CtrlAltF5 : Key : #run to_key("\e[15;7~"); - CtrlAltF6 : Key : #run to_key("\e[17;7~"); - CtrlAltF7 : Key : #run to_key("\e[18;7~"); - CtrlAltF8 : Key : #run to_key("\e[19;7~"); - CtrlAltF9 : Key : #run to_key("\e[20;7~"); - CtrlAltF10 : Key : #run to_key("\e[21;7~"); - CtrlAltF11 : Key : #run to_key("\e[23;7~"); - CtrlAltF12 : Key : #run to_key("\e[24;7~"); - - CtrlAltShiftF1 : Key : #run to_key("\e[1;8P"); - CtrlAltShiftF2 : Key : #run to_key("\e[1;8Q"); - CtrlAltShiftF3 : Key : #run to_key("\e[1;8R"); - CtrlAltShiftF4 : Key : #run to_key("\e[1;8S"); - CtrlAltShiftF5 : Key : #run to_key("\e[15;8~"); - CtrlAltShiftF6 : Key : #run to_key("\e[17;8~"); - CtrlAltShiftF7 : Key : #run to_key("\e[18;8~"); - CtrlAltShiftF8 : Key : #run to_key("\e[19;8~"); - CtrlAltShiftF9 : Key : #run to_key("\e[20;8~"); - CtrlAltShiftF10 : Key : #run to_key("\e[21;8~"); - CtrlAltShiftF11 : Key : #run to_key("\e[23;8~"); - CtrlAltShiftF12 : Key : #run to_key("\e[24;8~"); -} - -key_map: Table(string, Key); - -setup_key_map :: () { - - table_set(*key_map, "\e[A", to_key("#UP")); - table_set(*key_map, "\e[A", to_key("#UP")); - - table_set(*key_map, "\eOQ", to_key("#F2")); - table_set(*key_map, "\e[12~", to_key("#F2")); - - table_set(*key_map, "\eO2Q", to_key("#SF2")); - table_set(*key_map, "\e[1;2Q", to_key("#SF2")); - table_set(*key_map, "\e[12;2~", to_key("#SF2")); - - table_set(*key_map, "\e\e[12~", to_key("#AF2")); - table_set(*key_map, "\e\e[24~", to_key("#AF12")); - - table_set(*key_map, "\e[12^", to_key("#CF2")); - - // A good example - table_set(*key_map, "\e[21~", to_key("#F10")); - table_set(*key_map, "\e[21;1~", to_key("#MF10")); - table_set(*key_map, "\e[21;2~", to_key("#SF10")); - table_set(*key_map, "\e[21;3~", to_key("#AF10")); - table_set(*key_map, "\e[21;4~", to_key("#SAF10")); - table_set(*key_map, "\e[21;5~", to_key("#CF10")); - table_set(*key_map, "\e[21;6~", to_key("#SCF10")); - table_set(*key_map, "\e[21;7~", to_key("#ACF10")); - table_set(*key_map, "\e[21;8~", to_key("#SACF10")); - - table_set(*key_map, "\e[24~", to_key("#F12")); - table_set(*key_map, "\e[24;2~", to_key("#SF12")); - table_set(*key_map, "\e[24;3~", to_key("#AF12")); - - // TODO Try with: - // - konsole - // - xterm - // - rxvt-unicode - // - kittyterminal - // - termux - - // F2 : Key : #run to_key("\eOQ"); - // F2 : 1b 4f 51 : ^OQ : -> \e[12~ - // Shift+ F2 : 1b 4f 32 51 : ^O2Q : -> \e[12;2~ -} - to_key :: inline (str: $T) -> Key #modify { return T == ([]u8) || T == string; } { k: Key; // #if DEBUG { @@ -432,14 +186,411 @@ to_string :: inline (key: Key) -> string { // TODO FIXME TEMPORARY MEMORY } is_escape_code :: inline (key: Key) -> bool { + /* + TODO Check if LSB is not # but there is a `#`, then it's a escape code... or NONE... or RESIZE :S + Or...we could change the special codes and set the `#` at the end... then we could simply do: + return (key && 0x00FF) ^ # == 0 && (key && 0xFF00) == 0 + */ + result := false; + while key != 0 { key >>= 8; - result |= ((key ^ Keys.Escape) == 0); + result |= ((key ^ #char "#") == 0); } return result; } +Keys :: struct #type_info_none { + None : Key : #run to_key("#none"); + Resize : Key : #run to_key("#resize"); + + Space : Key : #char " "; + Enter : Key : #char "\r"; + Tab : Key : #char "\t"; + Escape : Key : 0x00000000_0000001B; + Backspace : Key : 0x00000000_0000007F; + Pause : Key : 0x00000000_0000001A; + + Up : Key : #run to_key("#up"); + Down : Key : #run to_key("#down"); + Right : Key : #run to_key("right"); + Left : Key : #run to_key("left"); + + Home : Key : #run to_key("#home"); + End : Key : #run to_key("#end"); + Insert : Key : #run to_key("#ins"); + Delete : Key : #run to_key("#del"); + PgUp : Key : #run to_key("#pup"); + PgDown : Key : #run to_key("#pdown"); + + F1 : Key : #run to_key("#f1"); + F2 : Key : #run to_key("#f2"); + F3 : Key : #run to_key("#f3"); + F4 : Key : #run to_key("#f4"); + F5 : Key : #run to_key("#f5"); + F6 : Key : #run to_key("#f6"); + F7 : Key : #run to_key("#f7"); + F8 : Key : #run to_key("#f8"); + F9 : Key : #run to_key("#f9"); + F10 : Key : #run to_key("#f10"); + F11 : Key : #run to_key("#f11"); + F12 : Key : #run to_key("#f12"); +} + +key_map: Table(string, Key); + +setup_key_map :: () { + /* + This table was created/tested using the following terminals: + - konsole + - kitty + - xterm + - linux console + */ + + // Up + table_set(*key_map, "\e[A", to_key("#up")); + table_set(*key_map, "\e[1;1A", to_key("#up+m")); + table_set(*key_map, "\e[1;2A", to_key("#up+s")); + table_set(*key_map, "\e[1;3A", to_key("#up+a")); + table_set(*key_map, "\e[1;4A", to_key("#up+A")); + table_set(*key_map, "\e[1;5A", to_key("#up+c")); + table_set(*key_map, "\e[1;6A", to_key("#up+C")); + table_set(*key_map, "\e[1;7A", to_key("#up+x")); + table_set(*key_map, "\e[1;8A", to_key("#up+X")); + // Up - kitty + table_set(*key_map, "\e[1;9A", to_key("#up+m")); + + // Down + table_set(*key_map, "\e[B", to_key("#down")); + table_set(*key_map, "\e[1;1B", to_key("#down+m")); + table_set(*key_map, "\e[1;2B", to_key("#down+s")); + table_set(*key_map, "\e[1;3B", to_key("#down+a")); + table_set(*key_map, "\e[1;4B", to_key("#down+A")); + table_set(*key_map, "\e[1;5B", to_key("#down+c")); + table_set(*key_map, "\e[1;6B", to_key("#down+C")); + table_set(*key_map, "\e[1;7B", to_key("#down+x")); + table_set(*key_map, "\e[1;8B", to_key("#down+X")); + // Down - kitty + table_set(*key_map, "\e[1;9B", to_key("#down+m")); + + // Right + table_set(*key_map, "\e[C", to_key("#right")); + table_set(*key_map, "\e[1;1C", to_key("#right+m")); + table_set(*key_map, "\e[1;2C", to_key("#right+s")); + table_set(*key_map, "\e[1;3C", to_key("#right+a")); + table_set(*key_map, "\e[1;4C", to_key("#right+A")); + table_set(*key_map, "\e[1;5C", to_key("#right+c")); + table_set(*key_map, "\e[1;6C", to_key("#right+C")); + table_set(*key_map, "\e[1;7C", to_key("#right+x")); + table_set(*key_map, "\e[1;8C", to_key("#right+X")); + // Right - kitty + table_set(*key_map, "\e[1;9C", to_key("#right+m")); + + // Left + table_set(*key_map, "\e[D", to_key("#left")); + table_set(*key_map, "\e[1;1D", to_key("#left+m")); + table_set(*key_map, "\e[1;2D", to_key("#left+s")); + table_set(*key_map, "\e[1;3D", to_key("#left+a")); + table_set(*key_map, "\e[1;4D", to_key("#left+A")); + table_set(*key_map, "\e[1;5D", to_key("#left+c")); + table_set(*key_map, "\e[1;6D", to_key("#left+C")); + table_set(*key_map, "\e[1;7D", to_key("#left+x")); + table_set(*key_map, "\e[1;8D", to_key("#left+X")); + // Left - kitty + table_set(*key_map, "\e[1;9D", to_key("#left+m")); + + // Home + table_set(*key_map, "\e[H", to_key("#home")); + table_set(*key_map, "\e[1~", to_key("#home")); + table_set(*key_map, "\e[1;1H", to_key("#home+m")); + table_set(*key_map, "\e[1;2H", to_key("#home+s")); + table_set(*key_map, "\e[1;3H", to_key("#home+a")); + table_set(*key_map, "\e[1;4H", to_key("#home+A")); + table_set(*key_map, "\e[1;5H", to_key("#home+c")); + table_set(*key_map, "\e[1;6H", to_key("#home+C")); + table_set(*key_map, "\e[1;7H", to_key("#home+x")); + table_set(*key_map, "\e[1;8H", to_key("#home+X")); + // Home - kitty + table_set(*key_map, "\e[1;9H", to_key("#home+m")); + + // End + table_set(*key_map, "\e[F", to_key("#end")); + table_set(*key_map, "\e[4~", to_key("#end")); + table_set(*key_map, "\e[1;1F", to_key("#end+m")); + table_set(*key_map, "\e[1;2F", to_key("#end+s")); + table_set(*key_map, "\e[1;3F", to_key("#end+a")); + table_set(*key_map, "\e[1;4F", to_key("#end+A")); + table_set(*key_map, "\e[1;5F", to_key("#end+c")); + table_set(*key_map, "\e[1;6F", to_key("#end+C")); + table_set(*key_map, "\e[1;7F", to_key("#end+x")); + table_set(*key_map, "\e[1;8F", to_key("#end+X")); + // End - kitty + table_set(*key_map, "\e[1;9F", to_key("#end+m")); + + // Insert + table_set(*key_map, "\e[2~", to_key("#ins")); + table_set(*key_map, "\e[2;1~", to_key("#ins+m")); + table_set(*key_map, "\e[2;2~", to_key("#ins+s")); + table_set(*key_map, "\e[2;3~", to_key("#ins+a")); + table_set(*key_map, "\e[2;4~", to_key("#ins+A")); + table_set(*key_map, "\e[2;5~", to_key("#ins+c")); + table_set(*key_map, "\e[2;6~", to_key("#ins+C")); + table_set(*key_map, "\e[2;7~", to_key("#ins+x")); + table_set(*key_map, "\e[2;8~", to_key("#ins+X")); + // Insert - kitty + table_set(*key_map, "\e[2;9~", to_key("#ins+m")); + + // Delete + table_set(*key_map, "\e[3~", to_key("#del")); + table_set(*key_map, "\e[3;1~", to_key("#del+m")); + table_set(*key_map, "\e[3;2~", to_key("#del+s")); + table_set(*key_map, "\e[3;3~", to_key("#del+a")); + table_set(*key_map, "\e[3;4~", to_key("#del+A")); + table_set(*key_map, "\e[3;5~", to_key("#del+c")); + table_set(*key_map, "\e[3;6~", to_key("#del+C")); + table_set(*key_map, "\e[3;7~", to_key("#del+x")); + table_set(*key_map, "\e[3;8~", to_key("#del+X")); + // Delete - kitty + table_set(*key_map, "\e[3;9~", to_key("#del+m")); + + // Page Up + table_set(*key_map, "\e[5~", to_key("#pup")); + table_set(*key_map, "\e[5;1~", to_key("#pup+m")); + table_set(*key_map, "\e[5;2~", to_key("#pup+s")); + table_set(*key_map, "\e[5;3~", to_key("#pup+a")); + table_set(*key_map, "\e[5;4~", to_key("#pup+A")); + table_set(*key_map, "\e[5;5~", to_key("#pup+c")); + table_set(*key_map, "\e[5;6~", to_key("#pup+C")); + table_set(*key_map, "\e[5;7~", to_key("#pup+x")); + table_set(*key_map, "\e[5;8~", to_key("#pup+X")); + // Page Up - kitty + table_set(*key_map, "\e[5;9~", to_key("#pup+m")); + + // Page Down + table_set(*key_map, "\e[6~", to_key("#pdown")); + table_set(*key_map, "\e[6;1~", to_key("#pdown+m")); + table_set(*key_map, "\e[6;2~", to_key("#pdown+s")); + table_set(*key_map, "\e[6;3~", to_key("#pdown+a")); + table_set(*key_map, "\e[6;4~", to_key("#pdown+A")); + table_set(*key_map, "\e[6;5~", to_key("#pdown+c")); + table_set(*key_map, "\e[6;6~", to_key("#pdown+C")); + table_set(*key_map, "\e[6;7~", to_key("#pdown+x")); + table_set(*key_map, "\e[6;8~", to_key("#pdown+X")); + // Page Down - kitty + table_set(*key_map, "\e[6;9~", to_key("#pdown+m")); + + // F1 + table_set(*key_map, "\eOP", to_key("#f1")); + table_set(*key_map, "\eO1P", to_key("#f1+m")); + table_set(*key_map, "\eO2P", to_key("#f1+s")); + table_set(*key_map, "\eO3P", to_key("#f1+a")); + table_set(*key_map, "\eO4P", to_key("#f1+A")); + table_set(*key_map, "\eO5P", to_key("#f1+c")); + table_set(*key_map, "\eO6P", to_key("#f1+C")); + table_set(*key_map, "\eO7P", to_key("#f1+x")); + table_set(*key_map, "\eO8P", to_key("#f1+X")); + // F1 - xterm + table_set(*key_map, "\e[1;2P", to_key("#f1+s")); + table_set(*key_map, "\e[1;3P", to_key("#f1+a")); + table_set(*key_map, "\e[1;4P", to_key("#f1+A")); + table_set(*key_map, "\e[1;5P", to_key("#f1+c")); + table_set(*key_map, "\e[1;6P", to_key("#f1+C")); + table_set(*key_map, "\e[1;7P", to_key("#f1+x")); + table_set(*key_map, "\e[1;8P", to_key("#f1+X")); + // F1 - kitty + table_set(*key_map, "\e[1;9P", to_key("#f1+m")); + // F1 - linux console + table_set(*key_map, "\e[[A", to_key("#f1")); + table_set(*key_map, "\e[25~", to_key("#f1+s")); + + // F2 + table_set(*key_map, "\eOQ", to_key("#f2")); + table_set(*key_map, "\eO1Q", to_key("#f2+m")); + table_set(*key_map, "\eO2Q", to_key("#f2+s")); + table_set(*key_map, "\eO3Q", to_key("#f2+a")); + table_set(*key_map, "\eO4Q", to_key("#f2+A")); + table_set(*key_map, "\eO5Q", to_key("#f2+c")); + table_set(*key_map, "\eO6Q", to_key("#f2+C")); + table_set(*key_map, "\eO7Q", to_key("#f2+x")); + table_set(*key_map, "\eO8Q", to_key("#f2+X")); + // F2 - xterm + table_set(*key_map, "\e[1;2Q", to_key("#f2+s")); + table_set(*key_map, "\e[1;3Q", to_key("#f2+a")); + table_set(*key_map, "\e[1;4Q", to_key("#f2+A")); + table_set(*key_map, "\e[1;5Q", to_key("#f2+c")); + table_set(*key_map, "\e[1;6Q", to_key("#f2+C")); + table_set(*key_map, "\e[1;7Q", to_key("#f2+x")); + table_set(*key_map, "\e[1;8Q", to_key("#f2+X")); + // F2 - kitty + table_set(*key_map, "\e[1;9Q", to_key("#f2+m")); + // F2 - linux console + table_set(*key_map, "\e[[B", to_key("#f2")); + table_set(*key_map, "\e[26~", to_key("#f2+s")); + + // F3 + table_set(*key_map, "\eOR", to_key("#f3")); + table_set(*key_map, "\eO1R", to_key("#f3+m")); + table_set(*key_map, "\eO2R", to_key("#f3+s")); + table_set(*key_map, "\eO3R", to_key("#f3+a")); + table_set(*key_map, "\eO4R", to_key("#f3+A")); + table_set(*key_map, "\eO5R", to_key("#f3+c")); + table_set(*key_map, "\eO6R", to_key("#f3+C")); + table_set(*key_map, "\eO7R", to_key("#f3+x")); + table_set(*key_map, "\eO8R", to_key("#f3+X")); + // F3 - xterm + table_set(*key_map, "\e[1;2R", to_key("#f3+s")); + table_set(*key_map, "\e[1;3R", to_key("#f3+a")); + table_set(*key_map, "\e[1;4R", to_key("#f3+A")); + table_set(*key_map, "\e[1;5R", to_key("#f3+c")); + table_set(*key_map, "\e[1;6R", to_key("#f3+C")); + table_set(*key_map, "\e[1;7R", to_key("#f3+x")); + table_set(*key_map, "\e[1;8R", to_key("#f3+X")); + // F3 - kitty + table_set(*key_map, "\e[1;9R", to_key("#f3+m")); + // F3 - linux console + table_set(*key_map, "\e[[C", to_key("#f3")); + table_set(*key_map, "\e[28~", to_key("#f3+s")); + + // F4 + table_set(*key_map, "\eOS", to_key("#f4")); + table_set(*key_map, "\eO1S", to_key("#f4+m")); + table_set(*key_map, "\eO2S", to_key("#f4+s")); + table_set(*key_map, "\eO3S", to_key("#f4+a")); + table_set(*key_map, "\eO4S", to_key("#f4+A")); + table_set(*key_map, "\eO5S", to_key("#f4+c")); + table_set(*key_map, "\eO6S", to_key("#f4+C")); + table_set(*key_map, "\eO7S", to_key("#f4+x")); + table_set(*key_map, "\eO8S", to_key("#f4+X")); + // F4 - xterm + table_set(*key_map, "\e[1;2S", to_key("#f4+s")); + table_set(*key_map, "\e[1;3S", to_key("#f4+a")); + table_set(*key_map, "\e[1;4S", to_key("#f4+A")); + table_set(*key_map, "\e[1;5S", to_key("#f4+c")); + table_set(*key_map, "\e[1;6S", to_key("#f4+C")); + table_set(*key_map, "\e[1;7S", to_key("#f4+x")); + table_set(*key_map, "\e[1;8S", to_key("#f4+X")); + // F4 - kitty + table_set(*key_map, "\e[1;9S", to_key("#f4+m")); + // F4 - linux console + table_set(*key_map, "\e[[D", to_key("#f4")); + table_set(*key_map, "\e[29~", to_key("#f4+s")); + + // F5 + table_set(*key_map, "\e[15~", to_key("#f5")); + table_set(*key_map, "\e[15;1~", to_key("#f5+m")); + table_set(*key_map, "\e[15;2~", to_key("#f5+s")); + table_set(*key_map, "\e[15;3~", to_key("#f5+a")); + table_set(*key_map, "\e[15;4~", to_key("#f5+A")); + table_set(*key_map, "\e[15;5~", to_key("#f5+c")); + table_set(*key_map, "\e[15;6~", to_key("#f5+C")); + table_set(*key_map, "\e[15;7~", to_key("#f5+x")); + table_set(*key_map, "\e[15;8~", to_key("#f5+X")); + // F5 - kitty + table_set(*key_map, "\e[15;9~", to_key("#f5+m")); + // F5 - linux console + table_set(*key_map, "\e[[E", to_key("#f5")); + table_set(*key_map, "\e[31~", to_key("#f5+s")); + + // F6 + table_set(*key_map, "\e[17~", to_key("#f6")); + table_set(*key_map, "\e[17;1~", to_key("#f6+m")); + table_set(*key_map, "\e[17;2~", to_key("#f6+s")); + table_set(*key_map, "\e[17;3~", to_key("#f6+a")); + table_set(*key_map, "\e[17;4~", to_key("#f6+A")); + table_set(*key_map, "\e[17;5~", to_key("#f6+c")); + table_set(*key_map, "\e[17;6~", to_key("#f6+C")); + table_set(*key_map, "\e[17;7~", to_key("#f6+x")); + table_set(*key_map, "\e[17;8~", to_key("#f6+X")); + // F6 - kitty + table_set(*key_map, "\e[17;9~", to_key("#f6+m")); + // F6 - linux console + table_set(*key_map, "\e[32~", to_key("#f6+s")); + + // F7 + table_set(*key_map, "\e[18~", to_key("#f7")); + table_set(*key_map, "\e[18;1~", to_key("#f7+m")); + table_set(*key_map, "\e[18;2~", to_key("#f7+s")); + table_set(*key_map, "\e[18;3~", to_key("#f7+a")); + table_set(*key_map, "\e[18;4~", to_key("#f7+A")); + table_set(*key_map, "\e[18;5~", to_key("#f7+c")); + table_set(*key_map, "\e[18;6~", to_key("#f7+C")); + table_set(*key_map, "\e[18;7~", to_key("#f7+x")); + table_set(*key_map, "\e[18;8~", to_key("#f7+X")); + // F7 - kitty + table_set(*key_map, "\e[18;9~", to_key("#f7+m")); + // F7 - linux console + table_set(*key_map, "\e[33~", to_key("#f7+s")); + + // F8 + table_set(*key_map, "\e[19~", to_key("#f8")); + table_set(*key_map, "\e[19;1~", to_key("#f8+m")); + table_set(*key_map, "\e[19;2~", to_key("#f8+s")); + table_set(*key_map, "\e[19;3~", to_key("#f8+a")); + table_set(*key_map, "\e[19;4~", to_key("#f8+A")); + table_set(*key_map, "\e[19;5~", to_key("#f8+c")); + table_set(*key_map, "\e[19;6~", to_key("#f8+C")); + table_set(*key_map, "\e[19;7~", to_key("#f8+x")); + table_set(*key_map, "\e[19;8~", to_key("#f8+X")); + // F8 - kitty + table_set(*key_map, "\e[19;9~", to_key("#f8+m")); + // F8 - linux console + table_set(*key_map, "\e[34~", to_key("#f8+s")); + + // F9 + table_set(*key_map, "\e[20~", to_key("#f9")); + table_set(*key_map, "\e[20;1~", to_key("#f9+m")); + table_set(*key_map, "\e[20;2~", to_key("#f9+s")); + table_set(*key_map, "\e[20;3~", to_key("#f9+a")); + table_set(*key_map, "\e[20;4~", to_key("#f9+A")); + table_set(*key_map, "\e[20;5~", to_key("#f9+c")); + table_set(*key_map, "\e[20;6~", to_key("#f9+C")); + table_set(*key_map, "\e[20;7~", to_key("#f9+x")); + table_set(*key_map, "\e[20;8~", to_key("#f9+X")); + // F9 - kitty + table_set(*key_map, "\e[20;9~", to_key("#f9+m")); + + // F10 + table_set(*key_map, "\e[21~", to_key("#f10")); + table_set(*key_map, "\e[21;1~", to_key("#f10+m")); + table_set(*key_map, "\e[21;2~", to_key("#f10+s")); + table_set(*key_map, "\e[21;3~", to_key("#f10+a")); + table_set(*key_map, "\e[21;4~", to_key("#f10+A")); + table_set(*key_map, "\e[21;5~", to_key("#f10+c")); + table_set(*key_map, "\e[21;6~", to_key("#f10+C")); + table_set(*key_map, "\e[21;7~", to_key("#f10+x")); + table_set(*key_map, "\e[21;8~", to_key("#f10+X")); + // F10 - kitty + table_set(*key_map, "\e[21;9~", to_key("#f10+m")); + + // F11 + table_set(*key_map, "\e[23~", to_key("#f11")); + table_set(*key_map, "\e[23;1~", to_key("#f11+m")); + table_set(*key_map, "\e[23;2~", to_key("#f11+s")); + table_set(*key_map, "\e[23;3~", to_key("#f11+a")); + table_set(*key_map, "\e[23;4~", to_key("#f11+A")); + table_set(*key_map, "\e[23;5~", to_key("#f11+c")); + table_set(*key_map, "\e[23;6~", to_key("#f11+C")); + table_set(*key_map, "\e[23;7~", to_key("#f11+x")); + table_set(*key_map, "\e[23;8~", to_key("#f11+X")); + // F11 - kitty + table_set(*key_map, "\e[23;9~", to_key("#f11+m")); + + // F12 + table_set(*key_map, "\e[24~", to_key("#f12")); + table_set(*key_map, "\e[24;1~", to_key("#f12+m")); + table_set(*key_map, "\e[24;2~", to_key("#f12+s")); + table_set(*key_map, "\e[24;3~", to_key("#f12+a")); + table_set(*key_map, "\e[24;4~", to_key("#f12+A")); + table_set(*key_map, "\e[24;5~", to_key("#f12+c")); + table_set(*key_map, "\e[24;6~", to_key("#f12+C")); + table_set(*key_map, "\e[24;7~", to_key("#f12+x")); + table_set(*key_map, "\e[24;8~", to_key("#f12+X")); + // F12 - kitty + table_set(*key_map, "\e[24;9~", to_key("#f12+m")); +} + initialized := false; //input_buffer : [64] u8; // TODO FIXME Input buffer is too small!!! @@ -543,28 +694,12 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { // as we ding a match on the LUT. // If the LUT is too big... maybe use a hash-table. + // TEMPORARY HACK key, success := table_find(*key_map, to_parse); if success { advance(*input_string, to_parse.count); return key; } - - if compare(to_parse, "\e[A") == 0 { - advance(*input_string, to_parse.count); - return to_key("#UP"); - } - else if compare(to_parse, "\e[B") == 0 { - advance(*input_string, to_parse.count); - return to_key("#DOWN"); - } - else if compare(to_parse, "\e[C") == 0 { - advance(*input_string, to_parse.count); - return to_key("#RIGHT"); - } - else if compare(to_parse, "\e[D") == 0 { - advance(*input_string, to_parse.count); - return to_key("#LEFT"); - } } advance(*input_string, to_parse.count); diff --git a/ttt.jai b/ttt.jai index 94e868c..1a74b37 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1348,12 +1348,6 @@ main :: () { TUI.clear_terminal(); drop_down = 0; } - - case TUI.Keys.MetaF7; { - TUI.set_cursor_position(3+drop_down, 2); - drop_down += 1; - write_string("META F7"); - } case; { TUI.set_cursor_position(3+drop_down, 2); @@ -1420,7 +1414,7 @@ main :: () { TUI.stop(); } - write_string("DONE"); + write_string("DONE\n"); exit(0); // -- -- -- Testing TUI -- STOP -- cgit v1.2.3 From c9cad034362a1b433579e8424514e4246c721b92 Mon Sep 17 00:00:00 2001 From: dam Date: Wed, 13 Mar 2024 00:43:47 +0000 Subject: WIP : Adding more key_map entries --- modules/TUI/module.jai | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index a5f5837..49cdec0 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -510,16 +510,26 @@ setup_key_map :: () { // F7 table_set(*key_map, "\e[18~", to_key("#f7")); - table_set(*key_map, "\e[18;1~", to_key("#f7+m")); - table_set(*key_map, "\e[18;2~", to_key("#f7+s")); - table_set(*key_map, "\e[18;3~", to_key("#f7+a")); - table_set(*key_map, "\e[18;4~", to_key("#f7+A")); - table_set(*key_map, "\e[18;5~", to_key("#f7+c")); + +TODO FINISH THIS... WIP + + // table_set(*key_map, "\e[18;1~", to_key("#f7+m")); + table_set(*key_map, "\e[18;2~", to_key("#f7+s")); // #f7+^ + table_set(*key_map, "\e[18;3~", to_key("#f7+a")); // #f7+a + table_set(*key_map, "\e[18;4~", to_key("#f7+A")); // #f7+A + table_set(*key_map, "\e[18;5~", to_key("#f7+c")); // #f7+s table_set(*key_map, "\e[18;6~", to_key("#f7+C")); - table_set(*key_map, "\e[18;7~", to_key("#f7+x")); - table_set(*key_map, "\e[18;8~", to_key("#f7+X")); + table_set(*key_map, "\e[18;7~", to_key("#f7+x")); // x + table_set(*key_map, "\e[18;8~", to_key("#f7+X")); // X // F7 - kitty - table_set(*key_map, "\e[18;9~", to_key("#f7+m")); + // table_set(*key_map, "\e[18;9~", to_key("#f7+m")); // m + // table_set(*key_map, "\e[18;10~",to_key("#f7+M")); // M + // table_set(*key_map, "\e[18;11~",to_key("#f7+ma")); // ma -> y + // table_set(*key_map, "\e[18;12~",to_key("#f7+mA")); // MA -> Y + // table_set(*key_map, "\e[18;13~",to_key("#f7+mc")); // mc -> z + // table_set(*key_map, "\e[18;14~",to_key("#f7+mC")); // MC -> Z + // table_set(*key_map, "\e[18;15~",to_key("#f7+mx")); // mac-> w + // table_set(*key_map, "\e[18;16~",to_key("#f7+mX")); // MAC-> W // F7 - linux console table_set(*key_map, "\e[33~", to_key("#f7+s")); -- cgit v1.2.3 From 0087918da456195648012e790d8f3a96a6dde403 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 14 Mar 2024 00:22:58 +0000 Subject: WIP : Mapping the keyboard codes. --- modules/TUI/module.jai | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 49cdec0..11b5247 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -247,6 +247,24 @@ setup_key_map :: () { - kitty - xterm - linux console + + To signal modifier keys, a letter is appended after a + (plus sign): + "#f1" -> F1 + "#f1+$" -> F1+Shift + "#f1+a" -> F1+Alt + "#f1+A" -> F1+Shift+Alt + "#f1+c" -> F1+Ctrl + "#f1+C" -> F1+Shift+Ctrl + "#f1+w" -> F1+Alt+Ctrl + "#f1+W" -> F1+Shift+Alt+Ctrl + "#f1+s" -> F1+Super + "#f1+S" -> F1+Shift+Super + "#f1+x" -> F1+Alt+Super + "#f1+X" -> F1+Shift+Alt+Super + "#f1+y" -> F1+Ctrl+Super + "#f1+Y" -> F1+Shift+Ctrl+Super + "#f1+z" -> F1+Alt+Ctrl+Super + "#f1+Z" -> F1+Shift+Alt+Ctrl+Super */ // Up -- cgit v1.2.3 From ac92da96856603d450e39fa536f287bcf67c0fd7 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 15 Mar 2024 01:46:01 +0000 Subject: Improved key map. --- modules/TUI/key_map.jai | 501 ++++++++++++++++++++++++++++++++++++++++++++++++ modules/TUI/module.jai | 388 +------------------------------------ ttt.jai | 8 +- 3 files changed, 510 insertions(+), 387 deletions(-) create mode 100644 modules/TUI/key_map.jai (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/key_map.jai b/modules/TUI/key_map.jai new file mode 100644 index 0000000..42acabf --- /dev/null +++ b/modules/TUI/key_map.jai @@ -0,0 +1,501 @@ +#import "Hash_Table"; + +key_map: Table(string, Key); + +setup_key_map :: () { + /* + This table was created/tested using the following terminals: + - g: gnome (terminal) + - i: kitty + - k: konsole + - l: linux (console) + - x: xterm + + To signal modifier keys, a letter is appended after a + (plus sign): + "#f1" -> F1 + "#f1+$" -> F1 + Shift + "#f1+a" -> F1 + Alt + "#f1+A" -> F1 + Shift + Alt + "#f1+c" -> F1 + Ctrl + "#f1+C" -> F1 + Shift + Ctrl + "#f1+w" -> F1 + Alt + Ctrl + "#f1+W" -> F1 + Shift + Alt + Ctrl + "#f1+s" -> F1 + Super + "#f1+S" -> F1 + Shift + Super + "#f1+x" -> F1 + Alt + Super + "#f1+X" -> F1 + Shift + Alt + Super + "#f1+y" -> F1 + Ctrl + Super + "#f1+Y" -> F1 + Shift + Ctrl + Super + "#f1+z" -> F1 + Alt + Ctrl + Super + "#f1+Z" -> F1 + Shift + Alt + Ctrl + Super + */ + + // Up // g i k l x + // table_set(*key_map, "\e[A", to_key("#up")); // + + + + + + table_set(*key_map, "\e[1;1A", to_key("#up")); // + table_set(*key_map, "\e[1;2A", to_key("#up+$")); // + + + + + table_set(*key_map, "\e[1;3A", to_key("#up+a")); // + + + + + table_set(*key_map, "\e[1;4A", to_key("#up+A")); // + + + + + table_set(*key_map, "\e[1;5A", to_key("#up+c")); // + + + + + table_set(*key_map, "\e[1;6A", to_key("#up+C")); // + + + + + table_set(*key_map, "\e[1;7A", to_key("#up+w")); // + + + + + table_set(*key_map, "\e[1;8A", to_key("#up+W")); // + + + + + table_set(*key_map, "\e[1;9A", to_key("#up+s")); // + + table_set(*key_map, "\e[1;10A", to_key("#up+S")); // + + table_set(*key_map, "\e[1;11A", to_key("#up+x")); // + + table_set(*key_map, "\e[1;12A", to_key("#up+X")); // + + table_set(*key_map, "\e[1;13A", to_key("#up+y")); // + + table_set(*key_map, "\e[1;14A", to_key("#up+Y")); // + + table_set(*key_map, "\e[1;15A", to_key("#up+z")); // + + table_set(*key_map, "\e[1;16A", to_key("#up+Z")); // + + + // Down // g i k l x + table_set(*key_map, "\e[B", to_key("#down")); // + + + + + + table_set(*key_map, "\e[1;1B", to_key("#down")); // + table_set(*key_map, "\e[1;2B", to_key("#down+$")); // + + + + + table_set(*key_map, "\e[1;3B", to_key("#down+a")); // + + + + + table_set(*key_map, "\e[1;4B", to_key("#down+A")); // + + + + + table_set(*key_map, "\e[1;5B", to_key("#down+c")); // + + + + + table_set(*key_map, "\e[1;6B", to_key("#down+C")); // + + + + + table_set(*key_map, "\e[1;7B", to_key("#down+w")); // + + + + + table_set(*key_map, "\e[1;8B", to_key("#down+W")); // + + + + + table_set(*key_map, "\e[1;9B", to_key("#down+s")); // + + table_set(*key_map, "\e[1;10B", to_key("#down+S")); // + + table_set(*key_map, "\e[1;11B", to_key("#down+x")); // + + table_set(*key_map, "\e[1;12B", to_key("#down+X")); // + + table_set(*key_map, "\e[1;13B", to_key("#down+y")); // + + table_set(*key_map, "\e[1;14B", to_key("#down+Y")); // + + table_set(*key_map, "\e[1;15B", to_key("#down+z")); // + + table_set(*key_map, "\e[1;16B", to_key("#down+Z")); // + + + // Right // g i k l x + table_set(*key_map, "\e[C", to_key("#right")); // + + + + + + table_set(*key_map, "\e[1;1C", to_key("#right")); // + table_set(*key_map, "\e[1;2C", to_key("#right+$")); // + + + + + table_set(*key_map, "\e[1;3C", to_key("#right+a")); // + + + + + table_set(*key_map, "\e[1;4C", to_key("#right+A")); // + + + + + table_set(*key_map, "\e[1;5C", to_key("#right+c")); // + + + + + table_set(*key_map, "\e[1;6C", to_key("#right+C")); // + + + + + table_set(*key_map, "\e[1;7C", to_key("#right+w")); // + + + + + table_set(*key_map, "\e[1;8C", to_key("#right+W")); // + + + + + table_set(*key_map, "\e[1;9C", to_key("#right+s")); // + + table_set(*key_map, "\e[1;10C", to_key("#right+S")); // + + table_set(*key_map, "\e[1;11C", to_key("#right+x")); // + + table_set(*key_map, "\e[1;12C", to_key("#right+X")); // + + table_set(*key_map, "\e[1;13C", to_key("#right+y")); // + + table_set(*key_map, "\e[1;14C", to_key("#right+Y")); // + + table_set(*key_map, "\e[1;15C", to_key("#right+z")); // + + table_set(*key_map, "\e[1;16C", to_key("#right+Z")); // + + + // Left // g i k l x + table_set(*key_map, "\e[D", to_key("#left")); // + + + + + + table_set(*key_map, "\e[1;1D", to_key("#left")); // + table_set(*key_map, "\e[1;2D", to_key("#left+$")); // + + + + + table_set(*key_map, "\e[1;3D", to_key("#left+a")); // + + + + + table_set(*key_map, "\e[1;4D", to_key("#left+A")); // + + + + + table_set(*key_map, "\e[1;5D", to_key("#left+c")); // + + + + + table_set(*key_map, "\e[1;6D", to_key("#left+C")); // + + + + + table_set(*key_map, "\e[1;7D", to_key("#left+w")); // + + + + + table_set(*key_map, "\e[1;8D", to_key("#left+W")); // + + + + + table_set(*key_map, "\e[1;9D", to_key("#left+s")); // + + table_set(*key_map, "\e[1;10D", to_key("#left+S")); // + + table_set(*key_map, "\e[1;11D", to_key("#left+x")); // + + table_set(*key_map, "\e[1;12D", to_key("#left+X")); // + + table_set(*key_map, "\e[1;13D", to_key("#left+y")); // + + table_set(*key_map, "\e[1;14D", to_key("#left+Y")); // + + table_set(*key_map, "\e[1;15D", to_key("#left+z")); // + + table_set(*key_map, "\e[1;16D", to_key("#left+Z")); // + + + // Home // g i k l x + table_set(*key_map, "\e[1~", to_key("#home")); // + + table_set(*key_map, "\e[H", to_key("#home")); // + + + + + table_set(*key_map, "\e[1;1H", to_key("#home")); // + table_set(*key_map, "\e[1;2H", to_key("#home+$")); // + + + + + table_set(*key_map, "\e[1;3H", to_key("#home+a")); // + + + + + table_set(*key_map, "\e[1;4H", to_key("#home+A")); // + + + + + table_set(*key_map, "\e[1;5H", to_key("#home+c")); // + + + + + table_set(*key_map, "\e[1;6H", to_key("#home+C")); // + + + + + table_set(*key_map, "\e[1;7H", to_key("#home+w")); // + + + + + table_set(*key_map, "\e[1;8H", to_key("#home+W")); // + + + + + table_set(*key_map, "\e[1;9H", to_key("#home+s")); // + + table_set(*key_map, "\e[1;10H", to_key("#home+S")); // + + table_set(*key_map, "\e[1;11H", to_key("#home+x")); // + + table_set(*key_map, "\e[1;12H", to_key("#home+X")); // + + table_set(*key_map, "\e[1;13H", to_key("#home+y")); // + + table_set(*key_map, "\e[1;14H", to_key("#home+Y")); // + + table_set(*key_map, "\e[1;15H", to_key("#home+z")); // + + table_set(*key_map, "\e[1;16H", to_key("#home+Z")); // + + + // End // g i k l x + table_set(*key_map, "\e[4~", to_key("#end")); // + + table_set(*key_map, "\e[F", to_key("#end")); // + + + + + table_set(*key_map, "\e[1;1F", to_key("#end")); // + table_set(*key_map, "\e[1;2F", to_key("#end+$")); // + + + + + table_set(*key_map, "\e[1;3F", to_key("#end+a")); // + + + + + table_set(*key_map, "\e[1;4F", to_key("#end+A")); // + + + + + table_set(*key_map, "\e[1;5F", to_key("#end+c")); // + + + + + table_set(*key_map, "\e[1;6F", to_key("#end+C")); // + + + + + table_set(*key_map, "\e[1;7F", to_key("#end+w")); // + + + + + table_set(*key_map, "\e[1;8F", to_key("#end+W")); // + + + + + table_set(*key_map, "\e[1;9F", to_key("#end+s")); // + + table_set(*key_map, "\e[1;10F", to_key("#end+S")); // + + table_set(*key_map, "\e[1;11F", to_key("#end+x")); // + + table_set(*key_map, "\e[1;12F", to_key("#end+X")); // + + table_set(*key_map, "\e[1;13F", to_key("#end+y")); // + + table_set(*key_map, "\e[1;14F", to_key("#end+Y")); // + + table_set(*key_map, "\e[1;15F", to_key("#end+z")); // + + table_set(*key_map, "\e[1;16F", to_key("#end+Z")); // + + + // Insert // g i k l x + table_set(*key_map, "\e[2~", to_key("#ins")); // + + + + + + table_set(*key_map, "\e[2;1~", to_key("#ins")); // + table_set(*key_map, "\e[2;2~", to_key("#ins+$")); // + + + + + table_set(*key_map, "\e[2;3~", to_key("#ins+a")); // + + + + + table_set(*key_map, "\e[2;4~", to_key("#ins+A")); // + + + + + table_set(*key_map, "\e[2;5~", to_key("#ins+c")); // + + + + + table_set(*key_map, "\e[2;6~", to_key("#ins+C")); // + + + + + table_set(*key_map, "\e[2;7~", to_key("#ins+w")); // + + + + + table_set(*key_map, "\e[2;8~", to_key("#ins+W")); // + + + + + table_set(*key_map, "\e[2;9~", to_key("#ins+s")); // + + table_set(*key_map, "\e[2;10~", to_key("#ins+S")); // + + table_set(*key_map, "\e[2;11~", to_key("#ins+x")); // + + table_set(*key_map, "\e[2;12~", to_key("#ins+X")); // + + table_set(*key_map, "\e[2;13~", to_key("#ins+y")); // + + table_set(*key_map, "\e[2;14~", to_key("#ins+Y")); // + + table_set(*key_map, "\e[2;15~", to_key("#ins+z")); // + + table_set(*key_map, "\e[2;16~", to_key("#ins+Z")); // + + + // Delete // g i k l x + table_set(*key_map, "\e[3~", to_key("#del")); // + + + + + + table_set(*key_map, "\e[3;1~", to_key("#del")); // + table_set(*key_map, "\e[3;2~", to_key("#del+$")); // + + + + + table_set(*key_map, "\e[3;3~", to_key("#del+a")); // + + + + + table_set(*key_map, "\e[3;4~", to_key("#del+A")); // + + + + + table_set(*key_map, "\e[3;5~", to_key("#del+c")); // + + + + + table_set(*key_map, "\e[3;6~", to_key("#del+C")); // + + + + + table_set(*key_map, "\e[3;7~", to_key("#del+w")); // + + + + + table_set(*key_map, "\e[3;8~", to_key("#del+W")); // + + + + + table_set(*key_map, "\e[3;9~", to_key("#del+s")); // + + table_set(*key_map, "\e[3;10~", to_key("#del+S")); // + + table_set(*key_map, "\e[3;11~", to_key("#del+x")); // + + table_set(*key_map, "\e[3;12~", to_key("#del+X")); // + + table_set(*key_map, "\e[3;13~", to_key("#del+y")); // + + table_set(*key_map, "\e[3;14~", to_key("#del+Y")); // + + table_set(*key_map, "\e[3;15~", to_key("#del+z")); // + + table_set(*key_map, "\e[3;16~", to_key("#del+Z")); // + + + // Page Up // g i k l x + table_set(*key_map, "\e[5~", to_key("#pup")); // + + + + + + table_set(*key_map, "\e[5;1~", to_key("#pup")); // + table_set(*key_map, "\e[5;2~", to_key("#pup+$")); // + + + + + table_set(*key_map, "\e[5;3~", to_key("#pup+a")); // + + + + + table_set(*key_map, "\e[5;4~", to_key("#pup+A")); // + + + + + table_set(*key_map, "\e[5;5~", to_key("#pup+c")); // + + + + + table_set(*key_map, "\e[5;6~", to_key("#pup+C")); // + + + + + table_set(*key_map, "\e[5;7~", to_key("#pup+w")); // + + + + + table_set(*key_map, "\e[5;8~", to_key("#pup+W")); // + + + + + table_set(*key_map, "\e[5;9~", to_key("#pup+s")); // + + table_set(*key_map, "\e[5;10~", to_key("#pup+S")); // + + table_set(*key_map, "\e[5;11~", to_key("#pup+x")); // + + table_set(*key_map, "\e[5;12~", to_key("#pup+X")); // + + table_set(*key_map, "\e[5;13~", to_key("#pup+y")); // + + table_set(*key_map, "\e[5;14~", to_key("#pup+Y")); // + + table_set(*key_map, "\e[5;15~", to_key("#pup+z")); // + + table_set(*key_map, "\e[5;16~", to_key("#pup+Z")); // + + + // Page Down // g i k l x + table_set(*key_map, "\e[6~", to_key("#pdown")); // + + + + + + table_set(*key_map, "\e[6;1~", to_key("#pdown")); // + table_set(*key_map, "\e[6;2~", to_key("#pdown+$")); // + + + + + table_set(*key_map, "\e[6;3~", to_key("#pdown+a")); // + + + + + table_set(*key_map, "\e[6;4~", to_key("#pdown+A")); // + + + + + table_set(*key_map, "\e[6;5~", to_key("#pdown+c")); // + + + + + table_set(*key_map, "\e[6;6~", to_key("#pdown+C")); // + + + + + table_set(*key_map, "\e[6;7~", to_key("#pdown+w")); // + + + + + table_set(*key_map, "\e[6;8~", to_key("#pdown+W")); // + + + + + table_set(*key_map, "\e[6;9~", to_key("#pdown+s")); // + + table_set(*key_map, "\e[6;10~", to_key("#pdown+S")); // + + table_set(*key_map, "\e[6;11~", to_key("#pdown+x")); // + + table_set(*key_map, "\e[6;12~", to_key("#pdown+X")); // + + table_set(*key_map, "\e[6;13~", to_key("#pdown+y")); // + + table_set(*key_map, "\e[6;14~", to_key("#pdown+Y")); // + + table_set(*key_map, "\e[6;15~", to_key("#pdown+z")); // + + table_set(*key_map, "\e[6;16~", to_key("#pdown+Z")); // + + + // F1 // g i k l x + table_set(*key_map, "\e[[A", to_key("#f1")); // + + table_set(*key_map, "\e[25~", to_key("#f1+$")); // + + table_set(*key_map, "\eOP", to_key("#f1")); // + + + + + table_set(*key_map, "\eO1P", to_key("#f1+s")); // + + table_set(*key_map, "\eO2P", to_key("#f1+$")); // + + table_set(*key_map, "\eO3P", to_key("#f1+a")); // + + table_set(*key_map, "\eO4P", to_key("#f1+A")); // + + table_set(*key_map, "\eO5P", to_key("#f1+c")); // + + table_set(*key_map, "\eO6P", to_key("#f1+C")); // + + table_set(*key_map, "\eO7P", to_key("#f1+w")); // + + table_set(*key_map, "\eO8P", to_key("#f1+W")); // + + table_set(*key_map, "\e[1P", to_key("#f1")); // + table_set(*key_map, "\e[1;1P", to_key("#f1")); // + table_set(*key_map, "\e[1;2P", to_key("#f1+$")); // + + + + table_set(*key_map, "\e[1;3P", to_key("#f1+a")); // + + + + table_set(*key_map, "\e[1;4P", to_key("#f1+A")); // + + + + table_set(*key_map, "\e[1;5P", to_key("#f1+c")); // + + + + table_set(*key_map, "\e[1;6P", to_key("#f1+C")); // + + + + table_set(*key_map, "\e[1;7P", to_key("#f1+w")); // + + + + table_set(*key_map, "\e[1;8P", to_key("#f1+W")); // + + + + table_set(*key_map, "\e[1;9P", to_key("#f1+s")); // + + table_set(*key_map, "\e[1;10P", to_key("#f1+S")); // + + table_set(*key_map, "\e[1;11P", to_key("#f1+x")); // + + table_set(*key_map, "\e[1;12P", to_key("#f1+X")); // + + table_set(*key_map, "\e[1;13P", to_key("#f1+y")); // + + table_set(*key_map, "\e[1;14P", to_key("#f1+Y")); // + + table_set(*key_map, "\e[1;15P", to_key("#f1+z")); // + + table_set(*key_map, "\e[1;16P", to_key("#f1+Z")); // + + + // F2 // g i k l x + table_set(*key_map, "\e[[B", to_key("#f2")); // + + table_set(*key_map, "\e[26~", to_key("#f2+$")); // + + table_set(*key_map, "\eOQ", to_key("#f2")); // + + + + + table_set(*key_map, "\eO1Q", to_key("#f2+s")); // + + table_set(*key_map, "\eO2Q", to_key("#f2+$")); // + + table_set(*key_map, "\eO3Q", to_key("#f2+a")); // + + table_set(*key_map, "\eO4Q", to_key("#f2+A")); // + + table_set(*key_map, "\eO5Q", to_key("#f2+c")); // + + table_set(*key_map, "\eO6Q", to_key("#f2+C")); // + + table_set(*key_map, "\eO7Q", to_key("#f2+w")); // + + table_set(*key_map, "\eO8Q", to_key("#f2+W")); // + + table_set(*key_map, "\e[1Q", to_key("#f2")); // + table_set(*key_map, "\e[1;1Q", to_key("#f2")); // + table_set(*key_map, "\e[1;2Q", to_key("#f2+$")); // + + + + table_set(*key_map, "\e[1;3Q", to_key("#f2+a")); // + + + + table_set(*key_map, "\e[1;4Q", to_key("#f2+A")); // + + + + table_set(*key_map, "\e[1;5Q", to_key("#f2+c")); // + + + + table_set(*key_map, "\e[1;6Q", to_key("#f2+C")); // + + + + table_set(*key_map, "\e[1;7Q", to_key("#f2+w")); // + + + + table_set(*key_map, "\e[1;8Q", to_key("#f2+W")); // + + + + table_set(*key_map, "\e[1;9Q", to_key("#f2+s")); // + + table_set(*key_map, "\e[1;10Q", to_key("#f2+S")); // + + table_set(*key_map, "\e[1;11Q", to_key("#f2+x")); // + + table_set(*key_map, "\e[1;12Q", to_key("#f2+X")); // + + table_set(*key_map, "\e[1;13Q", to_key("#f2+y")); // + + table_set(*key_map, "\e[1;14Q", to_key("#f2+Y")); // + + table_set(*key_map, "\e[1;15Q", to_key("#f2+z")); // + + table_set(*key_map, "\e[1;16Q", to_key("#f2+Z")); // + + + // F3 // g i k l x + table_set(*key_map, "\e[[C", to_key("#f3")); // + + table_set(*key_map, "\e[28~", to_key("#f3+$")); // + + table_set(*key_map, "\eOR", to_key("#f3")); // + + + + + table_set(*key_map, "\eO1R", to_key("#f3+s")); // + + table_set(*key_map, "\eO2R", to_key("#f3+$")); // + + table_set(*key_map, "\eO3R", to_key("#f3+a")); // + + table_set(*key_map, "\eO4R", to_key("#f3+A")); // + + table_set(*key_map, "\eO5R", to_key("#f3+c")); // + + table_set(*key_map, "\eO6R", to_key("#f3+C")); // + + table_set(*key_map, "\eO7R", to_key("#f3+w")); // + + table_set(*key_map, "\eO8R", to_key("#f3+W")); // + + table_set(*key_map, "\e[1R", to_key("#f3")); // + table_set(*key_map, "\e[1;1R", to_key("#f3")); // + table_set(*key_map, "\e[1;2R", to_key("#f3+$")); // + + + + table_set(*key_map, "\e[1;3R", to_key("#f3+a")); // + + + + table_set(*key_map, "\e[1;4R", to_key("#f3+A")); // + + + + table_set(*key_map, "\e[1;5R", to_key("#f3+c")); // + + + + table_set(*key_map, "\e[1;6R", to_key("#f3+C")); // + + + + table_set(*key_map, "\e[1;7R", to_key("#f3+w")); // + + + + table_set(*key_map, "\e[1;8R", to_key("#f3+W")); // + + + + table_set(*key_map, "\e[1;9R", to_key("#f3+s")); // + + table_set(*key_map, "\e[1;10R", to_key("#f3+S")); // + + table_set(*key_map, "\e[1;11R", to_key("#f3+x")); // + + table_set(*key_map, "\e[1;12R", to_key("#f3+X")); // + + table_set(*key_map, "\e[1;13R", to_key("#f3+y")); // + + table_set(*key_map, "\e[1;14R", to_key("#f3+Y")); // + + table_set(*key_map, "\e[1;15R", to_key("#f3+z")); // + + table_set(*key_map, "\e[1;16R", to_key("#f3+Z")); // + + + // F4 // g i k l x + table_set(*key_map, "\e[[D", to_key("#f4")); // + + table_set(*key_map, "\e[29~", to_key("#f4+$")); // + + table_set(*key_map, "\eOS", to_key("#f4")); // + + + + + table_set(*key_map, "\eO1S", to_key("#f4+s")); // + + table_set(*key_map, "\eO2S", to_key("#f4+$")); // + + table_set(*key_map, "\eO3S", to_key("#f4+a")); // + + table_set(*key_map, "\eO4S", to_key("#f4+A")); // + + table_set(*key_map, "\eO5S", to_key("#f4+c")); // + + table_set(*key_map, "\eO6S", to_key("#f4+C")); // + + table_set(*key_map, "\eO7S", to_key("#f4+w")); // + + table_set(*key_map, "\eO8S", to_key("#f4+W")); // + + table_set(*key_map, "\e[1S", to_key("#f4")); // + table_set(*key_map, "\e[1;1S", to_key("#f4")); // + table_set(*key_map, "\e[1;2S", to_key("#f4+$")); // + + + + table_set(*key_map, "\e[1;3S", to_key("#f4+a")); // + + + + table_set(*key_map, "\e[1;4S", to_key("#f4+A")); // + + + + table_set(*key_map, "\e[1;5S", to_key("#f4+c")); // + + + + table_set(*key_map, "\e[1;6S", to_key("#f4+C")); // + + + + table_set(*key_map, "\e[1;7S", to_key("#f4+w")); // + + + + table_set(*key_map, "\e[1;8S", to_key("#f4+W")); // + + + + table_set(*key_map, "\e[1;9S", to_key("#f4+s")); // + + table_set(*key_map, "\e[1;10S", to_key("#f4+S")); // + + table_set(*key_map, "\e[1;11S", to_key("#f4+x")); // + + table_set(*key_map, "\e[1;12S", to_key("#f4+X")); // + + table_set(*key_map, "\e[1;13S", to_key("#f4+y")); // + + table_set(*key_map, "\e[1;14S", to_key("#f4+Y")); // + + table_set(*key_map, "\e[1;15S", to_key("#f4+z")); // + + table_set(*key_map, "\e[1;16S", to_key("#f4+Z")); // + + + // F5 // g i k l x + table_set(*key_map, "\e[[E", to_key("#f5")); // + + table_set(*key_map, "\e[31~", to_key("#f5+$")); // + + table_set(*key_map, "\e[15~", to_key("#f5")); // + + + + + table_set(*key_map, "\e[15;1~", to_key("#f5")); // + table_set(*key_map, "\e[15;2~", to_key("#f5+$")); // + + + + + table_set(*key_map, "\e[15;3~", to_key("#f5+a")); // + + + + + table_set(*key_map, "\e[15;4~", to_key("#f5+A")); // + + + + + table_set(*key_map, "\e[15;5~", to_key("#f5+c")); // + + + + + table_set(*key_map, "\e[15;6~", to_key("#f5+C")); // + + + + + table_set(*key_map, "\e[15;7~", to_key("#f5+w")); // + + + + + table_set(*key_map, "\e[15;8~", to_key("#f5+W")); // + + + + + table_set(*key_map, "\e[15;9~", to_key("#f5+s")); // + + table_set(*key_map, "\e[15;10~",to_key("#f5+S")); // + + table_set(*key_map, "\e[15;11~",to_key("#f5+x")); // + + table_set(*key_map, "\e[15;12~",to_key("#f5+X")); // + + table_set(*key_map, "\e[15;13~",to_key("#f5+y")); // + + table_set(*key_map, "\e[15;14~",to_key("#f5+Y")); // + + table_set(*key_map, "\e[15;15~",to_key("#f5+z")); // + + table_set(*key_map, "\e[15;16~",to_key("#f5+Z")); // + + + // F6 // g i k l x + table_set(*key_map, "\e[32~", to_key("#f6+$")); // + + table_set(*key_map, "\e[17~", to_key("#f6")); // + + + + + + table_set(*key_map, "\e[17;1~", to_key("#f6")); // + table_set(*key_map, "\e[17;2~", to_key("#f6+$")); // + + + + + table_set(*key_map, "\e[17;3~", to_key("#f6+a")); // + + + + + table_set(*key_map, "\e[17;4~", to_key("#f6+A")); // + + + + + table_set(*key_map, "\e[17;5~", to_key("#f6+c")); // + + + + + table_set(*key_map, "\e[17;6~", to_key("#f6+C")); // + + + + + table_set(*key_map, "\e[17;7~", to_key("#f6+w")); // + + + + + table_set(*key_map, "\e[17;8~", to_key("#f6+W")); // + + + + + table_set(*key_map, "\e[17;9~", to_key("#f6+s")); // + + table_set(*key_map, "\e[17;10~",to_key("#f6+S")); // + + table_set(*key_map, "\e[17;11~",to_key("#f6+x")); // + + table_set(*key_map, "\e[17;12~",to_key("#f6+X")); // + + table_set(*key_map, "\e[17;13~",to_key("#f6+y")); // + + table_set(*key_map, "\e[17;14~",to_key("#f6+Y")); // + + table_set(*key_map, "\e[17;15~",to_key("#f6+z")); // + + table_set(*key_map, "\e[17;16~",to_key("#f6+Z")); // + + + // F7 // g i k l x + table_set(*key_map, "\e[33~", to_key("#f7+$")); // + + table_set(*key_map, "\e[18~", to_key("#f7")); // + + + + + + table_set(*key_map, "\e[18;1~", to_key("#f7")); // + table_set(*key_map, "\e[18;2~", to_key("#f7+$")); // + + + + + table_set(*key_map, "\e[18;3~", to_key("#f7+a")); // + + + + + table_set(*key_map, "\e[18;4~", to_key("#f7+A")); // + + + + + table_set(*key_map, "\e[18;5~", to_key("#f7+c")); // + + + + + table_set(*key_map, "\e[18;6~", to_key("#f7+C")); // + + + + + table_set(*key_map, "\e[18;7~", to_key("#f7+w")); // + + + + + table_set(*key_map, "\e[18;8~", to_key("#f7+W")); // + + + + + table_set(*key_map, "\e[18;9~", to_key("#f7+s")); // + + table_set(*key_map, "\e[18;10~",to_key("#f7+S")); // + + table_set(*key_map, "\e[18;11~",to_key("#f7+x")); // + + table_set(*key_map, "\e[18;12~",to_key("#f7+X")); // + + table_set(*key_map, "\e[18;13~",to_key("#f7+y")); // + + table_set(*key_map, "\e[18;14~",to_key("#f7+Y")); // + + table_set(*key_map, "\e[18;15~",to_key("#f7+z")); // + + table_set(*key_map, "\e[18;16~",to_key("#f7+Z")); // + + + // F8 // g i k l x + table_set(*key_map, "\e[34~", to_key("#f8+$")); // + + table_set(*key_map, "\e[19~", to_key("#f8")); // + + + + + + table_set(*key_map, "\e[19;1~", to_key("#f8")); // + table_set(*key_map, "\e[19;2~", to_key("#f8+$")); // + + + + + table_set(*key_map, "\e[19;3~", to_key("#f8+a")); // + + + + + table_set(*key_map, "\e[19;4~", to_key("#f8+A")); // + + + + + table_set(*key_map, "\e[19;5~", to_key("#f8+c")); // + + + + + table_set(*key_map, "\e[19;6~", to_key("#f8+C")); // + + + + + table_set(*key_map, "\e[19;7~", to_key("#f8+w")); // + + + + + table_set(*key_map, "\e[19;8~", to_key("#f8+W")); // + + + + + table_set(*key_map, "\e[19;9~", to_key("#f8+s")); // + + table_set(*key_map, "\e[19;10~",to_key("#f8+S")); // + + table_set(*key_map, "\e[19;11~",to_key("#f8+x")); // + + table_set(*key_map, "\e[19;12~",to_key("#f8+X")); // + + table_set(*key_map, "\e[19;13~",to_key("#f8+y")); // + + table_set(*key_map, "\e[19;14~",to_key("#f8+Y")); // + + table_set(*key_map, "\e[19;15~",to_key("#f8+z")); // + + table_set(*key_map, "\e[19;16~",to_key("#f8+Z")); // + + + // F9 // g i k l x + table_set(*key_map, "\e[20~", to_key("#f9")); // + + + + + + table_set(*key_map, "\e[20;1~", to_key("#f9")); // + table_set(*key_map, "\e[20;2~", to_key("#f9+$")); // + + + + + table_set(*key_map, "\e[20;3~", to_key("#f9+a")); // + + + + + table_set(*key_map, "\e[20;4~", to_key("#f9+A")); // + + + + + table_set(*key_map, "\e[20;5~", to_key("#f9+c")); // + + + + + table_set(*key_map, "\e[20;6~", to_key("#f9+C")); // + + + + + table_set(*key_map, "\e[20;7~", to_key("#f9+w")); // + + + + + table_set(*key_map, "\e[20;8~", to_key("#f9+W")); // + + + + + table_set(*key_map, "\e[20;9~", to_key("#f9+s")); // + + table_set(*key_map, "\e[20;10~",to_key("#f9+S")); // + + table_set(*key_map, "\e[20;11~",to_key("#f9+x")); // + + table_set(*key_map, "\e[20;12~",to_key("#f9+X")); // + + table_set(*key_map, "\e[20;13~",to_key("#f9+y")); // + + table_set(*key_map, "\e[20;14~",to_key("#f9+Y")); // + + table_set(*key_map, "\e[20;15~",to_key("#f9+z")); // + + table_set(*key_map, "\e[20;16~",to_key("#f9+Z")); // + + + // F10 // g i k l x + table_set(*key_map, "\e[21~", to_key("#f10")); // + + + + + + table_set(*key_map, "\e[21;1~", to_key("#f10")); // + table_set(*key_map, "\e[21;2~", to_key("#f10+$")); // + + + + + table_set(*key_map, "\e[21;3~", to_key("#f10+a")); // + + + + + table_set(*key_map, "\e[21;4~", to_key("#f10+A")); // + + + + + table_set(*key_map, "\e[21;5~", to_key("#f10+c")); // + + + + + table_set(*key_map, "\e[21;6~", to_key("#f10+C")); // + + + + + table_set(*key_map, "\e[21;7~", to_key("#f10+w")); // + + + + + table_set(*key_map, "\e[21;8~", to_key("#f10+W")); // + + + + + table_set(*key_map, "\e[21;9~", to_key("#f10+s")); // + + table_set(*key_map, "\e[21;10~",to_key("#f10+S")); // + + table_set(*key_map, "\e[21;11~",to_key("#f10+x")); // + + table_set(*key_map, "\e[21;12~",to_key("#f10+X")); // + + table_set(*key_map, "\e[21;13~",to_key("#f10+y")); // + + table_set(*key_map, "\e[21;14~",to_key("#f10+Y")); // + + table_set(*key_map, "\e[21;15~",to_key("#f10+z")); // + + table_set(*key_map, "\e[21;16~",to_key("#f10+Z")); // + + + // F11 // g i k l x + table_set(*key_map, "\e[23~", to_key("#f11")); // + + + + + + table_set(*key_map, "\e[23;1~", to_key("#f11")); // + table_set(*key_map, "\e[23;2~", to_key("#f11+$")); // + + + + + table_set(*key_map, "\e[23;3~", to_key("#f11+a")); // + + + + + table_set(*key_map, "\e[23;4~", to_key("#f11+A")); // + + + + + table_set(*key_map, "\e[23;5~", to_key("#f11+c")); // + + + + + table_set(*key_map, "\e[23;6~", to_key("#f11+C")); // + + + + + table_set(*key_map, "\e[23;7~", to_key("#f11+w")); // + + + + + table_set(*key_map, "\e[23;8~", to_key("#f11+W")); // + + + + + table_set(*key_map, "\e[23;9~", to_key("#f11+s")); // + + table_set(*key_map, "\e[23;10~",to_key("#f11+S")); // + + table_set(*key_map, "\e[23;11~",to_key("#f11+x")); // + + table_set(*key_map, "\e[23;12~",to_key("#f11+X")); // + + table_set(*key_map, "\e[23;13~",to_key("#f11+y")); // + + table_set(*key_map, "\e[23;14~",to_key("#f11+Y")); // + + table_set(*key_map, "\e[23;15~",to_key("#f11+z")); // + + table_set(*key_map, "\e[23;16~",to_key("#f11+Z")); // + + + // F12 // g i k l x + table_set(*key_map, "\e[24~", to_key("#f12")); // + + + + + + table_set(*key_map, "\e[24;1~", to_key("#f12")); // + table_set(*key_map, "\e[24;2~", to_key("#f12+$")); // + + + + + table_set(*key_map, "\e[24;3~", to_key("#f12+a")); // + + + + + table_set(*key_map, "\e[24;4~", to_key("#f12+A")); // + + + + + table_set(*key_map, "\e[24;5~", to_key("#f12+c")); // + + + + + table_set(*key_map, "\e[24;6~", to_key("#f12+C")); // + + + + + table_set(*key_map, "\e[24;7~", to_key("#f12+w")); // + + + + + table_set(*key_map, "\e[24;8~", to_key("#f12+W")); // + + + + + table_set(*key_map, "\e[24;9~", to_key("#f12+s")); // + + table_set(*key_map, "\e[24;10~",to_key("#f12+S")); // + + table_set(*key_map, "\e[24;11~",to_key("#f12+x")); // + + table_set(*key_map, "\e[24;12~",to_key("#f12+X")); // + + table_set(*key_map, "\e[24;13~",to_key("#f12+y")); // + + table_set(*key_map, "\e[24;14~",to_key("#f12+Y")); // + + table_set(*key_map, "\e[24;15~",to_key("#f12+z")); // + + table_set(*key_map, "\e[24;16~",to_key("#f12+Z")); // + +} diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 11b5247..d93e4ff 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,4 +1,3 @@ -// TODO Move TUI into ./modules/TUI so we can stop calling --import_dir on compile. #if OS == { case .LINUX; #load "unix.jai"; @@ -13,7 +12,7 @@ #import "Basic"; #import "String"; #import "Thread"; -#import "Hash_Table"; +#load "key_map.jai"; // Special Graphics Characters Drawings :: struct { @@ -238,387 +237,6 @@ Keys :: struct #type_info_none { F12 : Key : #run to_key("#f12"); } -key_map: Table(string, Key); - -setup_key_map :: () { - /* - This table was created/tested using the following terminals: - - konsole - - kitty - - xterm - - linux console - - To signal modifier keys, a letter is appended after a + (plus sign): - "#f1" -> F1 - "#f1+$" -> F1+Shift - "#f1+a" -> F1+Alt - "#f1+A" -> F1+Shift+Alt - "#f1+c" -> F1+Ctrl - "#f1+C" -> F1+Shift+Ctrl - "#f1+w" -> F1+Alt+Ctrl - "#f1+W" -> F1+Shift+Alt+Ctrl - "#f1+s" -> F1+Super - "#f1+S" -> F1+Shift+Super - "#f1+x" -> F1+Alt+Super - "#f1+X" -> F1+Shift+Alt+Super - "#f1+y" -> F1+Ctrl+Super - "#f1+Y" -> F1+Shift+Ctrl+Super - "#f1+z" -> F1+Alt+Ctrl+Super - "#f1+Z" -> F1+Shift+Alt+Ctrl+Super - */ - - // Up - table_set(*key_map, "\e[A", to_key("#up")); - table_set(*key_map, "\e[1;1A", to_key("#up+m")); - table_set(*key_map, "\e[1;2A", to_key("#up+s")); - table_set(*key_map, "\e[1;3A", to_key("#up+a")); - table_set(*key_map, "\e[1;4A", to_key("#up+A")); - table_set(*key_map, "\e[1;5A", to_key("#up+c")); - table_set(*key_map, "\e[1;6A", to_key("#up+C")); - table_set(*key_map, "\e[1;7A", to_key("#up+x")); - table_set(*key_map, "\e[1;8A", to_key("#up+X")); - // Up - kitty - table_set(*key_map, "\e[1;9A", to_key("#up+m")); - - // Down - table_set(*key_map, "\e[B", to_key("#down")); - table_set(*key_map, "\e[1;1B", to_key("#down+m")); - table_set(*key_map, "\e[1;2B", to_key("#down+s")); - table_set(*key_map, "\e[1;3B", to_key("#down+a")); - table_set(*key_map, "\e[1;4B", to_key("#down+A")); - table_set(*key_map, "\e[1;5B", to_key("#down+c")); - table_set(*key_map, "\e[1;6B", to_key("#down+C")); - table_set(*key_map, "\e[1;7B", to_key("#down+x")); - table_set(*key_map, "\e[1;8B", to_key("#down+X")); - // Down - kitty - table_set(*key_map, "\e[1;9B", to_key("#down+m")); - - // Right - table_set(*key_map, "\e[C", to_key("#right")); - table_set(*key_map, "\e[1;1C", to_key("#right+m")); - table_set(*key_map, "\e[1;2C", to_key("#right+s")); - table_set(*key_map, "\e[1;3C", to_key("#right+a")); - table_set(*key_map, "\e[1;4C", to_key("#right+A")); - table_set(*key_map, "\e[1;5C", to_key("#right+c")); - table_set(*key_map, "\e[1;6C", to_key("#right+C")); - table_set(*key_map, "\e[1;7C", to_key("#right+x")); - table_set(*key_map, "\e[1;8C", to_key("#right+X")); - // Right - kitty - table_set(*key_map, "\e[1;9C", to_key("#right+m")); - - // Left - table_set(*key_map, "\e[D", to_key("#left")); - table_set(*key_map, "\e[1;1D", to_key("#left+m")); - table_set(*key_map, "\e[1;2D", to_key("#left+s")); - table_set(*key_map, "\e[1;3D", to_key("#left+a")); - table_set(*key_map, "\e[1;4D", to_key("#left+A")); - table_set(*key_map, "\e[1;5D", to_key("#left+c")); - table_set(*key_map, "\e[1;6D", to_key("#left+C")); - table_set(*key_map, "\e[1;7D", to_key("#left+x")); - table_set(*key_map, "\e[1;8D", to_key("#left+X")); - // Left - kitty - table_set(*key_map, "\e[1;9D", to_key("#left+m")); - - // Home - table_set(*key_map, "\e[H", to_key("#home")); - table_set(*key_map, "\e[1~", to_key("#home")); - table_set(*key_map, "\e[1;1H", to_key("#home+m")); - table_set(*key_map, "\e[1;2H", to_key("#home+s")); - table_set(*key_map, "\e[1;3H", to_key("#home+a")); - table_set(*key_map, "\e[1;4H", to_key("#home+A")); - table_set(*key_map, "\e[1;5H", to_key("#home+c")); - table_set(*key_map, "\e[1;6H", to_key("#home+C")); - table_set(*key_map, "\e[1;7H", to_key("#home+x")); - table_set(*key_map, "\e[1;8H", to_key("#home+X")); - // Home - kitty - table_set(*key_map, "\e[1;9H", to_key("#home+m")); - - // End - table_set(*key_map, "\e[F", to_key("#end")); - table_set(*key_map, "\e[4~", to_key("#end")); - table_set(*key_map, "\e[1;1F", to_key("#end+m")); - table_set(*key_map, "\e[1;2F", to_key("#end+s")); - table_set(*key_map, "\e[1;3F", to_key("#end+a")); - table_set(*key_map, "\e[1;4F", to_key("#end+A")); - table_set(*key_map, "\e[1;5F", to_key("#end+c")); - table_set(*key_map, "\e[1;6F", to_key("#end+C")); - table_set(*key_map, "\e[1;7F", to_key("#end+x")); - table_set(*key_map, "\e[1;8F", to_key("#end+X")); - // End - kitty - table_set(*key_map, "\e[1;9F", to_key("#end+m")); - - // Insert - table_set(*key_map, "\e[2~", to_key("#ins")); - table_set(*key_map, "\e[2;1~", to_key("#ins+m")); - table_set(*key_map, "\e[2;2~", to_key("#ins+s")); - table_set(*key_map, "\e[2;3~", to_key("#ins+a")); - table_set(*key_map, "\e[2;4~", to_key("#ins+A")); - table_set(*key_map, "\e[2;5~", to_key("#ins+c")); - table_set(*key_map, "\e[2;6~", to_key("#ins+C")); - table_set(*key_map, "\e[2;7~", to_key("#ins+x")); - table_set(*key_map, "\e[2;8~", to_key("#ins+X")); - // Insert - kitty - table_set(*key_map, "\e[2;9~", to_key("#ins+m")); - - // Delete - table_set(*key_map, "\e[3~", to_key("#del")); - table_set(*key_map, "\e[3;1~", to_key("#del+m")); - table_set(*key_map, "\e[3;2~", to_key("#del+s")); - table_set(*key_map, "\e[3;3~", to_key("#del+a")); - table_set(*key_map, "\e[3;4~", to_key("#del+A")); - table_set(*key_map, "\e[3;5~", to_key("#del+c")); - table_set(*key_map, "\e[3;6~", to_key("#del+C")); - table_set(*key_map, "\e[3;7~", to_key("#del+x")); - table_set(*key_map, "\e[3;8~", to_key("#del+X")); - // Delete - kitty - table_set(*key_map, "\e[3;9~", to_key("#del+m")); - - // Page Up - table_set(*key_map, "\e[5~", to_key("#pup")); - table_set(*key_map, "\e[5;1~", to_key("#pup+m")); - table_set(*key_map, "\e[5;2~", to_key("#pup+s")); - table_set(*key_map, "\e[5;3~", to_key("#pup+a")); - table_set(*key_map, "\e[5;4~", to_key("#pup+A")); - table_set(*key_map, "\e[5;5~", to_key("#pup+c")); - table_set(*key_map, "\e[5;6~", to_key("#pup+C")); - table_set(*key_map, "\e[5;7~", to_key("#pup+x")); - table_set(*key_map, "\e[5;8~", to_key("#pup+X")); - // Page Up - kitty - table_set(*key_map, "\e[5;9~", to_key("#pup+m")); - - // Page Down - table_set(*key_map, "\e[6~", to_key("#pdown")); - table_set(*key_map, "\e[6;1~", to_key("#pdown+m")); - table_set(*key_map, "\e[6;2~", to_key("#pdown+s")); - table_set(*key_map, "\e[6;3~", to_key("#pdown+a")); - table_set(*key_map, "\e[6;4~", to_key("#pdown+A")); - table_set(*key_map, "\e[6;5~", to_key("#pdown+c")); - table_set(*key_map, "\e[6;6~", to_key("#pdown+C")); - table_set(*key_map, "\e[6;7~", to_key("#pdown+x")); - table_set(*key_map, "\e[6;8~", to_key("#pdown+X")); - // Page Down - kitty - table_set(*key_map, "\e[6;9~", to_key("#pdown+m")); - - // F1 - table_set(*key_map, "\eOP", to_key("#f1")); - table_set(*key_map, "\eO1P", to_key("#f1+m")); - table_set(*key_map, "\eO2P", to_key("#f1+s")); - table_set(*key_map, "\eO3P", to_key("#f1+a")); - table_set(*key_map, "\eO4P", to_key("#f1+A")); - table_set(*key_map, "\eO5P", to_key("#f1+c")); - table_set(*key_map, "\eO6P", to_key("#f1+C")); - table_set(*key_map, "\eO7P", to_key("#f1+x")); - table_set(*key_map, "\eO8P", to_key("#f1+X")); - // F1 - xterm - table_set(*key_map, "\e[1;2P", to_key("#f1+s")); - table_set(*key_map, "\e[1;3P", to_key("#f1+a")); - table_set(*key_map, "\e[1;4P", to_key("#f1+A")); - table_set(*key_map, "\e[1;5P", to_key("#f1+c")); - table_set(*key_map, "\e[1;6P", to_key("#f1+C")); - table_set(*key_map, "\e[1;7P", to_key("#f1+x")); - table_set(*key_map, "\e[1;8P", to_key("#f1+X")); - // F1 - kitty - table_set(*key_map, "\e[1;9P", to_key("#f1+m")); - // F1 - linux console - table_set(*key_map, "\e[[A", to_key("#f1")); - table_set(*key_map, "\e[25~", to_key("#f1+s")); - - // F2 - table_set(*key_map, "\eOQ", to_key("#f2")); - table_set(*key_map, "\eO1Q", to_key("#f2+m")); - table_set(*key_map, "\eO2Q", to_key("#f2+s")); - table_set(*key_map, "\eO3Q", to_key("#f2+a")); - table_set(*key_map, "\eO4Q", to_key("#f2+A")); - table_set(*key_map, "\eO5Q", to_key("#f2+c")); - table_set(*key_map, "\eO6Q", to_key("#f2+C")); - table_set(*key_map, "\eO7Q", to_key("#f2+x")); - table_set(*key_map, "\eO8Q", to_key("#f2+X")); - // F2 - xterm - table_set(*key_map, "\e[1;2Q", to_key("#f2+s")); - table_set(*key_map, "\e[1;3Q", to_key("#f2+a")); - table_set(*key_map, "\e[1;4Q", to_key("#f2+A")); - table_set(*key_map, "\e[1;5Q", to_key("#f2+c")); - table_set(*key_map, "\e[1;6Q", to_key("#f2+C")); - table_set(*key_map, "\e[1;7Q", to_key("#f2+x")); - table_set(*key_map, "\e[1;8Q", to_key("#f2+X")); - // F2 - kitty - table_set(*key_map, "\e[1;9Q", to_key("#f2+m")); - // F2 - linux console - table_set(*key_map, "\e[[B", to_key("#f2")); - table_set(*key_map, "\e[26~", to_key("#f2+s")); - - // F3 - table_set(*key_map, "\eOR", to_key("#f3")); - table_set(*key_map, "\eO1R", to_key("#f3+m")); - table_set(*key_map, "\eO2R", to_key("#f3+s")); - table_set(*key_map, "\eO3R", to_key("#f3+a")); - table_set(*key_map, "\eO4R", to_key("#f3+A")); - table_set(*key_map, "\eO5R", to_key("#f3+c")); - table_set(*key_map, "\eO6R", to_key("#f3+C")); - table_set(*key_map, "\eO7R", to_key("#f3+x")); - table_set(*key_map, "\eO8R", to_key("#f3+X")); - // F3 - xterm - table_set(*key_map, "\e[1;2R", to_key("#f3+s")); - table_set(*key_map, "\e[1;3R", to_key("#f3+a")); - table_set(*key_map, "\e[1;4R", to_key("#f3+A")); - table_set(*key_map, "\e[1;5R", to_key("#f3+c")); - table_set(*key_map, "\e[1;6R", to_key("#f3+C")); - table_set(*key_map, "\e[1;7R", to_key("#f3+x")); - table_set(*key_map, "\e[1;8R", to_key("#f3+X")); - // F3 - kitty - table_set(*key_map, "\e[1;9R", to_key("#f3+m")); - // F3 - linux console - table_set(*key_map, "\e[[C", to_key("#f3")); - table_set(*key_map, "\e[28~", to_key("#f3+s")); - - // F4 - table_set(*key_map, "\eOS", to_key("#f4")); - table_set(*key_map, "\eO1S", to_key("#f4+m")); - table_set(*key_map, "\eO2S", to_key("#f4+s")); - table_set(*key_map, "\eO3S", to_key("#f4+a")); - table_set(*key_map, "\eO4S", to_key("#f4+A")); - table_set(*key_map, "\eO5S", to_key("#f4+c")); - table_set(*key_map, "\eO6S", to_key("#f4+C")); - table_set(*key_map, "\eO7S", to_key("#f4+x")); - table_set(*key_map, "\eO8S", to_key("#f4+X")); - // F4 - xterm - table_set(*key_map, "\e[1;2S", to_key("#f4+s")); - table_set(*key_map, "\e[1;3S", to_key("#f4+a")); - table_set(*key_map, "\e[1;4S", to_key("#f4+A")); - table_set(*key_map, "\e[1;5S", to_key("#f4+c")); - table_set(*key_map, "\e[1;6S", to_key("#f4+C")); - table_set(*key_map, "\e[1;7S", to_key("#f4+x")); - table_set(*key_map, "\e[1;8S", to_key("#f4+X")); - // F4 - kitty - table_set(*key_map, "\e[1;9S", to_key("#f4+m")); - // F4 - linux console - table_set(*key_map, "\e[[D", to_key("#f4")); - table_set(*key_map, "\e[29~", to_key("#f4+s")); - - // F5 - table_set(*key_map, "\e[15~", to_key("#f5")); - table_set(*key_map, "\e[15;1~", to_key("#f5+m")); - table_set(*key_map, "\e[15;2~", to_key("#f5+s")); - table_set(*key_map, "\e[15;3~", to_key("#f5+a")); - table_set(*key_map, "\e[15;4~", to_key("#f5+A")); - table_set(*key_map, "\e[15;5~", to_key("#f5+c")); - table_set(*key_map, "\e[15;6~", to_key("#f5+C")); - table_set(*key_map, "\e[15;7~", to_key("#f5+x")); - table_set(*key_map, "\e[15;8~", to_key("#f5+X")); - // F5 - kitty - table_set(*key_map, "\e[15;9~", to_key("#f5+m")); - // F5 - linux console - table_set(*key_map, "\e[[E", to_key("#f5")); - table_set(*key_map, "\e[31~", to_key("#f5+s")); - - // F6 - table_set(*key_map, "\e[17~", to_key("#f6")); - table_set(*key_map, "\e[17;1~", to_key("#f6+m")); - table_set(*key_map, "\e[17;2~", to_key("#f6+s")); - table_set(*key_map, "\e[17;3~", to_key("#f6+a")); - table_set(*key_map, "\e[17;4~", to_key("#f6+A")); - table_set(*key_map, "\e[17;5~", to_key("#f6+c")); - table_set(*key_map, "\e[17;6~", to_key("#f6+C")); - table_set(*key_map, "\e[17;7~", to_key("#f6+x")); - table_set(*key_map, "\e[17;8~", to_key("#f6+X")); - // F6 - kitty - table_set(*key_map, "\e[17;9~", to_key("#f6+m")); - // F6 - linux console - table_set(*key_map, "\e[32~", to_key("#f6+s")); - - // F7 - table_set(*key_map, "\e[18~", to_key("#f7")); - -TODO FINISH THIS... WIP - - // table_set(*key_map, "\e[18;1~", to_key("#f7+m")); - table_set(*key_map, "\e[18;2~", to_key("#f7+s")); // #f7+^ - table_set(*key_map, "\e[18;3~", to_key("#f7+a")); // #f7+a - table_set(*key_map, "\e[18;4~", to_key("#f7+A")); // #f7+A - table_set(*key_map, "\e[18;5~", to_key("#f7+c")); // #f7+s - table_set(*key_map, "\e[18;6~", to_key("#f7+C")); - table_set(*key_map, "\e[18;7~", to_key("#f7+x")); // x - table_set(*key_map, "\e[18;8~", to_key("#f7+X")); // X - // F7 - kitty - // table_set(*key_map, "\e[18;9~", to_key("#f7+m")); // m - // table_set(*key_map, "\e[18;10~",to_key("#f7+M")); // M - // table_set(*key_map, "\e[18;11~",to_key("#f7+ma")); // ma -> y - // table_set(*key_map, "\e[18;12~",to_key("#f7+mA")); // MA -> Y - // table_set(*key_map, "\e[18;13~",to_key("#f7+mc")); // mc -> z - // table_set(*key_map, "\e[18;14~",to_key("#f7+mC")); // MC -> Z - // table_set(*key_map, "\e[18;15~",to_key("#f7+mx")); // mac-> w - // table_set(*key_map, "\e[18;16~",to_key("#f7+mX")); // MAC-> W - // F7 - linux console - table_set(*key_map, "\e[33~", to_key("#f7+s")); - - // F8 - table_set(*key_map, "\e[19~", to_key("#f8")); - table_set(*key_map, "\e[19;1~", to_key("#f8+m")); - table_set(*key_map, "\e[19;2~", to_key("#f8+s")); - table_set(*key_map, "\e[19;3~", to_key("#f8+a")); - table_set(*key_map, "\e[19;4~", to_key("#f8+A")); - table_set(*key_map, "\e[19;5~", to_key("#f8+c")); - table_set(*key_map, "\e[19;6~", to_key("#f8+C")); - table_set(*key_map, "\e[19;7~", to_key("#f8+x")); - table_set(*key_map, "\e[19;8~", to_key("#f8+X")); - // F8 - kitty - table_set(*key_map, "\e[19;9~", to_key("#f8+m")); - // F8 - linux console - table_set(*key_map, "\e[34~", to_key("#f8+s")); - - // F9 - table_set(*key_map, "\e[20~", to_key("#f9")); - table_set(*key_map, "\e[20;1~", to_key("#f9+m")); - table_set(*key_map, "\e[20;2~", to_key("#f9+s")); - table_set(*key_map, "\e[20;3~", to_key("#f9+a")); - table_set(*key_map, "\e[20;4~", to_key("#f9+A")); - table_set(*key_map, "\e[20;5~", to_key("#f9+c")); - table_set(*key_map, "\e[20;6~", to_key("#f9+C")); - table_set(*key_map, "\e[20;7~", to_key("#f9+x")); - table_set(*key_map, "\e[20;8~", to_key("#f9+X")); - // F9 - kitty - table_set(*key_map, "\e[20;9~", to_key("#f9+m")); - - // F10 - table_set(*key_map, "\e[21~", to_key("#f10")); - table_set(*key_map, "\e[21;1~", to_key("#f10+m")); - table_set(*key_map, "\e[21;2~", to_key("#f10+s")); - table_set(*key_map, "\e[21;3~", to_key("#f10+a")); - table_set(*key_map, "\e[21;4~", to_key("#f10+A")); - table_set(*key_map, "\e[21;5~", to_key("#f10+c")); - table_set(*key_map, "\e[21;6~", to_key("#f10+C")); - table_set(*key_map, "\e[21;7~", to_key("#f10+x")); - table_set(*key_map, "\e[21;8~", to_key("#f10+X")); - // F10 - kitty - table_set(*key_map, "\e[21;9~", to_key("#f10+m")); - - // F11 - table_set(*key_map, "\e[23~", to_key("#f11")); - table_set(*key_map, "\e[23;1~", to_key("#f11+m")); - table_set(*key_map, "\e[23;2~", to_key("#f11+s")); - table_set(*key_map, "\e[23;3~", to_key("#f11+a")); - table_set(*key_map, "\e[23;4~", to_key("#f11+A")); - table_set(*key_map, "\e[23;5~", to_key("#f11+c")); - table_set(*key_map, "\e[23;6~", to_key("#f11+C")); - table_set(*key_map, "\e[23;7~", to_key("#f11+x")); - table_set(*key_map, "\e[23;8~", to_key("#f11+X")); - // F11 - kitty - table_set(*key_map, "\e[23;9~", to_key("#f11+m")); - - // F12 - table_set(*key_map, "\e[24~", to_key("#f12")); - table_set(*key_map, "\e[24;1~", to_key("#f12+m")); - table_set(*key_map, "\e[24;2~", to_key("#f12+s")); - table_set(*key_map, "\e[24;3~", to_key("#f12+a")); - table_set(*key_map, "\e[24;4~", to_key("#f12+A")); - table_set(*key_map, "\e[24;5~", to_key("#f12+c")); - table_set(*key_map, "\e[24;6~", to_key("#f12+C")); - table_set(*key_map, "\e[24;7~", to_key("#f12+x")); - table_set(*key_map, "\e[24;8~", to_key("#f12+X")); - // F12 - kitty - table_set(*key_map, "\e[24;9~", to_key("#f12+m")); -} - initialized := false; //input_buffer : [64] u8; // TODO FIXME Input buffer is too small!!! @@ -728,6 +346,10 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { advance(*input_string, to_parse.count); return key; } + TODO If failed to parse... lets return a single char... but it's not working! + else { + to_parse.count = 1; + } } advance(*input_string, to_parse.count); diff --git a/ttt.jai b/ttt.jai index 1a74b37..2921ff0 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1249,7 +1249,7 @@ main :: () { print("- success\n", to_standard_error = true); } - if 1 { + if 0 { print("TEST : test key input\n", to_standard_error = true); auto_release_temp(); TUI.start(); @@ -1277,7 +1277,7 @@ main :: () { print("- success\n", to_standard_error = true); } - if 1 { + if 0 { print("TEST : draw box\n", to_standard_error = true); auto_release_temp(); TUI.start(); // TODO Should start() call flush_input internally? @@ -1292,7 +1292,7 @@ main :: () { print("- success\n", to_standard_error = true); } - if 1 { + if 0 { print("TEST : get terminal size\n", to_standard_error = true); auto_release_temp(); TUI.start(); @@ -1309,7 +1309,7 @@ main :: () { print("- success\n", to_standard_error = true); } - if 1 { + if 0 { print("TEST : set terminal title\n", to_standard_error = true); TUI.start(); title := "BAZINGA"; -- cgit v1.2.3 From b47287aaa3cfa384ec683ff58e0d247e8bad32b1 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 21 Mar 2024 00:50:42 +0000 Subject: Fix get_key to support escape codes. --- modules/TUI/key_map.jai | 2 +- modules/TUI/module.jai | 48 +++++++++++++++++------------------------------- ttt.jai | 13 ++++++++++--- 3 files changed, 28 insertions(+), 35 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/key_map.jai b/modules/TUI/key_map.jai index 42acabf..b272253 100644 --- a/modules/TUI/key_map.jai +++ b/modules/TUI/key_map.jai @@ -31,7 +31,7 @@ setup_key_map :: () { */ // Up // g i k l x - // table_set(*key_map, "\e[A", to_key("#up")); // + + + + + + table_set(*key_map, "\e[A", to_key("#up")); // + + + + + table_set(*key_map, "\e[1;1A", to_key("#up")); // table_set(*key_map, "\e[1;2A", to_key("#up+$")); // + + + + table_set(*key_map, "\e[1;3A", to_key("#up+a")); // + + + + diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index d93e4ff..93e410e 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -126,6 +126,8 @@ Colors8b :: struct { White :: 15; } +// Fix color tables using this: https://devmemo.io/cheatsheets/terminal_escape_code/ + // TODO Maybe rename. set_style_colors :: (foreground: u8, background: u8) { print( @@ -261,15 +263,6 @@ set_next_key :: (key: Key) { get_key :: (timeout_milliseconds: s32 = -1) -> Key { assert_is_initialized(); - /* - TODO - get_key already deals with utf8 codes, but we don't know when we're receiving ANSI escape codes. If initial key is escape and other keys are awaiting in the input buffer, we need to parse them as escaped sequences. See wikiedia* for help on that. Lets use the escape sequences used on windows amd forget all others. Those should be the most used ones; at least they are the cross-platform compatible ones :P - * https://en.m.wikipedia.org/wiki/ANSI_escape_code - - Check this - https://devmemo.io/cheatsheets/terminal_escape_code/ - */ - // BBBB BBBB & 1100 0000 == 10XX XXXX -> is continuation byte is_utf8_continuation_byte :: inline (byte: u8) -> bool { @@ -321,38 +314,31 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { if input_string.count == 0 return Keys.None; - // Assume we're parsing just a single char. + // By default, parse a single UTF8 character (1 to 4 bytes). to_parse := input_string; - to_parse.count = 1; - - // Try to parse UTF8 character. - if is_utf8_continuation_byte(input_string[0]) { - to_parse.count = count_utf8_bytes(input_string[0]); - } - + to_parse.count = count_utf8_bytes(input_string[0]); + defer advance(*input_string, to_parse.count); // Advance over parsed input. + // Try to parse escape code. if input_string[0] == #char "\e" && input_string.count > 1 { - assert(input_string.count <= KEY_SIZE, "Received oversized terminal sequence."); // TODO - to_parse.count = ifx input_string.count > KEY_SIZE then KEY_SIZE else input_string.count; // TODO We should look into the input_string and search for the following escape sequence or somehting!? - // WIP HERE - // A possible way to solve this is to create a LUT, and then, grow the to_parse.count from 2 to KEY_SIZE and return as soon - // as we ding a match on the LUT. - // If the LUT is too big... maybe use a hash-table. + // Limit number of chars to parse. + to_parse.count = ifx input_string.count > KEY_SIZE then KEY_SIZE else input_string.count; - // TEMPORARY HACK + // Search for the longest escape code. key, success := table_find(*key_map, to_parse); + while success == false && to_parse.count > 1 { + to_parse.count -= 1; + key, success = table_find(*key_map, to_parse); + } + + // If an escape code was found, use it. if success { - advance(*input_string, to_parse.count); return key; } - TODO If failed to parse... lets return a single char... but it's not working! - else { - to_parse.count = 1; - } + // else { ... } If we entered this block and did not succeed, then we'll return a single escape character.â } - - advance(*input_string, to_parse.count); + return to_key(to_parse); } diff --git a/ttt.jai b/ttt.jai index 2921ff0..9b08f6d 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1332,7 +1332,12 @@ main :: () { TUI.set_terminal_title("bazinga"); key: TUI.Key = #char "d"; last_none_char := "X"; + + size_r, size_c := TUI.get_terminal_size(); + TUI.clear_terminal(); + TUI.draw_box(1, 1, size_c, size_r); drop_down := 0; + while(key != #char "q") { __mark := get_temporary_storage_mark(); @@ -1345,7 +1350,9 @@ main :: () { case TUI.Keys.Resize; #through; case #char "c"; { + size_r, size_c = TUI.get_terminal_size(); TUI.clear_terminal(); + TUI.draw_box(1, 1, size_c, size_r); drop_down = 0; } @@ -1369,13 +1376,13 @@ main :: () { drop_down += 1; } } - size_r, size_c := TUI.get_terminal_size(); - TUI.draw_box(1, 1, size_c, size_r); + + x := ifx size_r > 1 then size_r-1 else 1; y := ifx size_c > 24 then size_c-24 else 1; + TUI.set_cursor_position(x, y); print("size(CxR): %x%\n", size_c, size_r); - key = TUI.get_key(1000); set_temporary_storage_mark(__mark); -- cgit v1.2.3 From f1fe4f6bcf27e0843d424f4885b0913e220ade2e Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 24 Mar 2024 00:50:01 +0000 Subject: Improved robustness and code clarity on get_key. --- modules/TUI/module.jai | 8 +++++--- ttt.jai | 7 +++---- 2 files changed, 8 insertions(+), 7 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 93e410e..6aeac68 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -332,11 +332,13 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { key, success = table_find(*key_map, to_parse); } - // If an escape code was found, use it. + if success { - return key; + return key; // Escape code found, return it. + } + else { + to_parse.count = 1; // No escape code found, return a single (escape) character. } - // else { ... } If we entered this block and did not succeed, then we'll return a single escape character.â } return to_key(to_parse); diff --git a/ttt.jai b/ttt.jai index 9b08f6d..593e30f 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1324,9 +1324,8 @@ main :: () { assert(key == #char "y", "# Failed to set terminal title.\n"); print("- success\n", to_standard_error = true); } - - // TODO Setup this test... check for Meta+FX or Ctrl+FX compatibility between windows and *nix. - if 1 { + + if 0 { print("TEST : print keys and set terminal title\n", to_standard_error = true); TUI.start(); TUI.set_terminal_title("bazinga"); @@ -1390,7 +1389,7 @@ main :: () { TUI.stop(); } - if 0 { + if 1 { print("TEST : user input\n", to_standard_error = true); auto_release_temp(); TUI.start(); -- cgit v1.2.3 From 26b6d4c48be97e20b25063f136ec978753fead67 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 24 Mar 2024 01:20:32 +0000 Subject: WIP : Fixing read_input_line. --- modules/TUI/module.jai | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 6aeac68..97a520b 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -187,19 +187,9 @@ to_string :: inline (key: Key) -> string { // TODO FIXME TEMPORARY MEMORY } is_escape_code :: inline (key: Key) -> bool { - /* - TODO Check if LSB is not # but there is a `#`, then it's a escape code... or NONE... or RESIZE :S - Or...we could change the special codes and set the `#` at the end... then we could simply do: - return (key && 0x00FF) ^ # == 0 && (key && 0xFF00) == 0 - */ - - result := false; - - while key != 0 { - key >>= 8; - result |= ((key ^ #char "#") == 0); - } - return result; + beginsWithEscape := ((key & 0xFF) ^ #char "#") == 0; + hasSomethingElse := (key & (~0xFF)) != 0; + return beginsWithEscape && hasSomethingElse; } Keys :: struct #type_info_none { @@ -215,8 +205,8 @@ Keys :: struct #type_info_none { Up : Key : #run to_key("#up"); Down : Key : #run to_key("#down"); - Right : Key : #run to_key("right"); - Left : Key : #run to_key("left"); + Right : Key : #run to_key("#right"); + Left : Key : #run to_key("#left"); Home : Key : #run to_key("#home"); End : Key : #run to_key("#end"); @@ -412,11 +402,13 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // TODO Some of these may be nice to have: // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line - while key != Keys.Resize && key != Keys.Escape { + while true { // TODO How to alloc/release temporary memory here? key = get_key(); if key == { + case Keys.Resize; #through; + case Keys.Escape; #through; case Keys.Enter; break; @@ -424,10 +416,16 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { if idx == 0 continue; idx -= 1; + case Keys.Home; + idx = 0; + case Keys.Right; if idx == str.count-1 continue; idx += 1; + case Keys.End; + idx = str.count-1; + case Keys.Delete; if idx == str.count-1 continue; for idx..str.count-2 { @@ -442,10 +440,11 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { } case; - if is_escape_code(key) continue; // TODO NOT WORKING FIX THIS - key_str := to_string(key); - str.data[idx] = key_str.data[0]; - idx += 1; + if !is_escape_code(key) { + key_str := to_string(key); + str.data[idx] = key_str.data[0]; + idx += 1; + } } -- cgit v1.2.3 From b5b70e2977e2ef6510e2b4e3d117cde25cac7c27 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 24 Mar 2024 01:36:11 +0000 Subject: Finished base implementation of read_input_line. --- modules/TUI/module.jai | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 97a520b..9ef0a7e 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -425,12 +425,17 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { case Keys.End; idx = str.count-1; + while str[idx] == 0 && idx > 0 { + idx -= 1; + } + idx += 1; case Keys.Delete; if idx == str.count-1 continue; for idx..str.count-2 { str.data[it] = str.data[it+1]; } + str.data[str.count-1] = 0; case Keys.Backspace; if idx == 0 continue; @@ -438,10 +443,15 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { for idx..str.count-2 { str.data[it] = str.data[it+1]; } + str.data[str.count-1] = 0; case; + if idx >= str.count continue; if !is_escape_code(key) { key_str := to_string(key); + for < str.count-1..idx { + str.data[it] = str.data[it-1]; + } str.data[idx] = key_str.data[0]; idx += 1; } -- cgit v1.2.3 From 95646a4d1e62fe70461d2ad6adc878442ea464d1 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 24 Mar 2024 01:38:35 +0000 Subject: Fixed typo on comment. --- modules/TUI/module.jai | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 9ef0a7e..5993676 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -473,7 +473,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { /* Use the get_key to read user input and show it on screen... should allow to move the cursor left and right and to delete/backspace. Enter should end the input, returning the input string and the Enter key. - Escape should discard the input returning an empty string and a None key. + Escape should discard the input returning an empty string and a Escape key. Resize should discard the input returning an empty string and a Resize key. */ // result := ifx key == Keys.Enter then builder_to_string(*builder) else ""; -- cgit v1.2.3 From 1ea51da155882a946cc0d9af8e7f90d674682c3b Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 24 Mar 2024 01:57:38 +0000 Subject: Implemented hidden input on read_input_line. --- modules/TUI/module.jai | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 5993676..98afe3d 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -389,9 +389,8 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { // TODO UNTESTED read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { assert(count_limit >= 0, "Invalid value on count_limit parameter."); - str := alloc_string(count_limit); + str := alloc_string(count_limit); // TODO Alloc like a boss... - // TODO Show blinking cursor write_strings(Commands.StartBlinking, Commands.BlinkingUnderlineShape); row, col := get_cursor_position(); @@ -461,10 +460,19 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // append(*builder, str); // set_cursor_position(row, col); // write_builder(*builder, false); - set_cursor_position(row, col+idx); - for idx..count_limit print_character(#char " "); - set_cursor_position(row, col); - write_string(str); + if is_visible { + set_cursor_position(row, col+idx); + for idx..count_limit print_character(#char " "); + set_cursor_position(row, col); + write_string(str); + } + else { + set_cursor_position(row, col); + for 0..str.count-1 { + char := cast(u8) ifx str[it] != 0 then #char "*" else #char " "; + print_character(char); + } + } // print(">%<", builder_to_string(*builder,, temporary_allocator)); set_cursor_position(row, col+idx); } -- cgit v1.2.3 From 10215fb3b2d5a178b8ab0419d58d30af3abab625 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 29 Mar 2024 01:04:23 +0000 Subject: Fixed cursor control on read_input_line. --- modules/TUI/module.jai | 51 +++++++++++++++++++++++++------------------------- ttt.jai | 10 ++++++---- 2 files changed, 31 insertions(+), 30 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 98afe3d..5fdf3b9 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -389,18 +389,18 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { // TODO UNTESTED read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { assert(count_limit >= 0, "Invalid value on count_limit parameter."); - str := alloc_string(count_limit); // TODO Alloc like a boss... - write_strings(Commands.StartBlinking, Commands.BlinkingUnderlineShape); - - row, col := get_cursor_position(); + str := alloc_string(count_limit); // TODO Alloc like a boss... + str.count = 0; idx := 0; - key := Keys.None; - // TODO Some of these may be nice to have: // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line + row, col := get_cursor_position(); + write_strings(Commands.StartBlinking, Commands.BlinkingUnderlineShape); + + key := Keys.None; while true { // TODO How to alloc/release temporary memory here? key = get_key(); @@ -412,29 +412,24 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { break; case Keys.Left; - if idx == 0 continue; - idx -= 1; + if idx > 0 then idx -= 1; + + case Keys.Right; + if idx < str.count then idx += 1; case Keys.Home; idx = 0; - case Keys.Right; - if idx == str.count-1 continue; - idx += 1; - case Keys.End; - idx = str.count-1; - while str[idx] == 0 && idx > 0 { - idx -= 1; - } - idx += 1; + idx = str.count; case Keys.Delete; - if idx == str.count-1 continue; + if idx == str.count continue; for idx..str.count-2 { str.data[it] = str.data[it+1]; } str.data[str.count-1] = 0; + str.count -= 1; case Keys.Backspace; if idx == 0 continue; @@ -443,17 +438,21 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { str.data[it] = str.data[it+1]; } str.data[str.count-1] = 0; + str.count -= 1; case; - if idx >= str.count continue; - if !is_escape_code(key) { - key_str := to_string(key); - for < str.count-1..idx { - str.data[it] = str.data[it-1]; - } - str.data[idx] = key_str.data[0]; - idx += 1; + if idx >= count_limit continue; + if is_escape_code(key) continue; + + for < count_limit..idx+1 { + str.data[it] = str.data[it-1]; } + + key_str := to_string(key); + str.data[idx] = key_str.data[0]; + + if str.count < count_limit then str.count += 1; + idx += 1; } diff --git a/ttt.jai b/ttt.jai index f0a3a9e..6f0f426 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1396,7 +1396,8 @@ main :: () { TUI.clear_terminal(); TUI.set_cursor_position(1, 1); print("Enter some text (use Enter to finish, Esc to cancel, or resize to abort):"); - TUI.set_cursor_position(2, 1); + r, c := TUI.get_cursor_position(); + TUI.set_cursor_position(r+1, 1); str, key := TUI.read_input_line(15); TUI.set_cursor_position(3, 1); if key == { @@ -1421,16 +1422,17 @@ main :: () { } // BUG - SECOND TIME CALLING TO read_input_line fails... + // SECOND TIME CALLING TO read_input_line fails... - if 1 { + if 0 { print("TEST : hidden user input\n", to_standard_error = true); auto_release_temp(); TUI.start(); TUI.clear_terminal(); TUI.set_cursor_position(1, 1); print("Enter some text (use Enter to finish, Esc to cancel, or resize to abort):"); - TUI.set_cursor_position(2, 1); + r, c := TUI.get_cursor_position(); + TUI.set_cursor_position(r+1, 1); str, key := TUI.read_input_line(15, false); TUI.set_cursor_position(3, 1); if key == { -- cgit v1.2.3 From bc5b1e651e17e97177af29762ef7d0558f83db89 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 29 Mar 2024 01:18:46 +0000 Subject: Fixed input echo on read_input_line. --- modules/TUI/module.jai | 9 +++------ ttt.jai | 7 ++----- 2 files changed, 5 insertions(+), 11 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 5fdf3b9..9cc7e5d 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -460,17 +460,14 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // set_cursor_position(row, col); // write_builder(*builder, false); if is_visible { - set_cursor_position(row, col+idx); - for idx..count_limit print_character(#char " "); set_cursor_position(row, col); write_string(str); + for str.count..count_limit-1 print_character(#char " "); } else { set_cursor_position(row, col); - for 0..str.count-1 { - char := cast(u8) ifx str[it] != 0 then #char "*" else #char " "; - print_character(char); - } + for 0..str.count-1 print_character(#char "*"); + for str.count..count_limit-1 print_character(#char " "); } // print(">%<", builder_to_string(*builder,, temporary_allocator)); set_cursor_position(row, col+idx); diff --git a/ttt.jai b/ttt.jai index 6f0f426..7690c68 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1420,17 +1420,14 @@ main :: () { } TUI.stop(); } - - // BUG - // SECOND TIME CALLING TO read_input_line fails... - if 0 { + if 1 { print("TEST : hidden user input\n", to_standard_error = true); auto_release_temp(); TUI.start(); TUI.clear_terminal(); TUI.set_cursor_position(1, 1); - print("Enter some text (use Enter to finish, Esc to cancel, or resize to abort):"); + print("Enter some secret (use Enter to finish, Esc to cancel, or resize to abort):"); r, c := TUI.get_cursor_position(); TUI.set_cursor_position(r+1, 1); str, key := TUI.read_input_line(15, false); -- cgit v1.2.3 From b57c7beee2eb8830cbebdf7593001670e7029327 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 29 Mar 2024 01:26:00 +0000 Subject: First version of read_input_line. --- modules/TUI/module.jai | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 9cc7e5d..796334a 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -386,11 +386,18 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { } } -// TODO UNTESTED read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { + /* + Use the get_key to read user input and show it on screen. + Should allow to move the cursor left and right and to delete/backspace. + Enter should end the input, returning the input string and the Enter key. + Escape should discard the input returning an empty string and a Escape key. + Resize should discard the input returning an empty string and a Resize key. + */ + assert(count_limit >= 0, "Invalid value on count_limit parameter."); - str := alloc_string(count_limit); // TODO Alloc like a boss... + str := alloc_string(count_limit); str.count = 0; idx := 0; @@ -402,7 +409,8 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { key := Keys.None; while true { - // TODO How to alloc/release temporary memory here? + auto_release_temp(); + key = get_key(); if key == { @@ -455,10 +463,6 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { idx += 1; } - - // append(*builder, str); - // set_cursor_position(row, col); - // write_builder(*builder, false); if is_visible { set_cursor_position(row, col); write_string(str); @@ -469,18 +473,12 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { for 0..str.count-1 print_character(#char "*"); for str.count..count_limit-1 print_character(#char " "); } - // print(">%<", builder_to_string(*builder,, temporary_allocator)); + set_cursor_position(row, col+idx); } write_strings(Commands.StopBlinking, Commands.DefaultShape); - /* - Use the get_key to read user input and show it on screen... should allow to move the cursor left and right and to delete/backspace. - Enter should end the input, returning the input string and the Enter key. - Escape should discard the input returning an empty string and a Escape key. - Resize should discard the input returning an empty string and a Resize key. - */ - // result := ifx key == Keys.Enter then builder_to_string(*builder) else ""; + result := ifx key == Keys.Enter then str else ""; return result, key; } -- cgit v1.2.3 From 3ea02310f77dcf6aac26152a13a7ec848c78077f Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 31 Mar 2024 23:47:35 +0100 Subject: WIP : Setup TUI styles. --- modules/TUI/module.jai | 87 ++++++++++++++++++++++++++++++++++++++------------ ttt.jai | 10 ++++-- 2 files changed, 73 insertions(+), 24 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 796334a..225b13e 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -103,40 +103,77 @@ Commands :: struct { QueryWindowSizeInChars :: "\e[18t"; // Emits the window size as: "ESC [ 8 ; t" Where = row and = column. TODO Does not work on windows. } -clear_style :: () { - write_string(#run sprint(Commands.SetGraphicsRendition, "0")); +Palette_8b :: enum u8 { + BLACK :: 0; + MAROON :: 1; + GREEN :: 2; + OLIVE :: 3; + NAVY :: 4; + PURPLE :: 5; + TEAL :: 6; + SILVER :: 7; + GRAY :: 8; + RED :: 9; + LIME :: 10; + YELLOW :: 11; + BLUE :: 12; + MAGENTA :: 13; + CYAN :: 14; + WHITE :: 15; } -Colors8b :: struct { - Black :: 0; - Maroon :: 1; - Green :: 2; - Olive :: 3; - Navy :: 4; - Purple :: 5; - Teal :: 6; - Silver :: 7; - Gray :: 8; - Red :: 9; - Lime :: 10; - Yellow :: 11; - Blue :: 12; - Magenta :: 13; - Cyan :: 14; - White :: 15; +Color_24b :: struct { + r: u8; + g: u8; + b: u8; } // Fix color tables using this: https://devmemo.io/cheatsheets/terminal_escape_code/ + // TODO Maybe rename. -set_style_colors :: (foreground: u8, background: u8) { +set_style_colors :: (foreground: Palette_8b, background: Palette_8b) { print( #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), - foreground, background); + cast(u8)foreground, cast(u8)background); +} + +set_colors_24b :: (foreground_r: Color_24b, background: Color_24b) { + print( + #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), + foreground.r, foreground.g, foreground.b, + background.r, background.g, background.b); } // set_colors_24b :: (foreground_r: u255) // TODO https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit +Style :: struct { + background: Palette_8b; + foreground: Palette_8b; + bold: bool; + underline: bool; + strike_through: bool; + negative: bool; +} + +test :: () { + start(); + set_cursor_position(2, 2); + set_style_colors(.RED, .BLACK); + print(" RED \n"); + set_style_colors(.MAGENTA, .BLACK); + print(" MAGENTA \n"); + set_style_colors(.TEAL, .BLACK); + print(" TEAL \n"); + sleep_milliseconds(3000); + stop(); +} + + +clear_style :: () { + write_string(#run sprint(Commands.SetGraphicsRendition, "0")); +} + set_style :: (bold: bool, underline: bool = false, strike_through: bool = false, negative: bool = false) { print( #run sprint(Commands.SetGraphicsRendition, "%;%;%;%"), @@ -386,6 +423,7 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { } } +// TODO Provide an advanced read_input_line function that allows some styling and to set a placeholder text. read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { /* Use the get_key to read user input and show it on screen. @@ -401,6 +439,13 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { str.count = 0; idx := 0; + // placeholder: string = "", + // { + // copy_size := ifx placeholder.count > str.count then str.count else placeholder.count; + // memcpy(str.data, placeholder.data, copy_size); + // idx = copy_size; + // } + // TODO Some of these may be nice to have: // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line diff --git a/ttt.jai b/ttt.jai index 5229914..35a4bc2 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1059,7 +1059,7 @@ draw_tui :: (db: *Database, layout: *Layout) { // Apply theme. if (task == active_task && task == selected_task) { // attron(COLOR_PAIR(xx Styles.ACTIVE_SELECTED) | A_BOLD); TODO DAM - TUI.set_style_colors(TUI.Colors8b.Red, 6); // TODO TESTING COLORS + TUI.set_style_colors(.RED, 6); // TODO TESTING COLORS TUI.set_style(true, true, true, false); } else if (task == selected_task) { @@ -1067,7 +1067,7 @@ draw_tui :: (db: *Database, layout: *Layout) { TUI.set_style_colors(4, 6); // TODO TESTING COLORS } else if (task == active_task) { - TUI.set_style_colors(TUI.Colors8b.Red, 6); // TODO TESTING COLORS + TUI.set_style_colors(.RED, 6); // TODO TESTING COLORS TUI.set_style(false, false, false, true); // TODO TESTING STYLE // attron(COLOR_PAIR(xx Styles.ACTIVE) | A_BOLD); TODO DAM } @@ -1224,7 +1224,7 @@ prompt_user_key :: (row: int, style: int, message: string) -> TUI.Key { TUI.set_cursor_position(row, 2); write_string(TUI.Commands.DrawingMode); - for 0..size_x-2 { + for 1..size_x-2 { print(TUI.Drawings.Checkerboard); } write_string(TUI.Commands.TextMode); @@ -1236,6 +1236,10 @@ prompt_user_key :: (row: int, style: int, message: string) -> TUI.Key { main :: () { + TUI.test(); + exit(0); + + // -- -- -- Testing TUI -- START perform_test := false; -- cgit v1.2.3 From ddb3e4f4192009a47fa094aaad5e57da9b8c2711 Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 6 Apr 2024 01:51:16 +0100 Subject: Added styles. --- modules/TUI/module.jai | 110 ++++++++-------- modules/TUI/palette_24b.jai | 44 +++++++ modules/TUI/palette_4b.jai | 19 +++ modules/TUI/palette_8b.jai | 307 ++++++++++++++++++++++++++++++++++++++++++++ ttt.jai | 167 +++++++++++++----------- 5 files changed, 514 insertions(+), 133 deletions(-) create mode 100644 modules/TUI/palette_24b.jai create mode 100644 modules/TUI/palette_4b.jai create mode 100644 modules/TUI/palette_8b.jai (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 225b13e..21cd88a 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,3 +1,5 @@ +#module_parameters(COLOR_BIT_DEPTH := 24); + #if OS == { case .LINUX; #load "unix.jai"; @@ -103,84 +105,80 @@ Commands :: struct { QueryWindowSizeInChars :: "\e[18t"; // Emits the window size as: "ESC [ 8 ; t" Where = row and = column. TODO Does not work on windows. } -Palette_8b :: enum u8 { - BLACK :: 0; - MAROON :: 1; - GREEN :: 2; - OLIVE :: 3; - NAVY :: 4; - PURPLE :: 5; - TEAL :: 6; - SILVER :: 7; - GRAY :: 8; - RED :: 9; - LIME :: 10; - YELLOW :: 11; - BLUE :: 12; - MAGENTA :: 13; - CYAN :: 14; - WHITE :: 15; +#if COLOR_BIT_DEPTH == 4 { + #load "palette_4b.jai"; + + set_colors :: inline (foreground: Palette, background: Palette) { + print( + #run sprint("% %", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), + cast(u8)foreground + 30, cast(u8)background + 40); + } } +else #if COLOR_BIT_DEPTH == 8 { + #load "palette_8b.jai"; -Color_24b :: struct { - r: u8; - g: u8; - b: u8; + set_colors :: inline (foreground: Palette, background: Palette) { + print( + #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), + cast(u8)foreground, cast(u8)background); + } } +else { + #load "palette_24b.jai"; -// Fix color tables using this: https://devmemo.io/cheatsheets/terminal_escape_code/ - - -// TODO Maybe rename. -set_style_colors :: (foreground: Palette_8b, background: Palette_8b) { - print( - #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), - cast(u8)foreground, cast(u8)background); -} + Color_24b :: struct { + r: u8; + g: u8; + b: u8; + } -set_colors_24b :: (foreground_r: Color_24b, background: Color_24b) { - print( - #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), - foreground.r, foreground.g, foreground.b, - background.r, background.g, background.b); + set_colors :: inline (foreground: Color_24b, background: Color_24b) { + print( + #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), + foreground.r, foreground.g, foreground.b, + background.r, background.g, background.b); + } } -// set_colors_24b :: (foreground_r: u255) // TODO https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit +#add_context tui_style: Style; Style :: struct { - background: Palette_8b; - foreground: Palette_8b; + #if COLOR_BIT_DEPTH == 4 || COLOR_BIT_DEPTH == 8 { + background: Palette = .BLACK; + foreground: Palette = .WHITE; + } else { + background: Color_24b; + foreground: Color_24b; + } bold: bool; underline: bool; strike_through: bool; negative: bool; } -test :: () { - start(); - set_cursor_position(2, 2); - set_style_colors(.RED, .BLACK); - print(" RED \n"); - set_style_colors(.MAGENTA, .BLACK); - print(" MAGENTA \n"); - set_style_colors(.TEAL, .BLACK); - print(" TEAL \n"); - sleep_milliseconds(3000); - stop(); +set_font_style :: inline (bold: bool, underline: bool = false, strike_through: bool = false, negative: bool = false) { + print( + #run sprint(Commands.SetGraphicsRendition, "%;%;%;%"), + ifx bold then 1 else 22, + ifx underline then 4 else 24, + ifx strike_through then 9 else 29, + ifx negative then 7 else 27); } +set_style :: (style: Style) { + set_font_style(style.bold, style.underline, style.strike_through, style.negative); + set_colors(style.foreground, style.background); + context.tui_style = style; +} clear_style :: () { write_string(#run sprint(Commands.SetGraphicsRendition, "0")); } -set_style :: (bold: bool, underline: bool = false, strike_through: bool = false, negative: bool = false) { - print( - #run sprint(Commands.SetGraphicsRendition, "%;%;%;%"), - ifx bold then 1 else 22, - ifx underline then 4 else 24, - ifx strike_through then 9 else 29, - ifx negative then 7 else 27); +using_style :: (style: Style) #expand { + __style := context.tui_style; + set_style(style); + `defer set_style(__style); } // TODO Maybe make the OS_* procedures as inline?! diff --git a/modules/TUI/palette_24b.jai b/modules/TUI/palette_24b.jai new file mode 100644 index 0000000..7a263a0 --- /dev/null +++ b/modules/TUI/palette_24b.jai @@ -0,0 +1,44 @@ +// https://www.ditig.com/publications/256-colors-cheat-sheet +Palette :: struct { + BLACK :: Color_24b.{0x00, 0x00, 0x00}; + MAROON :: Color_24b.{0x80, 0x00, 0x00}; + GREEN :: Color_24b.{0x00, 0x80, 0x00}; + OLIVE :: Color_24b.{0x80, 0x80, 0x00}; + NAVY :: Color_24b.{0x00, 0x00, 0x80}; + PURPLE :: Color_24b.{0x80, 0x00, 0x80}; + TEAL :: Color_24b.{0x00, 0x80, 0x80}; + SILVER :: Color_24b.{0xC0, 0xC0, 0xC0}; + GRAY :: Color_24b.{0x80, 0x80, 0x80}; + RED :: Color_24b.{0xFF, 0x00, 0x00}; + LIME :: Color_24b.{0x00, 0xFF, 0x00}; + YELLOW :: Color_24b.{0xFF, 0xFF, 0x00}; + BLUE :: Color_24b.{0x00, 0x00, 0xFF}; + MAGENTA :: Color_24b.{0xFF, 0x00, 0xFF}; + CYAN :: Color_24b.{0x00, 0xFF, 0xFF}; + WHITE :: Color_24b.{0xFF, 0xFF, 0xFF}; + + GRAY_3 :: Color_24b.{0x08, 0x08, 0x08}; + GRAY_7 :: Color_24b.{0x12, 0x12, 0x12}; + GRAY_10 :: Color_24b.{0x1C, 0x1C, 0x1C}; + GRAY_14 :: Color_24b.{0x26, 0x26, 0x26}; + GRAY_18 :: Color_24b.{0x30, 0x30, 0x30}; + GRAY_22 :: Color_24b.{0x3A, 0x3A, 0x3A}; + GRAY_26 :: Color_24b.{0x44, 0x44, 0x44}; + GRAY_30 :: Color_24b.{0x4E, 0x4E, 0x4E}; + GRAY_34 :: Color_24b.{0x58, 0x58, 0x58}; + GRAY_37 :: Color_24b.{0x62, 0x62, 0x62}; + GRAY_40 :: Color_24b.{0x6C, 0x6C, 0x6C}; + GRAY_46 :: Color_24b.{0x76, 0x76, 0x76}; + GRAY_50 :: GRAY; + GRAY_54 :: Color_24b.{0x8A, 0x8A, 0x8A}; + GRAY_58 :: Color_24b.{0x94, 0x94, 0x94}; + GRAY_61 :: Color_24b.{0x9E, 0x9E, 0x9E}; + GRAY_65 :: Color_24b.{0xA8, 0xA8, 0xA8}; + GRAY_69 :: Color_24b.{0xB2, 0xB2, 0xB2}; + GRAY_73 :: Color_24b.{0xBC, 0xBC, 0xBC}; + GRAY_77 :: Color_24b.{0xC6, 0xC6, 0xC6}; + GRAY_81 :: Color_24b.{0xD0, 0xD0, 0xD0}; + GRAY_85 :: Color_24b.{0xDA, 0xDA, 0xDA}; + GRAY_89 :: Color_24b.{0xE4, 0xE4, 0xE4}; + GRAY_93 :: Color_24b.{0xEE, 0xEE, 0xEE}; +} diff --git a/modules/TUI/palette_4b.jai b/modules/TUI/palette_4b.jai new file mode 100644 index 0000000..b0317d2 --- /dev/null +++ b/modules/TUI/palette_4b.jai @@ -0,0 +1,19 @@ +// https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit +Palette :: enum u8 { + BLACK :: 0; + MAROON :: 1; + GREEN :: 2; + OLIVE :: 3; + NAVY :: 4; + PURPLE :: 5; + TEAL :: 6; + SILVER :: 7; + GRAY :: 60; + RED :: 61; + LIME :: 62; + YELLOW :: 63; + BLUE :: 64; + MAGENTA :: 65; + CYAN :: 66; + WHITE :: 67; +} diff --git a/modules/TUI/palette_8b.jai b/modules/TUI/palette_8b.jai new file mode 100644 index 0000000..36a512f --- /dev/null +++ b/modules/TUI/palette_8b.jai @@ -0,0 +1,307 @@ +// https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit +Palette :: enum u8 { + BLACK :: 0; + MAROON :: 1; + GREEN :: 2; + OLIVE :: 3; + NAVY :: 4; + PURPLE :: 5; + TEAL :: 6; + SILVER :: 7; + GRAY :: 8; + RED :: 9; + LIME :: 10; + YELLOW :: 11; + BLUE :: 12; + MAGENTA :: 13; + CYAN :: 14; + WHITE :: 15; + + + x000000 :: 16; + x00005F :: 17; + x000087 :: 18; + x0000AF :: 19; + x0000D7 :: 20; + x0000FF :: 21; + + x005F00 :: 22; + x005F5F :: 23; + x005F87 :: 24; + x005FAF :: 25; + x005FD7 :: 26; + x005FFF :: 27; + + x008700 :: 28; + x00875F :: 29; + x008787 :: 30; + x0087AF :: 31; + x0087D7 :: 32; + x0087FF :: 33; + + x00AF00 :: 34; + x00AF5F :: 35; + x00AF87 :: 36; + x00AFAF :: 37; + x00AFD7 :: 38; + x00AFFF :: 39; + + x00D700 :: 40; + x00D75F :: 41; + x00D787 :: 42; + x00D7AF :: 43; + x00D7D7 :: 44; + x00D7FF :: 45; + + x00FF00 :: 46; + x00FF5F :: 47; + x00FF87 :: 48; + x00FFAF :: 49; + x00FFD7 :: 50; + x00FFFF :: 51; + + + x5F0000 :: 52; + x5F005F :: 53; + x5F0087 :: 54; + x5F00AF :: 55; + x5F00D7 :: 56; + x5F00FF :: 57; + + x5F5F00 :: 58; + x5F5F5F :: 59; + x5F5F87 :: 60; + x5F5FAF :: 61; + x5F5FD7 :: 62; + x5F5FFF :: 63; + + x5F8700 :: 64; + x5F875F :: 65; + x5F8787 :: 66; + x5F87AF :: 67; + x5F87D7 :: 68; + x5F87FF :: 69; + + x5FAF00 :: 70; + x5FAF5F :: 71; + x5FAF87 :: 72; + x5FAFAF :: 73; + x5FAFD7 :: 74; + x5FAFFF :: 75; + + x5FD700 :: 76; + x5FD75F :: 77; + x5FD787 :: 78; + x5FD7AF :: 79; + x5FD7D7 :: 80; + x5FD7FF :: 81; + + x5FFF00 :: 82; + x5FFF5F :: 83; + x5FFF87 :: 84; + x5FFFAF :: 85; + x5FFFD7 :: 86; + x5FFFFF :: 87; + + + x870000 :: 88; + x87005F :: 89; + x870087 :: 90; + x8700AF :: 91; + x8700D7 :: 92; + x8700FF :: 93; + + x875F00 :: 94; + x875F5F :: 95; + x875F87 :: 96; + x875FAF :: 97; + x875FD7 :: 98; + x875FFF :: 99; + + x878700 :: 100; + x87875F :: 101; + x878787 :: 102; + x8787AF :: 103; + x8787D7 :: 104; + x8787FF :: 105; + + x87AF00 :: 106; + x87AF5F :: 107; + x87AF87 :: 108; + x87AFAF :: 109; + x87AFD7 :: 110; + x87AFFF :: 111; + + x87D700 :: 112; + x87D75F :: 113; + x87D787 :: 114; + x87D7AF :: 115; + x87D7D7 :: 116; + x87D7FF :: 117; + + x87FF00 :: 118; + x87FF5F :: 119; + x87FF87 :: 120; + x87FFAF :: 121; + x87FFD7 :: 122; + x87FFFF :: 123; + + + xAF0000 :: 124; + xAF005F :: 125; + xAF0087 :: 126; + xAF00AF :: 127; + xAF00D7 :: 128; + xAF00FF :: 129; + + xAF5F00 :: 130; + xAF5F5F :: 131; + xAF5F87 :: 132; + xAF5FAF :: 133; + xAF5FD7 :: 134; + xAF5FFF :: 135; + + xAF8700 :: 136; + xAF875F :: 137; + xAF8787 :: 138; + xAF87AF :: 139; + xAF87D7 :: 140; + xAF87FF :: 141; + + xAFAF00 :: 142; + xAFAF5F :: 143; + xAFAF87 :: 144; + xAFAFAF :: 145; + xAFAFD7 :: 146; + xAFAFFF :: 147; + + xAFD700 :: 148; + xAFD75F :: 149; + xAFD787 :: 150; + xAFD7AF :: 151; + xAFD7D7 :: 152; + xAFD7FF :: 153; + + xAFFF00 :: 154; + xAFFF5F :: 155; + xAFFF87 :: 156; + xAFFFAF :: 157; + xAFFFD7 :: 158; + xAFFFFF :: 159; + + + xD70000 :: 160; + xD7005F :: 161; + xD70087 :: 162; + xD700AF :: 163; + xD700D7 :: 164; + xD700FF :: 165; + + xD75F00 :: 166; + xD75F5F :: 167; + xD75F87 :: 168; + xD75FAF :: 169; + xD75FD7 :: 170; + xD75FFF :: 171; + + xD78700 :: 172; + xD7875F :: 173; + xD78787 :: 174; + xD787AF :: 175; + xD787D7 :: 176; + xD787FF :: 177; + + xD7AF00 :: 178; + xD7AF5F :: 179; + xD7AF87 :: 180; + xD7AFAF :: 181; + xD7AFD7 :: 182; + xD7AFFF :: 183; + + xD7D700 :: 184; + xD7D75F :: 185; + xD7D787 :: 186; + xD7D7AF :: 187; + xD7D7D7 :: 188; + xD7D7FF :: 189; + + xD7FF00 :: 190; + xD7FF5F :: 191; + xD7FF87 :: 192; + xD7FFAF :: 193; + xD7FFD7 :: 194; + xD7FFFF :: 195; + + + xFF0000 :: 196; + xFF005F :: 197; + xFF0087 :: 198; + xFF00AF :: 199; + xFF00D7 :: 200; + xFF00FF :: 201; + + xFF5F00 :: 202; + xFF5F5F :: 203; + xFF5F87 :: 204; + xFF5FAF :: 205; + xFF5FD7 :: 206; + xFF5FFF :: 207; + + xFF8700 :: 208; + xFF875F :: 209; + xFF8787 :: 210; + xFF87AF :: 211; + xFF87D7 :: 212; + xFF87FF :: 213; + + xFFAF00 :: 214; + xFFAF5F :: 215; + xFFAF87 :: 216; + xFFAFAF :: 217; + xFFAFD7 :: 218; + xFFAFFF :: 219; + + xFFD700 :: 220; + xFFD75F :: 221; + xFFD787 :: 222; + xFFD7AF :: 223; + xFFD7D7 :: 224; + xFFD7FF :: 225; + + xFFFF00 :: 226; + xFFFF5F :: 227; + xFFFF87 :: 228; + xFFFFAF :: 229; + xFFFFD7 :: 230; + xFFFFFF :: 231; + + + // Grayscale + x080808 :: 232; + x121212 :: 233; + x1C1C1C :: 234; + x262626 :: 235; + x303030 :: 236; + x3A3A3A :: 237; + + x444444 :: 238; + x4E4E4E :: 239; + x585858 :: 240; + x636363 :: 241; + x6C6C6C :: 242; + x767676 :: 243; + + x808080 :: 244; + x8A8A8A :: 245; + x949494 :: 246; + x9E9E9E :: 247; + xA8A8A8 :: 248; + xB2B2B2 :: 249; + + xBCBCBC :: 250; + xC6C6C6 :: 251; + xD0D0D0 :: 252; + xDADADA :: 253; + xE4E4E4 :: 254; + xEEEEEE :: 255; +} diff --git a/ttt.jai b/ttt.jai index 35a4bc2..a07502f 100644 --- a/ttt.jai +++ b/ttt.jai @@ -25,7 +25,7 @@ #import "File_Utilities"; #import "String"; #import "Integer_Saturating_Arithmetic"; -TUI :: #import "TUI"; +TUI :: #import "TUI"(COLOR_BIT_DEPTH=8); // TODO List: @@ -84,13 +84,39 @@ size_y : int; pos_x : int; pos_y : int; -Styles :: enum s16 { - SELECTED :: 1; - SELECTED_INVERTED; - ACTIVE; - ACTIVE_SELECTED; - ERROR; -} +style_default := TUI.Style.{ + background = TUI.Palette.BLACK, + foreground = TUI.Palette.WHITE, +}; + +style_selected := TUI.Style.{ + background = TUI.Palette.CYAN, + foreground = TUI.Palette.BLACK, +}; + +style_selected_inverted := TUI.Style.{ + background = TUI.Palette.BLACK, + foreground = TUI.Palette.CYAN, + bold = true, +}; + +style_active := TUI.Style.{ + background = TUI.Palette.BLACK, + foreground = TUI.Palette.BLUE, + bold = true, +}; + +style_active_selected := TUI.Style.{ + background = TUI.Palette.NAVY, + foreground = TUI.Palette.WHITE, + bold = true, +}; + +style_error := TUI.Style.{ + background = TUI.Palette.BLACK, + foreground = TUI.Palette.RED, + bold = true, +}; Layouts :: enum u8 { NORMAL; @@ -157,7 +183,7 @@ trigger_autosave :: () { show_processing :: () { TUI.set_cursor_position(1, 1); - // TODO Maybe add some color? + TUI.using_style(style_active); write_strings(TUI.Commands.DrawingMode, TUI.Drawings.Diamond, TUI.Commands.TextMode); } @@ -915,22 +941,7 @@ initialize_tui :: () { } } - // TODO DAM TUI.start(); - // stdscr = initscr(); // Start curses mode. TODO DAM - // cbreak(); // Line buffering disabled; pass on everty thing to me. TODO DAM - // keypad(stdscr, true); // I need those nifty F1..F12. TODO DAM - // curs_set(0); // Set cursor invisible. TODO DAM - // noecho(); // Disable echoing input characters. TODO DAM - - // Initialize pairs of colors. - //start_color(); TODO DAM - //use_default_colors(); // Using default (-1) instead of COLOR_BLACK. TODO DAM - //init_pair(xx Styles.SELECTED, COLOR_BLACK, COLOR_CYAN); TODO DAM - //init_pair(xx Styles.SELECTED_INVERTED, COLOR_CYAN, -1); TODO DAM - //init_pair(xx Styles.ACTIVE, COLOR_BLUE, -1); TODO DAM - //init_pair(xx Styles.ACTIVE_SELECTED, COLOR_WHITE, COLOR_BLUE); TODO DAM - //init_pair(xx Styles.ERROR, COLOR_RED, -1); TODO DAM } update_layout :: () { @@ -1015,10 +1026,13 @@ draw_tui :: (db: *Database, layout: *Layout) { // Apply theme. if (idx == now_week_day && active_task != null) { - // attron(COLOR_PAIR(xx Styles.ACTIVE) | A_BOLD); TODO DAM + TUI.set_style(style_active); } else if (idx == now_week_day) { - // attron(COLOR_PAIR(xx Styles.SELECTED_INVERTED) | A_BOLD); TODO DAM + TUI.set_style(style_selected_inverted); + } + else { + TUI.set_style(style_default); } col = *layout.columns[L_DAYS_IDX + idx]; @@ -1026,9 +1040,6 @@ draw_tui :: (db: *Database, layout: *Layout) { TUI.set_cursor_position(y, x + col.alignment_offset); write_string(col.header); x += col.width; - - // Reset theme. - //attrset(A_NORMAL); TODO DAM } // Headers : total @@ -1058,18 +1069,13 @@ draw_tui :: (db: *Database, layout: *Layout) { // Apply theme. if (task == active_task && task == selected_task) { - // attron(COLOR_PAIR(xx Styles.ACTIVE_SELECTED) | A_BOLD); TODO DAM - TUI.set_style_colors(.RED, 6); // TODO TESTING COLORS - TUI.set_style(true, true, true, false); + TUI.set_style(style_active_selected); } else if (task == selected_task) { - // attron(COLOR_PAIR(xx Styles.SELECTED)); TODO DAM - TUI.set_style_colors(4, 6); // TODO TESTING COLORS + TUI.set_style(style_selected); } else if (task == active_task) { - TUI.set_style_colors(.RED, 6); // TODO TESTING COLORS - TUI.set_style(false, false, false, true); // TODO TESTING STYLE - // attron(COLOR_PAIR(xx Styles.ACTIVE) | A_BOLD); TODO DAM + TUI.set_style(style_active); } // Task title. @@ -1136,19 +1142,19 @@ draw_tui :: (db: *Database, layout: *Layout) { // Apply theme. if (idx == now_week_day && active_task != null) { - // attron(COLOR_PAIR(xx Styles.ACTIVE) | A_BOLD); TODO DAM + TUI.set_style(style_active); } else if (idx == now_week_day) { - // attron(COLOR_PAIR(xx Styles.SELECTED_INVERTED) | A_BOLD); TODO DAM + TUI.set_style(style_selected_inverted); + } + else { + TUI.set_style(style_default); } column_width = layout.columns[L_DAYS_IDX + idx].width; total_time = add(total_time, daily_total); print_time(y, x, daily_total, column_width); x += column_width; - - // Reset theme. - //attrset(A_NORMAL); TODO DAM } x += 1; print_time(y, x, total_time, layout.columns[L_TOTAL_IDX].width); @@ -1164,9 +1170,7 @@ free_memory :: () { //reset_temporary_storage(); } -read_input_string :: (row: int, column: int, style: s32, input_limit: int, padding: int = 0) -> value: string, success: bool { - - // TODO We still need to apply the style. +read_input_string :: (row: int, column: int, input_limit: int, padding: int = 0) -> value: string, success: bool { // TODO Draw padding (at end of inputbox)... padding was renamed to input_width... is this the best name? // TODO Maybe add another optional arg with the placeholder text (to be preset on the input)? @@ -1188,7 +1192,7 @@ read_input_string :: (row: int, column: int, style: s32, input_limit: int, paddi } // Returns success. -read_input_int :: (row: int, style: s32, message: string) -> value: int, success: bool { +read_input_int :: (row: int, message: string) -> value: int, success: bool { // attron(xx style); TODO DAM //move(xx row, 1); TODO DAM //addch(ACS_CKBOARD); TODO DAM @@ -1198,14 +1202,14 @@ read_input_int :: (row: int, style: s32, message: string) -> value: int, success // Get line number. input_pos_x := 1;//getcurx(stdscr); TODO DAM input_width := size_x - input_pos_x; - str := read_input_string(row, input_pos_x, style, input_width); + str := read_input_string(row, input_pos_x, input_width); value, success := parse_int(*str); return value, success; } // Shows message to user and waits for user key press. -prompt_user_key :: (row: int, style: int, message: string) -> TUI.Key { +prompt_user_key :: (row: int, message: string) -> TUI.Key { /* assert(message.data != null, ASSERT_NOT_NULL, "message"); attron(xx style); @@ -1236,10 +1240,6 @@ prompt_user_key :: (row: int, style: int, message: string) -> TUI.Key { main :: () { - TUI.test(); - exit(0); - - // -- -- -- Testing TUI -- START perform_test := false; @@ -1656,9 +1656,14 @@ main :: () { db := *database; layout := *layouts[Layouts.COMPACT]; + action_style: TUI.Style; + + TUI.flush_input(); TUI.set_next_key(TUI.Keys.Resize); while (true) { + + TUI.set_style(style_default); if (is_terminal_too_small) { INVALID_WINDOW_MESSAGE :: "Terminal is too small: minimum 60x3."; @@ -1679,18 +1684,14 @@ main :: () { // TODO WIP Remove `selected_task` and `active_task` and helper functions. selected_task := get_selected_task(db); active_task := get_active_task(db); - action_style: s32; - error_style: s32; selected_task_row: int; { // TODO Recheck this code. using db; - action_style = A_BOLD | COLOR_PAIR(xx - ifx selected_idx == active_idx && selected_idx != -1 then Styles.ACTIVE - else Styles.SELECTED_INVERTED); - error_style = A_BOLD | COLOR_PAIR(xx Styles.ERROR); - selected_task_row = ifx is_terminal_too_small then 0 - else ifx (selected_idx < 0) then 1 - else (selected_idx % layout_tasks_rows) + NUM_HEADER_ROWS + 1; + action_style = ifx selected_idx == active_idx && selected_idx != -1 then style_active else style_selected_inverted; + + selected_task_row = ifx is_terminal_too_small then 0 + else ifx (selected_idx < 0) then 1 + else (selected_idx % layout_tasks_rows) + NUM_HEADER_ROWS + 1; } if key == { @@ -1736,7 +1737,8 @@ main :: () { case #char "n"; #through; case #char "N"; if is_database_full(db) { - prompt_user_key(selected_task_row, error_style, "Unable to create task: database is full."); + TUI.using_style(style_error); + prompt_user_key(selected_task_row, "Unable to create task: database is full."); continue; } @@ -1762,7 +1764,8 @@ main :: () { // Change task name. // TODO WIPWIPWIP - input := read_input_string(selected_task_row, 1, action_style, Task.name.count, size_x - 2 - Task.name.count); + TUI.using_style(action_style); + input := read_input_string(selected_task_row, 1, Task.name.count, size_x - 2 - Task.name.count); if is_empty_string(input) == false { replace_chars(input, "\t\x0B\x0C\r", #char " "); // Replace weird spaces with space. memset(selected_task.name.data, 0, Task.name.count); @@ -1773,16 +1776,18 @@ main :: () { // Reset task timers. case TUI.Keys.Backspace; if (selected_task == null) continue; - - if (prompt_user_key(selected_task_row, action_style, "Press enter to reset task.") == TUI.Keys.Enter) { + + TUI.using_style(action_style); + if (prompt_user_key(selected_task_row, "Press enter to reset task.") == TUI.Keys.Enter) { reset_task_times(db, db.selected_idx); trigger_autosave(); } case TUI.Keys.Delete; if (selected_task == null || selected_task == active_task) continue; - - if (prompt_user_key(selected_task_row, action_style, "Press enter to delete task.") == TUI.Keys.Enter) { + + TUI.using_style(action_style); + if (prompt_user_key(selected_task_row, "Press enter to delete task.") == TUI.Keys.Enter) { delete_task(db, db.selected_idx); trigger_autosave(); } @@ -1808,7 +1813,8 @@ main :: () { input_pos_x += 1; // Get input string. - input := read_input_string(selected_task_row, input_pos_x, action_style, input_width); // TODO Temp stringzes. + TUI.using_style(action_style); + input := read_input_string(selected_task_row, input_pos_x, input_width); // TODO Temp stringzes. // Abort if input if empty. if is_empty_string(input) continue; @@ -1850,8 +1856,9 @@ main :: () { case #char "m"; #through; case #char "M"; if selected_task == null continue; - - value, success := read_input_int(selected_task_row, action_style, " Move to: "); + + TUI.using_style(action_style); + value, success := read_input_int(selected_task_row, " Move to: "); if success == false continue; move_task(db, db.selected_idx, value-1); // -1 to adjust for zero based index trigger_autosave(); @@ -1860,8 +1867,9 @@ main :: () { case #char "g"; #through; case #char "G"; if selected_task == null continue; - - value, success := read_input_int(selected_task_row, action_style, " Go to: "); + + TUI.using_style(action_style); + value, success := read_input_int(selected_task_row, " Go to: "); if success == false continue; target_index := clamp(value, 1, MAX_DATABASE_TASKS) - 1; select_task(db, target_index); @@ -1872,7 +1880,8 @@ main :: () { if selected_task == null continue; if is_database_full(db) { - prompt_user_key(selected_task_row, error_style, "Unable to duplicate task: database is full."); + TUI.using_style(style_error); + prompt_user_key(selected_task_row, "Unable to duplicate task: database is full."); continue; } @@ -1938,7 +1947,8 @@ main :: () { if (db != *archive || selected_task == null) continue; if is_database_full(*database) { - prompt_user_key(selected_task_row, error_style, "Unable to restore task: database is full."); + TUI.using_style(style_error); + prompt_user_key(selected_task_row, "Unable to restore task: database is full."); continue; } @@ -1953,7 +1963,8 @@ main :: () { case #char "s"; #through; case #char "S"; // TODO The initial part should only decide what's the sorting procedure... then we would would all in a single place. - sort_by := prompt_user_key(selected_task_row, action_style, "Sort by (n) name, (1..7) day, or (t) total time."); + TUI.using_style(action_style); + sort_by := prompt_user_key(selected_task_row, "Sort by (n) name, (1..7) day, or (t) total time."); show_processing(); sort_procedure: (a: Task, b: Task) -> s64; @@ -2001,7 +2012,8 @@ main :: () { case #char "w"; #through; case #char "W"; if (db != *database || db.tasks.count <= 0) continue; - if (prompt_user_key(selected_task_row, action_style, "Press enter to archive duplicates and reset all.") != TUI.Keys.Enter) continue; + TUI.using_style(action_style); + if (prompt_user_key(selected_task_row, "Press enter to archive duplicates and reset all.") != TUI.Keys.Enter) continue; show_processing(); for db.tasks { @@ -2016,7 +2028,8 @@ main :: () { case #char "c"; #through; case #char "C"; if (db.tasks.count <= 0) continue; - if (prompt_user_key(selected_task_row, action_style, "Press enter to coalesce similar tasks.") != TUI.Keys.Enter) continue; + TUI.using_style(action_style); + if (prompt_user_key(selected_task_row, "Press enter to coalesce similar tasks.") != TUI.Keys.Enter) continue; show_processing(); head_idx := 0; -- cgit v1.2.3 From c8fb1f06ec6f83fc71c2f68bb2fc337e971e25b9 Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 6 Apr 2024 02:15:41 +0100 Subject: Improved user input prompts. --- modules/TUI/module.jai | 8 +++++++- ttt.jai | 45 ++++++++++++++++++--------------------------- 2 files changed, 25 insertions(+), 28 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 21cd88a..4e67db5 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -448,7 +448,13 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line row, col := get_cursor_position(); - write_strings(Commands.StartBlinking, Commands.BlinkingUnderlineShape); + write_strings(Commands.StartBlinking, Commands.BlinkingBarShape); + + // Clear line for input. + for 1..count_limit { + print_character(#char " "); + } + set_cursor_position(row, col); key := Keys.None; while true { diff --git a/ttt.jai b/ttt.jai index a07502f..517723d 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1175,8 +1175,6 @@ read_input_string :: (row: int, column: int, input_limit: int, padding: int = 0) // TODO Draw padding (at end of inputbox)... padding was renamed to input_width... is this the best name? // TODO Maybe add another optional arg with the placeholder text (to be preset on the input)? - // TODO WIPWIPWIP - column += 1; // TODO FIX THE NCURSES INDEXING CHAOS TUI.set_cursor_position(row, column + input_limit); @@ -1193,15 +1191,24 @@ read_input_string :: (row: int, column: int, input_limit: int, padding: int = 0) // Returns success. read_input_int :: (row: int, message: string) -> value: int, success: bool { - // attron(xx style); TODO DAM - //move(xx row, 1); TODO DAM - //addch(ACS_CKBOARD); TODO DAM - //addstr(message.data); // TODO Convert to C type TODO DAM - //attrset(A_NORMAL); TODO DAM + TUI.set_cursor_position(row, 2); + write_string(TUI.Commands.DrawingMode); + for 1..size_x-2 { + print(TUI.Drawings.Checkerboard); + } + write_string(TUI.Commands.TextMode); + + TUI.set_cursor_position(row, 3); + write_strings(" ", message, " "); // Get line number. - input_pos_x := 1;//getcurx(stdscr); TODO DAM - input_width := size_x - input_pos_x; + input_pos_x := 1 + 1 + 1 + message.count + 1; + input_width := size_x - input_pos_x - 1; + + style_input := context.tui_style; + style_input.underline = true; + TUI.using_style(style_input); + str := read_input_string(row, input_pos_x, input_width); value, success := parse_int(*str); @@ -1210,22 +1217,6 @@ read_input_int :: (row: int, message: string) -> value: int, success: bool { // Shows message to user and waits for user key press. prompt_user_key :: (row: int, message: string) -> TUI.Key { -/* - assert(message.data != null, ASSERT_NOT_NULL, "message"); - attron(xx style); - move(xx row, 1); - for 0..size_x-3 { - // for (int idx = 0; idx < size_x - 2; idx++) { - addch(ACS_CKBOARD); - } - mvaddstr(xx row, 2, message.data); - attrset(A_NORMAL); - - return getch(); -*/ - - // TODO We still need to apply the style. - TUI.set_cursor_position(row, 2); write_string(TUI.Commands.DrawingMode); for 1..size_x-2 { @@ -1858,7 +1849,7 @@ main :: () { if selected_task == null continue; TUI.using_style(action_style); - value, success := read_input_int(selected_task_row, " Move to: "); + value, success := read_input_int(selected_task_row, "Move to:"); if success == false continue; move_task(db, db.selected_idx, value-1); // -1 to adjust for zero based index trigger_autosave(); @@ -1869,7 +1860,7 @@ main :: () { if selected_task == null continue; TUI.using_style(action_style); - value, success := read_input_int(selected_task_row, " Go to: "); + value, success := read_input_int(selected_task_row, "Go to:"); if success == false continue; target_index := clamp(value, 1, MAX_DATABASE_TASKS) - 1; select_task(db, target_index); -- cgit v1.2.3 From 8016c5c04e3d452dab583b2c9c2161a5a950ea65 Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 6 Apr 2024 02:31:50 +0100 Subject: Code cleanup. --- modules/TUI/module.jai | 2 +- ttt.jai | 45 +++++++++++++++++++-------------------------- 2 files changed, 20 insertions(+), 27 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 4e67db5..fbf2dd9 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -718,7 +718,7 @@ set_terminal_title :: (title: string) { print(Commands.SetWindowTitle, title); } - +// TODO #if OS == .WINDOWS { // Prototyping zone... keep clear! } diff --git a/ttt.jai b/ttt.jai index 517723d..9fa62d3 100644 --- a/ttt.jai +++ b/ttt.jai @@ -27,15 +27,6 @@ #import "Integer_Saturating_Arithmetic"; TUI :: #import "TUI"(COLOR_BIT_DEPTH=8); - -// TODO List: -// [ ] Every time we add or remove tasks to the database, it may be reallocated, thus making the selected_task and active_task pointers invalid. Check if this is messing up the app. -stdscr: *void; // TODO DAM -A_BOLD: s32 = 0; // TODO DAM -COLOR_PAIR :: (a: s32) -> s32 { return 0; } // TODO DAM -WINDOW :: struct { } // TODO DAM - - VERSION :: "2.0"; // Use only 3 chars (to fit layouts). YEAR :: "2024"; FIRST_DAY_OF_WEEK :: 1; // (0-6, Sunday = 0). @@ -123,35 +114,36 @@ Layouts :: enum u8 { COMPACT; } -error_window : *WINDOW = null; +// TODO NEXT Implement error window! + error_time_limit := Apollo_Time.{0, 0}; print_error :: (format :string, args : .. Any) { - if stdscr == null { // || isendwin() == true { // Not in ncurses mode? TODO DAM - print(format, ..args); - print("\n"); - } - else { - CHAR_SPACE :: #char " "; - w_size_x: int = ifx size_x > 120 then 120 else size_x - 2; - w_size_y: int = 4; - if (error_window == null) { + // if stdscr == null { // || isendwin() == true { // Not in ncurses mode? TODO DAM + // print(format, ..args); + // print("\n"); + // } + // else { + // CHAR_SPACE :: #char " "; + // w_size_x: int = ifx size_x > 120 then 120 else size_x - 2; + // w_size_y: int = 4; + // if (error_window == null) { //error_window = newwin(w_size_y, w_size_x, (size_y - w_size_y) / 2, (size_x - w_size_x) / 2); TODO DAM //wattron(error_window, COLOR_PAIR(xx Styles.ERROR)); TODO DAM //wborder(error_window, CHAR_SPACE, CHAR_SPACE, 0, 0, ACS_HLINE, ACS_HLINE, ACS_HLINE, ACS_HLINE); TODO DAM //mvwprintw(error_window, 0, 1, " Error "); TODO DAM //wmove(error_window, 1, 0); TODO DAM - } - else { + // } + // else { //waddch(error_window, CHAR_SPACE); TODO DAM - } + // } //wprintw(error_window, temp_c_string(tprint(format, args))); TODO DAM - error_time_limit = current_time_monotonic() + seconds_to_apollo(5); - } + // error_time_limit = current_time_monotonic() + seconds_to_apollo(5); + // } } draw_error_window :: () { - if (error_window == null) return; + // if (error_window == null) return; // Hide error window after time-limit or if terminal is shrank. w_size_x: s32; @@ -162,7 +154,7 @@ draw_error_window :: () { || size_y - w_size_y < 2 ) { //delwin(error_window); TODO DAM - error_window = null; + // error_window = null; return; } @@ -1673,6 +1665,7 @@ main :: () { //timeout(INPUT_AWAIT_INF); TODO DAM // TODO WIP Remove `selected_task` and `active_task` and helper functions. + // TODO Every time we add or remove tasks to the database, it may be reallocated, thus making the selected_task and active_task pointers invalid. Check if this is messing up the app. selected_task := get_selected_task(db); active_task := get_active_task(db); selected_task_row: int; -- cgit v1.2.3 From 194d919eb827e6c6df03917b1732f253e5eac0b5 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 7 Apr 2024 02:46:59 +0100 Subject: Implement error window. --- modules/TUI/module.jai | 32 ++++++++++----------- ttt.jai | 76 +++++++++++++++++++++++--------------------------- 2 files changed, 51 insertions(+), 57 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index fbf2dd9..764effe 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -264,7 +264,7 @@ Keys :: struct #type_info_none { F12 : Key : #run to_key("#f12"); } -initialized := false; +active := false; //input_buffer : [64] u8; // TODO FIXME Input buffer is too small!!! input_buffer : [8] u8; // TODO FIXME Input buffer is too small!!! @@ -277,17 +277,17 @@ input_override : Key; assert(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire terminal (6 bytes) or UTF8 (4 bytes) code."); } -assert_is_initialized :: inline () { - assert(initialized, "TUI is not ready."); +assert_is_active :: inline () { + assert(active, "TUI is not ready."); } set_next_key :: (key: Key) { - assert_is_initialized(); + assert_is_active(); input_override = key; } get_key :: (timeout_milliseconds: s32 = -1) -> Key { - assert_is_initialized(); + assert_is_active(); // BBBB BBBB & 1100 0000 == 10XX XXXX -> is continuation byte is_utf8_continuation_byte :: inline (byte: u8) -> bool { @@ -371,7 +371,7 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { // TODO Review me! read_input :: (count_limit: int = -1, terminators: .. u8) -> string { - assert_is_initialized(); + assert_is_active(); assert(count_limit >= 0 || terminators.count > 0, "Infinite loop detected, aborting."); // TODO Maybe just return!? @@ -533,7 +533,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { } start :: () { - if initialized == true return; + if active == true return; setup_key_map(); // TODO This is being called multiple times... please fix me! @@ -551,12 +551,12 @@ start :: () { OS_prepare_terminal(); - initialized = true; + active = true; } stop :: () { - if initialized == false return; - initialized = false; + if active == false return; + active = false; OS_reset_terminal(); write_strings(Commands.MainScreenBuffer, Commands.RestoreCursorPosition, Commands.ShowCursor); @@ -574,7 +574,7 @@ flush_input :: () { // } draw_box :: (x: int, y: int, width: int, height: int) { - assert_is_initialized(); + assert_is_active(); // TODO Check if using a String_Builder improves performance (measure it)! // TODO Validate input parameters against the terminal size. @@ -620,13 +620,13 @@ draw_box :: (x: int, y: int, width: int, height: int) { // TODO Maybe rename to "clear()" clear_terminal :: inline () { - assert_is_initialized(); + assert_is_active(); write_string(Commands.ClearScreen); } // TODO Maybe rename to "get_size()" get_terminal_size :: () -> rows: int, columns: int { - assert_is_initialized(); + assert_is_active(); auto_release_temp(); @@ -674,13 +674,13 @@ get_terminal_size :: () -> rows: int, columns: int { } set_cursor_position :: (row: int, column: int) { - assert_is_initialized(); + assert_is_active(); auto_release_temp(); print(Commands.SetCursorPosition, row, column); } get_cursor_position :: () -> row: int, column: int { - assert_is_initialized(); + assert_is_active(); auto_release_temp(); @@ -714,7 +714,7 @@ get_cursor_position :: () -> row: int, column: int { } set_terminal_title :: (title: string) { - assert_is_initialized(); + assert_is_active(); print(Commands.SetWindowTitle, title); } diff --git a/ttt.jai b/ttt.jai index 9fa62d3..2bf3d28 100644 --- a/ttt.jai +++ b/ttt.jai @@ -114,59 +114,53 @@ Layouts :: enum u8 { COMPACT; } -// TODO NEXT Implement error window! -error_time_limit := Apollo_Time.{0, 0}; +error_message: string; + +error_time_limit := Apollo_Time.{0, 0}; print_error :: (format :string, args : .. Any) { - // if stdscr == null { // || isendwin() == true { // Not in ncurses mode? TODO DAM - // print(format, ..args); - // print("\n"); - // } - // else { - // CHAR_SPACE :: #char " "; - // w_size_x: int = ifx size_x > 120 then 120 else size_x - 2; - // w_size_y: int = 4; - // if (error_window == null) { - //error_window = newwin(w_size_y, w_size_x, (size_y - w_size_y) / 2, (size_x - w_size_x) / 2); TODO DAM - //wattron(error_window, COLOR_PAIR(xx Styles.ERROR)); TODO DAM - //wborder(error_window, CHAR_SPACE, CHAR_SPACE, 0, 0, ACS_HLINE, ACS_HLINE, ACS_HLINE, ACS_HLINE); TODO DAM - //mvwprintw(error_window, 0, 1, " Error "); TODO DAM - //wmove(error_window, 1, 0); TODO DAM - // } - // else { - //waddch(error_window, CHAR_SPACE); TODO DAM - // } - //wprintw(error_window, temp_c_string(tprint(format, args))); TODO DAM - // error_time_limit = current_time_monotonic() + seconds_to_apollo(5); - // } + + if TUI.active == false { + print(format, ..args, to_standard_error = true); + print("\n"); + return; + } + + if error_message.data != null { + free(error_message.data); + } + error_message = sprint(format, args); + + error_time_limit = current_time_monotonic() + seconds_to_apollo(5); } draw_error_window :: () { - // if (error_window == null) return; + if error_time_limit < current_time_monotonic() return; - // Hide error window after time-limit or if terminal is shrank. - w_size_x: s32; - w_size_y: s32; - //getmaxyx(error_window, *w_size_y, *w_size_x); TODO DAM + // Don't show error if main window is too small. + w_size_x: int = ifx size_x > 120 then 120 else size_x - 2; + w_size_y: int = 3; if (current_time_monotonic() >= error_time_limit || size_x - w_size_x < 2 || size_y - w_size_y < 2 ) { - //delwin(error_window); TODO DAM - // error_window = null; return; } - // Adjust error window position. - pos_x := (size_x - w_size_x) / 2; - pos_y := (size_y - w_size_y) / 2; - //mvwin(error_window, pos_y, pos_x); TODO DAM - - // Avoid being overwritten by main window content. - //refresh(); TODO DAM - //touchwin(error_window); TODO DAM - //wrefresh(error_window); TODO DAM + pos_x := 1 + (size_x - w_size_x) / 2; + pos_y := 1 + (size_y - w_size_y) / 2; + + TUI.using_style(style_error); + TUI.draw_box(pos_x, pos_y, w_size_x, w_size_y); + TUI.set_cursor_position(pos_y, pos_x + 1); + write_string(" Error "); + TUI.set_cursor_position(pos_y + 1, pos_x + 1); + for 1..w_size_x-2 { + print_character(#char " "); + } + TUI.set_cursor_position(pos_y + 1, pos_x + 1); + write_string(error_message); } trigger_autosave :: () { @@ -1857,8 +1851,8 @@ main :: () { if success == false continue; target_index := clamp(value, 1, MAX_DATABASE_TASKS) - 1; select_task(db, target_index); - - // Delete. + + // Duplicate. case #char "d"; #through; case #char "D"; if selected_task == null continue; -- cgit v1.2.3 From 7ddda0dac4b75adc30799eb1a36a84ecc1e41a86 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 7 Apr 2024 04:14:51 +0100 Subject: Replaced row/column arguments by x/y which are more familiar. --- modules/TUI/module.jai | 34 +++++++------- ttt.jai | 118 ++++++++++++++++++++++++------------------------- 2 files changed, 76 insertions(+), 76 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 764effe..5b7861e 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -447,14 +447,14 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // TODO Some of these may be nice to have: // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line - row, col := get_cursor_position(); + x, y := get_cursor_position(); write_strings(Commands.StartBlinking, Commands.BlinkingBarShape); // Clear line for input. for 1..count_limit { print_character(#char " "); } - set_cursor_position(row, col); + set_cursor_position(x, y); key := Keys.None; while true { @@ -513,17 +513,17 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { } if is_visible { - set_cursor_position(row, col); + set_cursor_position(x, y); write_string(str); for str.count..count_limit-1 print_character(#char " "); } else { - set_cursor_position(row, col); + set_cursor_position(x, y); for 0..str.count-1 print_character(#char "*"); for str.count..count_limit-1 print_character(#char " "); } - set_cursor_position(row, col+idx); + set_cursor_position(x+idx, y); } write_strings(Commands.StopBlinking, Commands.DefaultShape); @@ -535,6 +535,8 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { start :: () { if active == true return; + // TODO Should start() call flush_input internally? + setup_key_map(); // TODO This is being called multiple times... please fix me! input_string.data = input_buffer.data; @@ -625,7 +627,7 @@ clear_terminal :: inline () { } // TODO Maybe rename to "get_size()" -get_terminal_size :: () -> rows: int, columns: int { +get_terminal_size :: () -> width: int, height: int { assert_is_active(); auto_release_temp(); @@ -659,27 +661,25 @@ get_terminal_size :: () -> rows: int, columns: int { rows = parse_int(*parts[1]); columns = parse_int(*parts[2]); } - // Some systems don't allow to query the terminal size directly. + // Some systems don't allow to query the terminal size directly... or the answer takes too much time. // In such cases, measure it indirectly by the maximum possible cursor position. else { - flush_input(); - cursor_row, cursor_column := get_cursor_position(); - defer set_cursor_position(cursor_row, cursor_column); + x, y := get_cursor_position(); + defer set_cursor_position(x, y); set_cursor_position(0xFFFF, 0xFFFF); - rows, columns = get_cursor_position(); + columns, rows = get_cursor_position(); } - return rows, columns; + return columns, rows; } -set_cursor_position :: (row: int, column: int) { +set_cursor_position :: (x: int, y: int) { assert_is_active(); - auto_release_temp(); - print(Commands.SetCursorPosition, row, column); + print(Commands.SetCursorPosition, y, x); } -get_cursor_position :: () -> row: int, column: int { +get_cursor_position :: () -> x: int, y: int { assert_is_active(); auto_release_temp(); @@ -710,7 +710,7 @@ get_cursor_position :: () -> row: int, column: int { parts := split(input, ";",, temporary_allocator); row := parse_int(*parts[0]); column := parse_int(*parts[1]); - return row, column; + return column, row; } set_terminal_title :: (title: string) { diff --git a/ttt.jai b/ttt.jai index 8c8d31c..88f1323 100644 --- a/ttt.jai +++ b/ttt.jai @@ -153,13 +153,13 @@ draw_error_window :: () { TUI.using_style(style_error); TUI.draw_box(pos_x, pos_y, w_size_x, w_size_y); - TUI.set_cursor_position(pos_y, pos_x + 1); + TUI.set_cursor_position(pos_x + 1, pos_y); write_string(" Error "); - TUI.set_cursor_position(pos_y + 1, pos_x + 1); + TUI.set_cursor_position(pos_x + 1, pos_y + 1); for 1..w_size_x-2 { print_character(#char " "); } - TUI.set_cursor_position(pos_y + 1, pos_x + 1); + TUI.set_cursor_position(pos_x + 1, pos_y + 1); write_string(error_message); } @@ -272,7 +272,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { } } - TUI.set_cursor_position(y, x); + TUI.set_cursor_position(x, y); if time < 0 { print_padding(left_padding); @@ -980,13 +980,13 @@ draw_tui :: (db: *Database, layout: *Layout) { for 0..layout.columns.count-2 { column := layout.columns[it]; x += 1 + column.width; - TUI.set_cursor_position(y, x); + TUI.set_cursor_position(x, y); write_string(TUI.Drawings.TeeT); for row: 2..size_y { - TUI.set_cursor_position(row, x); + TUI.set_cursor_position(x, row); write_string(TUI.Drawings.LineV); } - TUI.set_cursor_position(size_y, x); + TUI.set_cursor_position(x, size_y); write_string(TUI.Drawings.TeeB); } write_string(TUI.Commands.TextMode); @@ -1001,7 +1001,7 @@ draw_tui :: (db: *Database, layout: *Layout) { x += 1; col = *layout.columns[L_TITLE_IDX]; //mvaddstr(xx y, xx (x + col.alignment_offset), ifx db == *archive then layout.archive_title.data else col.header.data); TODO DAM - TUI.set_cursor_position(y, x + col.alignment_offset); + TUI.set_cursor_position(x + col.alignment_offset, y); write_string(ifx db == *archive then layout.archive_title else col.header); x += col.width; @@ -1022,7 +1022,7 @@ draw_tui :: (db: *Database, layout: *Layout) { } col = *layout.columns[L_DAYS_IDX + idx]; //mvaddstr(xx y, xx (x + col.alignment_offset), col.header.data); TODO DAM - TUI.set_cursor_position(y, x + col.alignment_offset); + TUI.set_cursor_position(x + col.alignment_offset, y); write_string(col.header); x += col.width; } @@ -1032,7 +1032,7 @@ draw_tui :: (db: *Database, layout: *Layout) { x += 1; col = *layout.columns[L_TOTAL_IDX]; //mvaddstr(xx y, xx (x + col.alignment_offset), col.header.data); TODO DAM - TUI.set_cursor_position(y, x + col.alignment_offset); + TUI.set_cursor_position(x + col.alignment_offset, y); write_string(col.header); @@ -1069,14 +1069,14 @@ draw_tui :: (db: *Database, layout: *Layout) { column_width = layout.columns[L_TITLE_IDX].width; // mvprintw(xx y, xx x, "%-*.*s", column_width, column_width, temp_c_string(xx task.name)); //task.name); TODO Fix required for LLVM/Cncurses. TODO DAM // TODO FIXME OH MY GOD SUCH BAD CODEZ - TUI.set_cursor_position(y, x); + TUI.set_cursor_position(x, y); white_spaces := column_width; // FIXME Improve by using buffer instead of printing one char at the time. while white_spaces > 0 { print_character(#char " "); white_spaces -= 1; } - TUI.set_cursor_position(y, x); + TUI.set_cursor_position(x, y); task_name: string = cast(string)task.name; task_name.count = ifx task_name.count > column_width then column_width else task_name.count; print("%", task_name); @@ -1107,7 +1107,7 @@ draw_tui :: (db: *Database, layout: *Layout) { /////////////////////////////////////////////////////////////////////////// // Draw selected/total tasks. size := 1 + count_digits(db.selected_idx + 1) + 1 + count_digits(db.tasks.count) + 1; // " XXX/YYY " - TUI.set_cursor_position(size_y, 2); + TUI.set_cursor_position(2, size_y); if (size <= layout.columns[L_TITLE_IDX].width) { print(" %/% ", db.selected_idx + 1, db.tasks.count); } @@ -1157,61 +1157,64 @@ free_memory :: () { //reset_temporary_storage(); } -read_input_string :: (row: int, column: int, input_limit: int, padding: int = 0) -> value: string, success: bool { +read_input_string :: (x: int, y: int, input_limit: int, padding: int = 0) -> value: string, success: bool { // TODO Draw padding (at end of inputbox)... padding was renamed to input_width... is this the best name? // TODO Maybe add another optional arg with the placeholder text (to be preset on the input)? - column += 1; // TODO FIX THE NCURSES INDEXING CHAOS - - TUI.set_cursor_position(row, column + input_limit); + TUI.set_cursor_position(x + input_limit, y); write_string(TUI.Commands.DrawingMode); for 1..padding { write_string(TUI.Drawings.Checkerboard); } write_string(TUI.Commands.TextMode); - TUI.set_cursor_position(row, column); + TUI.set_cursor_position(x, y); value, key := TUI.read_input_line(input_limit); return value, key == TUI.Keys.Enter; } // Returns success. -read_input_int :: (row: int, message: string) -> value: int, success: bool { - TUI.set_cursor_position(row, 2); +read_input_int :: (y: int, message: string) -> value: int, success: bool { + x :: 3; + + // Draw checkerboard. + TUI.set_cursor_position(2, y); write_string(TUI.Commands.DrawingMode); - for 1..size_x-2 { + for 2..x { print(TUI.Drawings.Checkerboard); } write_string(TUI.Commands.TextMode); - TUI.set_cursor_position(row, 3); + TUI.set_cursor_position(x, y); write_strings(" ", message, " "); - - // Get line number. - input_pos_x := 1 + 1 + 1 + message.count + 1; - input_width := size_x - input_pos_x - 1; + input_pos_x := x + message.count + 2; + input_width := size_x - input_pos_x - 1; + style_input := context.tui_style; style_input.underline = true; TUI.using_style(style_input); - str := read_input_string(row, input_pos_x, input_width); + str := read_input_string(input_pos_x, y, input_width); value, success := parse_int(*str); return value, success; } // Shows message to user and waits for user key press. -prompt_user_key :: (row: int, message: string) -> TUI.Key { - TUI.set_cursor_position(row, 2); +prompt_user_key :: (y: int, message: string) -> TUI.Key { + x :: 3; + + // Draw checkerboard. + TUI.set_cursor_position(2, y); write_string(TUI.Commands.DrawingMode); for 1..size_x-2 { print(TUI.Drawings.Checkerboard); } write_string(TUI.Commands.TextMode); - TUI.set_cursor_position(row, 3); + TUI.set_cursor_position(x, y); write_strings(" ", message, " "); return TUI.get_key(); } @@ -1234,19 +1237,19 @@ main :: () { } next_line :: inline () { - r, c := TUI.get_cursor_position(); - TUI.set_cursor_position(r+1, 1); + x, y := TUI.get_cursor_position(); + TUI.set_cursor_position(1, y+1); } if perform_test && 1 { print("TEST : set and get cursor position\n", to_standard_error = true); TUI.start(); - ROW :: 3; - COLUMN :: 3; - TUI.set_cursor_position(ROW, COLUMN); - row, column := TUI.get_cursor_position(); + X :: 2; + Y :: 3; + TUI.set_cursor_position(X, Y); + x, y := TUI.get_cursor_position(); TUI.stop(); - assert_result(row == ROW && column == COLUMN, "Failed set/get cursor position.\n"); + assert_result(x == X && y == Y, "Failed set/get cursor position.\n"); } if perform_test && 1 { @@ -1278,7 +1281,7 @@ main :: () { if perform_test && 1 { print("TEST : draw box\n", to_standard_error = true); auto_release_temp(); - TUI.start(); // TODO Should start() call flush_input internally? + TUI.start(); TUI.flush_input(); TUI.clear_terminal(); TUI.draw_box(1, 2, 5, 3); @@ -1294,9 +1297,9 @@ main :: () { auto_release_temp(); TUI.start(); TUI.clear_terminal(); - rows, columns := TUI.get_terminal_size(); + width, height := TUI.get_terminal_size(); TUI.set_cursor_position(1, 1); - print("Is terminal size % columns and % rows? (y/n)", columns, rows); + print("Is terminal size %x%? (y/n)", width, height); key: TUI.Key = xx TUI.Keys.None; while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) { key = TUI.get_key(); @@ -1328,9 +1331,9 @@ main :: () { key: TUI.Key = #char "d"; last_none_char := "X"; - size_r, size_c := TUI.get_terminal_size(); + width, height := TUI.get_terminal_size(); TUI.clear_terminal(); - TUI.draw_box(1, 1, size_c, size_r); + TUI.draw_box(1, 1, width, height); drop_down := 0; while(key != #char "q") { @@ -1344,14 +1347,14 @@ main :: () { case TUI.Keys.Resize; #through; case #char "c"; { - size_r, size_c = TUI.get_terminal_size(); + width, height = TUI.get_terminal_size(); TUI.clear_terminal(); - TUI.draw_box(1, 1, size_c, size_r); + TUI.draw_box(1, 1, width, height); drop_down = 0; } case; { - TUI.set_cursor_position(3+drop_down, 2); + TUI.set_cursor_position(2, 3+drop_down); str := TUI.to_string(key); array_to_print: [..] string; for 0..str.count-1 { @@ -1371,12 +1374,11 @@ main :: () { } } - - x := ifx size_r > 1 then size_r-1 else 1; - y := ifx size_c > 24 then size_c-24 else 1; + x := ifx width > 24 then width-24 else 1; + y := ifx height > 1 then height-1 else 1; TUI.set_cursor_position(x, y); - print("size(CxR): %x%\n", size_c, size_r); + print("size = %x%\n", width, height); key = TUI.get_key(1000); // __mark := get_temporary_storage_mark(); @@ -1395,7 +1397,7 @@ main :: () { print("Enter some text (use Enter to finish, Esc to cancel, or resize to abort):"); next_line(); str, key := TUI.read_input_line(15); - TUI.set_cursor_position(3, 1); + TUI.set_cursor_position(1, 3); error_message: string; if key == { case TUI.Keys.Escape; { @@ -1427,7 +1429,7 @@ main :: () { print("Enter some secret (use Enter to finish, Esc to cancel, or resize to abort):"); next_line(); str, key := TUI.read_input_line(15, false); - TUI.set_cursor_position(3, 1); + TUI.set_cursor_position(1, 3); error_message: string; if key == { case TUI.Keys.Escape; { @@ -1645,7 +1647,7 @@ main :: () { if (is_terminal_too_small) { INVALID_WINDOW_MESSAGE :: "Terminal is too small: minimum 60x3."; - TUI.set_cursor_position(size_y / 2, (size_x - INVALID_WINDOW_MESSAGE.count) / 2); + TUI.set_cursor_position((size_x - INVALID_WINDOW_MESSAGE.count) / 2, size_y / 2); write_strings(INVALID_WINDOW_MESSAGE); } else { @@ -1691,7 +1693,7 @@ main :: () { // When terminal is resized. case TUI.Keys.Resize; TUI.clear_terminal(); - size_y, size_x = TUI.get_terminal_size(); + size_x, size_y = TUI.get_terminal_size(); is_terminal_too_small = size_x < 60 || size_y < 3; update_layout(); layout = *layouts[ifx size_x > 100 then Layouts.NORMAL else Layouts.COMPACT]; @@ -1742,9 +1744,8 @@ main :: () { if (selected_task == null) continue; // Change task name. - // TODO WIPWIPWIP TUI.using_style(action_style); - input := read_input_string(selected_task_row, 1, Task.name.count, size_x - 2 - Task.name.count); + input := read_input_string(2, selected_task_row, Task.name.count, size_x - 2 - Task.name.count); if is_empty_string(input) == false { replace_chars(input, "\t\x0B\x0C\r", #char " "); // Replace weird spaces with space. memset(selected_task.name.data, 0, Task.name.count); @@ -1784,16 +1785,15 @@ main :: () { // Prepare position to input time operation. selected_day := cast(int)(key - #char "1"); // TODO DAM this cast... input_width := layout.columns[L_DAYS_IDX + selected_day].width; - input_pos_x := 1 + layout.columns[L_TITLE_IDX].width; - + input_pos_x := 2 + layout.columns[L_TITLE_IDX].width; for 0..selected_day-1 { input_pos_x += 1 + layout.columns[L_DAYS_IDX + it].width; } input_pos_x += 1; - + // Get input string. TUI.using_style(action_style); - input := read_input_string(selected_task_row, input_pos_x, input_width); // TODO Temp stringzes. + input := read_input_string(input_pos_x, selected_task_row, input_width); // TODO Temp stringzes. // Abort if input if empty. if is_empty_string(input) continue; -- cgit v1.2.3 From 6aed607156f476937bc18a80540e2cb2e8c212ec Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 7 Apr 2024 04:26:46 +0100 Subject: Add/remove TODO entries. --- modules/TUI/module.jai | 4 +++- ttt.jai | 5 ----- 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 5b7861e..5f143dd 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -421,7 +421,6 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { } } -// TODO Provide an advanced read_input_line function that allows some styling and to set a placeholder text. read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { /* Use the get_key to read user input and show it on screen. @@ -498,6 +497,9 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { str.count -= 1; case; + + // TODO FIXME Does not support UTF8 input... + if idx >= count_limit continue; if is_escape_code(key) continue; diff --git a/ttt.jai b/ttt.jai index 88f1323..364d161 100644 --- a/ttt.jai +++ b/ttt.jai @@ -966,7 +966,6 @@ draw_tui :: (db: *Database, layout: *Layout) { now_week_day := to_calendar(now_utc, .LOCAL).day_of_week_starting_at_0; // Reset theme and clear screen. - //attrset(A_NORMAL); TODO DAM TUI.clear_terminal(); // Draw outer border. @@ -1000,7 +999,6 @@ draw_tui :: (db: *Database, layout: *Layout) { // Headers : title x += 1; col = *layout.columns[L_TITLE_IDX]; - //mvaddstr(xx y, xx (x + col.alignment_offset), ifx db == *archive then layout.archive_title.data else col.header.data); TODO DAM TUI.set_cursor_position(x + col.alignment_offset, y); write_string(ifx db == *archive then layout.archive_title else col.header); x += col.width; @@ -1021,7 +1019,6 @@ draw_tui :: (db: *Database, layout: *Layout) { TUI.set_style(style_default); } col = *layout.columns[L_DAYS_IDX + idx]; - //mvaddstr(xx y, xx (x + col.alignment_offset), col.header.data); TODO DAM TUI.set_cursor_position(x + col.alignment_offset, y); write_string(col.header); x += col.width; @@ -1031,7 +1028,6 @@ draw_tui :: (db: *Database, layout: *Layout) { // Headers : total x += 1; col = *layout.columns[L_TOTAL_IDX]; - //mvaddstr(xx y, xx (x + col.alignment_offset), col.header.data); TODO DAM TUI.set_cursor_position(x + col.alignment_offset, y); write_string(col.header); @@ -1099,7 +1095,6 @@ draw_tui :: (db: *Database, layout: *Layout) { print_time(y, x, total_time, layout.columns[L_TOTAL_IDX].width); // Reset theme. - //attrset(A_NORMAL); TODO DAM TUI.clear_style(); } -- cgit v1.2.3 From a3cf506defb4a01759db6f920a363e23de63e984 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 7 Apr 2024 04:35:43 +0100 Subject: Fixed input width. --- modules/TUI/module.jai | 2 +- ttt.jai | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 5f143dd..bda326b 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,4 +1,4 @@ -#module_parameters(COLOR_BIT_DEPTH := 24); +#module_parameters(COLOR_BIT_DEPTH := 24); // TODO - Do #assert module parameters to make sure they are valid. #if OS == { case .LINUX; diff --git a/ttt.jai b/ttt.jai index 364d161..414ab8c 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1164,6 +1164,11 @@ read_input_string :: (x: int, y: int, input_limit: int, padding: int = 0) -> val } write_string(TUI.Commands.TextMode); TUI.set_cursor_position(x, y); + + style_input := context.tui_style; + style_input.underline = true; + TUI.using_style(style_input); + value, key := TUI.read_input_line(input_limit); return value, key == TUI.Keys.Enter; @@ -1185,11 +1190,7 @@ read_input_int :: (y: int, message: string) -> value: int, success: bool { write_strings(" ", message, " "); input_pos_x := x + message.count + 2; - input_width := size_x - input_pos_x - 1; - - style_input := context.tui_style; - style_input.underline = true; - TUI.using_style(style_input); + input_width := size_x - input_pos_x; str := read_input_string(input_pos_x, y, input_width); -- cgit v1.2.3 From 353d8b1145db12ffc42e4f6c2148a848ef8bba84 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 7 Apr 2024 23:23:06 +0100 Subject: Preparing for UTF8 support on read_input_line. --- modules/TUI/module.jai | 18 ++----------- modules/UTF8.jai | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ ttt.jai | 63 +------------------------------------------- 3 files changed, 74 insertions(+), 78 deletions(-) create mode 100644 modules/UTF8.jai (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index bda326b..e7dc21a 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -14,6 +14,7 @@ #import "Basic"; #import "String"; #import "Thread"; +#import "UTF8"; #load "key_map.jai"; // Special Graphics Characters @@ -289,21 +290,6 @@ set_next_key :: (key: Key) { get_key :: (timeout_milliseconds: s32 = -1) -> Key { assert_is_active(); - // BBBB BBBB & 1100 0000 == 10XX XXXX -> is continuation byte - is_utf8_continuation_byte :: inline (byte: u8) -> bool { - return (byte & 0xC0) == 0x80; - } - - // BBBB BBBB & 1110 0000 == 110X XXXX -> 1 initial + 1 continuation byte - // BBBB BBBB & 1111 0000 == 1110 XXXX -> 1 initial + 2 continuation byte - // BBBB BBBB & 1111 1000 == 1111 0XXX -> 1 initial + 3 continuation byte - count_utf8_bytes :: inline (byte: u8) -> int { - if (byte & 0xE0) == 0xC0 return 1+1; - if (byte & 0xF0) == 0xE0 return 1+2; - if (byte & 0xF8) == 0xF0 return 1+3; - return 1; - } - if input_override != xx Keys.None { defer input_override = xx Keys.None; return input_override; @@ -421,7 +407,7 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { } } -read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { +read_input_line :: (count_limit: int, is_visible: bool = true, placeholder: string = "") -> string, Key { /* Use the get_key to read user input and show it on screen. Should allow to move the cursor left and right and to delete/backspace. diff --git a/modules/UTF8.jai b/modules/UTF8.jai new file mode 100644 index 0000000..fac1326 --- /dev/null +++ b/modules/UTF8.jai @@ -0,0 +1,71 @@ +// BBBB BBBB & 1100 0000 == 10XX XXXX -> is continuation byte +is_utf8_continuation_byte :: inline (byte: u8) -> bool { + return (byte & 0xC0) == 0x80; +} + +// BBBB BBBB & 1110 0000 == 110X XXXX -> 1 initial + 1 continuation byte +// BBBB BBBB & 1111 0000 == 1110 XXXX -> 1 initial + 2 continuation byte +// BBBB BBBB & 1111 1000 == 1111 0XXX -> 1 initial + 3 continuation byte +count_utf8_bytes :: inline (byte: u8) -> int { + if (byte & 0xE0) == 0xC0 return 1+1; + if (byte & 0xF0) == 0xE0 return 1+2; + if (byte & 0xF8) == 0xF0 return 1+3; + return 1; +} + +// Truncates the string to the length provided or shorter, in case of UTF8 strings that require so. +// Truncation is done by zeroing the tail of the string in place. +// Returns length of truncated string. +truncate_string :: (str: string, length: int) -> length: int { + if str.data == null then return -1; + + if str.count < length then length = str.count; + + data := str.data; + count := str.count; + + // Find index of first continuation byte. + idx := length; + // while (idx > 0 && ((data[idx - 1] & 0xC0) == 0x80)) { TODO REMOVE AFTER TESTING + while (idx > 0 && is_utf8_continuation_byte(data[idx - 1])) { + idx -= 1; + } + continuation_bytes := length - idx; + + // If string starts with continuation bytes, it's an invalid UTF8 string. + if (idx == 0 && continuation_bytes > 0) { + length = 0; + } + // If length truncates some continuation bytes, remove incomplete UTF8 character. + else if (idx > 0 // string is not empty + // continuation bytes are not complete + && !(continuation_bytes == 0 && (data[idx - 1] & 0x80) == 0x00) + && !(continuation_bytes == 1 && (data[idx - 1] & 0xE0) == 0xC0) + && !(continuation_bytes == 2 && (data[idx - 1] & 0xF0) == 0xE0) + && !(continuation_bytes == 3 && (data[idx - 1] & 0xF8) == 0xF0) + ) { + length -= (continuation_bytes + 1); // Remove start byte, ence '+ 1'. + } + + memset(data + length, 0, count - length); + return length; +} + +// Returns true when the string is empty or consists of space characters. +is_empty_string :: (str: string) -> bool { + for 0..str.count-1 { + if str[it] == { + case #char "\0"; #through; + case #char "\t"; #through; // horizontal tab + case #char "\n"; #through; // line feed + case #char "\x0B"; #through; // vertical tabulation + case #char "\x0C"; #through; // form feed + case #char "\r"; #through; // carriage return + case #char " "; + continue; + case; + return false; + } + } + return true; +} diff --git a/ttt.jai b/ttt.jai index 414ab8c..43a972f 100644 --- a/ttt.jai +++ b/ttt.jai @@ -25,6 +25,7 @@ #import "File_Utilities"; #import "String"; #import "Integer_Saturating_Arithmetic"; +#import "UTF8"; TUI :: #import "TUI"(COLOR_BIT_DEPTH=8); VERSION :: "2.0"; // Use only 3 chars (to fit layouts). @@ -189,68 +190,6 @@ count_digits :: (number: s64, base: s64 = 10) -> s64 { return digits; } -Text_Encoding :: enum u8 #specified { - ASCII :: 1; - UTF8 :: 2; -} - -// Truncates the string to the length provided or shorter, in case of UTF8 strings that require so. -// Truncation is done by zeroing the tail of the string in place. -// Returns length of truncated string. -truncate_string :: (str: string, length: s64, $encoding: Text_Encoding = .UTF8) -> length: s64 { - assert(str.data != null, ASSERT_NOT_NULL, "str"); - assert(str.count >= length, "'str.count' should be equal or greater to 'length'."); - - data := str.data; - count := str.count; - - #if encoding == .UTF8 { - // Find index of first continuation byte. - idx := length; - while (idx > 0 && ((data[idx - 1] & 0xC0) == 0x80)) { - idx -= 1; - } - continuation_bytes := length - idx; - - // If string starts with continuation bytes, it's an invalid UTF8 string. - if (idx == 0 && continuation_bytes > 0) { - length = 0; - } - // If length truncates some continuation bytes, remove incomplete UTF8 character. - else if (idx > 0 // string is not empty - // continuation bytes are not complete - && !(continuation_bytes == 0 && (data[idx - 1] & 0x80) == 0x00) - && !(continuation_bytes == 1 && (data[idx - 1] & 0xE0) == 0xC0) - && !(continuation_bytes == 2 && (data[idx - 1] & 0xF0) == 0xE0) - && !(continuation_bytes == 3 && (data[idx - 1] & 0xF8) == 0xF0) - ) { - length -= (continuation_bytes + 1); // Remove start byte, ence '+ 1'. - } - } - - memset(data + length, 0, count - length); - return length; -} - -// Returns true when the string is empty or consists of space characters. -is_empty_string :: (str: string) -> bool { - for 0..str.count-1 { - if str[it] == { - case #char "\0"; #through; - case #char "\t"; #through; // horizontal tab - case #char "\n"; #through; // line feed - case #char "\x0B"; #through; // vertical tabulation - case #char "\x0C"; #through; // form feed - case #char "\r"; #through; // carriage return - case #char " "; - continue; - case; - return false; - } - } - return true; -} - // Prints, on row y and column x, the time using 5 characters centered on space. // Returns the result of a call to mvprintw. print_time :: (y: int, x: int, time: s64, space: int) -> int { -- cgit v1.2.3 From 3491fff478a7ebf7e9281595230528726828f48c Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 9 Apr 2024 15:13:26 +0100 Subject: Clear style when stoping TUI. --- modules/TUI/module.jai | 3 ++- ttt.jai | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index e7dc21a..e0bd9b4 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -547,7 +547,8 @@ start :: () { stop :: () { if active == false return; active = false; - + + clear_style(); OS_reset_terminal(); write_strings(Commands.MainScreenBuffer, Commands.RestoreCursorPosition, Commands.ShowCursor); } diff --git a/ttt.jai b/ttt.jai index 43a972f..7fdd9af 100644 --- a/ttt.jai +++ b/ttt.jai @@ -26,7 +26,7 @@ #import "String"; #import "Integer_Saturating_Arithmetic"; #import "UTF8"; -TUI :: #import "TUI"(COLOR_BIT_DEPTH=8); +TUI :: #import "TUI"(COLOR_BIT_DEPTH=4); VERSION :: "2.0"; // Use only 3 chars (to fit layouts). YEAR :: "2024"; @@ -99,7 +99,7 @@ style_active := TUI.Style.{ }; style_active_selected := TUI.Style.{ - background = TUI.Palette.NAVY, + background = TUI.Palette.BLUE, foreground = TUI.Palette.WHITE, bold = true, }; -- cgit v1.2.3 From 52c375a02e663a87140ef34f140de16c71f7af5f Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 11 Apr 2024 02:46:03 +0100 Subject: Added UTF8 support to read_input_line. --- modules/TUI/module.jai | 117 +++++++++++++++++++++---------------------------- modules/UTF8.jai | 48 +++++++++++++++++++- ttt.jai | 2 +- 3 files changed, 97 insertions(+), 70 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index e0bd9b4..2d23bff 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,4 +1,4 @@ -#module_parameters(COLOR_BIT_DEPTH := 24); // TODO - Do #assert module parameters to make sure they are valid. +#module_parameters(COLOR_BIT_DEPTH := 24); // TODO - Do #assert module parameters to make sure they are valid... and maybe rename to: COLOR_MODE with a enum. #if OS == { case .LINUX; @@ -111,7 +111,7 @@ Commands :: struct { set_colors :: inline (foreground: Palette, background: Palette) { print( - #run sprint("% %", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), + #run sprint("%0%0", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), cast(u8)foreground + 30, cast(u8)background + 40); } } @@ -407,13 +407,13 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { } } -read_input_line :: (count_limit: int, is_visible: bool = true, placeholder: string = "") -> string, Key { +read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { /* - Use the get_key to read user input and show it on screen. - Should allow to move the cursor left and right and to delete/backspace. - Enter should end the input, returning the input string and the Enter key. - Escape should discard the input returning an empty string and a Escape key. - Resize should discard the input returning an empty string and a Resize key. + Uses the get_key to read user input and show it on screen. + Allows to move the cursor left and right and to delete/backspace. + Enter ends the input, returning the input string and the Enter 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(count_limit >= 0, "Invalid value on count_limit parameter."); @@ -421,32 +421,34 @@ read_input_line :: (count_limit: int, is_visible: bool = true, placeholder: stri str := alloc_string(count_limit); str.count = 0; idx := 0; - - // placeholder: string = "", - // { - // copy_size := ifx placeholder.count > str.count then str.count else placeholder.count; - // memcpy(str.data, placeholder.data, copy_size); - // idx = copy_size; - // } - + // TODO Some of these may be nice to have: // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line x, y := get_cursor_position(); write_strings(Commands.StartBlinking, Commands.BlinkingBarShape); - // Clear line for input. - for 1..count_limit { - print_character(#char " "); - } - set_cursor_position(x, y); - key := Keys.None; while true { auto_release_temp(); - - key = get_key(); + chars_count := count_characters(str); + + // Preview input. + if is_visible { + set_cursor_position(x, y); + write_string(str); + for chars_count..count_limit-1 print_character(#char " "); + } + else { + set_cursor_position(x, y); + for 0..chars_count print_character(#char "*"); + for chars_count..count_limit-1 print_character(#char " "); + } + set_cursor_position(x+idx, y); + + // Process input key. + key = get_key(); if key == { case Keys.Resize; #through; case Keys.Escape; #through; @@ -455,63 +457,47 @@ read_input_line :: (count_limit: int, is_visible: bool = true, placeholder: stri case Keys.Left; if idx > 0 then idx -= 1; - + case Keys.Right; - if idx < str.count then idx += 1; - + if idx < chars_count then idx += 1; + case Keys.Home; idx = 0; case Keys.End; - idx = str.count; + idx = chars_count; case Keys.Delete; - if idx == str.count continue; - for idx..str.count-2 { - str.data[it] = str.data[it+1]; - } - str.data[str.count-1] = 0; - str.count -= 1; - + if idx == chars_count continue; + delete_character(*str, idx); + case Keys.Backspace; if idx == 0 continue; idx -= 1; - for idx..str.count-2 { - str.data[it] = str.data[it+1]; - } - str.data[str.count-1] = 0; - str.count -= 1; - + delete_character(*str, idx); + case; - - // TODO FIXME Does not support UTF8 input... - - if idx >= count_limit continue; if is_escape_code(key) continue; + + buff_idx := map_character_to_buffer_idx(str, idx); + key_str := to_string(key); - for < count_limit..idx+1 { - str.data[it] = str.data[it-1]; + // Make sure we have space to append the new character at the end (in case we're trying to do it). + if buff_idx > count_limit - key_str.count then continue; + + // Move text to allow inserting new character. + for < count_limit..buff_idx+key_str.count { + str.data[it] = str.data[it-key_str.count]; } - key_str := to_string(key); - str.data[idx] = key_str.data[0]; + memcpy(*str.data[buff_idx], key_str.data, key_str.count); - if str.count < count_limit then str.count += 1; + if str.count < count_limit then str.count += key_str.count; idx += 1; + + // Truncate string to avoid incomplete utf8 codes on the string tail. + str.count = truncate_string(str, count_limit); } - - if is_visible { - set_cursor_position(x, y); - write_string(str); - for str.count..count_limit-1 print_character(#char " "); - } - else { - set_cursor_position(x, y); - for 0..str.count-1 print_character(#char "*"); - for str.count..count_limit-1 print_character(#char " "); - } - - set_cursor_position(x+idx, y); } write_strings(Commands.StopBlinking, Commands.DefaultShape); @@ -559,11 +545,6 @@ flush_input :: () { input_string.count = 0; } -// TODO move style related procedures here! -// set_style :: () { - // print("", Commands.) -- -// } - draw_box :: (x: int, y: int, width: int, height: int) { assert_is_active(); diff --git a/modules/UTF8.jai b/modules/UTF8.jai index fac1326..eba4585 100644 --- a/modules/UTF8.jai +++ b/modules/UTF8.jai @@ -1,4 +1,5 @@ // BBBB BBBB & 1100 0000 == 10XX XXXX -> is continuation byte +// TODO Maybe rename to: is_continuation_byte is_utf8_continuation_byte :: inline (byte: u8) -> bool { return (byte & 0xC0) == 0x80; } @@ -6,6 +7,7 @@ is_utf8_continuation_byte :: inline (byte: u8) -> bool { // BBBB BBBB & 1110 0000 == 110X XXXX -> 1 initial + 1 continuation byte // BBBB BBBB & 1111 0000 == 1110 XXXX -> 1 initial + 2 continuation byte // BBBB BBBB & 1111 1000 == 1111 0XXX -> 1 initial + 3 continuation byte +// TODO Maybe rename to: count_character_bytes count_utf8_bytes :: inline (byte: u8) -> int { if (byte & 0xE0) == 0xC0 return 1+1; if (byte & 0xF0) == 0xE0 return 1+2; @@ -16,6 +18,7 @@ count_utf8_bytes :: inline (byte: u8) -> int { // Truncates the string to the length provided or shorter, in case of UTF8 strings that require so. // Truncation is done by zeroing the tail of the string in place. // Returns length of truncated string. +// TODO Maybe rename to: truncate truncate_string :: (str: string, length: int) -> length: int { if str.data == null then return -1; @@ -26,7 +29,6 @@ truncate_string :: (str: string, length: int) -> length: int { // Find index of first continuation byte. idx := length; - // while (idx > 0 && ((data[idx - 1] & 0xC0) == 0x80)) { TODO REMOVE AFTER TESTING while (idx > 0 && is_utf8_continuation_byte(data[idx - 1])) { idx -= 1; } @@ -48,10 +50,12 @@ truncate_string :: (str: string, length: int) -> length: int { } memset(data + length, 0, count - length); + // str.count = length; TODO We should be doing this... return length; } // Returns true when the string is empty or consists of space characters. +// TODO Maybe rename to: is_empty is_empty_string :: (str: string) -> bool { for 0..str.count-1 { if str[it] == { @@ -69,3 +73,45 @@ is_empty_string :: (str: string) -> bool { } return true; } + +// Counts number of characters in string. +count_characters :: (str: string) -> int { + characters := 0; + idx := 0; + while idx < str.count { + idx += count_utf8_bytes(str[idx]); + characters += 1; + } + return characters; +} + +// Delete character. +delete_character :: (str: *string, character_idx: int) { + buffer_idx := map_character_to_buffer_idx(str.*, character_idx); + bytes_to_delete := count_utf8_bytes(str.data[buffer_idx]); + + for buffer_idx..str.count-1-bytes_to_delete { + str.data[it] = str.data[it+bytes_to_delete]; + } + for str.count-bytes_to_delete..str.count-1 { + str.data[it] = 0; + } + + str.count -= bytes_to_delete; +} + +// Get character index. +// TODO Maybe rename to: map_character_to_byte_idx or get_character_byte_idx +map_character_to_buffer_idx :: (str: string, character_idx: int) -> buffer_idx: int, success: bool { + if character_idx < 0 then return -1, false; + if character_idx > str.count then return -2, false; + if character_idx == 0 then return 0, true; + + buff_idx := 0; + char_idx := 0; + while buff_idx < str.count && char_idx != character_idx { + buff_idx += count_utf8_bytes(str[buff_idx]); + char_idx += 1; + } + return buff_idx, char_idx == character_idx; +} diff --git a/ttt.jai b/ttt.jai index 7fdd9af..ffe9494 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1674,7 +1674,7 @@ main :: () { TUI.flush_input(); TUI.set_next_key(TUI.Keys.F2); - // Rename task. + // Rename. case TUI.Keys.F2; if (selected_task == null) continue; -- cgit v1.2.3 From 972508efbcfa8f65514fa115d4d49adc6f01683a Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 14 Apr 2024 02:45:03 +0100 Subject: Add UTF8 support on windows. --- modules/TUI/module.jai | 23 ++--- modules/TUI/windows.jai | 266 ++++++++++++++++++++++++------------------------ 2 files changed, 142 insertions(+), 147 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 2d23bff..7c9d7b1 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -267,21 +267,22 @@ Keys :: struct #type_info_none { active := false; -//input_buffer : [64] u8; // TODO FIXME Input buffer is too small!!! -input_buffer : [8] u8; // TODO FIXME Input buffer is too small!!! +input_buffer : [1024] u8; input_string : string; input_override : Key; #run { - // TODO FIXME DEBUG HACK or maybe... let it be?! - // Some tests. - assert(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire terminal (6 bytes) or UTF8 (4 bytes) code."); + 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)."); } assert_is_active :: inline () { assert(active, "TUI is not ready."); } +//////////////////////////////////////////////////////////////////////////////// + +// #scope_export TODO Setup the scope_export and scope_file + set_next_key :: (key: Key) { assert_is_active(); input_override = key; @@ -295,7 +296,7 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { return input_override; } - if OS_was_terminal_resized() return xx Keys.Resize; + if OS_was_terminal_resized() return Keys.Resize; should_read_input := false; is_input_available := false; @@ -309,7 +310,7 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { is_input_available = OS_wait_for_input(0); } - if OS_was_terminal_resized() return xx Keys.Resize; + if OS_was_terminal_resized() return Keys.Resize; if should_read_input && is_input_available { // Copy buffered bytes to the start, and read the remaining ones from input. @@ -687,11 +688,3 @@ set_terminal_title :: (title: string) { assert_is_active(); print(Commands.SetWindowTitle, title); } - -// TODO -#if OS == .WINDOWS { - // Prototyping zone... keep clear! -} -else #if OS == .LINUX || OS == .MACOS { - // Prototyping zone... keep clear! -} diff --git a/modules/TUI/windows.jai b/modules/TUI/windows.jai index f79a5cf..5155a3f 100644 --- a/modules/TUI/windows.jai +++ b/modules/TUI/windows.jai @@ -1,9 +1,12 @@ #scope_file #import "Basic"; -#import "Atomics"; #import "System"; #import "Windows"; +#import "Windows_Utf8"; + + // Code page identifiers. + CP_UTF8 :: 65001; // https://learn.microsoft.com/windows/win32/winprog/windows-data-types LPVOID :: *void; @@ -13,42 +16,18 @@ SHORT :: s16; USHORT :: u16; WORD :: u16; - DWORD :: s32; - LPDWORD :: *s32; + DWORD :: u32; + LPDWORD :: *u32; UINT :: u32; - PINPUT_RECORD :: *INPUT_RECORD; -// https://learn.microsoft.com/windows/console/console-virtual-terminal-sequences -// https://learn.microsoft.com/windows/console/console-virtual-terminal-sequences#designate-character-set -// https://github.com/MicrosoftDocs/Console-Docs/blob/main/docs/console-virtual-terminal-sequences.md - - kernel32 :: #system_library "kernel32"; - - // TODO Cleanup unused foreign procedures. - - // https://learn.microsoft.com/windows/console/getconsolescreenbufferinfo - GetConsoleScreenBufferInfo :: (hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: *CONSOLE_SCREEN_BUFFER_INFO) -> bool #foreign kernel32; - // https://learn.microsoft.com/en-us/windows/console/readconsoleinput - ReadConsoleInputA :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> success: bool #foreign kernel32; + ReadConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> success: bool #foreign kernel32; // https://learn.microsoft.com/windows/console/readconsole - ReadConsoleA :: (hConsoleInput: HANDLE, lpBuffer: LPVOID, nNumberOfCharsToRead: DWORD, lpNumberOfCharsRead: LPVOID, pInputControl := LPVOID) -> success: bool #foreign kernel32; - - // https://learn.microsoft.com/windows/console/getconsolemode - GetConsoleMode :: (hConsoleHandle: HANDLE, lpMode: *DWORD) -> BOOL #foreign kernel32; - - // https://learn.microsoft.com/windows/console/setconsolemode - SetConsoleMode :: (hConsoleHandle: HANDLE, dwMode: DWORD) -> BOOL #foreign kernel32; - - // https://learn.microsoft.com/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror - GetLastError :: () -> s32 #foreign kernel32; - - // https://learn.microsoft.com/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject - WaitForSingleObject :: (hHandle: HANDLE, dwMilliseconds: DWORD) -> s32 #foreign kernel32; - + ReadConsoleW :: (hConsoleInput: HANDLE, lpBuffer: LPVOID, nNumberOfchars_to_read: DWORD, lpNumberOfchars_read: LPVOID, pInputControl: LPVOID) -> success: bool #foreign kernel32; + // https://learn.microsoft.com/windows/console/flushconsoleinputbuffer FlushConsoleInputBuffer :: (hConsoleInput: HANDLE) -> bool #foreign kernel32; @@ -56,8 +35,8 @@ GetNumberOfConsoleInputEvents :: (hConsoleInput: HANDLE, lpcNumberOfEvents: LPDWORD) -> bool #foreign kernel32; // https://learn.microsoft.com/en-us/windows/console/peekconsoleinput - PeekConsoleInputA :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> bool #foreign kernel32; - + PeekConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> bool #foreign kernel32; + // https://learn.microsoft.com/en-us/windows/console/setconsolemode // https://learn.microsoft.com/en-us/windows/console/high-level-console-modes Console_Input_Mode :: enum_flags u32 { @@ -65,12 +44,12 @@ ENABLE_LINE_INPUT; // If enable, ReadFile or ReadConsole function return on CR; otherwise they return when one or more characters are available. ENABLE_ECHO_INPUT; // Echoes input on screen. Only available if ENABLE_LINE_INPUT is set. ENABLE_WINDOW_INPUT; - ENABLE_MOUSE_INPUT; // + ENABLE_MOUSE_INPUT; // Makes mouse events available to the ReadConsoleInput function. ENABLE_INSERT_MODE; // If enabled, text entered will be inserted at the current cursor location and all text following that location will not be overwritten. When disabled, all following text will be overwritten. _UNUSED_0040_; _UNUSED_0080_; _UNUSED_0100_; - ENABLE_VIRTUAL_TERMINAL_INPUT; // + ENABLE_VIRTUAL_TERMINAL_INPUT; // If enable, makes user input available to the ReadConsole function. _UNUSED_0400_; _UNUSED_0800_; } @@ -167,124 +146,111 @@ initial_stdout_mode: u32; raw_stdout_mode: Console_Output_Mode; + was_resized: bool; -//////////////////////////////////////////////////////////////////////////////// -// Resize detection -was_resized : bool; - -resize_handler :: (signal_code : s32) #c_call { - /* TODO - Try to implement using: - https://learn.microsoft.com/en-us/windows/console/reading-input-buffer-events - https://learn.microsoft.com/en-us/windows/console/readconsoleinput - https://learn.microsoft.com/en-us/windows/console/getnumberofconsoleinputevents - */ - new_context : Context; - push_context new_context { - if signal_code != SIGWINCH then return; - atomic_swap(*was_resized, true); - } -} + windows_buffer: [512] u16; -prepare_resize_handler :: () { - /* TODO - sa : sigaction_t; - sa.sa_handler = resize_handler; - sigemptyset(*(sa.sa_mask)); - sa.sa_flags = SA_RESTART; - sigaction(SIGWINCH, *sa, null); - */ -} -restore_resize_handler :: () { - /* TODO - sa : sigaction_t; - sa.sa_handler = SIG_DFL; - sigaction(SIGWINCH, null, *sa); - */ -} - -peek_input :: inline () -> INPUT_RECORD { +peek_input :: inline () -> INPUT_RECORD, success := true { record: INPUT_RECORD; - records_read: s32; - if PeekConsoleInputA(stdin, *record, 1, *records_read) == false { - _, error_message := get_error_value_and_string(); - assert(false, error_message); + records_read: u32; + if PeekConsoleInputW(stdin, *record, 1, *records_read) == false { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to peek input: code %, %", error_code, error_string); + return record, false; } return record; } -read_input :: inline () -> INPUT_RECORD { +read_input :: inline () -> INPUT_RECORD, success := true { record: INPUT_RECORD; - records_read: s32; - if ReadConsoleInputA(stdin, *record, 1, *records_read) == false { - _, error_message := get_error_value_and_string(); - assert(false, error_message); + records_read: u32; + if ReadConsoleInputW(stdin, *record, 1, *records_read) == false { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to read input: code %, %", error_code, error_string); + return record, false; } return record; } -count_input :: inline () -> s32 { - count: s32; +count_input :: inline () -> u32, success := true { + count: u32; if GetNumberOfConsoleInputEvents(stdin, *count) == false { - _, error_message := get_error_value_and_string(); - assert(false, error_message); + error_code, error_string := get_error_value_and_string(); + log_error("Failed to count input: code %, %", error_code, error_string); + return 0, false; } return count; } - //////////////////////////////////////////////////////////////////////////////// #scope_export +// TODO All the log_error calls will be hidden by the terminal setup... we should store the logs internally, or write it to a file. + OS_prepare_terminal :: () { // stdin stdin = GetStdHandle(STD_INPUT_HANDLE); if stdin == INVALID_HANDLE_VALUE { - print("Invalid input handler.", to_standard_error = true); + error_code, error_string := get_error_value_and_string(); + log_error("Invalid input handler: code %, %", error_code, error_string); return; } if xx GetConsoleMode(stdin, *initial_stdin_mode) == false { - print("Failed to get input mode.", to_standard_error = true); + error_code, error_string := get_error_value_and_string(); + log_error("Failed to get input mode: code %, %", error_code, error_string); return; } raw_stdin_mode = (cast(Console_Input_Mode) initial_stdin_mode); raw_stdin_mode |= (.ENABLE_VIRTUAL_TERMINAL_INPUT); raw_stdin_mode &= ~(.ENABLE_LINE_INPUT | .ENABLE_PROCESSED_INPUT | .ENABLE_ECHO_INPUT); - if SetConsoleMode(stdin, xx raw_stdin_mode) == false { - print("Failed to set input mode: %.", GetLastError(), to_standard_error = true); + if xx SetConsoleMode(stdin, xx raw_stdin_mode) == false { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to set input mode: code %, %", error_code, error_string); return; } // stdout stdout = GetStdHandle(STD_OUTPUT_HANDLE); if stdout == INVALID_HANDLE_VALUE { - print("Invalid output handler.", to_standard_error = true); + error_code, error_string := get_error_value_and_string(); + log_error("Invalid output handler: code %, %", error_code, error_string); return; } if xx GetConsoleMode(stdout, *initial_stdout_mode) == false { - print("Failed to get output mode.", to_standard_error = true); + error_code, error_string := get_error_value_and_string(); + log_error("Failed to get output mode: code %, %", error_code, error_string); return; } raw_stdout_mode = (cast(Console_Output_Mode) initial_stdout_mode); raw_stdout_mode |= (.ENABLE_VIRTUAL_TERMINAL_PROCESSING | .ENABLE_PROCESSED_OUTPUT | .ENABLE_WRAP_AT_EOL_OUTPUT); - if SetConsoleMode(stdout, xx raw_stdout_mode) == false { - print("Failed to set output mode: %.", GetLastError(), to_standard_error = true); + if xx SetConsoleMode(stdout, xx raw_stdout_mode) == false { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to set output mode: code %, %", error_code, error_string); return; } + + // Acording to [documentation](https://learn.microsoft.com/en-us/windows/win32/intl/code-pages) + // only the ANSI functions (ending in A) use the Code Page info. The Unicode functions (ending in W) + // already handle Unicode text. + // As long we use the Unicode functions, we shouldn't need to set the code page to UTF8. + // SetConsoleCP(CP_UTF8); + // SetConsoleOutputCP(CP_UTF8); } OS_reset_terminal :: () { if xx SetConsoleMode(stdin, initial_stdin_mode) == false { - print("Failed to reset input mode: %.", GetLastError(), to_standard_error = true); + error_code, error_string := get_error_value_and_string(); + log_error("Failed to reset input mode: code %, %", error_code, error_string); return; } if xx SetConsoleMode(stdout, initial_stdout_mode) == false { - print("Failed to reset output mode: %.", GetLastError(), to_standard_error = true); + error_code, error_string := get_error_value_and_string(); + log_error("Failed to reset output mode: code %, %", error_code, error_string); return; } } @@ -294,33 +260,73 @@ OS_flush_input :: inline () { This API is not recommended and does not have a virtual terminal equivalent. Attempting to empty the input queue all at once can destroy state in the queue in an unexpected manner. */ - success := FlushConsoleInputBuffer(stdin); - if success == false { - _, error_message := get_error_value_and_string(); - assert(false, error_message); // TODO A bit harsh arent we? + if FlushConsoleInputBuffer(stdin) == false { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to flush input: code %, %", error_code, error_string); } } -OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, error: bool = false, error_message: string = "" { - - assert(bytes_to_read <= 0x7fff_ffff, "The Windows API only allows to read up to s32 bytes from the standard input."); +OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success := true { - bytes_read: s32 = 0; - available_inputs := count_input(); + S32_MAX :: 0x7fff_ffff; + if (bytes_to_read > S32_MAX) { + log_error("The Windows API only allows to read up to 2^32 bytes from the standard input. Clamping input argument."); + bytes_to_read = S32_MAX; + } + success: bool; + bytes_read: s64 = 0; + available_inputs:, success = count_input(); + while bytes_to_read > 0 && available_inputs > 0 { - record := read_input(); - if record.EventType == .WINDOW_BUFFER_SIZE_EVENT { - was_resized = true; - } + record := peek_input(); + + if record.EventType == { + case .WINDOW_BUFFER_SIZE_EVENT; + was_resized = true; + read_input(); + available_inputs -= 1; + + case .KEY_EVENT; + if record.KeyEvent.bKeyDown == false { + read_input(); + available_inputs -= 1; + } + else { + + chars_view: [] u16; + chars_view.data = windows_buffer.data; + + chars_to_read := ifx available_inputs <= windows_buffer.count then available_inputs else windows_buffer.count; + + success = ReadConsoleW(stdin, chars_view.data, chars_to_read, *chars_view.count, null); + if success == false { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to read console: code %, %", error_code, error_string); + return 0, false; + } + + result:, success = wide_to_utf8_new(chars_view.data, xx chars_view.count); + if success == false { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to convert from wide to utf8: code %, %", error_code, error_string); + return 0, false; + } + + memcpy(*buffer[bytes_read], result.data, result.count); + + bytes_to_read -= xx result.count; + bytes_read += result.count; - if record.EventType == .KEY_EVENT && record.KeyEvent.bKeyDown == true { - buffer[bytes_read] = xx record.KeyEvent.AsciiChar; - bytes_to_read -= 1; - bytes_read += 1; + } + + // Discard other input events. + case; + read_input(); + } - available_inputs -= 1; + available_inputs = count_input(); } return bytes_read; } @@ -328,7 +334,7 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, error: bo // timeout_milliseconds // 0: do not wait // -1: wait indefinitely -OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool { +OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool, success := true { /* TODO Add a good comment explaining how the windows part of the module was implemented... what's the idea behind it. @@ -340,27 +346,22 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo expiration := current_time_monotonic() + to_apollo(timeout_milliseconds / 1000.0); - // Possible values for poll_return TODO NOT BEING USED - WAIT_ABANDONED :: 0x00000080; // Mutex stuff. + // Possible values for poll_return: https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject WAIT_OBJECT_0 :: 0x00000000; // Detected input. WAIT_TIMEOUT :: 0x00000102; // Reached timeout. WAIT_FAILED :: 0xFFFFFFFF; // Something went wrong. - + while true { - poll_return := WaitForSingleObject(stdin, timeout_milliseconds); - - // TODO Weird looking code... - if poll_return == { - case WAIT_TIMEOUT; - return false; - case WAIT_ABANDONED; - print("MUTEX STUFF"); // https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject - return false; - case WAIT_FAILED; - _, error_message := get_error_value_and_string(); - assert(false, error_message); + wait_result := WaitForSingleObject(stdin, cast,no_check(u32)timeout_milliseconds); + + if wait_result == WAIT_FAILED { + error_code, error_string := get_error_value_and_string(); + log_error("Error while waiting for input: code %, %", error_code, error_string); + return false, false; } - + + if wait_result != WAIT_OBJECT_0 then return false; + // Discard invalid input events. count := count_input(); while count > 0 { @@ -369,9 +370,10 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo if record.EventType == .WINDOW_BUFFER_SIZE_EVENT { // Discard any additional resize event. while peek_input().EventType == .WINDOW_BUFFER_SIZE_EVENT { - was_resized = true; read_input(); } + + was_resized = true; return false; } @@ -382,13 +384,13 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo read_input(); count -= 1; } - + // When waiting indefinitely... just continue. if timeout_milliseconds < 0 then continue; // Either break due to timeout, or update the remaining timeout. now := current_time_monotonic(); - if now >= expiration then return false; + if now >= expiration then break; timeout_milliseconds = xx to_milliseconds(expiration - now); } @@ -400,7 +402,7 @@ OS_was_terminal_resized :: () -> bool { was_resized = true; read_input(); } - + defer was_resized = false; return was_resized; } -- cgit v1.2.3 From 5844ab303d3d7fc396beb7c6d8a0104496608f08 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 14 Apr 2024 18:08:15 +0100 Subject: Add validation to COLOR_MODE module parameter. --- modules/TUI/module.jai | 17 +++++++++-------- ttt.jai | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 7c9d7b1..3843cd7 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,4 +1,4 @@ -#module_parameters(COLOR_BIT_DEPTH := 24); // TODO - Do #assert module parameters to make sure they are valid... and maybe rename to: COLOR_MODE with a enum. +#module_parameters(COLOR_MODE := 24); #if OS == { case .LINUX; @@ -67,10 +67,10 @@ Commands :: struct { SetWindowTitle :: "\e]0;%\e\\"; - RefreshWindow :: "\e[7t"; // TODO Not yet tested. - - SetIEC2022 :: "\e%@"; - SetUTF8 :: "\e%G"; + RefreshWindow :: "\e[7t"; // TODO Not yet tested. Is this required? + + SetIEC2022 :: "\e%@"; // TODO Remove this!? + SetUTF8 :: "\e%G"; // TODO Remove this!? SetGraphicsRendition :: "\e[%m"; @@ -106,7 +106,7 @@ Commands :: struct { QueryWindowSizeInChars :: "\e[18t"; // Emits the window size as: "ESC [ 8 ; t" Where = row and = column. TODO Does not work on windows. } -#if COLOR_BIT_DEPTH == 4 { +#if COLOR_MODE == 4 { #load "palette_4b.jai"; set_colors :: inline (foreground: Palette, background: Palette) { @@ -115,7 +115,7 @@ Commands :: struct { cast(u8)foreground + 30, cast(u8)background + 40); } } -else #if COLOR_BIT_DEPTH == 8 { +else #if COLOR_MODE == 8 { #load "palette_8b.jai"; set_colors :: inline (foreground: Palette, background: Palette) { @@ -144,7 +144,7 @@ else { #add_context tui_style: Style; Style :: struct { - #if COLOR_BIT_DEPTH == 4 || COLOR_BIT_DEPTH == 8 { + #if COLOR_MODE == 4 || COLOR_MODE == 8 { background: Palette = .BLACK; foreground: Palette = .WHITE; } else { @@ -272,6 +272,7 @@ input_string : string; input_override : Key; #run { + assert(COLOR_MODE == 4 || COLOR_MODE == 8 || COLOR_MODE == 24, "Invalid COLOR_MODE. 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)."); } diff --git a/ttt.jai b/ttt.jai index ffe9494..13c9fcf 100644 --- a/ttt.jai +++ b/ttt.jai @@ -26,7 +26,7 @@ #import "String"; #import "Integer_Saturating_Arithmetic"; #import "UTF8"; -TUI :: #import "TUI"(COLOR_BIT_DEPTH=4); +TUI :: #import "TUI"(COLOR_MODE=4); VERSION :: "2.0"; // Use only 3 chars (to fit layouts). YEAR :: "2024"; -- cgit v1.2.3 From 3112131c24ede1ea343383ffd4f8ca7bc4783f47 Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 15 Apr 2024 12:38:18 +0100 Subject: Add module logger that switches between main/alternate screen buffers to write logs. --- modules/TUI/module.jai | 27 ++++++++++++++++++++++----- modules/TUI/windows.jai | 44 +++++++++++++------------------------------- ttt.jai | 27 ++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 37 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 3843cd7..070161c 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -17,6 +17,11 @@ #import "UTF8"; #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(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)."); +} + // Special Graphics Characters Drawings :: struct { Blank :: "\x5F"; @@ -271,9 +276,15 @@ input_buffer : [1024] u8; input_string : string; input_override : Key; -#run { - assert(COLOR_MODE == 4 || COLOR_MODE == 8 || COLOR_MODE == 24, "Invalid COLOR_MODE. 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)."); +previous_logger : (message: string, data: *void, info: Log_Info); + +module_logger :: (message: string, data: *void, info: Log_Info) { + // print("%0%0\n%0", Commands.MainScreenBuffer, message, Commands.AlternateScreenBuffer); + x, y := get_cursor_position(); + write_string(Commands.MainScreenBuffer); + previous_logger(message, data, info); + write_string(Commands.AlternateScreenBuffer); + set_cursor_position(x, y); } assert_is_active :: inline () { @@ -444,7 +455,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { } else { set_cursor_position(x, y); - for 0..chars_count print_character(#char "*"); + for 1..chars_count print_character(#char "*"); for chars_count..count_limit-1 print_character(#char " "); } set_cursor_position(x+idx, y); @@ -510,7 +521,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { start :: () { if active == true return; - + // TODO Should start() call flush_input internally? setup_key_map(); // TODO This is being called multiple times... please fix me! @@ -526,6 +537,9 @@ start :: () { Commands.SetUTF8, Commands.CursorNormalMode, Commands.KeypadNumMode); + + previous_logger = context.logger; + context.logger = module_logger; OS_prepare_terminal(); @@ -538,6 +552,9 @@ stop :: () { clear_style(); OS_reset_terminal(); + + context.logger = previous_logger; + write_strings(Commands.MainScreenBuffer, Commands.RestoreCursorPosition, Commands.ShowCursor); } diff --git a/modules/TUI/windows.jai b/modules/TUI/windows.jai index 5155a3f..5755ef4 100644 --- a/modules/TUI/windows.jai +++ b/modules/TUI/windows.jai @@ -72,21 +72,6 @@ Y : SHORT; } - SMALL_RECT :: struct { - Left : SHORT; - Top : SHORT; - Right : SHORT; - Bottom : SHORT; - } - - CONSOLE_SCREEN_BUFFER_INFO :: struct { - dwSize : COORD; - dwCursorPosition : COORD; - wAttributes : WORD; - srWindow : SMALL_RECT; - dwMaximumWindowSize : COORD; - } - INPUT_RECORD_EVENT_TYPE :: enum u16 { KEY_EVENT :: 0x0001; MOUSE_EVENT :: 0x0002; @@ -148,7 +133,7 @@ was_resized: bool; - windows_buffer: [512] u16; + widechar_buffer: [512] u16; peek_input :: inline () -> INPUT_RECORD, success := true { @@ -187,8 +172,6 @@ count_input :: inline () -> u32, success := true { #scope_export -// TODO All the log_error calls will be hidden by the terminal setup... we should store the logs internally, or write it to a file. - OS_prepare_terminal :: () { // stdin @@ -256,7 +239,7 @@ OS_reset_terminal :: () { } OS_flush_input :: inline () { - /* NOTE + /* This API is not recommended and does not have a virtual terminal equivalent. Attempting to empty the input queue all at once can destroy state in the queue in an unexpected manner. */ @@ -295,19 +278,19 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : } else { - chars_view: [] u16; - chars_view.data = windows_buffer.data; + widechar_view: [] u16; + widechar_view.data = widechar_buffer.data; - chars_to_read := ifx available_inputs <= windows_buffer.count then available_inputs else windows_buffer.count; + chars_to_read := ifx available_inputs <= widechar_buffer.count then available_inputs else widechar_buffer.count; - success = ReadConsoleW(stdin, chars_view.data, chars_to_read, *chars_view.count, null); + success = ReadConsoleW(stdin, widechar_view.data, chars_to_read, *widechar_view.count, null); if success == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to read console: code %, %", error_code, error_string); return 0, false; } - result:, success = wide_to_utf8_new(chars_view.data, xx chars_view.count); + result:, success = wide_to_utf8_new(widechar_view.data, xx widechar_view.count); if success == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to convert from wide to utf8: code %, %", error_code, error_string); @@ -335,13 +318,12 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : // 0: do not wait // -1: wait indefinitely OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool, success := true { - - /* TODO - Add a good comment explaining how the windows part of the module was implemented... what's the idea behind it. - Something like, Since windows provides a single input buffer with all events, we need to peek through them and - discard the ones that are of no use for us. - Because it's a single buffer, all functions need to do repeated work (see if it's resize, see if it's a key press) - ... and so on. + /* + The Windows API provides all input events (keyboard, mouse, window resize) on a single input buffer. + To make it match this module's API, we need to do some pre-processing while waiting for input. + This means that OS_wait_for_input will peek at the input events, signal if a window resize is found, + and discard unwanted events (like button release events). + A similar logic is applied in OS_read_input. */ expiration := current_time_monotonic() + to_apollo(timeout_milliseconds / 1000.0); diff --git a/ttt.jai b/ttt.jai index 13c9fcf..8dda204 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1157,7 +1157,7 @@ prompt_user_key :: (y: int, message: string) -> TUI.Key { main :: () { // -- -- -- Testing TUI -- START - + perform_test := false; assert_result :: (result: bool, error_message: string) { @@ -1187,6 +1187,31 @@ main :: () { 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."); + TUI.start(); + + TUI.set_cursor_position(3, 3); + print("wait"); + sleep_milliseconds(1000); + log("- log: while module is active."); + sleep_milliseconds(1000); + print(" a bit"); + sleep_milliseconds(1000); + + #import "Windows"; + handle: HANDLE = ---; + initial_stdin_mode: u32; + if xx GetConsoleMode(handle, *initial_stdin_mode) == false { + error_code, error_string := get_error_value_and_string(); + log_error("- log: error code %, %", error_code, error_string); + } + + TUI.stop(); + log("- log: after module stop."); + } + if perform_test && 1 { print("TEST : test key input\n", to_standard_error = true); auto_release_temp(); -- cgit v1.2.3 From bb1d53daa526d734fb2e33c2090f8396f87544ec Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 15 Apr 2024 16:50:42 +0100 Subject: Improved TUI logger; added snake example using TUI. --- modules/TUI/module.jai | 72 ++++++++++++----------- modules/TUI/unix.jai | 2 +- snake.jai | 157 +++++++++++++++++++++++++++++++++++++++++++++++++ ttt.jai | 10 ---- 4 files changed, 195 insertions(+), 46 deletions(-) create mode 100644 snake.jai (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 070161c..b5866d5 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -59,38 +59,44 @@ Drawings :: struct { } Commands :: struct { + // Screen buffers AlternateScreenBuffer :: "\e[?1049h"; - MainScreenBuffer :: "\e[?1049l"; + MainScreenBuffer :: "\e[?1049l"; + // Device. + Bell :: "\x07"; + QueryDeviceAttributes :: "\e[0c"; + + // Draw/text. DrawingMode :: "\e(0"; TextMode :: "\e(B"; ClearScreen :: "\e[2J"; ClearLine :: "\e[2K"; ClearScrollBack :: "\e[3J"; - - Bell :: "\x07"; - + SetGraphicsRendition :: "\e[%m"; + + // Character encoding. + EncodingIEC2022 :: "\e%@"; + EncodingUTF8 :: "\e%G"; + + // Window. SetWindowTitle :: "\e]0;%\e\\"; - - RefreshWindow :: "\e[7t"; // TODO Not yet tested. Is this required? + RefreshWindow :: "\e[7t"; + QueryWindowSizeInChars :: "\e[18t"; - SetIEC2022 :: "\e%@"; // TODO Remove this!? - SetUTF8 :: "\e%G"; // TODO Remove this!? - - SetGraphicsRendition :: "\e[%m"; - - // Cursor Position + // Cursor position. + SaveCursorPosition :: "\e7"; + RestoreCursorPosition :: "\e8"; SetCursorPosition :: "\e[%;%H"; - - // Cursor Visibility + QueryCursorPosition :: "\e[6n"; + + // Cursor visibility. ShowCursor :: "\e[?25h"; HideCursor :: "\e[?25l"; - StartBlinking :: "\e[?25h"; - StopBlinking :: "\e[?25l"; - SaveCursorPosition :: "\e7"; - RestoreCursorPosition :: "\e8"; - - // Cursor Shape + StartBlinking :: "\e[?12h"; + StopBlinking :: "\e[?12l"; + + // Cursor shape DefaultShape :: "\e[0 q"; BlinkingBlockShape :: "\e[1 q"; SteadyBlockShape :: "\e[2 q"; @@ -98,17 +104,12 @@ Commands :: struct { SteadyUnderlineShape :: "\e[4 q"; BlinkingBarShape :: "\e[5 q"; SteadyBarShape :: "\e[6 q"; - - // Input Mode + + // Input mode. KeypadAppMode :: "\e="; KeypadNumMode :: "\e>"; CursorAppMode :: "\e[?1h"; CursorNormalMode :: "\e[?1l"; - - // Query State - QueryCursorPosition :: "\e[6n"; // Emits the cursor position as: "ESC [ ; R" Where = row and = column. - QueryDeviceAttributes :: "\e[0c"; - QueryWindowSizeInChars :: "\e[18t"; // Emits the window size as: "ESC [ 8 ; t" Where = row and = column. TODO Does not work on windows. } #if COLOR_MODE == 4 { @@ -150,12 +151,16 @@ else { Style :: struct { #if COLOR_MODE == 4 || COLOR_MODE == 8 { - background: Palette = .BLACK; - foreground: Palette = .WHITE; + background: Palette; + foreground: Palette; } else { background: Color_24b; foreground: Color_24b; } + + background = Palette.BLACK; + foreground = Palette.WHITE; + bold: bool; underline: bool; strike_through: bool; @@ -279,12 +284,9 @@ input_override : Key; previous_logger : (message: string, data: *void, info: Log_Info); module_logger :: (message: string, data: *void, info: Log_Info) { - // print("%0%0\n%0", Commands.MainScreenBuffer, message, Commands.AlternateScreenBuffer); - x, y := get_cursor_position(); - write_string(Commands.MainScreenBuffer); + write_strings(Commands.SaveCursorPosition, Commands.MainScreenBuffer); previous_logger(message, data, info); - write_string(Commands.AlternateScreenBuffer); - set_cursor_position(x, y); + write_strings(Commands.AlternateScreenBuffer, Commands.RestoreCursorPosition); } assert_is_active :: inline () { @@ -534,7 +536,7 @@ start :: () { Commands.HideCursor, Commands.SaveCursorPosition, Commands.AlternateScreenBuffer, - Commands.SetUTF8, + Commands.EncodingUTF8, Commands.CursorNormalMode, Commands.KeypadNumMode); diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index 861fe11..7103d47 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -7,7 +7,7 @@ // Required to do unlocking input. libc :: #system_library "libc"; - // TODO Remote this. + // TODO Remove this. // cfmakeraw :: (termios: *Terminal_IO_Mode) -> void #foreign libc; /* https://elixir.bootlin.com/glibc/glibc-2.28/source/termios/cfmakeraw.c#L22 void diff --git a/snake.jai b/snake.jai new file mode 100644 index 0000000..ea8926f --- /dev/null +++ b/snake.jai @@ -0,0 +1,157 @@ +#import "Basic"; +#import "Random"; +TUI :: #import "TUI"; + +Vec2D :: struct { + x: int; + y: int; +} + +operator == :: (a: Vec2D, b: Vec2D) -> bool { + return a.x == b.x && a.y == b.y; +} + +screen_size_x: int = ---; +screen_size_y: int = ---; +player_name: string = ---; + +main :: () { + + game_loop :: () { + + LOOP_PERIOD_MS :: 30; + + score := 0; + dir := Vec2D.{1, 0}; + food := Vec2D.{5, 5}; + + random_food :: () -> Vec2D { + return Vec2D.{ + cast(int)(random_get_zero_to_one_open() * (screen_size_x-3) + 2), + cast(int)(random_get_zero_to_one_open() * (screen_size_y-3) + 2) + }; + } + + snake_parts: [..] Vec2D; + for 0..13 array_add(*snake_parts, Vec2D.{3, 3}); + snake_parts[0].x += 1; + + TUI.flush_input(); + TUI.set_next_key(TUI.Keys.Resize); + timer := current_time_monotonic(); + while main_loop := true { + + timestamp := current_time_monotonic(); + key := TUI.get_key(LOOP_PERIOD_MS); + + if key == { + case TUI.Keys.Resize; + TUI.clear_terminal(); + screen_size_x, screen_size_y = TUI.get_terminal_size(); + TUI.draw_box(1, 1, screen_size_x, screen_size_y); + TUI.set_cursor_position(3, screen_size_y); + write_strings(" ", player_name, " "); + food = random_food(); + + case #char "q"; #through; + case #char "Q"; #through; + case TUI.Keys.Escape; + break main_loop; + + case TUI.Keys.Up; + if dir != Vec2D.{0, 1} then dir = Vec2D.{0, -1}; + + case TUI.Keys.Down; + if dir != Vec2D.{0, -1} then dir = Vec2D.{0, 1}; + + case TUI.Keys.Left; + if dir != Vec2D.{1, 0} then dir = Vec2D.{-1, 0}; + + case TUI.Keys.Right; + if dir != Vec2D.{-1, 0} then dir = Vec2D.{1, 0}; + } + + last_pos := snake_parts[snake_parts.count-1]; + + // Update position. + for < snake_parts.count-1..1 { + if snake_parts[it] != snake_parts[it-1] { + snake_parts[it] = snake_parts[it-1]; + } + } + snake_parts[0].x += dir.x; + snake_parts[0].y += dir.y; + + // Teleport on borders. + if snake_parts[0].x < 2 then snake_parts[0].x = screen_size_x - 1; + if snake_parts[0].x >= screen_size_x then snake_parts[0].x = 2; + if snake_parts[0].y < 2 then snake_parts[0].y = screen_size_y - 1; + if snake_parts[0].y >= screen_size_y then snake_parts[0].y = 2; + + // Check for game-over. + for 1..snake_parts.count-1 { + if snake_parts[it] == snake_parts[0] { + break main_loop; + } + } + + // Check for food. + if snake_parts[0] == food { + score += 1; + array_add(*snake_parts, snake_parts[snake_parts.count-1]); + food = random_food(); + } + + // Wait to match game loop time. + delta := to_milliseconds(current_time_monotonic() - timestamp); + if delta < LOOP_PERIOD_MS { + sleep_milliseconds(xx (LOOP_PERIOD_MS - delta)); + } + + // Draw snake. + write_string(TUI.Commands.DrawingMode); + TUI.set_cursor_position(last_pos.x, last_pos.y); + write_string(TUI.Drawings.Blank); + for snake_parts { + TUI.set_cursor_position(it.x, it.y); + write_string(TUI.Drawings.Checkerboard); + } + // Draw food. + { + TUI.using_style(TUI.Style.{ foreground = TUI.Palette.RED, bold = true, }); + TUI.set_cursor_position(food.x, food.y); + write_string(TUI.Drawings.Diamond); + } + write_string(TUI.Commands.TextMode); + + // Set score + TUI.set_cursor_position(3, 1); + print(" % ", score); + } + } + + GAME_OVER_TEXT :: "~ game over ~"; + INSTRUCTIONS_TEXT :: "(esc to exit)"; + + TUI.start(); + TUI.set_cursor_position(1, 1); + + write_string("Please enter player name: "); + player_name = TUI.read_input_line(64); + + while true { + game_loop(); + + // Game over screen. + TUI.draw_box(screen_size_x/3, screen_size_y/2-1, screen_size_x/3, 4); + TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, screen_size_y/2); + write_string(GAME_OVER_TEXT); + TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, screen_size_y/2+1); + write_string(INSTRUCTIONS_TEXT); + sleep_milliseconds(100); + TUI.flush_input(); + if TUI.get_key() == TUI.Keys.Escape then break; + } + + TUI.stop(); +} diff --git a/ttt.jai b/ttt.jai index 8dda204..8f39b56 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1191,7 +1191,6 @@ main :: () { print("TEST : module logger\n", to_standard_error = true); log("- log: before module start."); TUI.start(); - TUI.set_cursor_position(3, 3); print("wait"); sleep_milliseconds(1000); @@ -1199,15 +1198,6 @@ main :: () { sleep_milliseconds(1000); print(" a bit"); sleep_milliseconds(1000); - - #import "Windows"; - handle: HANDLE = ---; - initial_stdin_mode: u32; - if xx GetConsoleMode(handle, *initial_stdin_mode) == false { - error_code, error_string := get_error_value_and_string(); - log_error("- log: error code %, %", error_code, error_string); - } - TUI.stop(); log("- log: after module stop."); } -- cgit v1.2.3 From b8d743d5b307e0c230ebdb2fe314e041483d6a14 Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 16 Apr 2024 10:10:59 +0100 Subject: Adding error logging on TUI/unix. --- modules/TUI/module.jai | 8 ++- modules/TUI/unix.jai | 140 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 112 insertions(+), 36 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index b5866d5..5fd29f6 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,5 +1,7 @@ #module_parameters(COLOR_MODE := 24); +#scope_file + #if OS == { case .LINUX; #load "unix.jai"; @@ -22,6 +24,8 @@ 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 { Blank :: "\x5F"; @@ -441,7 +445,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line x, y := get_cursor_position(); - write_strings(Commands.StartBlinking, Commands.BlinkingBarShape); + write_strings(Commands.ShowCursor, Commands.StartBlinking, Commands.BlinkingBarShape); key := Keys.None; while true { @@ -515,7 +519,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { } } - write_strings(Commands.StopBlinking, Commands.DefaultShape); + write_strings(Commands.StopBlinking, Commands.DefaultShape, Commands.HideCursor); result := ifx key == Keys.Enter then str else ""; return result, key; diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index 7103d47..8eeb6c0 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -1,5 +1,10 @@ #scope_file +/* +TODO : then log all error on unix... +TODO : then do a good implementation of the libc functions about attributes... +*/ + #import "Atomics"; #import "System"; #import "POSIX"; @@ -7,22 +12,6 @@ // Required to do unlocking input. libc :: #system_library "libc"; - // TODO Remove this. - // cfmakeraw :: (termios: *Terminal_IO_Mode) -> void #foreign libc; - /* https://elixir.bootlin.com/glibc/glibc-2.28/source/termios/cfmakeraw.c#L22 - void - cfmakeraw (struct termios *t) - { - t->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); - t->c_oflag &= ~OPOST; - t->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); - t->c_cflag &= ~(CSIZE|PARENB); - t->c_cflag |= CS8; - t->c_cc[VMIN] = 1; // read returns when one char is available. - t->c_cc[VTIME] = 0; - } - */ - // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcsetattr.c.html // tcsetattr :: (fd: s32, optional_actions: s32, termios_p : *Terminal_IO_Mode) -> s32 #foreign libc; tcsetattr :: (fd: s32, optional_actions: s32, termios_p : *Terminal_IO_Mode) -> s32 { @@ -82,10 +71,68 @@ } // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html - tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 #foreign libc; - // tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 { - // TODO IMPLEMENT ME - // } + // tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 #foreign libc; + tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 { + TCSETS :: 0x5402; + TCSETSW :: 0x5403; + TCSETSF :: 0x5404; + tcflag_t :: u32; + cc_t :: u8; + __KERNEL_NCCS :: 19; + __kernel_termios :: struct { + c_iflag : tcflag_t; // input mode flags + c_oflag : tcflag_t; // output mode flags + c_cflag : tcflag_t; // control mode flags + c_lflag : tcflag_t; // local mode flags + c_line : cc_t; // line discipline + c_cc : [__KERNEL_NCCS]cc_t; // control characters + }; + + + // int + // __tcgetattr (int fd, struct termios *termios_p) + // { + // struct __kernel_termios k_termios; + k_termios: __kernel_termios; + retval: int; + retval = ioctl(fd, TCGETS, *k_termios); + if retval == 0 { + termios_p.c_iflag = xx k_termios.c_iflag; + termios_p.c_oflag = xx k_termios.c_oflag; + termios_p.c_cflag = xx k_termios.c_cflag; + termios_p.c_lflag = xx k_termios.c_lflag; + termios_p.c_line = xx k_termios.c_line; + // #if _HAVE_STRUCT_TERMIOS_C_ISPEED + // # if _HAVE_C_ISPEED + // termios_p->c_ispeed = k_termios.c_ispeed; + // # else + // termios_p->c_ispeed = k_termios.c_cflag & (CBAUD | CBAUDEX); + // # endif + // #endif + // #if _HAVE_STRUCT_TERMIOS_C_OSPEED + // # if _HAVE_C_OSPEED + // termios_p->c_ospeed = k_termios.c_ospeed; + // # else + // termios_p->c_ospeed = k_termios.c_cflag & (CBAUD | CBAUDEX); + // # endif + // #endif + size_of_cc_t := __KERNEL_NCCS * 1; + memcpy(*termios_p.c_cc[0], *k_termios.c_cc[0], size_of_cc_t); + // memset(*termios_p.c_cc[0] + size_of_cc_t + 1, _POSIX_VDISABLE, (NCCS - __KERNEL_NCCS) * 1); + // + // if (sizeof (cc_t) == 1 || _POSIX_VDISABLE == 0 || (unsigned char) _POSIX_VDISABLE == (unsigned char) -1) { + // memset (__mempcpy (&termios_p->c_cc[0], &k_termios.c_cc[0], __KERNEL_NCCS * sizeof (cc_t)), _POSIX_VDISABLE, (NCCS - __KERNEL_NCCS) * sizeof (cc_t)); + // } + // else + // { + // memcpy (&termios_p->c_cc[0], &k_termios.c_cc[0], __KERNEL_NCCS * sizeof (cc_t)); + // for (size_t cnt = __KERNEL_NCCS; cnt < NCCS; ++cnt) { + // termios_p->c_cc[cnt] = _POSIX_VDISABLE; + // } + // } + } + return xx retval; + } // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcflush.c.html // tcflush :: (fd: s32, queue_selector: s32) -> s32 #foreign libc; @@ -232,8 +279,15 @@ restore_resize_handler :: () { #scope_export -OS_prepare_terminal :: () { - tcgetattr(STDIN_FILENO, *initial_tio_mode); // TODO Log error using `log()` from jai/modules/Basic/Print.jai ? +OS_prepare_terminal :: () -> success := true { // On critical paths, we may use the #must for the success return value. + error: int = ---; + + error = tcgetattr(STDIN_FILENO, *initial_tio_mode); + if error { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to get initial_tio_mode: code %, %", error_code, error_string); + } + raw_tio_mode = initial_tio_mode; raw_tio_mode.c_iflag &= ~(.IGNBRK | .BRKINT | .PARMRK | .ISTRIP | .INLCR | .IGNCR | .ICRNL | .IXON); raw_tio_mode.c_oflag &= ~(.OPOST); @@ -242,28 +296,48 @@ OS_prepare_terminal :: () { raw_tio_mode.c_cflag |= .CS8; raw_tio_mode.c_cc[Control_Chars.VMIN] = 1; raw_tio_mode.c_cc[Control_Chars.VTIME] = 0; - tcsetattr(STDIN_FILENO, 0, *raw_tio_mode); // TODO Log on error. - + + error = tcsetattr(STDIN_FILENO, 0, *raw_tio_mode); + if error { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to set raw_tio_mode: code %, %", error_code, error_string); + return false; + } + was_resized = false; prepare_resize_handler(); + return; } -OS_reset_terminal :: () { +OS_reset_terminal :: inline () -> success := true { restore_resize_handler(); - tcsetattr(STDIN_FILENO, 0, *initial_tio_mode); // TODO Log on error. + error := tcsetattr(STDIN_FILENO, 0, *initial_tio_mode); + if error { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to set initial_tio_mode: code %, %", error_code, error_string); + return false; + } + return; } -OS_flush_input :: inline () { +OS_flush_input :: inline () -> success := true { TCIFLUSH :: 0; // TODO Is this always zero in all systems? - tcflush(STDIN_FILENO, TCIFLUSH); + error := tcflush(STDIN_FILENO, TCIFLUSH); + if error { + error_code, error_string := get_error_value_and_string(); + log_error("Failed to flush input: code %, %", error_code, error_string); + return false; + } + return; } // TODO Nothing is checking for the errors returned by this... shame! -OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, error: bool = false, error_message: string = "" { +OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success := true { bytes_read := read(STDIN_FILENO, buffer, xx bytes_to_read); if bytes_read < 0 { - error_code, error_message := get_error_value_and_string(); - return -1, true, error_message; + error_code, error_string := get_error_value_and_string(); + log_error("Failed to read input: code %, %", error_code, error_string); + return 0, false; } return bytes_read; } @@ -279,8 +353,6 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo return ifx poll_return > 0 then true else false; } -// TODO This procedure hides the behaviour of reseting on read. -// We should have the `was_resized` on module.jai so that we know it may be used in another thread. OS_was_terminal_resized :: () -> bool { - return atomic_swap(*was_resized, false); // TODO If the windows implementation is similar, we may push this into the main module file. + return atomic_swap(*was_resized, false); } -- cgit v1.2.3 From d7c2c312fe2ac08cadc3534a0a35357ef50cca20 Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 30 Apr 2024 00:05:26 +0100 Subject: WIP : Add logger and cleanup TUI module. --- modules/TUI/module.jai | 38 ++++--- modules/TUI/unix.jai | 297 ++++++++++++++++++++++++++++-------------------- modules/TUI/windows.jai | 118 +++++++++---------- snake.jai | 7 +- ttt.jai | 42 +++---- 5 files changed, 285 insertions(+), 217 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 5fd29f6..121aa42 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -2,6 +2,9 @@ #scope_file +// - fix/implement/finish `TODO` on `TUI\module` (use some sort of buffet to reduce io/print calls) +// - fix/implement/finish `TODO` on `TUI\module` (use `dirty_bit_flag` to only update ehat has been changed) + #if OS == { case .LINUX; #load "unix.jai"; @@ -525,16 +528,17 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { return result, key; } -start :: () { +start :: () -> success := true #must { if active == true return; - // TODO Should start() call flush_input internally? - - setup_key_map(); // TODO This is being called multiple times... please fix me! - input_string.data = input_buffer.data; input_string.count = 0; input_override = xx Keys.None; + + previous_logger = context.logger; + context.logger = module_logger; + + setup_key_map(); write_strings( Commands.HideCursor, @@ -542,26 +546,34 @@ start :: () { Commands.AlternateScreenBuffer, Commands.EncodingUTF8, Commands.CursorNormalMode, - Commands.KeypadNumMode); - - previous_logger = context.logger; - context.logger = module_logger; + Commands.KeypadNumMode + ); - OS_prepare_terminal(); + if !OS_prepare_terminal() then return false; active = true; + + return; } -stop :: () { +stop :: () -> success := true #must { if active == false return; + active = false; clear_style(); - OS_reset_terminal(); + + if !OS_reset_terminal() then return false; context.logger = previous_logger; - write_strings(Commands.MainScreenBuffer, Commands.RestoreCursorPosition, Commands.ShowCursor); + write_strings( + Commands.MainScreenBuffer, + Commands.RestoreCursorPosition, + Commands.ShowCursor + ); + + return; } flush_input :: () { diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index 8eeb6c0..a6cd467 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -1,22 +1,171 @@ #scope_file /* -TODO : then log all error on unix... TODO : then do a good implementation of the libc functions about attributes... */ +USE_LIBC :: true; + #import "Atomics"; #import "System"; #import "POSIX"; + // Set Attributes Actions. + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-tcflow.h + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html + // OptionalActions :: enum s32 { + TCSANOW :: 0; // Change immediately. + TCSADRAIN :: 1; // Change when pending output is written. + TCSAFLUSH :: 2; // Flush pending input before changing. + // } + + // TODO + // QueueSelector :: enum s32 { + // queue_selector + #if OS == { + case .LINUX; + // https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h + TCIFLUSH :: 0; // Discard data received but not yet read. + TCOFLUSH :: 1; // Discard data written but not yet sent. + TCIOFLUSH :: 2; // Discard all pending data. + + case .MACOS; + // https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html + TCIFLUSH :: 1; // Discard data received but not yet read. + TCOFLUSH :: 2; // Discard data written but not yet sent. + TCIOFLUSH :: 3; // Discard all pending data. + } + } + + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-struct.h + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html + #if OS == { + case .LINUX; + NCCS :: 32; + + case .MACOS; + NCCS :: 20; + } + + // Information for the termios.h enums is platform dependent and was retrieved from: + // https://elixir.bootlin.com/glibc/latest/source/sysdeps/unix/sysv/linux/bits/termios.h + + + + // The struct termios. + Terminal_IO_Mode :: struct { + c_iflag : Input_Modes; // Input mode flags. + c_oflag : Output_Modes; // Output mode flags. + c_cflag : Control_Modes; // Control modes flags. + c_lflag : Local_Modes; // Local modes flags. + c_line : u8; // Line discipline. + c_cc : [NCCS]Control_Chars; // Control characters. + c_ispeed : u32; // Input speed (baud rates). + c_ospeed : u32; // Output speed (baud rates). + } + + // Input modes. + Input_Modes :: enum_flags u32 { + IGNBRK :: 0000001; // Ignore break condition. + BRKINT :: 0000002; // Signal interrupt on break. + IGNPAR :: 0000004; // Ignore characters with parity errors. + PARMRK :: 0000010; // Mark parity and framing errors. + INPCK :: 0000020; // Enable input parity check. + ISTRIP :: 0000040; // Strip 8th bit off characters. + INLCR :: 0000100; // Map NL to CR on input. + IGNCR :: 0000200; // Ignore CR. + ICRNL :: 0000400; // Map CR to NL on input. + IUCLC :: 0001000; // Translate upper case input to lower case (not in POSIX). + IXON :: 0002000; // Enable start/stop output control. + IXANY :: 0004000; // Any character will restart after stop. + IXOFF :: 0010000; // Enable start/stop input control. + IMAXBEL :: 0020000; // Ring bell when input queue is full (not in POSIX). + IUTF8 :: 0040000; // Input is UTF8 (not in POSIX). + } + + // Output modes. + Output_Modes :: enum_flags u32 { + OPOST :: 0000001; // Perform output processing. + OLCUC :: 0000002; // Map lowercase characters to uppercase on output (not in POSIX). + ONLCR :: 0000004; // Map NL to CR-NL on output. + OCRNL :: 0000010; // Map CR to NL. + ONOCR :: 0000020; // Discard CR's when on column 0. + ONLRET :: 0000040; // Move to column 0 on NL. + OFILL :: 0000100; // Send fill characters for delays. + OFDEL :: 0000200; // Fill is DEL. + VTDLY :: 0040000; // Select vertical-tab delays: + VT0 :: 0000000; // Vertical-tab delay type 0. + VT1 :: 0040000; // Vertical-tab delay type 1. + } + + // Control modes. + Control_Modes :: enum u32 { + CS5 :: 0000000; // 5 bits per byte. + CS6 :: 0000020; // 6 bits per byte. + CS7 :: 0000040; // 7 bits per byte. + CS8 :: 0000060; // 8 bits per byte. + CSIZE :: 0000060; // Number of bits per byte (mask). + CSTOPB :: 0000100; // Two stop bits instead of one. + CREAD :: 0000200; // Enable receiver. + PARENB :: 0000400; // Parity enable. + PARODD :: 0001000; // Odd parity instead of even. + HUPCL :: 0002000; // Hang up on last close. + CLOCAL :: 0004000; + } + + // Local modes. + Local_Modes :: enum_flags u32 { + ISIG :: 0000001; // Enable signals. + ICANON :: 0000002; // Do erase and kill processing. + ECHO :: 0000010; // Enable echo. + ECHOE :: 0000020; // Visual erase for ERASE. + ECHOK :: 0000040; // Echo NL after KILL. + ECHONL :: 0000100; // Echo NL even if ECHO is off. + NOFLSH :: 0000200; // Disable flush after interrupt. + TOSTOP :: 0000400; // Send SIGTTOU for background output. + IEXTEN :: 0100000; // Enable DISCARD and LNEXT. + } + + // Control Characters + Control_Chars :: enum u8 { + VINTR :: 0; + VQUIT :: 1; + VERASE :: 2; + VKILL :: 3; + VEOF :: 4; + VTIME :: 5; // Time-out value (tenths of a second) [!ICANON]. + VMIN :: 6; // Minimum number of bytes read at once [!ICANON]. + VSWTC :: 7; + VSTART :: 8; + VSTOP :: 9; + VSUSP :: 10; + VEOL :: 11; + VREPRINT :: 12; + VDISCARD :: 13; + VWERASE :: 14; + VLNEXT :: 15; + VEOL2 :: 16; + } + + +#if USE_LIBC { // Required to do unlocking input. libc :: #system_library "libc"; + + // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcsetattr.c.html + tcsetattr :: (fd: s32, optional_actions: s32, termios_p : *Terminal_IO_Mode) -> s32 #foreign libc; + + // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html + tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 #foreign libc; + + // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcflush.c.html + tcflush :: (fd: s32, queue_selector: s32) -> s32 #foreign libc; +} +else { // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcsetattr.c.html - // tcsetattr :: (fd: s32, optional_actions: s32, termios_p : *Terminal_IO_Mode) -> s32 #foreign libc; tcsetattr :: (fd: s32, optional_actions: s32, termios_p : *Terminal_IO_Mode) -> s32 { - // TODO IMPLEMENT ME - + #if OS == .LINUX { TCSETS :: 0x5402; TCSETSW :: 0x5403; @@ -71,7 +220,6 @@ TODO : then do a good implementation of the libc functions about attributes... } // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html - // tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 #foreign libc; tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 { TCSETS :: 0x5402; TCSETSW :: 0x5403; @@ -135,128 +283,25 @@ TODO : then do a good implementation of the libc functions about attributes... } // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcflush.c.html - // tcflush :: (fd: s32, queue_selector: s32) -> s32 #foreign libc; tcflush :: inline (fd: s32, queue_selector: s32) -> s32 { TCFLSH :: 0x540B; return ioctl(fd, TCFLSH, queue_selector); } - - // Information for the termios.h enums is platform dependent and was retrieved from: - // https://elixir.bootlin.com/glibc/latest/source/sysdeps/unix/sysv/linux/bits/termios.h - - // Set Attributes Actions. - SetAttributesActions :: enum u32 { - TCSANOW :: 0; // Change immediately. - TCSADRAIN :: 1; // Change when pending output is written. - TCSAFLUSH :: 2; // Flush pending input before changing. - } - - // Input modes. - Input_Modes :: enum_flags u32 { - IGNBRK :: 0000001; // Ignore break condition. - BRKINT :: 0000002; // Signal interrupt on break. - IGNPAR :: 0000004; // Ignore characters with parity errors. - PARMRK :: 0000010; // Mark parity and framing errors. - INPCK :: 0000020; // Enable input parity check. - ISTRIP :: 0000040; // Strip 8th bit off characters. - INLCR :: 0000100; // Map NL to CR on input. - IGNCR :: 0000200; // Ignore CR. - ICRNL :: 0000400; // Map CR to NL on input. - IUCLC :: 0001000; // Translate upper case input to lower case (not in POSIX). - IXON :: 0002000; // Enable start/stop output control. - IXANY :: 0004000; // Any character will restart after stop. - IXOFF :: 0010000; // Enable start/stop input control. - IMAXBEL :: 0020000; // Ring bell when input queue is full (not in POSIX). - IUTF8 :: 0040000; // Input is UTF8 (not in POSIX). - } - - // Output modes. - Output_Modes :: enum_flags u32 { - OPOST :: 0000001; // Perform output processing. - OLCUC :: 0000002; // Map lowercase characters to uppercase on output (not in POSIX). - ONLCR :: 0000004; // Map NL to CR-NL on output. - OCRNL :: 0000010; // Map CR to NL. - ONOCR :: 0000020; // Discard CR's when on column 0. - ONLRET :: 0000040; // Move to column 0 on NL. - OFILL :: 0000100; // Send fill characters for delays. - OFDEL :: 0000200; // Fill is DEL. - VTDLY :: 0040000; // Select vertical-tab delays: - VT0 :: 0000000; // Vertical-tab delay type 0. - VT1 :: 0040000; // Vertical-tab delay type 1. - } - - // Control modes. - Control_Modes :: enum u32 { - CS5 :: 0000000; // 5 bits per byte. - CS6 :: 0000020; // 6 bits per byte. - CS7 :: 0000040; // 7 bits per byte. - CS8 :: 0000060; // 8 bits per byte. - CSIZE :: 0000060; // Number of bits per byte (mask). - CSTOPB :: 0000100; // Two stop bits instead of one. - CREAD :: 0000200; // Enable receiver. - PARENB :: 0000400; // Parity enable. - PARODD :: 0001000; // Odd parity instead of even. - HUPCL :: 0002000; // Hang up on last close. - CLOCAL :: 0004000; - } - - // Local modes. - Local_Modes :: enum_flags u32 { - ISIG :: 0000001; // Enable signals. - ICANON :: 0000002; // Do erase and kill processing. - ECHO :: 0000010; // Enable echo. - ECHOE :: 0000020; // Visual erase for ERASE. - ECHOK :: 0000040; // Echo NL after KILL. - ECHONL :: 0000100; // Echo NL even if ECHO is off. - NOFLSH :: 0000200; // Disable flush after interrupt. - TOSTOP :: 0000400; // Send SIGTTOU for background output. - IEXTEN :: 0100000; // Enable DISCARD and LNEXT. - } +} - // Control Characters - Control_Chars :: enum u8 { - VINTR :: 0; - VQUIT :: 1; - VERASE :: 2; - VKILL :: 3; - VEOF :: 4; - VTIME :: 5; // Time-out value (tenths of a second) [!ICANON]. - VMIN :: 6; // Minimum number of bytes read at once [!ICANON]. - VSWTC :: 7; - VSTART :: 8; - VSTOP :: 9; - VSUSP :: 10; - VEOL :: 11; - VREPRINT :: 12; - VDISCARD :: 13; - VWERASE :: 14; - VLNEXT :: 15; - VEOL2 :: 16; - } +//////////////////////////////////////////////////////////////////////////////// - // The struct termios. - Terminal_IO_Mode :: struct { - c_iflag : Input_Modes; // Input mode flags. - c_oflag : Output_Modes; // Output mode flags. - c_cflag : Control_Modes; // Control modes flags. - c_lflag : Local_Modes; // Local modes flags. - c_line : u8; // Line discipline. - c_cc : [32]Control_Chars;// Control characters. - c_ispeed : u32; // Input speed (baud rates). - c_ospeed : u32; // Output speed (baud rates). - } - initial_tio_mode: Terminal_IO_Mode; raw_tio_mode: Terminal_IO_Mode; + was_resized : bool; + //////////////////////////////////////////////////////////////////////////////// -// Resize detection -was_resized : bool; resize_handler :: (signal_code : s32) #c_call { new_context : Context; push_context new_context { - if signal_code != SIGWINCH then return; + if signal_code != SIGWINCH then return; atomic_swap(*was_resized, true); } } @@ -279,13 +324,14 @@ restore_resize_handler :: () { #scope_export -OS_prepare_terminal :: () -> success := true { // On critical paths, we may use the #must for the success return value. +OS_prepare_terminal :: () -> success := true { error: int = ---; error = tcgetattr(STDIN_FILENO, *initial_tio_mode); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to get initial_tio_mode: code %, %", error_code, error_string); + return false; } raw_tio_mode = initial_tio_mode; @@ -297,7 +343,7 @@ OS_prepare_terminal :: () -> success := true { // On critical paths, we may use raw_tio_mode.c_cc[Control_Chars.VMIN] = 1; raw_tio_mode.c_cc[Control_Chars.VTIME] = 0; - error = tcsetattr(STDIN_FILENO, 0, *raw_tio_mode); + error = tcsetattr(STDIN_FILENO, TCSANOW, *raw_tio_mode); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to set raw_tio_mode: code %, %", error_code, error_string); @@ -311,7 +357,7 @@ OS_prepare_terminal :: () -> success := true { // On critical paths, we may use OS_reset_terminal :: inline () -> success := true { restore_resize_handler(); - error := tcsetattr(STDIN_FILENO, 0, *initial_tio_mode); + error := tcsetattr(STDIN_FILENO, TCSANOW, *initial_tio_mode); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to set initial_tio_mode: code %, %", error_code, error_string); @@ -321,7 +367,6 @@ OS_reset_terminal :: inline () -> success := true { } OS_flush_input :: inline () -> success := true { - TCIFLUSH :: 0; // TODO Is this always zero in all systems? error := tcflush(STDIN_FILENO, TCIFLUSH); if error { error_code, error_string := get_error_value_and_string(); @@ -331,7 +376,6 @@ OS_flush_input :: inline () -> success := true { return; } -// TODO Nothing is checking for the errors returned by this... shame! OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success := true { bytes_read := read(STDIN_FILENO, buffer, xx bytes_to_read); if bytes_read < 0 { @@ -345,12 +389,21 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : // timeout_milliseconds // 0: do not wait // -1: wait indefinitely -OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool { +OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool, success := true { fds := pollfd.[ .{ fd = STDIN_FILENO, events = POLLIN, revents = 0 } ]; nfds := fds.count; - poll_return := poll(fds.data, xx nfds, xx timeout_milliseconds); // TODO Wait for input using poll syscall. This breaks and throws '-1 | 4 | Interrupted system call' when we resize the window while polling. - error_code, error_message := get_error_value_and_string(); // FIXME Not used. - return ifx poll_return > 0 then true else false; + result := poll(fds.data, xx nfds, xx timeout_milliseconds); // Returns '-1' with errno '4 | Interrupted system call' on window resize. + + if result == -1 { + error_code, error_string := get_error_value_and_string(); + // Ignore window resize events (error_code 4). + if error_code != 4 { + log_error("Unexpected error while waiting for input: code %, %", error_code, error_string); + return false, false; + } + } + + return ifx result > 0 then true else false; } OS_was_terminal_resized :: () -> bool { diff --git a/modules/TUI/windows.jai b/modules/TUI/windows.jai index 5755ef4..608266d 100644 --- a/modules/TUI/windows.jai +++ b/modules/TUI/windows.jai @@ -9,47 +9,30 @@ CP_UTF8 :: 65001; // https://learn.microsoft.com/windows/win32/winprog/windows-data-types - LPVOID :: *void; - BOOL :: bool; - CHAR :: s8; - WCHAR :: s16; - SHORT :: s16; - USHORT :: u16; - WORD :: u16; - DWORD :: u32; - LPDWORD :: *u32; - UINT :: u32; - - PINPUT_RECORD :: *INPUT_RECORD; - - // https://learn.microsoft.com/en-us/windows/console/readconsoleinput - ReadConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> success: bool #foreign kernel32; - - // https://learn.microsoft.com/windows/console/readconsole - ReadConsoleW :: (hConsoleInput: HANDLE, lpBuffer: LPVOID, nNumberOfchars_to_read: DWORD, lpNumberOfchars_read: LPVOID, pInputControl: LPVOID) -> success: bool #foreign kernel32; - - // https://learn.microsoft.com/windows/console/flushconsoleinputbuffer - FlushConsoleInputBuffer :: (hConsoleInput: HANDLE) -> bool #foreign kernel32; - - // https://learn.microsoft.com/windows/console/getnumberofconsoleinputevents - GetNumberOfConsoleInputEvents :: (hConsoleInput: HANDLE, lpcNumberOfEvents: LPDWORD) -> bool #foreign kernel32; - - // https://learn.microsoft.com/en-us/windows/console/peekconsoleinput - PeekConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> bool #foreign kernel32; + LPVOID :: *void; + BOOL :: bool; + CHAR :: s8; + WCHAR :: s16; + SHORT :: s16; + WORD :: u16; + DWORD :: u32; + LPDWORD :: *u32; + UINT :: u32; + PINPUT_RECORD :: *INPUT_RECORD; // https://learn.microsoft.com/en-us/windows/console/setconsolemode // https://learn.microsoft.com/en-us/windows/console/high-level-console-modes Console_Input_Mode :: enum_flags u32 { - ENABLE_PROCESSED_INPUT; // If enable, control keys (Ctrl+C, Backspace, ...) are processed by the system. - ENABLE_LINE_INPUT; // If enable, ReadFile or ReadConsole function return on CR; otherwise they return when one or more characters are available. - ENABLE_ECHO_INPUT; // Echoes input on screen. Only available if ENABLE_LINE_INPUT is set. + ENABLE_PROCESSED_INPUT; // If set, control sequences are processed by the system. + ENABLE_LINE_INPUT; // If set, ReadFile and ReadConsole function return on CR; otherwise return when characters are available. + ENABLE_ECHO_INPUT; // If set, Echoes input on screen. Only available if ENABLE_LINE_INPUT is set. ENABLE_WINDOW_INPUT; - ENABLE_MOUSE_INPUT; // Makes mouse events available to the ReadConsoleInput function. - ENABLE_INSERT_MODE; // If enabled, text entered will be inserted at the current cursor location and all text following that location will not be overwritten. When disabled, all following text will be overwritten. + ENABLE_MOUSE_INPUT; + ENABLE_INSERT_MODE; _UNUSED_0040_; _UNUSED_0080_; _UNUSED_0100_; - ENABLE_VIRTUAL_TERMINAL_INPUT; // If enable, makes user input available to the ReadConsole function. + ENABLE_VIRTUAL_TERMINAL_INPUT; // If set, makes user input available to the ReadConsole function. _UNUSED_0400_; _UNUSED_0800_; } @@ -57,11 +40,11 @@ // https://learn.microsoft.com/en-us/windows/console/setconsolemode // https://learn.microsoft.com/en-us/windows/console/high-level-console-modes Console_Output_Mode :: enum_flags u32 { - ENABLE_PROCESSED_OUTPUT; // - ENABLE_WRAP_AT_EOL_OUTPUT; // - ENABLE_VIRTUAL_TERMINAL_PROCESSING; // - DISABLE_NEWLINE_AUTO_RETURN; // - ENABLE_LVB_GRID_WORLDWIDE; // + ENABLE_PROCESSED_OUTPUT; // If set, ASCII control sequences are processed by the system. + ENABLE_WRAP_AT_EOL_OUTPUT; // If set, the cursor moves to the beginning of the next row when it reaches the end of the current row. + ENABLE_VIRTUAL_TERMINAL_PROCESSING; // If set, VT100 control sequences are processed by the system. + DISABLE_NEWLINE_AUTO_RETURN; + ENABLE_LVB_GRID_WORLDWIDE; _UNUSED_0020_; _UNUSED_0040_; _UNUSED_0080_; @@ -123,6 +106,23 @@ bSetFocus : BOOL; } + // https://learn.microsoft.com/en-us/windows/console/readconsoleinput + ReadConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> success: bool #foreign kernel32; + + // https://learn.microsoft.com/windows/console/readconsole + ReadConsoleW :: (hConsoleInput: HANDLE, lpBuffer: LPVOID, nNumberOfchars_to_read: DWORD, lpNumberOfchars_read: LPVOID, pInputControl: LPVOID) -> success: bool #foreign kernel32; + + // https://learn.microsoft.com/windows/console/flushconsoleinputbuffer + FlushConsoleInputBuffer :: (hConsoleInput: HANDLE) -> bool #foreign kernel32; + + // https://learn.microsoft.com/windows/console/getnumberofconsoleinputevents + GetNumberOfConsoleInputEvents :: (hConsoleInput: HANDLE, lpcNumberOfEvents: LPDWORD) -> bool #foreign kernel32; + + // https://learn.microsoft.com/en-us/windows/console/peekconsoleinput + PeekConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> bool #foreign kernel32; + +//////////////////////////////////////////////////////////////////////////////// + stdin: HANDLE; initial_stdin_mode: u32; raw_stdin_mode: Console_Input_Mode; @@ -135,6 +135,7 @@ widechar_buffer: [512] u16; +//////////////////////////////////////////////////////////////////////////////// peek_input :: inline () -> INPUT_RECORD, success := true { record: INPUT_RECORD; @@ -172,19 +173,19 @@ count_input :: inline () -> u32, success := true { #scope_export -OS_prepare_terminal :: () { +OS_prepare_terminal :: () -> success := true { // stdin stdin = GetStdHandle(STD_INPUT_HANDLE); if stdin == INVALID_HANDLE_VALUE { error_code, error_string := get_error_value_and_string(); log_error("Invalid input handler: code %, %", error_code, error_string); - return; + return false; } if xx GetConsoleMode(stdin, *initial_stdin_mode) == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to get input mode: code %, %", error_code, error_string); - return; + return false; } raw_stdin_mode = (cast(Console_Input_Mode) initial_stdin_mode); raw_stdin_mode |= (.ENABLE_VIRTUAL_TERMINAL_INPUT); @@ -193,7 +194,7 @@ OS_prepare_terminal :: () { if xx SetConsoleMode(stdin, xx raw_stdin_mode) == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to set input mode: code %, %", error_code, error_string); - return; + return false; } // stdout @@ -201,12 +202,12 @@ OS_prepare_terminal :: () { if stdout == INVALID_HANDLE_VALUE { error_code, error_string := get_error_value_and_string(); log_error("Invalid output handler: code %, %", error_code, error_string); - return; + return false; } if xx GetConsoleMode(stdout, *initial_stdout_mode) == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to get output mode: code %, %", error_code, error_string); - return; + return false; } raw_stdout_mode = (cast(Console_Output_Mode) initial_stdout_mode); raw_stdout_mode |= (.ENABLE_VIRTUAL_TERMINAL_PROCESSING | .ENABLE_PROCESSED_OUTPUT | .ENABLE_WRAP_AT_EOL_OUTPUT); @@ -214,7 +215,7 @@ OS_prepare_terminal :: () { if xx SetConsoleMode(stdout, xx raw_stdout_mode) == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to set output mode: code %, %", error_code, error_string); - return; + return false; } // Acording to [documentation](https://learn.microsoft.com/en-us/windows/win32/intl/code-pages) @@ -223,26 +224,27 @@ OS_prepare_terminal :: () { // As long we use the Unicode functions, we shouldn't need to set the code page to UTF8. // SetConsoleCP(CP_UTF8); // SetConsoleOutputCP(CP_UTF8); + + return; } -OS_reset_terminal :: () { +OS_reset_terminal :: () -> success := true { if xx SetConsoleMode(stdin, initial_stdin_mode) == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to reset input mode: code %, %", error_code, error_string); - return; + return false; } if xx SetConsoleMode(stdout, initial_stdout_mode) == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to reset output mode: code %, %", error_code, error_string); - return; + return false; } + return; } OS_flush_input :: inline () { - /* - This API is not recommended and does not have a virtual terminal equivalent. - Attempting to empty the input queue all at once can destroy state in the queue in an unexpected manner. - */ + // This API is not recommended and does not have a virtual terminal equivalent. + // Attempting to empty the input queue all at once can destroy state in the queue in an unexpected manner. if FlushConsoleInputBuffer(stdin) == false { error_code, error_string := get_error_value_and_string(); log_error("Failed to flush input: code %, %", error_code, error_string); @@ -318,13 +320,11 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : // 0: do not wait // -1: wait indefinitely OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool, success := true { - /* - The Windows API provides all input events (keyboard, mouse, window resize) on a single input buffer. - To make it match this module's API, we need to do some pre-processing while waiting for input. - This means that OS_wait_for_input will peek at the input events, signal if a window resize is found, - and discard unwanted events (like button release events). - A similar logic is applied in OS_read_input. - */ + // The Windows API provides all input events (keyboard, mouse, window resize) on a single input buffer. + // To make it match this module's API, we need to do some pre-processing while waiting for input. + // This means that OS_wait_for_input will peek at the input events, signal if a window resize is found, + // and discard unwanted events (like button release events). + // A similar logic is applied in OS_read_input. expiration := current_time_monotonic() + to_apollo(timeout_milliseconds / 1000.0); diff --git a/snake.jai b/snake.jai index e3a15f6..465ae4d 100644 --- a/snake.jai +++ b/snake.jai @@ -136,8 +136,11 @@ main :: () { GAME_OVER_TEXT :: "~ game over ~"; INSTRUCTIONS_TEXT :: "(esc to exit)"; + + seed: u64 = xx to_milliseconds(current_time_monotonic()) | 0x01; // Seed must be odd. + random_seed(seed); - TUI.start(); + assert(TUI.start(), "Failed to start TUI."); TUI.set_cursor_position(1, 1); write_string("Please enter player name: "); @@ -158,5 +161,5 @@ main :: () { if TUI.get_key() == TUI.Keys.Escape then break; } - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); } diff --git a/ttt.jai b/ttt.jai index 8f39b56..45dc7f8 100644 --- a/ttt.jai +++ b/ttt.jai @@ -866,7 +866,7 @@ initialize_tui :: () { } } - TUI.start(); + assert(TUI.start(), "Failed to start TUI."); } update_layout :: () { @@ -1165,7 +1165,7 @@ main :: () { print("- success\n", to_standard_error = true); } else { - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); print("- ERROR: %", error_message, to_standard_error = true); exit(1); } @@ -1178,19 +1178,19 @@ main :: () { if perform_test && 1 { print("TEST : set and get cursor position\n", to_standard_error = true); - TUI.start(); + assert(TUI.start(), "Failed to start TUI."); X :: 2; Y :: 3; TUI.set_cursor_position(X, Y); x, y := TUI.get_cursor_position(); - TUI.stop(); + assert(TUI.stop(), "Failed to stop 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."); - TUI.start(); + assert(TUI.start(), "Failed to start TUI."); TUI.set_cursor_position(3, 3); print("wait"); sleep_milliseconds(1000); @@ -1198,14 +1198,14 @@ main :: () { sleep_milliseconds(1000); print(" a bit"); sleep_milliseconds(1000); - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); log("- log: after module stop."); } if perform_test && 1 { print("TEST : test key input\n", to_standard_error = true); auto_release_temp(); - TUI.start(); + assert(TUI.start(), "Failed to start 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."); @@ -1224,28 +1224,28 @@ main :: () { write_string(TUI.to_string(key)); } } - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); print("- success\n", to_standard_error = true); } if perform_test && 1 { print("TEST : draw box\n", to_standard_error = true); auto_release_temp(); - TUI.start(); + assert(TUI.start(), "Failed to start 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(); - TUI.stop(); + assert(TUI.stop(), "Failed to stop 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(); - TUI.start(); + assert(TUI.start(), "Failed to start TUI."); TUI.clear_terminal(); width, height := TUI.get_terminal_size(); TUI.set_cursor_position(1, 1); @@ -1254,13 +1254,13 @@ main :: () { while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) { key = TUI.get_key(); } - TUI.stop(); + assert(TUI.stop(), "Failed to stop 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); - TUI.start(); + assert(TUI.start(), "Failed to start TUI."); title := "BAZINGA"; TUI.set_terminal_title(title); TUI.set_cursor_position(1, 1); @@ -1269,14 +1269,14 @@ main :: () { while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) { key = TUI.get_key(); } - TUI.stop(); + assert(TUI.stop(), "Failed to stop 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(); - TUI.start(); + assert(TUI.start(), "Failed to start TUI."); TUI.set_terminal_title("bazinga"); key: TUI.Key = #char "d"; last_none_char := "X"; @@ -1335,13 +1335,13 @@ main :: () { // set_temporary_storage_mark(__mark); } print("- success"); - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); } if perform_test && 1 { print("TEST : user input\n", to_standard_error = true); auto_release_temp(); - TUI.start(); + assert(TUI.start(), "Failed to start 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):"); @@ -1366,14 +1366,14 @@ main :: () { } } answer := TUI.get_key(); - TUI.stop(); + assert(TUI.stop(), "Failed to stop 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(); - TUI.start(); + assert(TUI.start(), "Failed to start 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):"); @@ -1397,7 +1397,7 @@ main :: () { } } answer := TUI.get_key(); - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); assert_result(answer == #char "y", error_message); } @@ -2018,7 +2018,7 @@ main :: () { TUI.get_key(); } - TUI.stop(); + assert(TUI.stop(), "Failed to stop TUI."); exit(xx ifx error_saving then 1 else 0); } -- cgit v1.2.3 From 90d17cde7d92be5e2c1bedb97c8c8d5ca324a39d Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 30 Apr 2024 18:32:28 +0100 Subject: Removed some 'inline' marks, and added some TODO entries. --- modules/TUI/module.jai | 5 +++-- modules/TUI/unix.jai | 6 +++--- modules/TUI/windows.jai | 10 +++++----- 3 files changed, 11 insertions(+), 10 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 121aa42..f8b8265 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -381,7 +381,7 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { read_input :: (count_limit: int = -1, terminators: .. u8) -> string { assert_is_active(); - assert(count_limit >= 0 || terminators.count > 0, "Infinite loop detected, aborting."); // TODO Maybe just return!? + 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 { builder: String_Builder; @@ -438,7 +438,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { Resize discards the input returning an empty string and a Resize key. */ - assert(count_limit >= 0, "Invalid value on count_limit parameter."); + assert(count_limit >= 0, "Invalid value on count_limit parameter."); // TODO Too agressive str := alloc_string(count_limit); str.count = 0; @@ -446,6 +446,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // TODO Some of these may be nice to have: // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line + // At least... add the Ctrl+u to clear the entire line. x, y := get_cursor_position(); write_strings(Commands.ShowCursor, Commands.StartBlinking, Commands.BlinkingBarShape); diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index e1c0b3a..a8e0edf 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -263,7 +263,7 @@ OS_prepare_terminal :: () -> success := true { return; } -OS_reset_terminal :: inline () -> success := true { +OS_reset_terminal :: () -> success := true { restore_resize_handler(); error := tcsetattr(STDIN_FILENO, xx Optional_Actions.TCSANOW, *initial_tio_mode); if error { @@ -274,7 +274,7 @@ OS_reset_terminal :: inline () -> success := true { return; } -OS_flush_input :: inline () -> success := true { +OS_flush_input :: () -> success := true { error := tcflush(STDIN_FILENO, xx Queue_Selector.TCIFLUSH); if error { error_code, error_string := get_error_value_and_string(); @@ -314,6 +314,6 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo return ifx result > 0 then true else false; } -OS_was_terminal_resized :: () -> bool { +OS_was_terminal_resized :: inline () -> bool { return atomic_swap(*was_resized, false); } diff --git a/modules/TUI/windows.jai b/modules/TUI/windows.jai index 608266d..fb50e49 100644 --- a/modules/TUI/windows.jai +++ b/modules/TUI/windows.jai @@ -137,7 +137,7 @@ //////////////////////////////////////////////////////////////////////////////// -peek_input :: inline () -> INPUT_RECORD, success := true { +peek_input :: () -> INPUT_RECORD, success := true { record: INPUT_RECORD; records_read: u32; if PeekConsoleInputW(stdin, *record, 1, *records_read) == false { @@ -148,7 +148,7 @@ peek_input :: inline () -> INPUT_RECORD, success := true { return record; } -read_input :: inline () -> INPUT_RECORD, success := true { +read_input :: () -> INPUT_RECORD, success := true { record: INPUT_RECORD; records_read: u32; if ReadConsoleInputW(stdin, *record, 1, *records_read) == false { @@ -159,7 +159,7 @@ read_input :: inline () -> INPUT_RECORD, success := true { return record; } -count_input :: inline () -> u32, success := true { +count_input :: () -> u32, success := true { count: u32; if GetNumberOfConsoleInputEvents(stdin, *count) == false { error_code, error_string := get_error_value_and_string(); @@ -242,7 +242,7 @@ OS_reset_terminal :: () -> success := true { return; } -OS_flush_input :: inline () { +OS_flush_input :: () { // This API is not recommended and does not have a virtual terminal equivalent. // Attempting to empty the input queue all at once can destroy state in the queue in an unexpected manner. if FlushConsoleInputBuffer(stdin) == false { @@ -379,7 +379,7 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo return false; } -OS_was_terminal_resized :: () -> bool { +OS_was_terminal_resized :: inline () -> bool { while peek_input().EventType == .WINDOW_BUFFER_SIZE_EVENT { was_resized = true; read_input(); -- cgit v1.2.3 From 66dfee359cbe2c18ce8400edeb801a2373a771f4 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 2 May 2024 00:57:53 +0100 Subject: WIP : Using string builder to improve TUI performance. --- modules/TUI/module.jai | 87 ++++++++++++++++++++++++++++++++++++++++++++++---- ttt.jai | 2 ++ 2 files changed, 83 insertions(+), 6 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index f8b8265..991dc74 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -3,7 +3,6 @@ #scope_file // - fix/implement/finish `TODO` on `TUI\module` (use some sort of buffet to reduce io/print calls) -// - fix/implement/finish `TODO` on `TUI\module` (use `dirty_bit_flag` to only update ehat has been changed) #if OS == { case .LINUX; @@ -297,7 +296,7 @@ module_logger :: (message: string, data: *void, info: Log_Info) { } assert_is_active :: inline () { - assert(active, "TUI is not ready."); + 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 } //////////////////////////////////////////////////////////////////////////////// @@ -444,10 +443,6 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { str.count = 0; idx := 0; - // TODO Some of these may be nice to have: - // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line - // At least... add the Ctrl+u to clear the entire line. - x, y := get_cursor_position(); write_strings(Commands.ShowCursor, Commands.StartBlinking, Commands.BlinkingBarShape); @@ -531,6 +526,11 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { start :: () -> 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; @@ -583,7 +583,19 @@ flush_input :: () { 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 { 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)! @@ -626,6 +638,69 @@ draw_box :: (x: int, y: int, width: int, height: int) { 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); + + append(*builder, Commands.DrawingMode); + append(*builder, tprint(Commands.SetCursorPosition, y, x)); + append(*builder, Drawings.CornerTL); + + for 1..width-2 { + append(*builder, Drawings.LineH); + } + append(*builder, Drawings.CornerTR); + + 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, Drawings.LineV); + append(*builder, tmpR); + append(*builder, Drawings.LineV); + } + + 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()" diff --git a/ttt.jai b/ttt.jai index 63317d7..50fa198 100644 --- a/ttt.jai +++ b/ttt.jai @@ -28,6 +28,8 @@ #import "UTF8"; TUI :: #import "TUI"(COLOR_MODE=4); +// - fix/implement/finish TODO : use `dirty_bit_flag` to only update ehat has been changed + VERSION :: "2.0"; // Use only 3 chars (to fit layouts). YEAR :: "2024"; FIRST_DAY_OF_WEEK :: 1; // (0-6, Sunday = 0). -- cgit v1.2.3 From 8b312bad33617f9b718232141d2855d80cb8b912 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 3 May 2024 00:32:06 +0100 Subject: WIP : Cleaning up TUI module. --- modules/TUI/module.jai | 178 +++++++++++++------------------------------- modules/TUI/palette_24b.jai | 2 +- snake.jai | 6 +- ttt.jai | 51 ++++++------- unused.jai | 15 ++++ 5 files changed, 95 insertions(+), 157 deletions(-) (limited to 'modules/TUI/module.jai') 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 @@ -38,6 +38,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. -- cgit v1.2.3 From 6b90a43d69142ff684b792ba2da951e5bbddc8a6 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 3 May 2024 01:00:54 +0100 Subject: WIP : Cleanup read_input_line on TUI module. --- modules/TUI/module.jai | 50 +++++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 21 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index d44edab..464b0d9 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -19,6 +19,8 @@ #import "UTF8"; #load "key_map.jai"; +KEY_SIZE :: #run type_info(Key).runtime_size; + #run { 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)."); @@ -213,8 +215,6 @@ using_style :: (style: Style) #expand { Key :: u64; // Terminal key-codes have 1 to 6 bytes so we'll use 8 bytes. -KEY_SIZE :: #run type_info(Key).runtime_size; - to_key :: inline (str: $T) -> Key #modify { return T == ([]u8) || T == string; } { k: Key; // #if DEBUG { @@ -304,7 +304,7 @@ assert_is_active :: inline () { // #scope_export TODO Setup the scope_export and scope_file -set_next_key :: (key: Key) { +set_next_key :: inline (key: Key) { assert_is_active(); input_override = key; } @@ -432,42 +432,50 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string, success := } } +// Uses the get_key to read user input and show it on screen. +// Allows to move the cursor left and right and to delete/backspace. +// Enter ends the input, returning the input string and the Enter 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. read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { - /* - Uses the get_key to read user input and show it on screen. - Allows to move the cursor left and right and to delete/backspace. - Enter ends the input, returning the input string and the Enter 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 + // TODO If we pass success... then, does it make sense to return success=true on resize? + assert(count_limit >= 0, "Invalid value passed to count_limit."); + // if count_limit < 0 { + // log_error("Invalid arguments passed to read_input_line(): (%, %).\n", count_limit, is_visible); + // return "", Keys.None, false; + // } + + builder := String_Builder.{ allocator = temporary_allocator }; + str := alloc_string(count_limit); str.count = 0; idx := 0; x, y := get_cursor_position(); - write_strings(Commands.ShowCursor, Commands.StartBlinking, Commands.BlinkingBarShape); - key := Keys.None; + + write_strings(Commands.ShowCursor, Commands.StartBlinking, Commands.BlinkingBarShape); + while true { auto_release_temp(); chars_count := count_characters(str); - // Preview input. TODO Optimize using temp_builder + // Draw preview. if is_visible { - set_cursor_position(x, y); - write_string(str); - for chars_count..count_limit-1 print_character(#char " "); + append(*builder, tprint(Commands.SetCursorPosition, y, x)); + append(*builder, str); + for chars_count..count_limit-1 append(*builder, " "); } else { - set_cursor_position(x, y); - for 1..chars_count print_character(#char "*"); - for chars_count..count_limit-1 print_character(#char " "); + append(*builder, tprint(Commands.SetCursorPosition, y, x)); + for 1..chars_count append(*builder, "*"); + for chars_count..count_limit-1 append(*builder, " "); } - set_cursor_position(x+idx, y); + append(*builder, tprint(Commands.SetCursorPosition, y, x+idx)); + write_string(builder_to_string(*builder)); // Process input key. key = get_key(); -- cgit v1.2.3 From 675b0a5fea60dc33a97b0dc1871bb9a0b61818cc Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 4 May 2024 01:43:34 +0100 Subject: WIP : Cleanup TUI module. Finally decided to go with hard-asserts (vs soft-errors/logs). --- modules/TUI/module.jai | 70 ++++++-------- modules/TUI/tests.jai | 247 +++++++++++++++++++++++++++++++++++++++++++++++ modules/TUI/unix.jai | 2 +- ttt.jai | 255 +------------------------------------------------ 4 files changed, 282 insertions(+), 292 deletions(-) create mode 100644 modules/TUI/tests.jai (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 464b0d9..a6a3d11 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -120,6 +120,9 @@ Commands :: struct #type_info_none { CursorNormalMode :: "\e[?1l"; } +// TODO Check which procedures need the assert_is_active call. +// TODO Review the error messages on the asserts. + #if COLOR_MODE_BITS == 4 { #load "palette_4b.jai"; @@ -189,15 +192,15 @@ set_font_style :: inline (bold: bool, underline: bool = false, strike_through: b set_style :: (style: Style) { set_font_style(style.bold, style.underline, style.strike_through, style.negative); set_colors(style.foreground, style.background); - context.tui_style = style; + context.terminal_style = style; } -clear_style :: () { +clear_style :: inline () { write_string(#run sprint(Commands.SetGraphicsRendition, "0")); } using_style :: (style: Style) #expand { - __style := context.tui_style; + __style := context.terminal_style; set_style(style); `defer set_style(__style); } @@ -215,24 +218,23 @@ using_style :: (style: Style) #expand { Key :: u64; // Terminal key-codes have 1 to 6 bytes so we'll use 8 bytes. -to_key :: inline (str: $T) -> Key #modify { return T == ([]u8) || T == string; } { +to_key :: (str: $T) -> Key #modify { return T == ([]u8) || T == string; } { + assert(str.count <= KEY_SIZE, "Invalid argument passed to to_key(): 'str.count' must be less-than or equal to %, but it was %.", KEY_SIZE, str.count); + k: Key; - // #if DEBUG { - // assert(str.count <= 8); // TODO Add DEBUG to module parameters. - // } - for 0..str.count-1 #no_abc { + for 0..str.count-1 { k |= ((cast(u64)str[it]) << (it*8)); } return k; } -to_string :: inline (key: Key) -> string { // TODO FIXME TEMPORARY MEMORY - str := talloc_string(KEY_SIZE); +to_string :: (key: Key) -> string { + str := alloc_string(KEY_SIZE); str.count = 0; - while key != 0 #no_abc { - str[str.count] = xx key & 0xFF; - key >>= 8; + while key != 0 { str.count += 1; + str[str.count-1] = xx key & 0xFF; + key >>= 8; } return str; } @@ -280,7 +282,7 @@ Keys :: struct #type_info_none { F12 : Key : #run to_key("#f12"); } -#add_context tui_style: Style; +#add_context terminal_style: Style; active := false; @@ -297,7 +299,7 @@ module_logger :: (message: string, data: *void, info: Log_Info) { } assert_is_active :: inline () { - 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 + assert(active, "Please call setup_terminal() before using the module procedures."); } //////////////////////////////////////////////////////////////////////////////// @@ -309,6 +311,7 @@ set_next_key :: inline (key: Key) { input_override = key; } +// TODO Provide some documentation comments. get_key :: (timeout_milliseconds: s32 = -1) -> Key { assert_is_active(); @@ -377,15 +380,15 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { return to_key(to_parse); } -// TODO Review me and add some comments. -read_input :: (count_limit: int = -1, terminators: .. u8) -> string, success := true { +// If count_limit has a non-negative value it will be used as the limit to the number of bytes on the returned string. +// If any characters are provided in the terminators list, they will be used to scan and interrupt the input, including +// the terminator as the last character. +// At least one of the arguments must be properly setup to avoid an infinite-loop reading the input. +read_input :: (count_limit: int = -1, terminators: .. u8) -> string { assert_is_active(); - - if count_limit < 0 && terminators.count <= 0 { - log_error("Invalid arguments passed to read_input(): (%).\n", count_limit); // TODO Improve error message. - return "", false; - } - + assert(count_limit >= 0 || terminators.count > 0, "Invalid arguments passed to read_input() will result in infinite-loop."); + + // TODO Provide some documentation comments. if count_limit < 0 { builder: String_Builder; init_string_builder(*builder); @@ -439,13 +442,7 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string, success := // Resize discards the input returning an empty string and a Resize key. read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { assert_is_active(); - - // TODO If we pass success... then, does it make sense to return success=true on resize? - assert(count_limit >= 0, "Invalid value passed to count_limit."); - // if count_limit < 0 { - // log_error("Invalid arguments passed to read_input_line(): (%, %).\n", count_limit, is_visible); - // return "", Keys.None, false; - // } + assert(count_limit >= 0, "Invalid value passed to count_limit(): %.", count_limit); builder := String_Builder.{ allocator = temporary_allocator }; @@ -510,7 +507,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { if is_escape_code(key) continue; buff_idx := map_character_to_buffer_idx(str, idx); - key_str := to_string(key); + key_str := to_string(key,, allocator = temporary_allocator); // Make sure we have space to append the new character at the end (in case we're trying to do it). if buff_idx > count_limit - key_str.count then continue; @@ -538,7 +535,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { setup_terminal :: () -> success := true #must { if active == true return; - + input_string.data = input_buffer.data; input_string.count = 0; input_override = xx Keys.None; @@ -591,14 +588,9 @@ flush_input :: () { input_string.count = 0; } -// TODO What if we return success and fail when input arguments are invalid? draw_box :: (x: int, y: int, width: int, height: int) { - assert_is_active(); //TODO NOT NEEDED - - 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; - } + assert_is_active(); + assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): (%, %, %, %).\n", x, y, width, height); auto_release_temp(); diff --git a/modules/TUI/tests.jai b/modules/TUI/tests.jai new file mode 100644 index 0000000..fe213cb --- /dev/null +++ b/modules/TUI/tests.jai @@ -0,0 +1,247 @@ +// build: jai -import_dir ../ tests.jai +#import "Basic"; +TUI :: #import "TUI"; + +main :: () { + + assert_result :: (result: bool, error_message: string) { + if result == true { + print("- success\n", to_standard_error = true); + } + else { + assert(TUI.reset_terminal(), "Failed to reset TUI."); + print("- ERROR: %", error_message, to_standard_error = true); + exit(1); + } + } + + next_line :: inline () { + x, y := TUI.get_cursor_position(); + TUI.set_cursor_position(1, y+1); + } + + if 1 { + print("TEST : set and get cursor position\n", to_standard_error = true); + 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.reset_terminal(), "Failed to reset TUI."); + assert_result(x == X && y == Y, "Failed set/get cursor position.\n"); + } + + if 1 { + print("TEST : module logger\n", to_standard_error = true); + log("- log: before module start."); + assert(TUI.setup_terminal(), "Failed to setup TUI."); + TUI.set_cursor_position(3, 3); + print("wait"); + sleep_milliseconds(500); + log("- log: while module is active."); + sleep_milliseconds(500); + print(" a bit"); + sleep_milliseconds(1000); + assert(TUI.reset_terminal(), "Failed to reset TUI."); + log("- log: after module stop."); + } + + if 1 { + print("TEST : test key input\n", to_standard_error = true); + auto_release_temp(); + 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."); + next_line(); + key: TUI.Key; + while(key != #char "q") { + key = TUI.get_key(1000); + if key == TUI.Keys.None { + write_string("-"); + } + else if key == TUI.Keys.Resize { + write_string("#"); + } + else { + // else if key >= 32 && key <= 128 then print_character(cast,force(u8)key) + write_string(TUI.to_string(key)); + } + } + assert(TUI.reset_terminal(), "Failed to reset TUI."); + print("- success\n", to_standard_error = true); + } + + if 1 { + print("TEST : draw box\n", to_standard_error = true); + auto_release_temp(); + 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.reset_terminal(), "Failed to reset TUI."); + assert_result(key == #char "y", "Failed to draw box.\n"); + } + + if 1 { + print("TEST : get terminal size\n", to_standard_error = true); + auto_release_temp(); + assert(TUI.setup_terminal(), "Failed to setup TUI."); + TUI.clear_terminal(); + width, height := TUI.get_terminal_size(); + TUI.set_cursor_position(1, 1); + print("Is terminal size %x%? (y/n)", width, height); + key: TUI.Key = xx TUI.Keys.None; + while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) { + key = TUI.get_key(); + } + assert(TUI.reset_terminal(), "Failed to reset TUI."); + assert_result(key == #char "y", "Failed to get terminal size.\n"); + } + + if 1 { + print("TEST : set terminal title\n", to_standard_error = true); + assert(TUI.setup_terminal(), "Failed to setup TUI."); + title := "BAZINGA"; + TUI.set_terminal_title(title); + TUI.set_cursor_position(1, 1); + print("Is terminal title '%'? (y/n)", title); + key: TUI.Key = xx TUI.Keys.None; + while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) { + key = TUI.get_key(); + } + assert(TUI.reset_terminal(), "Failed to reset TUI."); + assert_result(key == #char "y", "Failed to set terminal title.\n"); + } + + if 1 { + print("TEST : print keys\n", to_standard_error = true); + auto_release_temp(); + assert(TUI.setup_terminal(), "Failed to setup TUI."); + key: TUI.Key = #char "d"; + last_none_char := "X"; + + width, height := TUI.get_terminal_size(); + TUI.clear_terminal(); + TUI.draw_box(1, 1, width, height); + drop_down := 0; + + while(key != #char "q") { + + if key == { + case TUI.Keys.None; { + TUI.set_cursor_position(2, 2); + last_none_char = ifx last_none_char == "X" then "+" else "X"; + write_strings(last_none_char, " (press q to exit)"); + } + + case TUI.Keys.Resize; #through; + case #char "c"; { + width, height = TUI.get_terminal_size(); + TUI.clear_terminal(); + TUI.draw_box(1, 1, width, height); + drop_down = 0; + } + + case; { + TUI.set_cursor_position(2, 3+drop_down); + str := TUI.to_string(key); + array_to_print: [..] string; + write_string(": "); + for 0..str.count-1 { + print("% ", FormatInt.{value = cast(u8)str[it], base=16}); + } + write_string(": "); + for 0..str.count-1 { + if str[it] == #char "\e" { + str[it] = #char "#"; + } + } + write_string(str); + write_string(" :"); + drop_down += 1; + } + } + + x := ifx width > 24 then width-24 else 1; + y := ifx height > 1 then height-1 else 1; + + TUI.set_cursor_position(x, y); + print("size = %x%\n", width, height); + key = TUI.get_key(1000); + + // __mark := get_temporary_storage_mark(); + // set_temporary_storage_mark(__mark); + } + print("- success"); + assert(TUI.reset_terminal(), "Failed to reset TUI."); + } + + if 1 { + print("TEST : user input\n", to_standard_error = true); + auto_release_temp(); + 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):"); + next_line(); + str, key := TUI.read_input_line(15); + TUI.set_cursor_position(1, 3); + error_message: string; + if key == { + case TUI.Keys.Escape; { + print("Have you pressed Esc? (y/n)"); + error_message = "Failed to read line on Esc."; + } + + case TUI.Keys.Resize; { + print("Have you resized the terminal? (y/n)"); + error_message = "Failed to read line on resize."; + + } + case; { + print("Have you entered '%'? (y/n)", str); + error_message = "Failed to read line."; + } + } + answer := TUI.get_key(); + assert(TUI.reset_terminal(), "Failed to reset TUI."); + assert_result(answer == #char "y", error_message); + } + + if 1 { + print("TEST : hidden user input\n", to_standard_error = true); + auto_release_temp(); + 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):"); + next_line(); + str, key := TUI.read_input_line(15, false); + TUI.set_cursor_position(1, 3); + error_message: string; + if key == { + case TUI.Keys.Escape; { + print("Have you pressed Esc? (y/n)"); + error_message = "Failed to read line on Esc."; + } + + case TUI.Keys.Resize; { + print("Have you resized the terminal? (y/n)"); + error_message = "Failed to read line on resize."; + } + case; { + print("Have you entered '%'? (y/n)", str); + error_message = "Failed to read line."; + } + } + answer := TUI.get_key(); + assert(TUI.reset_terminal(), "Failed to reset TUI."); + assert_result(answer == #char "y", error_message); + } + + // -- -- -- Testing TUI -- STOP +} diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index a8e0edf..940ac80 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -195,7 +195,7 @@ tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 #foreign libc; // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcflush.c.html - tcflush :: (fd: s32, queue_selector: s32) -> s32 #foreign libc; + tcflush :: (fd: s32, queue_selector: s32) -> s32 #foreign libc; //////////////////////////////////////////////////////////////////////////////// diff --git a/ttt.jai b/ttt.jai index 3512d20..d471661 100644 --- a/ttt.jai +++ b/ttt.jai @@ -17,8 +17,7 @@ // - 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"; +#import "Basic"()(MEMORY_DEBUGGER=true); // TODO Remove after final debug sessions. This takes up ~30MB of RAM. #import "System"; #import "Sort"; #import "Math"; @@ -1107,7 +1106,7 @@ read_input_string :: (x: int, y: int, input_limit: int, padding: int = 0) -> val write_string(TUI.Commands.TextMode); TUI.set_cursor_position(x, y); - style_input := context.tui_style; + style_input := context.terminal_style; style_input.underline = true; TUI.using_style(style_input); @@ -1158,256 +1157,8 @@ prompt_user_key :: (y: int, message: string) -> TUI.Key { } main :: () { - - // -- -- -- Testing TUI -- START - - perform_test := false; - - assert_result :: (result: bool, error_message: string) { - if result == true { - print("- success\n", to_standard_error = true); - } - else { - assert(TUI.reset_terminal(), "Failed to reset TUI."); - print("- ERROR: %", error_message, to_standard_error = true); - exit(1); - } - } - - next_line :: inline () { - x, y := TUI.get_cursor_position(); - TUI.set_cursor_position(1, y+1); - } - - if perform_test && 1 { - print("TEST : set and get cursor position\n", to_standard_error = true); - 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.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.setup_terminal(), "Failed to setup TUI."); - TUI.set_cursor_position(3, 3); - print("wait"); - sleep_milliseconds(1000); - log("- log: while module is active."); - sleep_milliseconds(1000); - print(" a bit"); - sleep_milliseconds(1000); - 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.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."); - next_line(); - key: TUI.Key; - while(key != #char "q") { - key = TUI.get_key(1000); - if key == TUI.Keys.None { - write_string("-"); - } - else if key == TUI.Keys.Resize { - write_string("#"); - } - else { - // else if key >= 32 && key <= 128 then print_character(cast,force(u8)key) - write_string(TUI.to_string(key)); - } - } - 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.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.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.setup_terminal(), "Failed to setup TUI."); - TUI.clear_terminal(); - width, height := TUI.get_terminal_size(); - TUI.set_cursor_position(1, 1); - print("Is terminal size %x%? (y/n)", width, height); - key: TUI.Key = xx TUI.Keys.None; - while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) { - key = TUI.get_key(); - } - 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.setup_terminal(), "Failed to setup TUI."); - title := "BAZINGA"; - TUI.set_terminal_title(title); - TUI.set_cursor_position(1, 1); - print("Is terminal title '%'? (y/n)", title); - key: TUI.Key = xx TUI.Keys.None; - while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) { - key = TUI.get_key(); - } - 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.setup_terminal(), "Failed to setup TUI."); - TUI.set_terminal_title("bazinga"); - key: TUI.Key = #char "d"; - last_none_char := "X"; - - width, height := TUI.get_terminal_size(); - TUI.clear_terminal(); - TUI.draw_box(1, 1, width, height); - drop_down := 0; - - while(key != #char "q") { - - if key == { - case TUI.Keys.None; { - TUI.set_cursor_position(2, 2); - last_none_char = ifx last_none_char == "X" then "+" else "X"; - write_string(last_none_char); - } - - case TUI.Keys.Resize; #through; - case #char "c"; { - width, height = TUI.get_terminal_size(); - TUI.clear_terminal(); - TUI.draw_box(1, 1, width, height); - drop_down = 0; - } - - case; { - TUI.set_cursor_position(2, 3+drop_down); - str := TUI.to_string(key); - array_to_print: [..] string; - for 0..str.count-1 { - tmp := tprint("%", FormatInt.{value = cast(u8)str[it], base=16},, temporary_allocator); - array_add(*array_to_print, tmp); - } - string_to_print := join(..array_to_print, separator = " "); - print(": % : ", string_to_print); - for 0..str.count-1 { - if str[it] == #char "\e" { - str[it] = #char "#"; - } - } - write_string(str); - write_string(" :"); - drop_down += 1; - } - } - - x := ifx width > 24 then width-24 else 1; - y := ifx height > 1 then height-1 else 1; - - TUI.set_cursor_position(x, y); - print("size = %x%\n", width, height); - key = TUI.get_key(1000); - - // __mark := get_temporary_storage_mark(); - // set_temporary_storage_mark(__mark); - } - print("- success"); - 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.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):"); - next_line(); - str, key := TUI.read_input_line(15); - TUI.set_cursor_position(1, 3); - error_message: string; - if key == { - case TUI.Keys.Escape; { - print("Have you pressed Esc? (y/n)"); - error_message = "Failed to read line on Esc."; - } - - case TUI.Keys.Resize; { - print("Have you resized the terminal? (y/n)"); - error_message = "Failed to read line on resize."; - - } - case; { - print("Have you entered '%'? (y/n)", str); - error_message = "Failed to read line."; - } - } - answer := TUI.get_key(); - 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.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):"); - next_line(); - str, key := TUI.read_input_line(15, false); - TUI.set_cursor_position(1, 3); - error_message: string; - if key == { - case TUI.Keys.Escape; { - print("Have you pressed Esc? (y/n)"); - error_message = "Failed to read line on Esc."; - } - - case TUI.Keys.Resize; { - print("Have you resized the terminal? (y/n)"); - error_message = "Failed to read line on resize."; - } - case; { - print("Have you entered '%'? (y/n)", str); - error_message = "Failed to read line."; - } - } - answer := TUI.get_key(); - 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(); -- cgit v1.2.3 From b87e4a141da53cf903a0ab9fc9eb693c85a5c0b4 Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 4 May 2024 22:58:39 +0100 Subject: Documented get_key procedure and small cleanup. --- modules/TUI/module.jai | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index a6a3d11..772b30c 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -299,7 +299,7 @@ module_logger :: (message: string, data: *void, info: Log_Info) { } assert_is_active :: inline () { - assert(active, "Please call setup_terminal() before using the module procedures."); + assert(active, "Please call setup_terminal() to start using this module."); } //////////////////////////////////////////////////////////////////////////////// @@ -311,7 +311,6 @@ set_next_key :: inline (key: Key) { input_override = key; } -// TODO Provide some documentation comments. get_key :: (timeout_milliseconds: s32 = -1) -> Key { assert_is_active(); @@ -319,25 +318,22 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { defer input_override = xx Keys.None; return input_override; } - + if OS_was_terminal_resized() return Keys.Resize; - should_read_input := false; - is_input_available := false; + // If there's nothing on the input_string buffer, await for input to be available, + // otherwise, if we have less than a complete Key, check if there's more to read. + should_read_input := false; if input_string.count == 0 { - should_read_input = true; - is_input_available = OS_wait_for_input(timeout_milliseconds); + should_read_input = OS_wait_for_input(timeout_milliseconds); } else if input_string.count < KEY_SIZE { - should_read_input = true; - is_input_available = OS_wait_for_input(0); + should_read_input = OS_wait_for_input(0); } - if OS_was_terminal_resized() return Keys.Resize; - - if should_read_input && is_input_available { - // Copy buffered bytes to the start, and read the remaining ones from input. + if should_read_input { + // Copy data to the start of the input_string buffer. for 0..input_string.count-1 { input_buffer[it] = input_string[it]; } @@ -348,6 +344,9 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { input_string.count += bytes_read; } + // The terminal may have been resized while waiting for or reading the input; check it again. + if OS_was_terminal_resized() return Keys.Resize; + if input_string.count == 0 return Keys.None; // By default, parse a single UTF8 character (1 to 4 bytes). @@ -368,12 +367,13 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { key, success = table_find(*key_map, to_parse); } - + + // If found, return the escape code, otherwise return a single escape character. if success { - return key; // Escape code found, return it. + return key; } else { - to_parse.count = 1; // No escape code found, return a single (escape) character. + to_parse.count = 1; } } @@ -381,14 +381,15 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { } // If count_limit has a non-negative value it will be used as the limit to the number of bytes on the returned string. -// If any characters are provided in the terminators list, they will be used to scan and interrupt the input, including +// If any ASCII characters are provided in the terminators list, they will be used to scan and interrupt the input, including // the terminator as the last character. // At least one of the arguments must be properly setup to avoid an infinite-loop reading the input. read_input :: (count_limit: int = -1, terminators: .. u8) -> string { assert_is_active(); assert(count_limit >= 0 || terminators.count > 0, "Invalid arguments passed to read_input() will result in infinite-loop."); - // TODO Provide some documentation comments. + // Read until one of the terminator characters is found. + // Since we don't know the resulting size of the returned string, we must keep the string builder growing. if count_limit < 0 { builder: String_Builder; init_string_builder(*builder); @@ -411,6 +412,7 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { } return builder_to_string(*builder); } + // Do the same but limit the number of bytes in the returned string. else { buffer := alloc_string(count_limit); buffer.count = 0; -- cgit v1.2.3 From cb6c3caaa285bc8bd12c356d57bbfad86d70c0eb Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 5 May 2024 00:31:46 +0100 Subject: WIP : Cleanup TUI module scope. --- modules/TUI/module.jai | 222 +++++++++++++++++++++++--------------------- modules/TUI/palette_24b.jai | 6 ++ ttt.jai | 2 +- 3 files changed, 124 insertions(+), 106 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 772b30c..ab2567d 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,7 +1,9 @@ #module_parameters(COLOR_MODE_BITS := 24); + #scope_file + #if OS == { case .LINUX; #load "unix.jai"; @@ -26,8 +28,27 @@ KEY_SIZE :: #run type_info(Key).runtime_size; 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)."); } +active := false; +input_buffer : [1024] u8; +input_string : string; +input_override : Key; + +previous_logger : (message: string, data: *void, info: Log_Info); + +module_logger :: (message: string, data: *void, info: Log_Info) { + write_strings(Commands.SaveCursorPosition, Commands.MainScreenBuffer); + previous_logger(message, data, info); + write_strings(Commands.AlternateScreenBuffer, Commands.RestoreCursorPosition); +} + +assert_is_active :: inline () { + assert(active, "Please call setup_terminal() to start using this module."); +} + + #scope_export; + // Special Graphics Characters. Drawings :: struct #type_info_none { Blank :: "\x5F"; @@ -123,6 +144,12 @@ Commands :: struct #type_info_none { // TODO Check which procedures need the assert_is_active call. // TODO Review the error messages on the asserts. +//////////////////////////////////////////////////////////////////////////////// + + +#scope_file + + #if COLOR_MODE_BITS == 4 { #load "palette_4b.jai"; @@ -144,12 +171,6 @@ else #if COLOR_MODE_BITS == 8 { else { #load "palette_24b.jai"; - Color_24b :: struct { - r: u8; - g: u8; - b: u8; - } - set_colors :: inline (foreground: Color_24b, background: Color_24b) { print( #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), @@ -158,6 +179,19 @@ else { } } +set_font_style :: inline (bold: bool, underline: bool = false, strike_through: bool = false, negative: bool = false) { + print( + #run sprint(Commands.SetGraphicsRendition, "%;%;%;%"), + ifx bold then 1 else 22, + ifx underline then 4 else 24, + ifx strike_through then 9 else 29, + ifx negative then 7 else 27); +} + + +#scope_export + + Style :: struct { #if COLOR_MODE_BITS == 4 || COLOR_MODE_BITS == 8 { background: Palette; @@ -180,16 +214,9 @@ Style :: struct { negative: bool; } -set_font_style :: inline (bold: bool, underline: bool = false, strike_through: bool = false, negative: bool = false) { - print( - #run sprint(Commands.SetGraphicsRendition, "%;%;%;%"), - ifx bold then 1 else 22, - ifx underline then 4 else 24, - ifx strike_through then 9 else 29, - ifx negative then 7 else 27); -} +#add_context terminal_style: Style; -set_style :: (style: Style) { +set_style :: inline (style: Style) { set_font_style(style.bold, style.underline, style.strike_through, style.negative); set_colors(style.foreground, style.background); context.terminal_style = style; @@ -205,6 +232,9 @@ using_style :: (style: Style) #expand { `defer set_style(__style); } + +//////////////////////////////////////////////////////////////////////////////// + /* 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. @@ -216,34 +246,7 @@ using_style :: (style: Style) #expand { key/u64 |0|0|c|b|a| -> that in memory lays as (BE:|0|0|c|b|a|) and (LE:|a|b|c|0|0|) */ -Key :: u64; // Terminal key-codes have 1 to 6 bytes so we'll use 8 bytes. - -to_key :: (str: $T) -> Key #modify { return T == ([]u8) || T == string; } { - assert(str.count <= KEY_SIZE, "Invalid argument passed to to_key(): 'str.count' must be less-than or equal to %, but it was %.", KEY_SIZE, str.count); - - k: Key; - for 0..str.count-1 { - k |= ((cast(u64)str[it]) << (it*8)); - } - return k; -} - -to_string :: (key: Key) -> string { - str := alloc_string(KEY_SIZE); - str.count = 0; - while key != 0 { - str.count += 1; - str[str.count-1] = xx key & 0xFF; - key >>= 8; - } - return str; -} - -is_escape_code :: (key: Key) -> bool { - beginsWithEscape := ((key & 0xFF) ^ #char "#") == 0; - hasSomethingElse := (key & (~0xFF)) != 0; - return beginsWithEscape && hasSomethingElse; -} +Key :: u64; Keys :: struct #type_info_none { None : Key : #run to_key("#none"); @@ -282,29 +285,86 @@ Keys :: struct #type_info_none { F12 : Key : #run to_key("#f12"); } -#add_context terminal_style: Style; +to_key :: (str: $T) -> Key #modify { return T == ([]u8) || T == string; } { + assert(str.count <= KEY_SIZE, "Invalid argument passed to to_key(): 'str.count' must be less-than or equal to %, but it was %.", KEY_SIZE, str.count); + + k: Key; + for 0..str.count-1 { + k |= ((cast(u64)str[it]) << (it*8)); + } + return k; +} -active := false; +to_string :: (key: Key) -> string { + str := alloc_string(KEY_SIZE); + str.count = 0; + while key != 0 { + str.count += 1; + str[str.count-1] = xx key & 0xFF; + key >>= 8; + } + return str; +} -input_buffer : [1024] u8; -input_string : string; -input_override : Key; +is_escape_code :: (key: Key) -> bool { + beginsWithEscape := ((key & 0xFF) ^ #char "#") == 0; + hasSomethingElse := (key & (~0xFF)) != 0; + return beginsWithEscape && hasSomethingElse; +} -previous_logger : (message: string, data: *void, info: Log_Info); +//////////////////////////////////////////////////////////////////////////////// -module_logger :: (message: string, data: *void, info: Log_Info) { - write_strings(Commands.SaveCursorPosition, Commands.MainScreenBuffer); - previous_logger(message, data, info); - write_strings(Commands.AlternateScreenBuffer, Commands.RestoreCursorPosition); +is_active :: inline () -> bool { + return active; } -assert_is_active :: inline () { - assert(active, "Please call setup_terminal() to start using this module."); +setup_terminal :: () -> success := true #must { + if active == true return; + + input_string.data = input_buffer.data; + input_string.count = 0; + input_override = xx Keys.None; + + previous_logger = context.logger; + context.logger = module_logger; + + setup_key_map(); + + write_strings( + Commands.HideCursor, + Commands.SaveCursorPosition, + Commands.AlternateScreenBuffer, + Commands.EncodingUTF8, + Commands.CursorNormalMode, + Commands.KeypadNumMode + ); + + if !OS_prepare_terminal() then return false; + + active = true; + + return; } -//////////////////////////////////////////////////////////////////////////////// +reset_terminal :: () -> success := true #must { + if active == false return; + + active = false; -// #scope_export TODO Setup the scope_export and scope_file + clear_style(); + + if !OS_reset_terminal() then return false; + + context.logger = previous_logger; + + write_strings( + Commands.MainScreenBuffer, + Commands.RestoreCursorPosition, + Commands.ShowCursor + ); + + return; +} set_next_key :: inline (key: Key) { assert_is_active(); @@ -535,54 +595,6 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { return result, key; } -setup_terminal :: () -> success := true #must { - if active == true return; - - input_string.data = input_buffer.data; - input_string.count = 0; - input_override = xx Keys.None; - - previous_logger = context.logger; - context.logger = module_logger; - - setup_key_map(); - - write_strings( - Commands.HideCursor, - Commands.SaveCursorPosition, - Commands.AlternateScreenBuffer, - Commands.EncodingUTF8, - Commands.CursorNormalMode, - Commands.KeypadNumMode - ); - - if !OS_prepare_terminal() then return false; - - active = true; - - return; -} - -reset_terminal :: () -> success := true #must { - if active == false return; - - active = false; - - clear_style(); - - if !OS_reset_terminal() then return false; - - context.logger = previous_logger; - - write_strings( - Commands.MainScreenBuffer, - Commands.RestoreCursorPosition, - Commands.ShowCursor - ); - - return; -} - flush_input :: () { assert_is_active(); OS_flush_input(); diff --git a/modules/TUI/palette_24b.jai b/modules/TUI/palette_24b.jai index ea0191c..7545a0b 100644 --- a/modules/TUI/palette_24b.jai +++ b/modules/TUI/palette_24b.jai @@ -1,4 +1,10 @@ // https://www.ditig.com/publications/256-colors-cheat-sheet +Color_24b :: struct { + r: u8; + g: u8; + b: u8; +} + Palette :: struct #type_info_none { BLACK :: Color_24b.{0x00, 0x00, 0x00}; MAROON :: Color_24b.{0x80, 0x00, 0x00}; diff --git a/ttt.jai b/ttt.jai index d471661..f3f1905 100644 --- a/ttt.jai +++ b/ttt.jai @@ -124,7 +124,7 @@ error_time_limit := Apollo_Time.{0, 0}; print_error :: (format :string, args : .. Any) { - if TUI.active == false { + if TUI.is_active() == false { print(format, ..args, to_standard_error = true); print("\n"); return; -- cgit v1.2.3 From 36af624cdd9cb54454587bfae21b30096986d22e Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 5 May 2024 02:34:33 +0100 Subject: WIP : Cleanup TUI module and improved assert/error messages. --- modules/TUI/module.jai | 137 ++++++++++++++++++++---------------------------- modules/TUI/unix.jai | 14 ++--- modules/TUI/windows.jai | 34 ++++++------ 3 files changed, 81 insertions(+), 104 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index ab2567d..07d121f 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -15,36 +15,46 @@ #assert(false, "Unsupported OS."); } +#if COLOR_MODE_BITS == { + case 4; + #load "palette_4b.jai"; + case 8; + #load "palette_8b.jai"; + case 24; + #load "palette_24b.jai"; + _; + assert(false, "Invalid COLOR_MODE_BITS. Valid values are 4, 8, or 24 (default)."); +} + #import "Basic"; #import "String"; #import "Thread"; #import "UTF8"; #load "key_map.jai"; +active := false; +input_override : Key; +input_string : string; +input_buffer : [1024] u8; + KEY_SIZE :: #run type_info(Key).runtime_size; -#run { - 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)."); -} +#assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key. -active := false; -input_buffer : [1024] u8; -input_string : string; -input_override : Key; -previous_logger : (message: string, data: *void, info: Log_Info); +#scope_module -module_logger :: (message: string, data: *void, info: Log_Info) { - write_strings(Commands.SaveCursorPosition, Commands.MainScreenBuffer); - previous_logger(message, data, info); - write_strings(Commands.AlternateScreenBuffer, Commands.RestoreCursorPosition); -} assert_is_active :: inline () { assert(active, "Please call setup_terminal() to start using this module."); } +log_tui_error :: (format_string: string, args: .. Any) { + write_strings(Commands.SaveCursorPosition, Commands.MainScreenBuffer); + log_error(format_string, args); + write_strings(Commands.AlternateScreenBuffer, Commands.RestoreCursorPosition); +} + #scope_export; @@ -141,56 +151,7 @@ Commands :: struct #type_info_none { CursorNormalMode :: "\e[?1l"; } -// TODO Check which procedures need the assert_is_active call. -// TODO Review the error messages on the asserts. - -//////////////////////////////////////////////////////////////////////////////// - - -#scope_file - - -#if COLOR_MODE_BITS == 4 { - #load "palette_4b.jai"; - - set_colors :: inline (foreground: Palette, background: Palette) { - print( - #run sprint("%0%0", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), - cast(u8)foreground + 30, cast(u8)background + 40); - } -} -else #if COLOR_MODE_BITS == 8 { - #load "palette_8b.jai"; - - set_colors :: inline (foreground: Palette, background: Palette) { - print( - #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), - cast(u8)foreground, cast(u8)background); - } -} -else { - #load "palette_24b.jai"; - - set_colors :: inline (foreground: Color_24b, background: Color_24b) { - print( - #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), - foreground.r, foreground.g, foreground.b, - background.r, background.g, background.b); - } -} - -set_font_style :: inline (bold: bool, underline: bool = false, strike_through: bool = false, negative: bool = false) { - print( - #run sprint(Commands.SetGraphicsRendition, "%;%;%;%"), - ifx bold then 1 else 22, - ifx underline then 4 else 24, - ifx strike_through then 9 else 29, - ifx negative then 7 else 27); -} - - -#scope_export - +#add_context terminal_style: Style; Style :: struct { #if COLOR_MODE_BITS == 4 || COLOR_MODE_BITS == 8 { @@ -214,11 +175,32 @@ Style :: struct { negative: bool; } -#add_context terminal_style: Style; - set_style :: inline (style: Style) { - set_font_style(style.bold, style.underline, style.strike_through, style.negative); - set_colors(style.foreground, style.background); + print( + #run sprint(Commands.SetGraphicsRendition, "%;%;%;%"), + ifx style.bold then 1 else 22, + ifx style.underline then 4 else 24, + ifx style.strike_through then 9 else 29, + ifx style.negative then 7 else 27); + + #if COLOR_MODE_BITS == { + case 4; + print( + #run sprint("%0%0", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), + cast(u8)style.foreground + 30, cast(u8)style.background + 40); + + case 8; + print( + #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), + cast(u8)style.foreground, cast(u8)style.background); + + case 24; + print( + #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), + style.foreground.r, style.foreground.g, style.foreground.b, + style.background.r, style.background.g, style.background.b); + } + context.terminal_style = style; } @@ -286,7 +268,7 @@ Keys :: struct #type_info_none { } to_key :: (str: $T) -> Key #modify { return T == ([]u8) || T == string; } { - assert(str.count <= KEY_SIZE, "Invalid argument passed to to_key(): 'str.count' must be less-than or equal to %, but it was %.", KEY_SIZE, str.count); + assert(str.count <= KEY_SIZE, "Invalid arguments passed to to_key(): 'str' has more than % bytes and cannot be stored as a Key.", KEY_SIZE); k: Key; for 0..str.count-1 { @@ -325,9 +307,6 @@ setup_terminal :: () -> success := true #must { input_string.count = 0; input_override = xx Keys.None; - previous_logger = context.logger; - context.logger = module_logger; - setup_key_map(); write_strings( @@ -355,8 +334,6 @@ reset_terminal :: () -> success := true #must { if !OS_reset_terminal() then return false; - context.logger = previous_logger; - write_strings( Commands.MainScreenBuffer, Commands.RestoreCursorPosition, @@ -446,8 +423,8 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { // At least one of the arguments must be properly setup to avoid an infinite-loop reading the input. read_input :: (count_limit: int = -1, terminators: .. u8) -> string { assert_is_active(); - assert(count_limit >= 0 || terminators.count > 0, "Invalid arguments passed to read_input() will result in infinite-loop."); - + assert(count_limit >= 0 || terminators.count > 0, "Invalid arguments passed to read_input(): when 'count_limit' is less-than 0 (ignored), you need to provide 'terminators' to avoid an infinite-loop."); + // Read until one of the terminator characters is found. // Since we don't know the resulting size of the returned string, we must keep the string builder growing. if count_limit < 0 { @@ -504,7 +481,7 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { // Resize discards the input returning an empty string and a Resize key. read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { assert_is_active(); - assert(count_limit >= 0, "Invalid value passed to count_limit(): %.", count_limit); + assert(count_limit >= 0, "Invalid arguments passed to read_input_line(): 'count_limit' must be greater-than or equal to 0."); builder := String_Builder.{ allocator = temporary_allocator }; @@ -604,7 +581,7 @@ flush_input :: () { draw_box :: (x: int, y: int, width: int, height: int) { assert_is_active(); - assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): (%, %, %, %).\n", x, y, width, height); + assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): 'x' and 'y' must be greater-than 0; 'width' and 'height' must be greater-than 1."); auto_release_temp(); @@ -674,7 +651,7 @@ get_terminal_size :: () -> width: int, height: int { 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."); + "Failed to query window size: invalid response."); parts := split(input, ";",, temporary_allocator); rows = parse_int(*parts[1]); @@ -723,7 +700,7 @@ get_cursor_position :: () -> x: int, y: int { 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."); + "Failed to query cursor position: invalid response."); advance(*input, 2); parts := split(input, ";",, temporary_allocator); diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index 940ac80..99cc61d 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -230,7 +230,7 @@ restore_resize_handler :: () { //////////////////////////////////////////////////////////////////////////////// -#scope_export +#scope_module OS_prepare_terminal :: () -> success := true { error: int = ---; @@ -238,7 +238,7 @@ OS_prepare_terminal :: () -> success := true { error = tcgetattr(STDIN_FILENO, *initial_tio_mode); if error { error_code, error_string := get_error_value_and_string(); - log_error("Failed to get initial_tio_mode: code %, %", error_code, error_string); + log_tui_error("Failed to get initial_tio_mode: code %, %", error_code, error_string); return false; } @@ -254,7 +254,7 @@ OS_prepare_terminal :: () -> success := true { error = tcsetattr(STDIN_FILENO, xx Optional_Actions.TCSANOW, *raw_tio_mode); if error { error_code, error_string := get_error_value_and_string(); - log_error("Failed to set raw_tio_mode: code %, %", error_code, error_string); + log_tui_error("Failed to set raw_tio_mode: code %, %", error_code, error_string); return false; } @@ -268,7 +268,7 @@ OS_reset_terminal :: () -> success := true { error := tcsetattr(STDIN_FILENO, xx Optional_Actions.TCSANOW, *initial_tio_mode); if error { error_code, error_string := get_error_value_and_string(); - log_error("Failed to set initial_tio_mode: code %, %", error_code, error_string); + log_tui_error("Failed to set initial_tio_mode: code %, %", error_code, error_string); return false; } return; @@ -278,7 +278,7 @@ OS_flush_input :: () -> success := true { error := tcflush(STDIN_FILENO, xx Queue_Selector.TCIFLUSH); if error { error_code, error_string := get_error_value_and_string(); - log_error("Failed to flush input: code %, %", error_code, error_string); + log_tui_error("Failed to flush input: code %, %", error_code, error_string); return false; } return; @@ -288,7 +288,7 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : bytes_read := read(STDIN_FILENO, buffer, xx bytes_to_read); if bytes_read < 0 { error_code, error_string := get_error_value_and_string(); - log_error("Failed to read input: code %, %", error_code, error_string); + log_tui_error("Failed to read input: code %, %", error_code, error_string); return 0, false; } return bytes_read; @@ -306,7 +306,7 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo error_code, error_string := get_error_value_and_string(); // Ignore window resize events (error_code 4). if error_code != 4 { - log_error("Unexpected error while waiting for input: code %, %", error_code, error_string); + log_tui_error("Unexpected error while waiting for input: code %, %", error_code, error_string); return false, false; } } diff --git a/modules/TUI/windows.jai b/modules/TUI/windows.jai index fb50e49..f8d8bc8 100644 --- a/modules/TUI/windows.jai +++ b/modules/TUI/windows.jai @@ -142,7 +142,7 @@ peek_input :: () -> INPUT_RECORD, success := true { records_read: u32; if PeekConsoleInputW(stdin, *record, 1, *records_read) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to peek input: code %, %", error_code, error_string); + log_tui_error("Failed to peek input: code %, %", error_code, error_string); return record, false; } return record; @@ -153,7 +153,7 @@ read_input :: () -> INPUT_RECORD, success := true { records_read: u32; if ReadConsoleInputW(stdin, *record, 1, *records_read) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to read input: code %, %", error_code, error_string); + log_tui_error("Failed to read input: code %, %", error_code, error_string); return record, false; } return record; @@ -163,7 +163,7 @@ count_input :: () -> u32, success := true { count: u32; if GetNumberOfConsoleInputEvents(stdin, *count) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to count input: code %, %", error_code, error_string); + log_tui_error("Failed to count input: code %, %", error_code, error_string); return 0, false; } return count; @@ -171,7 +171,7 @@ count_input :: () -> u32, success := true { //////////////////////////////////////////////////////////////////////////////// -#scope_export +#scope_module OS_prepare_terminal :: () -> success := true { @@ -179,12 +179,12 @@ OS_prepare_terminal :: () -> success := true { stdin = GetStdHandle(STD_INPUT_HANDLE); if stdin == INVALID_HANDLE_VALUE { error_code, error_string := get_error_value_and_string(); - log_error("Invalid input handler: code %, %", error_code, error_string); + log_tui_error("Invalid input handler: code %, %", error_code, error_string); return false; } if xx GetConsoleMode(stdin, *initial_stdin_mode) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to get input mode: code %, %", error_code, error_string); + log_tui_error("Failed to get input mode: code %, %", error_code, error_string); return false; } raw_stdin_mode = (cast(Console_Input_Mode) initial_stdin_mode); @@ -193,7 +193,7 @@ OS_prepare_terminal :: () -> success := true { if xx SetConsoleMode(stdin, xx raw_stdin_mode) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to set input mode: code %, %", error_code, error_string); + log_tui_error("Failed to set input mode: code %, %", error_code, error_string); return false; } @@ -201,12 +201,12 @@ OS_prepare_terminal :: () -> success := true { stdout = GetStdHandle(STD_OUTPUT_HANDLE); if stdout == INVALID_HANDLE_VALUE { error_code, error_string := get_error_value_and_string(); - log_error("Invalid output handler: code %, %", error_code, error_string); + log_tui_error("Invalid output handler: code %, %", error_code, error_string); return false; } if xx GetConsoleMode(stdout, *initial_stdout_mode) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to get output mode: code %, %", error_code, error_string); + log_tui_error("Failed to get output mode: code %, %", error_code, error_string); return false; } raw_stdout_mode = (cast(Console_Output_Mode) initial_stdout_mode); @@ -214,7 +214,7 @@ OS_prepare_terminal :: () -> success := true { if xx SetConsoleMode(stdout, xx raw_stdout_mode) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to set output mode: code %, %", error_code, error_string); + log_tui_error("Failed to set output mode: code %, %", error_code, error_string); return false; } @@ -231,12 +231,12 @@ OS_prepare_terminal :: () -> success := true { OS_reset_terminal :: () -> success := true { if xx SetConsoleMode(stdin, initial_stdin_mode) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to reset input mode: code %, %", error_code, error_string); + log_tui_error("Failed to reset input mode: code %, %", error_code, error_string); return false; } if xx SetConsoleMode(stdout, initial_stdout_mode) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to reset output mode: code %, %", error_code, error_string); + log_tui_error("Failed to reset output mode: code %, %", error_code, error_string); return false; } return; @@ -247,7 +247,7 @@ OS_flush_input :: () { // Attempting to empty the input queue all at once can destroy state in the queue in an unexpected manner. if FlushConsoleInputBuffer(stdin) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to flush input: code %, %", error_code, error_string); + log_tui_error("Failed to flush input: code %, %", error_code, error_string); } } @@ -255,7 +255,7 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : S32_MAX :: 0x7fff_ffff; if (bytes_to_read > S32_MAX) { - log_error("The Windows API only allows to read up to 2^32 bytes from the standard input. Clamping input argument."); + log_tui_error("The Windows API only allows to read up to 2^32 bytes from the standard input. Clamping input argument."); bytes_to_read = S32_MAX; } @@ -288,14 +288,14 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : success = ReadConsoleW(stdin, widechar_view.data, chars_to_read, *widechar_view.count, null); if success == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to read console: code %, %", error_code, error_string); + log_tui_error("Failed to read console: code %, %", error_code, error_string); return 0, false; } result:, success = wide_to_utf8_new(widechar_view.data, xx widechar_view.count); if success == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to convert from wide to utf8: code %, %", error_code, error_string); + log_tui_error("Failed to convert from wide to UTF8: code %, %", error_code, error_string); return 0, false; } @@ -338,7 +338,7 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo if wait_result == WAIT_FAILED { error_code, error_string := get_error_value_and_string(); - log_error("Error while waiting for input: code %, %", error_code, error_string); + log_tui_error("Error while waiting for input: code %, %", error_code, error_string); return false, false; } -- cgit v1.2.3 From 1ae2933bcbda43d16d2583a28c3c5a74adaaa9e4 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 5 May 2024 03:22:42 +0100 Subject: Implemented default background and foreground colors. --- modules/TUI/module.jai | 36 +++++++++++++++++++++++------------- ttt.jai | 15 +++++++++++++-- 2 files changed, 36 insertions(+), 15 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 07d121f..3f824a4 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -165,7 +165,7 @@ Style :: struct { background = Palette.BLACK; foreground = Palette.WHITE; - // TODO Make this work... + // TODO Make this work... and now that is implemented... test it. use_default_background_color := false; use_default_foreground_color := false; @@ -176,31 +176,41 @@ Style :: struct { } set_style :: inline (style: Style) { - print( - #run sprint(Commands.SetGraphicsRendition, "%;%;%;%"), - ifx style.bold then 1 else 22, - ifx style.underline then 4 else 24, - ifx style.strike_through then 9 else 29, - ifx style.negative then 7 else 27); + auto_release_temp(); + builder := String_Builder.{ allocator = temporary_allocator }; + #if COLOR_MODE_BITS == { case 4; - print( + print_to_builder(*builder, #run sprint("%0%0", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), - cast(u8)style.foreground + 30, cast(u8)style.background + 40); + cast(u8)style.foreground + 30, cast(u8)style.background + 40 + ); case 8; - print( + print_to_builder(*builder, #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), - cast(u8)style.foreground, cast(u8)style.background); + cast(u8)style.foreground, cast(u8)style.background + ); case 24; - print( + print_to_builder(*builder, #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), style.foreground.r, style.foreground.g, style.foreground.b, - style.background.r, style.background.g, style.background.b); + style.background.r, style.background.g, style.background.b + ); + } + + if style.use_default_foreground_color { + append(*builder, #run sprint(Commands.SetGraphicsRendition, "39")); + } + + if style.use_default_background_color { + append(*builder, #run sprint(Commands.SetGraphicsRendition, "49")); } + write_string(builder_to_string(*builder,, allocator = temporary_allocator)); + context.terminal_style = style; } diff --git a/ttt.jai b/ttt.jai index f3f1905..0715544 100644 --- a/ttt.jai +++ b/ttt.jai @@ -17,7 +17,12 @@ // - release : jai ttt.jai -quiet -x64 -release // - debug : jai ttt.jai -quiet -x64 +DEBUG_MEMORY :: true; +#if DEBUG_MEMORY { #import "Basic"()(MEMORY_DEBUGGER=true); // TODO Remove after final debug sessions. This takes up ~30MB of RAM. +} else { +#import "Basic"; +} #import "System"; #import "Sort"; #import "Math"; @@ -1157,8 +1162,10 @@ prompt_user_key :: (y: int, message: string) -> TUI.Key { } main :: () { - + +#if DEBUG_MEMORY { defer report_memory_leaks(); // TODO Remove after final debug sessions. +} defer free_memory(); @@ -1774,7 +1781,11 @@ main :: () { } assert(TUI.reset_terminal(), "Failed to reset TUI."); - + +#if DEBUG_MEMORY { + return; +} else { exit(xx ifx error_saving then 1 else 0); } +} -- cgit v1.2.3 From 54103773365e06d6da8518d135c396949e683960 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 5 May 2024 04:19:06 +0100 Subject: Fixed memory leaks. --- modules/TUI/module.jai | 26 +++++++++++++------------- ttt.jai | 32 ++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 27 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 3f824a4..dc9643c 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -511,17 +511,17 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // Draw preview. if is_visible { - append(*builder, tprint(Commands.SetCursorPosition, y, x)); + print_to_builder(*builder, Commands.SetCursorPosition, y, x); append(*builder, str); for chars_count..count_limit-1 append(*builder, " "); } else { - append(*builder, tprint(Commands.SetCursorPosition, y, x)); + print_to_builder(*builder, Commands.SetCursorPosition, y, x); for 1..chars_count append(*builder, "*"); for chars_count..count_limit-1 append(*builder, " "); } - append(*builder, tprint(Commands.SetCursorPosition, y, x+idx)); - write_string(builder_to_string(*builder)); + print_to_builder(*builder, Commands.SetCursorPosition, y, x+idx); + write_string(builder_to_string(*builder,, allocator = temporary_allocator)); // Process input key. key = get_key(); @@ -600,7 +600,7 @@ draw_box :: (x: int, y: int, width: int, height: int) { append(*builder, Commands.DrawingMode); // Draw top line - append(*builder, tprint(Commands.SetCursorPosition, y, x)); + print_to_builder(*builder, Commands.SetCursorPosition, y, x); append(*builder, Drawings.CornerTL); for 1..width-2 { append(*builder, Drawings.LineH); @@ -609,14 +609,14 @@ draw_box :: (x: int, y: int, width: int, height: int) { // Draw left and right sides. for idx: y+1..y+height-2 { - append(*builder, tprint(Commands.SetCursorPosition, idx, x)); + print_to_builder(*builder, Commands.SetCursorPosition, idx, x); append(*builder, Drawings.LineV); - append(*builder, tprint(Commands.SetCursorPosition, idx, x+width-1)); + print_to_builder(*builder, Commands.SetCursorPosition, idx, x+width-1); append(*builder, Drawings.LineV); } // Draw bottom line. - append(*builder, tprint(Commands.SetCursorPosition, y+height-1, x)); + print_to_builder(*builder, Commands.SetCursorPosition, y+height-1, x); append(*builder, Drawings.CornerBL); for 1..width-2 { append(*builder, Drawings.LineH); @@ -625,7 +625,7 @@ draw_box :: (x: int, y: int, width: int, height: int) { append(*builder, Commands.TextMode); - write_string(builder_to_string(*builder)); + write_string(builder_to_string(*builder,, allocator = temporary_allocator)); } clear_terminal :: inline () { @@ -647,7 +647,7 @@ get_terminal_size :: () -> width: int, height: int { // Expected response format: \e[8;;t // where is the number of rows and of columns. FORMAT :: "\e[8;;t"; - input := read_input(64, #char "t",, temporary_allocator); + input := read_input(64, #char "t",, allocator = temporary_allocator); // Discard head noise. while input.count >= 3 && (input[0] != FORMAT[0] || input[1] != FORMAT[1] || input[2] != FORMAT[2]) { @@ -663,7 +663,7 @@ get_terminal_size :: () -> width: int, height: int { input[0] == FORMAT[0] && input[1] == FORMAT[1] && input[2] == FORMAT[2] && input[input.count-1] == FORMAT[FORMAT.count-1], "Failed to query window size: invalid response."); - parts := split(input, ";",, temporary_allocator); + parts := split(input, ";",, allocator = temporary_allocator); rows = parse_int(*parts[1]); columns = parse_int(*parts[2]); } @@ -696,7 +696,7 @@ get_cursor_position :: () -> x: int, y: int { // Expected response format: \e[;R // where is the number of rows and of columns. FORMAT :: "\e[;R"; - input := read_input(64, #char "R"); + input := read_input(64, #char "R",, allocator = temporary_allocator); // Discard head noise. while input.count >= 2 && (input[0] != FORMAT[0] || input[1] != FORMAT[1]) { @@ -713,7 +713,7 @@ get_cursor_position :: () -> x: int, y: int { "Failed to query cursor position: invalid response."); advance(*input, 2); - parts := split(input, ";",, temporary_allocator); + parts := split(input, ";",, allocator = temporary_allocator); row := parse_int(*parts[0]); column := parse_int(*parts[1]); return column, row; diff --git a/ttt.jai b/ttt.jai index 0715544..929a736 100644 --- a/ttt.jai +++ b/ttt.jai @@ -590,12 +590,14 @@ load_database :: (db: *Database, path: string) -> success: bool { // Returns success. export_to_csv :: (db: Database, path: string) -> success: bool { assert(xx path, ASSERT_NOT_EMPTY, "path"); + + auto_release_temp(); builder: String_Builder; defer reset(*builder); CSV_HEADER :: string.[ "task", "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ]; - print_to_builder(*builder, "%\n", join(..CSV_HEADER, separator = ",")); + print_to_builder(*builder, "%\n", join(..CSV_HEADER, separator = ",",, temporary_allocator)); buffer: [Task.name.count] u8; name: string = xx buffer; @@ -701,15 +703,16 @@ import_from_csv :: (db: *Database, path: string) -> bool { } { // Parse CSV lines. - auto_release_temp(); // TODO Needs to be tested. line := csv; while (true) { + auto_release_temp(); + line, success := consume_next_line(*csv); // line, success := next_line(*csv); if success == false break; task: Task; - csv_values := split(line, ",",, temporary_allocator); // TODO Temporary memory... if file is too big this may break. + csv_values := split(line, ",",, temporary_allocator); // Import task name. name_length := min(task.name.count, csv_values[0].count); @@ -891,6 +894,8 @@ update_layout :: () { draw_tui :: (db: *Database, layout: *Layout) { + // TODO During dirty_flag revamp...we may also start using the String_Builder. + adjust_first_day_of_week := int.[ (0 + FIRST_DAY_OF_WEEK) % NUM_WEEK_DAYS, (1 + FIRST_DAY_OF_WEEK) % NUM_WEEK_DAYS, @@ -990,7 +995,6 @@ draw_tui :: (db: *Database, layout: *Layout) { // Display up to rows allowed by the layout, or less if reached end of database. idx_stop := idx_start + (ifx layout_tasks_rows > db.tasks.count - idx_start then db.tasks.count - idx_start else layout_tasks_rows); for task_idx: idx_start..idx_stop-1 { - auto_release_temp(); // TODO Temporary memory being trashed?! task := *db.tasks[task_idx]; y += 1; x = 1; @@ -1138,7 +1142,7 @@ read_input_int :: (y: int, message: string) -> value: int, success: bool { input_pos_x := x + message.count + 2; input_width := size_x - input_pos_x; - str := read_input_string(input_pos_x, y, input_width); + str := read_input_string(input_pos_x, y, input_width,, temporary_allocator); value, success := parse_int(*str); return value, success; @@ -1170,7 +1174,6 @@ main :: () { defer free_memory(); { // Initialize app directory. - auto_release_temp(); home_dir, success_dir := get_home_directory(); // Returns system owned memory. if success_dir == false { home_dir = "."; @@ -1195,7 +1198,6 @@ main :: () { } { // Initialize database and archive files if needed. - auto_release_temp(); if (file_exists(db_file_path) == false) { if (store_database(database, db_file_path) == false) { print_error("Failed to initialize database."); @@ -1333,7 +1335,8 @@ main :: () { } if is_exit_requested { - exit(0); + // exit(0); // TODO fucking exit does not report memory leaks. + return; } } @@ -1353,7 +1356,9 @@ main :: () { TUI.flush_input(); TUI.set_next_key(TUI.Keys.Resize); while (true) { - + + reset_temporary_storage(); + TUI.set_style(style_default); if (is_terminal_too_small) { @@ -1366,7 +1371,6 @@ main :: () { draw_error_window(); } - reset_temporary_storage(); key := TUI.get_key(INPUT_TIMEOUT_MS); if key == #char "q" || key == #char "Q" break; update_times(*database); @@ -1456,7 +1460,7 @@ main :: () { // Change task name. TUI.using_style(action_style); - input := read_input_string(2, selected_task_row, Task.name.count, size_x - 2 - Task.name.count); + input := read_input_string(2, selected_task_row, Task.name.count, size_x - 2 - Task.name.count,, temporary_allocator); if is_empty_string(input) == false { replace_chars(input, "\t\x0B\x0C\r", #char " "); // Replace weird spaces with space. memset(selected_task.name.data, 0, Task.name.count); @@ -1492,9 +1496,9 @@ main :: () { case #char "6"; #through; case #char "7"; if (selected_task == null) continue; - + // Prepare position to input time operation. - selected_day := cast(int)(key - #char "1"); // TODO DAM this cast... + selected_day := cast(int)(key - #char "1"); input_width := layout.columns[L_DAYS_IDX + selected_day].width; input_pos_x := 2 + layout.columns[L_TITLE_IDX].width; for 0..selected_day-1 { @@ -1504,7 +1508,7 @@ main :: () { // Get input string. TUI.using_style(action_style); - input := read_input_string(input_pos_x, selected_task_row, input_width); // TODO Temp stringzes. + input := read_input_string(input_pos_x, selected_task_row, input_width,, temporary_allocator); // Abort if input if empty. if is_empty_string(input) continue; -- cgit v1.2.3 From 3ae6ce04674daaef4f3bf69adbfe4333402cb423 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 5 May 2024 04:28:56 +0100 Subject: Added TODO entry. --- modules/TUI/module.jai | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index dc9643c..27c2fcc 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -175,7 +175,7 @@ Style :: struct { negative: bool; } -set_style :: inline (style: Style) { +set_style :: (style: Style) { auto_release_temp(); builder := String_Builder.{ allocator = temporary_allocator }; @@ -214,8 +214,9 @@ set_style :: inline (style: Style) { context.terminal_style = style; } -clear_style :: inline () { +clear_style :: () { write_string(#run sprint(Commands.SetGraphicsRendition, "0")); + // TODO Maybe reset context.terminal_style? } using_style :: (style: Style) #expand { -- cgit v1.2.3 From b32a697e1898a37497cfc504a2746dcf1f0710da Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 5 May 2024 15:55:30 +0100 Subject: Finished first version of TUI. --- modules/TUI/module.jai | 5 ++--- ttt.jai | 13 +++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 27c2fcc..e25672f 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -22,7 +22,7 @@ #load "palette_8b.jai"; case 24; #load "palette_24b.jai"; - _; + case; assert(false, "Invalid COLOR_MODE_BITS. Valid values are 4, 8, or 24 (default)."); } @@ -165,7 +165,6 @@ Style :: struct { background = Palette.BLACK; foreground = Palette.WHITE; - // TODO Make this work... and now that is implemented... test it. use_default_background_color := false; use_default_foreground_color := false; @@ -216,7 +215,7 @@ set_style :: (style: Style) { clear_style :: () { write_string(#run sprint(Commands.SetGraphicsRendition, "0")); - // TODO Maybe reset context.terminal_style? + context.terminal_style = .{ }; } using_style :: (style: Style) #expand { diff --git a/ttt.jai b/ttt.jai index 929a736..3f7ac59 100644 --- a/ttt.jai +++ b/ttt.jai @@ -86,6 +86,7 @@ pos_y : int; style_default := TUI.Style.{ background = TUI.Palette.BLACK, foreground = TUI.Palette.WHITE, + // use_default_background_color = true, TODO }; style_selected := TUI.Style.{ @@ -97,12 +98,14 @@ style_selected_inverted := TUI.Style.{ background = TUI.Palette.BLACK, foreground = TUI.Palette.CYAN, bold = true, + // use_default_background_color = true, TODO }; style_active := TUI.Style.{ background = TUI.Palette.BLACK, foreground = TUI.Palette.BLUE, bold = true, + // use_default_background_color = true, TODO }; style_active_selected := TUI.Style.{ @@ -115,6 +118,7 @@ style_error := TUI.Style.{ background = TUI.Palette.BLACK, foreground = TUI.Palette.RED, bold = true, + // use_default_background_color = true, TODO }; Layouts :: enum u8 { @@ -1009,6 +1013,9 @@ draw_tui :: (db: *Database, layout: *Layout) { else if (task == active_task) { TUI.set_style(style_active); } + else { + TUI.set_style(style_default); + } // Task title. x += 1; @@ -1043,10 +1050,8 @@ draw_tui :: (db: *Database, layout: *Layout) { // Task total. x += 1; print_time(y, x, total_time, layout.columns[L_TOTAL_IDX].width); - - // Reset theme. - TUI.clear_style(); } + TUI.set_style(style_default); /////////////////////////////////////////////////////////////////////////// @@ -1099,7 +1104,7 @@ free_memory :: () { free(app_directory); free(db_file_path); free(ar_file_path); - //reset_temporary_storage(); + // reset_temporary_storage(); // TODO Not needed... I guess. } read_input_string :: (x: int, y: int, input_limit: int, padding: int = 0) -> value: string, success: bool { -- cgit v1.2.3 From 65227c476071914b82720084a2a775b44e4de885 Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 6 May 2024 12:50:22 +0100 Subject: Cleanup UTF helper. --- modules/TUI/module.jai | 6 ++--- modules/UTF8.jai | 60 ++++++++++++++++++++++++++------------------------ ttt.jai | 11 +++++---- 3 files changed, 39 insertions(+), 38 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index e25672f..7c3a71d 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -398,7 +398,7 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { // By default, parse a single UTF8 character (1 to 4 bytes). to_parse := input_string; - to_parse.count = count_utf8_bytes(input_string[0]); + to_parse.count = count_character_bytes(input_string[0]); defer advance(*input_string, to_parse.count); // Advance over parsed input. // Try to parse escape code. @@ -555,7 +555,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { case; if is_escape_code(key) continue; - buff_idx := map_character_to_buffer_idx(str, idx); + buff_idx := get_byte_idx(str, idx); key_str := to_string(key,, allocator = temporary_allocator); // Make sure we have space to append the new character at the end (in case we're trying to do it). @@ -572,7 +572,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { idx += 1; // Truncate string to avoid incomplete utf8 codes on the string tail. - str.count = truncate_string(str, count_limit); + truncate(*str, count_limit); } } diff --git a/modules/UTF8.jai b/modules/UTF8.jai index eba4585..b583809 100644 --- a/modules/UTF8.jai +++ b/modules/UTF8.jai @@ -1,25 +1,29 @@ -// BBBB BBBB & 1100 0000 == 10XX XXXX -> is continuation byte -// TODO Maybe rename to: is_continuation_byte -is_utf8_continuation_byte :: inline (byte: u8) -> bool { +// Some procedures to help working with UTF8 strings. +// https://en.wikipedia.org/wiki/UTF-8 + +// Returns true if argument is a continuation byte. +is_continuation_byte :: inline (byte: u8) -> bool { + // BBBB BBBB & 1100 0000 == 10XX XXXX -> is continuation byte return (byte & 0xC0) == 0x80; } -// BBBB BBBB & 1110 0000 == 110X XXXX -> 1 initial + 1 continuation byte -// BBBB BBBB & 1111 0000 == 1110 XXXX -> 1 initial + 2 continuation byte -// BBBB BBBB & 1111 1000 == 1111 0XXX -> 1 initial + 3 continuation byte -// TODO Maybe rename to: count_character_bytes -count_utf8_bytes :: inline (byte: u8) -> int { - if (byte & 0xE0) == 0xC0 return 1+1; - if (byte & 0xF0) == 0xE0 return 1+2; - if (byte & 0xF8) == 0xF0 return 1+3; +// Given a leading_byte, returns the number of bytes on the character. +count_character_bytes :: inline (leading_byte: u8) -> int { + // BBBB BBBB & 1110 0000 == 110X XXXX -> 1 initial + 1 continuation byte + if (leading_byte & 0xE0) == 0xC0 return 1+1; + + // BBBB BBBB & 1111 0000 == 1110 XXXX -> 1 initial + 2 continuation byte + if (leading_byte & 0xF0) == 0xE0 return 1+2; + + // BBBB BBBB & 1111 1000 == 1111 0XXX -> 1 initial + 3 continuation byte + if (leading_byte & 0xF8) == 0xF0 return 1+3; + return 1; } -// Truncates the string to the length provided or shorter, in case of UTF8 strings that require so. -// Truncation is done by zeroing the tail of the string in place. -// Returns length of truncated string. -// TODO Maybe rename to: truncate -truncate_string :: (str: string, length: int) -> length: int { +// Truncates the string to the provided length and zeroes the discarded bytes. +// Returns the length of truncated string or -1 if string has no data. +truncate :: (str: *string, length: int) -> length: int { if str.data == null then return -1; if str.count < length then length = str.count; @@ -29,7 +33,7 @@ truncate_string :: (str: string, length: int) -> length: int { // Find index of first continuation byte. idx := length; - while (idx > 0 && is_utf8_continuation_byte(data[idx - 1])) { + while (idx > 0 && is_continuation_byte(data[idx - 1])) { idx -= 1; } continuation_bytes := length - idx; @@ -50,13 +54,12 @@ truncate_string :: (str: string, length: int) -> length: int { } memset(data + length, 0, count - length); - // str.count = length; TODO We should be doing this... + str.count = length; return length; } // Returns true when the string is empty or consists of space characters. -// TODO Maybe rename to: is_empty -is_empty_string :: (str: string) -> bool { +is_empty :: (str: string) -> bool { for 0..str.count-1 { if str[it] == { case #char "\0"; #through; @@ -74,21 +77,21 @@ is_empty_string :: (str: string) -> bool { return true; } -// Counts number of characters in string. +// Counts the number of characters. count_characters :: (str: string) -> int { characters := 0; idx := 0; while idx < str.count { - idx += count_utf8_bytes(str[idx]); + idx += count_character_bytes(str[idx]); characters += 1; } return characters; } -// Delete character. +// Deletes character by it's index, and moves tail data to take its place. delete_character :: (str: *string, character_idx: int) { - buffer_idx := map_character_to_buffer_idx(str.*, character_idx); - bytes_to_delete := count_utf8_bytes(str.data[buffer_idx]); + buffer_idx := get_byte_idx(str.*, character_idx); + bytes_to_delete := count_character_bytes(str.data[buffer_idx]); for buffer_idx..str.count-1-bytes_to_delete { str.data[it] = str.data[it+bytes_to_delete]; @@ -100,9 +103,8 @@ delete_character :: (str: *string, character_idx: int) { str.count -= bytes_to_delete; } -// Get character index. -// TODO Maybe rename to: map_character_to_byte_idx or get_character_byte_idx -map_character_to_buffer_idx :: (str: string, character_idx: int) -> buffer_idx: int, success: bool { +// Searches for the given character index and returns its byte index on the string. +get_byte_idx :: (str: string, character_idx: int) -> buffer_idx: int, success: bool { if character_idx < 0 then return -1, false; if character_idx > str.count then return -2, false; if character_idx == 0 then return 0, true; @@ -110,7 +112,7 @@ map_character_to_buffer_idx :: (str: string, character_idx: int) -> buffer_idx: buff_idx := 0; char_idx := 0; while buff_idx < str.count && char_idx != character_idx { - buff_idx += count_utf8_bytes(str[buff_idx]); + buff_idx += count_character_bytes(str[buff_idx]); char_idx += 1; } return buff_idx, char_idx == character_idx; diff --git a/ttt.jai b/ttt.jai index 3f7ac59..0802220 100644 --- a/ttt.jai +++ b/ttt.jai @@ -718,10 +718,9 @@ import_from_csv :: (db: *Database, path: string) -> bool { task: Task; csv_values := split(line, ",",, temporary_allocator); - // Import task name. - name_length := min(task.name.count, csv_values[0].count); - memcpy(task.name.data, csv_values[0].data, name_length); - truncate_string(xx task.name, name_length); + // Truncate and import task name. + truncate(*csv_values[0], task.name.count); + memcpy(task.name.data, csv_values[0].data, csv_values[0].count); advance(*csv_values); for csv_values @@ -1466,7 +1465,7 @@ main :: () { // Change task name. TUI.using_style(action_style); input := read_input_string(2, selected_task_row, Task.name.count, size_x - 2 - Task.name.count,, temporary_allocator); - if is_empty_string(input) == false { + if is_empty(input) == false { replace_chars(input, "\t\x0B\x0C\r", #char " "); // Replace weird spaces with space. memset(selected_task.name.data, 0, Task.name.count); memcpy(selected_task.name.data, input.data, min(Task.name.count, input.count)); @@ -1516,7 +1515,7 @@ main :: () { input := read_input_string(input_pos_x, selected_task_row, input_width,, temporary_allocator); // Abort if input if empty. - if is_empty_string(input) continue; + if is_empty(input) continue; // Search for assign '=' operator and discard everything before it. assign_idx := find_index_from_left(input, "="); -- cgit v1.2.3 From 8779553b877a2bb728ddf9d7c21b272c2eedb653 Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 7 May 2024 10:27:22 +0100 Subject: Fixed biggest performance bottleneck on draw_user_interface: print white spaces. --- modules/TUI/module.jai | 2 +- modules/UTF8.jai | 40 ++++++++++++++++++++++++---------------- ttt.jai | 38 +++++++++++++++++++------------------- 3 files changed, 44 insertions(+), 36 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 7c3a71d..a5db3bf 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -572,7 +572,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { idx += 1; // Truncate string to avoid incomplete utf8 codes on the string tail. - truncate(*str, count_limit); + str.count = truncate(str, count_limit).count; } } diff --git a/modules/UTF8.jai b/modules/UTF8.jai index b583809..205e57d 100644 --- a/modules/UTF8.jai +++ b/modules/UTF8.jai @@ -21,15 +21,15 @@ count_character_bytes :: inline (leading_byte: u8) -> int { return 1; } -// Truncates the string to the provided length and zeroes the discarded bytes. -// Returns the length of truncated string or -1 if string has no data. -truncate :: (str: *string, length: int) -> length: int { - if str.data == null then return -1; +// Returns the string (using same str.data) truncated to the provided length. +truncate :: (str: string, length: int) -> string { - if str.count < length then length = str.count; + if str.data == null then return ""; + + if str.count < length then return .{str.count, str.data}; - data := str.data; - count := str.count; + data := str.data; + count := str.count; // Find index of first continuation byte. idx := length; @@ -50,12 +50,10 @@ truncate :: (str: *string, length: int) -> length: int { && !(continuation_bytes == 2 && (data[idx - 1] & 0xF0) == 0xE0) && !(continuation_bytes == 3 && (data[idx - 1] & 0xF8) == 0xF0) ) { - length -= (continuation_bytes + 1); // Remove start byte, ence '+ 1'. + length -= (continuation_bytes + 1); // Remove start byte, hence '+ 1'. } - - memset(data + length, 0, count - length); - str.count = length; - return length; + + return .{length, str.data}; } // Returns true when the string is empty or consists of space characters. @@ -78,13 +76,23 @@ is_empty :: (str: string) -> bool { } // Counts the number of characters. -count_characters :: (str: string) -> int { +count_characters :: (str: string, $is_null_terminated := false) -> int { characters := 0; idx := 0; - while idx < str.count { - idx += count_character_bytes(str[idx]); - characters += 1; + + #if is_null_terminated { + while idx < str.count && str[idx] != 0 { + idx += count_character_bytes(str[idx]); + characters += 1; + } + } + else { + while idx < str.count { + idx += count_character_bytes(str[idx]); + characters += 1; + } } + return characters; } diff --git a/ttt.jai b/ttt.jai index d8233f9..7d1559b 100644 --- a/ttt.jai +++ b/ttt.jai @@ -719,8 +719,8 @@ import_from_csv :: (db: *Database, path: string) -> bool { csv_values := split(line, ",",, temporary_allocator); // Truncate and import task name. - truncate(*csv_values[0], task.name.count); - memcpy(task.name.data, csv_values[0].data, csv_values[0].count); + task_name := truncate(csv_values[0], task.name.count); + memcpy(task.name.data, task_name.data, task_name.count); advance(*csv_values); for csv_values @@ -901,11 +901,11 @@ draw_user_interface :: (db: *Database, layout: *Layout) { start := current_time_monotonic(); // DEBUG - - - - // TODO During dirty_flag revamp...we may also start using the String_Builder. - + auto_release_temp(); + + empty_line := talloc_string(size_x); + memset(empty_line.data, #char " ", size_x); + adjust_first_day_of_week := int.[ (0 + FIRST_DAY_OF_WEEK) % NUM_WEEK_DAYS, (1 + FIRST_DAY_OF_WEEK) % NUM_WEEK_DAYS, @@ -1004,6 +1004,7 @@ draw_user_interface :: (db: *Database, layout: *Layout) { idx_start := (db.selected_idx / layout_tasks_rows) * layout_tasks_rows; // Display up to rows allowed by the layout, or less if reached end of database. idx_stop := idx_start + (ifx layout_tasks_rows > db.tasks.count - idx_start then db.tasks.count - idx_start else layout_tasks_rows); + for task_idx: idx_start..idx_stop-1 { task := *db.tasks[task_idx]; y += 1; @@ -1026,19 +1027,18 @@ draw_user_interface :: (db: *Database, layout: *Layout) { // Task title. x += 1; column_width = layout.columns[L_TITLE_IDX].width; - // mvprintw(xx y, xx x, "%-*.*s", column_width, column_width, temp_c_string(xx task.name)); //task.name); TODO Fix required for LLVM/Cncurses. TODO DAM - // TODO FIXME OH MY GOD SUCH BAD CODEZ + // Print title. + // When the column is wider than the name, we end up with the trailing zeros. + // Thankfully, the trailing zeros are not printed so, it's all good. + task_name := cast(string)task.name; + task_name = truncate(task_name, column_width); TUI.set_cursor_position(x, y); - white_spaces := column_width; - // FIXME Improve by using buffer instead of printing one char at the time. - while white_spaces > 0 { - print_character(#char " "); - white_spaces -= 1; - } - TUI.set_cursor_position(x, y); - task_name: string = cast(string)task.name; - task_name.count = ifx task_name.count > column_width then column_width else task_name.count; - print("%", task_name); + write_string(task_name); + // Paint the remaining column space. + task_name_char_count := count_characters(task_name, is_null_terminated = true); + paint_remaining := string.{ column_width - task_name_char_count, empty_line.data }; + write_string(paint_remaining); + x += column_width; // Task times. -- cgit v1.2.3 From 63c21762a8d8323facaf729a1bd17e9449eea72e Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 7 May 2024 16:54:23 +0100 Subject: WIP : Improve draw_user_interface by using buffer on the print/write calls. --- modules/TUI/module.jai | 122 +++++++++++++++++++++++++++++++++---------------- ttt.jai | 61 ++++++++++++++----------- 2 files changed, 116 insertions(+), 67 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index a5db3bf..f2a3a23 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -32,14 +32,17 @@ #import "UTF8"; #load "key_map.jai"; +#add_context tui_style : Style; // This contains the last style applied by the module. +#add_context tui_buffer : *String_Builder; // If set, this buffer will be used as output target of module procedures. + +KEY_SIZE :: #run type_info(Key).runtime_size; +#assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key. + active := false; input_override : Key; input_string : string; input_buffer : [1024] u8; - -KEY_SIZE :: #run type_info(Key).runtime_size; - -#assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key. +temp_buffer := String_Builder.{ allocator = temporary_allocator }; #scope_module @@ -51,7 +54,7 @@ assert_is_active :: inline () { log_tui_error :: (format_string: string, args: .. Any) { write_strings(Commands.SaveCursorPosition, Commands.MainScreenBuffer); - log_error(format_string, args); + log_error(format_string, ..args); write_strings(Commands.AlternateScreenBuffer, Commands.RestoreCursorPosition); } @@ -151,8 +154,6 @@ Commands :: struct #type_info_none { CursorNormalMode :: "\e[?1l"; } -#add_context terminal_style: Style; - Style :: struct { #if COLOR_MODE_BITS == 4 || COLOR_MODE_BITS == 8 { background: Palette; @@ -175,25 +176,26 @@ Style :: struct { } set_style :: (style: Style) { - auto_release_temp(); - builder := String_Builder.{ allocator = temporary_allocator }; + auto_release_temp(); + + builder := ifx context.tui_buffer != null then context.tui_buffer else *temp_buffer; #if COLOR_MODE_BITS == { case 4; - print_to_builder(*builder, + print_to_builder(builder, #run sprint("%0%0", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), cast(u8)style.foreground + 30, cast(u8)style.background + 40 ); case 8; - print_to_builder(*builder, + print_to_builder(builder, #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), cast(u8)style.foreground, cast(u8)style.background ); case 24; - print_to_builder(*builder, + print_to_builder(builder, #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), style.foreground.r, style.foreground.g, style.foreground.b, style.background.r, style.background.g, style.background.b @@ -201,25 +203,27 @@ set_style :: (style: Style) { } if style.use_default_foreground_color { - append(*builder, #run sprint(Commands.SetGraphicsRendition, "39")); + append(builder, #run sprint(Commands.SetGraphicsRendition, "39")); } if style.use_default_background_color { - append(*builder, #run sprint(Commands.SetGraphicsRendition, "49")); + append(builder, #run sprint(Commands.SetGraphicsRendition, "49")); } - write_string(builder_to_string(*builder,, allocator = temporary_allocator)); + if context.tui_buffer == null { + write_builder(builder); + } - context.terminal_style = style; + context.tui_style = style; } clear_style :: () { write_string(#run sprint(Commands.SetGraphicsRendition, "0")); - context.terminal_style = .{ }; + context.tui_style = .{ }; } using_style :: (style: Style) #expand { - __style := context.terminal_style; + __style := context.tui_style; set_style(style); `defer set_style(__style); } @@ -493,7 +497,11 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { assert_is_active(); assert(count_limit >= 0, "Invalid arguments passed to read_input_line(): 'count_limit' must be greater-than or equal to 0."); - builder := String_Builder.{ allocator = temporary_allocator }; + // builder := String_Builder.{ allocator = temporary_allocator }; + // builder := String_Builder.{}; + // init_string_builder(*builder, 10000); + // reset(*builder); + builder := temp_buffer; str := alloc_string(count_limit); str.count = 0; @@ -509,7 +517,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { chars_count := count_characters(str); - // Draw preview. + // Preview input line. if is_visible { print_to_builder(*builder, Commands.SetCursorPosition, y, x); append(*builder, str); @@ -521,6 +529,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { for chars_count..count_limit-1 append(*builder, " "); } print_to_builder(*builder, Commands.SetCursorPosition, y, x+idx); + // write_builder(*builder); // TODO Not sure why this is not working... write_string(builder_to_string(*builder,, allocator = temporary_allocator)); // Process input key. @@ -594,38 +603,40 @@ draw_box :: (x: int, y: int, width: int, height: int) { assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): 'x' and 'y' must be greater-than 0; 'width' and 'height' must be greater-than 1."); auto_release_temp(); - - builder := String_Builder.{ allocator = temporary_allocator }; - - append(*builder, Commands.DrawingMode); + + builder := ifx context.tui_buffer != null then context.tui_buffer else *temp_buffer; + + append(builder, Commands.DrawingMode); // Draw top line - print_to_builder(*builder, Commands.SetCursorPosition, y, x); - append(*builder, Drawings.CornerTL); + print_to_builder(builder, Commands.SetCursorPosition, y, x); + append(builder, Drawings.CornerTL); for 1..width-2 { - append(*builder, Drawings.LineH); + append(builder, Drawings.LineH); } - append(*builder, Drawings.CornerTR); + append(builder, Drawings.CornerTR); // Draw left and right sides. for idx: y+1..y+height-2 { - print_to_builder(*builder, Commands.SetCursorPosition, idx, x); - append(*builder, Drawings.LineV); - print_to_builder(*builder, Commands.SetCursorPosition, idx, x+width-1); - append(*builder, Drawings.LineV); + print_to_builder(builder, Commands.SetCursorPosition, idx, x); + append(builder, Drawings.LineV); + print_to_builder(builder, Commands.SetCursorPosition, idx, x+width-1); + append(builder, Drawings.LineV); } // Draw bottom line. - print_to_builder(*builder, Commands.SetCursorPosition, y+height-1, x); - append(*builder, Drawings.CornerBL); + print_to_builder(builder, Commands.SetCursorPosition, y+height-1, x); + append(builder, Drawings.CornerBL); for 1..width-2 { - append(*builder, Drawings.LineH); + append(builder, Drawings.LineH); } - append(*builder, Drawings.CornerBR); + append(builder, Drawings.CornerBR); - append(*builder, Commands.TextMode); - - write_string(builder_to_string(*builder,, allocator = temporary_allocator)); + append(builder, Commands.TextMode); + + if context.tui_buffer == null { + write_builder(builder); + } } clear_terminal :: inline () { @@ -682,7 +693,12 @@ get_terminal_size :: () -> width: int, height: int { set_cursor_position :: inline (x: int, y: int) { assert_is_active(); - print(Commands.SetCursorPosition, y, x); + if context.tui_buffer == null { + print(Commands.SetCursorPosition, y, x); + } + else { + print_to_builder(context.tui_buffer, Commands.SetCursorPosition, y, x); + } } get_cursor_position :: () -> x: int, y: int { @@ -723,3 +739,29 @@ set_terminal_title :: inline (title: string) { assert_is_active(); print(Commands.SetWindowTitle, title); } + +using_buffer :: (buffer: *String_Builder) #expand { + __buffer := context.tui_buffer; + context.tui_buffer = buffer; + `defer context.tui_buffer = __buffer; +} + +// TODO Maybe we should have a different name for this...? +tui_print :: inline (format_string: string, args: .. Any) { + if context.tui_buffer == null { + print(format_string, ..args, to_standard_error = false); + } + else { + print_to_builder(context.tui_buffer, format_string, ..args); + } +} + +// TODO Maybe we should have a different name for this...? +tui_write :: inline (format_string: string) { + if context.tui_buffer == null { + write_string(format_string); + } + else { + append(context.tui_buffer, format_string); + } +} diff --git a/ttt.jai b/ttt.jai index 7d1559b..ff3c757 100644 --- a/ttt.jai +++ b/ttt.jai @@ -216,29 +216,29 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { print_padding :: (size: int, char: u8 = #char " ") { assert(size >= 0, "Cannot print negative padding values. The procedure accepts signed values just for convenience."); - while size > 0 { - print_character(char); - size -= 1; - } + auto_release_temp(); + padding := talloc_string(size); + memset(padding.data, char, size); + TUI.tui_write(padding); } TUI.set_cursor_position(x, y); if time < 0 { print_padding(left_padding); - write_string(" - "); + TUI.tui_write(" - "); print_padding(right_padding); return 0; } else if time == 0 { print_padding(left_padding); - write_string(" 0 "); + TUI.tui_write(" 0 "); print_padding(right_padding); return 0; } else if time < SECONDS_IN_MINUTE { print_padding(left_padding); - print("%s ", FormatInt.{value = time, minimum_digits=3, padding=#char " "}); + TUI.tui_print("%s ", FormatInt.{value = time, minimum_digits=3, padding=#char " "}); print_padding(right_padding); return 0; } @@ -246,7 +246,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { hours := time / SECONDS_IN_HOUR; minutes := (time - (hours * SECONDS_IN_HOUR) ) / SECONDS_IN_MINUTE; print_padding(left_padding); - print("%:%", FormatInt.{value = hours, minimum_digits=2}, FormatInt.{value = minutes, minimum_digits=2}); + TUI.tui_print("%:%", FormatInt.{value = hours, minimum_digits=2}, FormatInt.{value = minutes, minimum_digits=2}); print_padding(right_padding); return 0; } @@ -257,7 +257,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { ifx time >= #run mul_f64_s64(9.995, SECONDS_IN_DAY) then 1 else 2; print_padding(left_padding); - print("%d", FormatFloat.{value = value, trailing_width=decimals, width=4}); + TUI.tui_print("%d", FormatFloat.{value = value, trailing_width=decimals, width=4}); print_padding(right_padding); return 0; } @@ -268,13 +268,13 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { ifx time >= #run mul_f64_s64(9.995, SECONDS_IN_YEAR) then 1 else 2; print_padding(left_padding); - print("%y", FormatFloat.{value = value, trailing_width=decimals, width=4}); + TUI.tui_print("%y", FormatFloat.{value = value, trailing_width=decimals, width=4}); print_padding(right_padding); return 0; } else { print_padding(left_padding); - write_string(" ∞ "); + TUI.tui_write(" ∞ "); print_padding(right_padding); return 0; } @@ -897,6 +897,7 @@ update_layout :: () { dbg_average := 0; // DEBUG dbg_count := 0; // DEBUG +buffer: String_Builder; // TODO draw_user_interface :: (db: *Database, layout: *Layout) { start := current_time_monotonic(); // DEBUG @@ -905,6 +906,11 @@ draw_user_interface :: (db: *Database, layout: *Layout) { empty_line := talloc_string(size_x); memset(empty_line.data, #char " ", size_x); + + init_string_builder(*buffer, 100000); + // builder := String_Builder.{ allocator = temporary_allocator }; + builder := buffer; + TUI.using_buffer(*builder); adjust_first_day_of_week := int.[ (0 + FIRST_DAY_OF_WEEK) % NUM_WEEK_DAYS, @@ -931,25 +937,25 @@ draw_user_interface :: (db: *Database, layout: *Layout) { // Draw outer border. TUI.draw_box(1, 1, size_x, size_y); - + // Draw table grids. // TODO Maybe this could be simplified? y = 1; x = 1; - write_string(TUI.Commands.DrawingMode); + TUI.tui_write(TUI.Commands.DrawingMode); // append(*builder, TUI.Commands.DrawingMode); TODO for 0..layout.columns.count-2 { column := layout.columns[it]; x += 1 + column.width; TUI.set_cursor_position(x, y); - write_string(TUI.Drawings.TeeT); + TUI.tui_write(TUI.Drawings.TeeT); // TODO append(*builder, TUI.Drawings.TeeT); for row: 2..size_y { TUI.set_cursor_position(x, row); - write_string(TUI.Drawings.LineV); + TUI.tui_write(TUI.Drawings.LineV); // TODO append(*builder, TUI.Drawings.LineV); } TUI.set_cursor_position(x, size_y); - write_string(TUI.Drawings.TeeB); + TUI.tui_write(TUI.Drawings.TeeB); // TODO append(*builder, TUI.Drawings.TeeB); } - write_string(TUI.Commands.TextMode); + TUI.tui_write(TUI.Commands.TextMode); // TODO append(*builder, TUI.Commands.TextMode); /////////////////////////////////////////////////////////////////////////// @@ -961,7 +967,7 @@ draw_user_interface :: (db: *Database, layout: *Layout) { x += 1; col = *layout.columns[L_TITLE_IDX]; TUI.set_cursor_position(x + col.alignment_offset, y); - write_string(ifx db == *archive then layout.archive_title else col.header); + TUI.tui_write(ifx db == *archive then layout.archive_title else col.header); // TODO append(*builder, ifx db == *archive then layout.archive_title else col.header); x += col.width; // Headers : days @@ -981,7 +987,7 @@ draw_user_interface :: (db: *Database, layout: *Layout) { } col = *layout.columns[L_DAYS_IDX + idx]; TUI.set_cursor_position(x + col.alignment_offset, y); - write_string(col.header); + TUI.tui_write(col.header); // TODO append(*builder, col.header); x += col.width; } TUI.set_style(style_default); @@ -990,7 +996,7 @@ draw_user_interface :: (db: *Database, layout: *Layout) { x += 1; col = *layout.columns[L_TOTAL_IDX]; TUI.set_cursor_position(x + col.alignment_offset, y); - write_string(col.header); + TUI.tui_write(col.header); // TODO append(*builder, col.header); /////////////////////////////////////////////////////////////////////////// @@ -1033,11 +1039,11 @@ draw_user_interface :: (db: *Database, layout: *Layout) { task_name := cast(string)task.name; task_name = truncate(task_name, column_width); TUI.set_cursor_position(x, y); - write_string(task_name); + TUI.tui_write(task_name); // TODO append(*builder, task_name); // Paint the remaining column space. task_name_char_count := count_characters(task_name, is_null_terminated = true); paint_remaining := string.{ column_width - task_name_char_count, empty_line.data }; - write_string(paint_remaining); + TUI.tui_write(paint_remaining); // TODO append(*builder, paint_remaining); x += column_width; @@ -1065,10 +1071,10 @@ draw_user_interface :: (db: *Database, layout: *Layout) { size := 1 + count_digits(db.selected_idx + 1) + 1 + count_digits(db.tasks.count) + 1; // " XXX/YYY " TUI.set_cursor_position(2, size_y); if (size <= layout.columns[L_TITLE_IDX].width) { - print(" %/% ", db.selected_idx + 1, db.tasks.count); + TUI.tui_print(" %/% ", db.selected_idx + 1, db.tasks.count); } else { - print("%", db.selected_idx + 1); + TUI.tui_print("%", db.selected_idx + 1); } @@ -1107,7 +1113,9 @@ draw_user_interface :: (db: *Database, layout: *Layout) { dbg_average = (dbg_sample + dbg_count * dbg_average) / (dbg_count + 1); // DEBUG dbg_count += 1; // DEBUG TUI.set_cursor_position(3, 1); - print("Average % us ---------", dbg_average/1000); // DEBUG + TUI.tui_print("Average % us (% / % : % bytes) ---------", dbg_average/1000, context.temporary_storage.total_bytes_occupied, context.temporary_storage.high_water_mark, context.temporary_storage.size); // DEBUG + // write_string(builder_to_string(*builder,, allocator = temporary_allocator)); + write_builder(*builder); } free_memory :: () { @@ -1117,7 +1125,6 @@ free_memory :: () { free(app_directory); free(db_file_path); free(ar_file_path); - // reset_temporary_storage(); // TODO Not needed... I guess. } read_input_string :: (x: int, y: int, input_limit: int, padding: int = 0) -> value: string, success: bool { @@ -1133,7 +1140,7 @@ read_input_string :: (x: int, y: int, input_limit: int, padding: int = 0) -> val write_string(TUI.Commands.TextMode); TUI.set_cursor_position(x, y); - style_input := context.terminal_style; + style_input := context.tui_style; style_input.underline = true; TUI.using_style(style_input); -- cgit v1.2.3 From 89157d00fc4110055d5816cd1897e4def120bcaf Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 7 May 2024 18:14:45 +0100 Subject: Fixed two very nasty bugs. --- modules/TUI/module.jai | 18 +++++++----------- ttt.jai | 2 ++ 2 files changed, 9 insertions(+), 11 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index f2a3a23..612f93e 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -177,8 +177,9 @@ Style :: struct { set_style :: (style: Style) { - auto_release_temp(); - + auto_release_temp(); // TODO Does this breaks if the context.tui_buffer is a temporary buffer... and we're writting to it? + + // TODO If context.tui_buffer is temporary... this will fail.... right? builder := ifx context.tui_buffer != null then context.tui_buffer else *temp_buffer; #if COLOR_MODE_BITS == { @@ -497,10 +498,6 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { assert_is_active(); assert(count_limit >= 0, "Invalid arguments passed to read_input_line(): 'count_limit' must be greater-than or equal to 0."); - // builder := String_Builder.{ allocator = temporary_allocator }; - // builder := String_Builder.{}; - // init_string_builder(*builder, 10000); - // reset(*builder); builder := temp_buffer; str := alloc_string(count_limit); @@ -529,8 +526,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { for chars_count..count_limit-1 append(*builder, " "); } print_to_builder(*builder, Commands.SetCursorPosition, y, x+idx); - // write_builder(*builder); // TODO Not sure why this is not working... - write_string(builder_to_string(*builder,, allocator = temporary_allocator)); + write_builder(*builder); // Process input key. key = get_key(); @@ -560,7 +556,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { if idx == 0 continue; idx -= 1; delete_character(*str, idx); - + case; if is_escape_code(key) continue; @@ -571,7 +567,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { if buff_idx > count_limit - key_str.count then continue; // Move text to allow inserting new character. - for < count_limit..buff_idx+key_str.count { + for < count_limit-1..buff_idx + key_str.count-1 { str.data[it] = str.data[it-key_str.count]; } @@ -602,7 +598,7 @@ draw_box :: (x: int, y: int, width: int, height: int) { assert_is_active(); assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): 'x' and 'y' must be greater-than 0; 'width' and 'height' must be greater-than 1."); - auto_release_temp(); + auto_release_temp(); // TODO Does this breaks if the context.tui_buffer is a temporary buffer... and we're writting to it? builder := ifx context.tui_buffer != null then context.tui_buffer else *temp_buffer; diff --git a/ttt.jai b/ttt.jai index ff3c757..612e6b6 100644 --- a/ttt.jai +++ b/ttt.jai @@ -218,6 +218,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { assert(size >= 0, "Cannot print negative padding values. The procedure accepts signed values just for convenience."); auto_release_temp(); padding := talloc_string(size); + padding.count = size; memset(padding.data, char, size); TUI.tui_write(padding); } @@ -1131,6 +1132,7 @@ read_input_string :: (x: int, y: int, input_limit: int, padding: int = 0) -> val // TODO Draw padding (at end of inputbox)... padding was renamed to input_width... is this the best name? // TODO Maybe add another optional arg with the placeholder text (to be preset on the input)? + // TODO COULD CACHE... but there's not need... TUI.set_cursor_position(x + input_limit, y); write_string(TUI.Commands.DrawingMode); -- cgit v1.2.3 From 2dd22e4847b7bc239f129c5ed2e7d0bdff38006e Mon Sep 17 00:00:00 2001 From: dam Date: Wed, 8 May 2024 16:20:07 +0100 Subject: Improved code readability. --- modules/TUI/module.jai | 5 ++++- modules/UTF8.jai | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 612f93e..112c666 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -560,9 +560,12 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { case; if is_escape_code(key) continue; - buff_idx := get_byte_idx(str, idx); key_str := to_string(key,, allocator = temporary_allocator); + // Get the buffer index to insert the next character. + buff_idx, success := get_byte_index(str, idx); + if success == false then buff_idx = str.count; + // Make sure we have space to append the new character at the end (in case we're trying to do it). if buff_idx > count_limit - key_str.count then continue; diff --git a/modules/UTF8.jai b/modules/UTF8.jai index 205e57d..72d3d75 100644 --- a/modules/UTF8.jai +++ b/modules/UTF8.jai @@ -97,8 +97,11 @@ count_characters :: (str: string, $is_null_terminated := false) -> int { } // Deletes character by it's index, and moves tail data to take its place. -delete_character :: (str: *string, character_idx: int) { - buffer_idx := get_byte_idx(str.*, character_idx); +delete_character :: (str: *string, character_idx: int) -> success := true { + buffer_idx := get_byte_index(str.*, character_idx); + + if buffer_idx < 0 return false; + bytes_to_delete := count_character_bytes(str.data[buffer_idx]); for buffer_idx..str.count-1-bytes_to_delete { @@ -109,19 +112,17 @@ delete_character :: (str: *string, character_idx: int) { } str.count -= bytes_to_delete; + return; } // Searches for the given character index and returns its byte index on the string. -get_byte_idx :: (str: string, character_idx: int) -> buffer_idx: int, success: bool { - if character_idx < 0 then return -1, false; - if character_idx > str.count then return -2, false; - if character_idx == 0 then return 0, true; - +get_byte_index :: (str: string, character_index: int) -> buffer_index: int, success := true { buff_idx := 0; char_idx := 0; - while buff_idx < str.count && char_idx != character_idx { + while buff_idx < str.count { + if char_idx == character_index return buff_idx; buff_idx += count_character_bytes(str[buff_idx]); char_idx += 1; } - return buff_idx, char_idx == character_idx; + return -1, false; } -- cgit v1.2.3 From f5fc7f6e700d272c2d60e4a1da219c993f01cad3 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 9 May 2024 11:43:31 +0100 Subject: Fixed TUI module to allow returning results in temporary memory. --- modules/TUI/module.jai | 58 +++++++++++++++++++++++++++++--------------------- ttt.jai | 20 +++++++---------- unused.jai | 1 + 3 files changed, 43 insertions(+), 36 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 112c666..f8e745e 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -33,7 +33,7 @@ #load "key_map.jai"; #add_context tui_style : Style; // This contains the last style applied by the module. -#add_context tui_buffer : *String_Builder; // If set, this buffer will be used as output target of module procedures. +#add_context tui_builder : *String_Builder; // If set, this will serve as an output buffer for this module procedures. KEY_SIZE :: #run type_info(Key).runtime_size; #assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key. @@ -42,7 +42,7 @@ active := false; input_override : Key; input_string : string; input_buffer : [1024] u8; -temp_buffer := String_Builder.{ allocator = temporary_allocator }; +temp_builder := String_Builder.{ allocator = temporary_allocator }; #scope_module @@ -176,11 +176,13 @@ Style :: struct { } set_style :: (style: Style) { - - auto_release_temp(); // TODO Does this breaks if the context.tui_buffer is a temporary buffer... and we're writting to it? - - // TODO If context.tui_buffer is temporary... this will fail.... right? - builder := ifx context.tui_buffer != null then context.tui_buffer else *temp_buffer; + // If no tui_builder is provided, use a temporary one and discard it afterwards. + builder := context.tui_builder; + temp_mark: Temporary_Storage_State = ---; + if context.tui_builder == null { + builder = *temp_builder; + temp_mark = get_temporary_storage_mark(); + } #if COLOR_MODE_BITS == { case 4; @@ -211,8 +213,9 @@ set_style :: (style: Style) { append(builder, #run sprint(Commands.SetGraphicsRendition, "49")); } - if context.tui_buffer == null { + if context.tui_builder == null { write_builder(builder); + set_temporary_storage_mark(temp_mark); } context.tui_style = style; @@ -497,9 +500,9 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { assert_is_active(); assert(count_limit >= 0, "Invalid arguments passed to read_input_line(): 'count_limit' must be greater-than or equal to 0."); - - builder := temp_buffer; - + + // The returned memory must be allocated before we start to use temporary memory. + // Otherwise, the returned memory would be invalid on calls of type (,, temporary_allocator). str := alloc_string(count_limit); str.count = 0; idx := 0; @@ -510,6 +513,8 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { write_strings(Commands.ShowCursor, Commands.StartBlinking, Commands.BlinkingBarShape); while true { + builder := temp_builder; + auto_release_temp(); chars_count := count_characters(str); @@ -601,9 +606,13 @@ draw_box :: (x: int, y: int, width: int, height: int) { assert_is_active(); assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): 'x' and 'y' must be greater-than 0; 'width' and 'height' must be greater-than 1."); - auto_release_temp(); // TODO Does this breaks if the context.tui_buffer is a temporary buffer... and we're writting to it? - - builder := ifx context.tui_buffer != null then context.tui_buffer else *temp_buffer; + // If no tui_builder is provided, use a temporary one and discard it afterwards. + builder := context.tui_builder; + temp_mark: Temporary_Storage_State = ---; + if context.tui_builder == null { + builder = *temp_builder; + temp_mark = get_temporary_storage_mark(); + } append(builder, Commands.DrawingMode); @@ -633,8 +642,9 @@ draw_box :: (x: int, y: int, width: int, height: int) { append(builder, Commands.TextMode); - if context.tui_buffer == null { + if context.tui_builder == null { write_builder(builder); + set_temporary_storage_mark(temp_mark); } } @@ -692,11 +702,11 @@ get_terminal_size :: () -> width: int, height: int { set_cursor_position :: inline (x: int, y: int) { assert_is_active(); - if context.tui_buffer == null { + if context.tui_builder == null { print(Commands.SetCursorPosition, y, x); } else { - print_to_builder(context.tui_buffer, Commands.SetCursorPosition, y, x); + print_to_builder(context.tui_builder, Commands.SetCursorPosition, y, x); } } @@ -740,27 +750,27 @@ set_terminal_title :: inline (title: string) { } using_buffer :: (buffer: *String_Builder) #expand { - __buffer := context.tui_buffer; - context.tui_buffer = buffer; - `defer context.tui_buffer = __buffer; + __buffer := context.tui_builder; + context.tui_builder = buffer; + `defer context.tui_builder = __buffer; } // TODO Maybe we should have a different name for this...? tui_print :: inline (format_string: string, args: .. Any) { - if context.tui_buffer == null { + if context.tui_builder == null { print(format_string, ..args, to_standard_error = false); } else { - print_to_builder(context.tui_buffer, format_string, ..args); + print_to_builder(context.tui_builder, format_string, ..args); } } // TODO Maybe we should have a different name for this...? tui_write :: inline (format_string: string) { - if context.tui_buffer == null { + if context.tui_builder == null { write_string(format_string); } else { - append(context.tui_buffer, format_string); + append(context.tui_builder, format_string); } } diff --git a/ttt.jai b/ttt.jai index 2dbc5eb..efafb00 100644 --- a/ttt.jai +++ b/ttt.jai @@ -214,13 +214,12 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { left_padding := (space - TIME_CHARS) / 2; right_padding := space - TIME_CHARS - left_padding; - print_padding :: (size: int, char: u8 = #char " ") { + print_padding :: (size: int) { assert(size >= 0, "Cannot print negative padding values. The procedure accepts signed values just for convenience."); - auto_release_temp(); - padding := talloc_string(size); - padding.count = size; - memset(padding.data, char, size); - TUI.tui_write(padding); + while size > 0 { + TUI.tui_write(" "); + size -= 1; + } } TUI.set_cursor_position(x, y); @@ -908,9 +907,9 @@ draw_user_interface :: (db: *Database, layout: *Layout) { empty_line := talloc_string(size_x); memset(empty_line.data, #char " ", size_x); - init_string_builder(*buffer, 100000); - // builder := String_Builder.{ allocator = temporary_allocator }; - builder := buffer; + // init_string_builder(*buffer, 100000); + // builder := buffer; + builder := String_Builder.{ allocator = temporary_allocator }; TUI.using_buffer(*builder); adjust_first_day_of_week := int.[ @@ -1194,9 +1193,6 @@ prompt_user_key :: (y: int, message: string) -> TUI.Key { main :: () { -// TODO WIP WIP WIP WIP WIP WIP WIP WIP - make some tests to see how auto_release_temporary works... check if something allocated right before are kept or discarddled... and how temp allocated StringBuilder behaves... - - #if DEBUG_MEMORY { defer report_memory_leaks(); // TODO Remove after final debug sessions. } diff --git a/unused.jai b/unused.jai index f9bb2f9..1f71b37 100644 --- a/unused.jai +++ b/unused.jai @@ -1,6 +1,7 @@ auto_release_temp(); // automatically release temporary memory. print(">%<", S64_MIN + delta, to_standard_error = true); print_to_builder(*builder, "Average % us (% / % bytes) ---------", dbg_average/1000, context.temporary_storage.total_bytes_occupied, context.temporary_storage.high_water_mark); // DEBUG +print("temp [% .. % .. %] \n", context.temporary_storage.data, get_temporary_storage_mark(), context.temporary_storage.data + context.temporary_storage.size-1); // MEASURE PERFORMANCE -- cgit v1.2.3 From c0dc68c4672f53f2b9c60074bfb1eb10ab87d980 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 9 May 2024 12:39:46 +0100 Subject: WIP : Renaming TUI print/write procedures. --- modules/TUI/module.jai | 6 +++--- ttt.jai | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 17 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index f8e745e..5399503 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -766,11 +766,11 @@ tui_print :: inline (format_string: string, args: .. Any) { } // TODO Maybe we should have a different name for this...? -tui_write :: inline (format_string: string) { +tui_write_string :: inline (s: string) { if context.tui_builder == null { - write_string(format_string); + write_string(s, to_standard_error = false); } else { - append(context.tui_builder, format_string); + append(context.tui_builder, s); } } diff --git a/ttt.jai b/ttt.jai index efafb00..e5ade13 100644 --- a/ttt.jai +++ b/ttt.jai @@ -217,7 +217,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { print_padding :: (size: int) { assert(size >= 0, "Cannot print negative padding values. The procedure accepts signed values just for convenience."); while size > 0 { - TUI.tui_write(" "); + TUI.tui_write_string(" "); size -= 1; } } @@ -226,13 +226,13 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { if time < 0 { print_padding(left_padding); - TUI.tui_write(" - "); + TUI.tui_write_string(" - "); print_padding(right_padding); return 0; } else if time == 0 { print_padding(left_padding); - TUI.tui_write(" 0 "); + TUI.tui_write_string(" 0 "); print_padding(right_padding); return 0; } @@ -274,7 +274,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { } else { print_padding(left_padding); - TUI.tui_write(" ∞ "); + TUI.tui_write_string(" ∞ "); print_padding(right_padding); return 0; } @@ -942,20 +942,20 @@ draw_user_interface :: (db: *Database, layout: *Layout) { // TODO Maybe this could be simplified? y = 1; x = 1; - TUI.tui_write(TUI.Commands.DrawingMode); // append(*builder, TUI.Commands.DrawingMode); TODO + TUI.tui_write_string(TUI.Commands.DrawingMode); // append(*builder, TUI.Commands.DrawingMode); TODO for 0..layout.columns.count-2 { column := layout.columns[it]; x += 1 + column.width; TUI.set_cursor_position(x, y); - TUI.tui_write(TUI.Drawings.TeeT); // TODO append(*builder, TUI.Drawings.TeeT); + TUI.tui_write_string(TUI.Drawings.TeeT); // TODO append(*builder, TUI.Drawings.TeeT); for row: 2..size_y { TUI.set_cursor_position(x, row); - TUI.tui_write(TUI.Drawings.LineV); // TODO append(*builder, TUI.Drawings.LineV); + TUI.tui_write_string(TUI.Drawings.LineV); // TODO append(*builder, TUI.Drawings.LineV); } TUI.set_cursor_position(x, size_y); - TUI.tui_write(TUI.Drawings.TeeB); // TODO append(*builder, TUI.Drawings.TeeB); + TUI.tui_write_string(TUI.Drawings.TeeB); // TODO append(*builder, TUI.Drawings.TeeB); } - TUI.tui_write(TUI.Commands.TextMode); // TODO append(*builder, TUI.Commands.TextMode); + TUI.tui_write_string(TUI.Commands.TextMode); // TODO append(*builder, TUI.Commands.TextMode); /////////////////////////////////////////////////////////////////////////// @@ -967,7 +967,7 @@ draw_user_interface :: (db: *Database, layout: *Layout) { x += 1; col = *layout.columns[L_TITLE_IDX]; TUI.set_cursor_position(x + col.alignment_offset, y); - TUI.tui_write(ifx db == *archive then layout.archive_title else col.header); // TODO append(*builder, ifx db == *archive then layout.archive_title else col.header); + TUI.tui_write_string(ifx db == *archive then layout.archive_title else col.header); // TODO append(*builder, ifx db == *archive then layout.archive_title else col.header); x += col.width; // Headers : days @@ -987,7 +987,7 @@ draw_user_interface :: (db: *Database, layout: *Layout) { } col = *layout.columns[L_DAYS_IDX + idx]; TUI.set_cursor_position(x + col.alignment_offset, y); - TUI.tui_write(col.header); // TODO append(*builder, col.header); + TUI.tui_write_string(col.header); // TODO append(*builder, col.header); x += col.width; } TUI.set_style(style_default); @@ -996,7 +996,7 @@ draw_user_interface :: (db: *Database, layout: *Layout) { x += 1; col = *layout.columns[L_TOTAL_IDX]; TUI.set_cursor_position(x + col.alignment_offset, y); - TUI.tui_write(col.header); // TODO append(*builder, col.header); + TUI.tui_write_string(col.header); // TODO append(*builder, col.header); /////////////////////////////////////////////////////////////////////////// @@ -1039,11 +1039,11 @@ draw_user_interface :: (db: *Database, layout: *Layout) { task_name := cast(string)task.name; task_name = truncate(task_name, column_width); TUI.set_cursor_position(x, y); - TUI.tui_write(task_name); // TODO append(*builder, task_name); + TUI.tui_write_string(task_name); // TODO append(*builder, task_name); // Paint the remaining column space. task_name_char_count := count_characters(task_name, is_null_terminated = true); paint_remaining := string.{ column_width - task_name_char_count, empty_line.data }; - TUI.tui_write(paint_remaining); // TODO append(*builder, paint_remaining); + TUI.tui_write_string(paint_remaining); // TODO append(*builder, paint_remaining); x += column_width; -- cgit v1.2.3 From 18cd73bb1830fd49089eb82667ba6ae14606938c Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 9 May 2024 22:24:16 +0100 Subject: Renamed stuff to make TUI output builder purpose more clear. --- modules/TUI/module.jai | 40 +++++++++++++++++++--------------------- ttt.jai | 29 ++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 30 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 5399503..70675c7 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -33,7 +33,7 @@ #load "key_map.jai"; #add_context tui_style : Style; // This contains the last style applied by the module. -#add_context tui_builder : *String_Builder; // If set, this will serve as an output buffer for this module procedures. +#add_context tui_output_builder : *String_Builder; // If set, this will serve as an output buffer for this module procedures. KEY_SIZE :: #run type_info(Key).runtime_size; #assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key. @@ -176,10 +176,10 @@ Style :: struct { } set_style :: (style: Style) { - // If no tui_builder is provided, use a temporary one and discard it afterwards. - builder := context.tui_builder; + // If no tui_output_builder is provided, use a temporary one and discard it afterwards. + builder := context.tui_output_builder; temp_mark: Temporary_Storage_State = ---; - if context.tui_builder == null { + if context.tui_output_builder == null { builder = *temp_builder; temp_mark = get_temporary_storage_mark(); } @@ -213,7 +213,7 @@ set_style :: (style: Style) { append(builder, #run sprint(Commands.SetGraphicsRendition, "49")); } - if context.tui_builder == null { + if context.tui_output_builder == null { write_builder(builder); set_temporary_storage_mark(temp_mark); } @@ -606,10 +606,10 @@ draw_box :: (x: int, y: int, width: int, height: int) { assert_is_active(); assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): 'x' and 'y' must be greater-than 0; 'width' and 'height' must be greater-than 1."); - // If no tui_builder is provided, use a temporary one and discard it afterwards. - builder := context.tui_builder; + // If no tui_output_builder is provided, use a temporary one and discard it afterwards. + builder := context.tui_output_builder; temp_mark: Temporary_Storage_State = ---; - if context.tui_builder == null { + if context.tui_output_builder == null { builder = *temp_builder; temp_mark = get_temporary_storage_mark(); } @@ -642,7 +642,7 @@ draw_box :: (x: int, y: int, width: int, height: int) { append(builder, Commands.TextMode); - if context.tui_builder == null { + if context.tui_output_builder == null { write_builder(builder); set_temporary_storage_mark(temp_mark); } @@ -702,11 +702,11 @@ get_terminal_size :: () -> width: int, height: int { set_cursor_position :: inline (x: int, y: int) { assert_is_active(); - if context.tui_builder == null { + if context.tui_output_builder == null { print(Commands.SetCursorPosition, y, x); } else { - print_to_builder(context.tui_builder, Commands.SetCursorPosition, y, x); + print_to_builder(context.tui_output_builder, Commands.SetCursorPosition, y, x); } } @@ -749,28 +749,26 @@ set_terminal_title :: inline (title: string) { print(Commands.SetWindowTitle, title); } -using_buffer :: (buffer: *String_Builder) #expand { - __buffer := context.tui_builder; - context.tui_builder = buffer; - `defer context.tui_builder = __buffer; +using_builder_as_output :: (builder: *String_Builder) #expand { + __builder := context.tui_output_builder; + context.tui_output_builder = builder; + `defer context.tui_output_builder = __builder; } -// TODO Maybe we should have a different name for this...? tui_print :: inline (format_string: string, args: .. Any) { - if context.tui_builder == null { + if context.tui_output_builder == null { print(format_string, ..args, to_standard_error = false); } else { - print_to_builder(context.tui_builder, format_string, ..args); + print_to_builder(context.tui_output_builder, format_string, ..args); } } -// TODO Maybe we should have a different name for this...? tui_write_string :: inline (s: string) { - if context.tui_builder == null { + if context.tui_output_builder == null { write_string(s, to_standard_error = false); } else { - append(context.tui_builder, s); + append(context.tui_output_builder, s); } } diff --git a/ttt.jai b/ttt.jai index e5ade13..01803b2 100644 --- a/ttt.jai +++ b/ttt.jai @@ -204,6 +204,11 @@ count_digits :: (number: s64, base: s64 = 10) -> s64 { // Prints, on row y and column x, the time using 5 characters centered on space. // Returns the result of a call to mvprintw. print_time :: (y: int, x: int, time: s64, space: int) -> int { + + // Use TUI stubs so that callers may choose to use the tui_builder buffer. + print :: TUI.tui_print; + write_string :: TUI.tui_write_string; + TIME_CHARS :: 5; assert(space >= TIME_CHARS); @@ -217,7 +222,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { print_padding :: (size: int) { assert(size >= 0, "Cannot print negative padding values. The procedure accepts signed values just for convenience."); while size > 0 { - TUI.tui_write_string(" "); + write_string(" "); size -= 1; } } @@ -226,19 +231,19 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { if time < 0 { print_padding(left_padding); - TUI.tui_write_string(" - "); + write_string(" - "); print_padding(right_padding); return 0; } else if time == 0 { print_padding(left_padding); - TUI.tui_write_string(" 0 "); + write_string(" 0 "); print_padding(right_padding); return 0; } else if time < SECONDS_IN_MINUTE { print_padding(left_padding); - TUI.tui_print("%s ", FormatInt.{value = time, minimum_digits=3, padding=#char " "}); + print("%s ", FormatInt.{value = time, minimum_digits=3, padding=#char " "}); print_padding(right_padding); return 0; } @@ -246,7 +251,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { hours := time / SECONDS_IN_HOUR; minutes := (time - (hours * SECONDS_IN_HOUR) ) / SECONDS_IN_MINUTE; print_padding(left_padding); - TUI.tui_print("%:%", FormatInt.{value = hours, minimum_digits=2}, FormatInt.{value = minutes, minimum_digits=2}); + print("%:%", FormatInt.{value = hours, minimum_digits=2}, FormatInt.{value = minutes, minimum_digits=2}); print_padding(right_padding); return 0; } @@ -257,7 +262,7 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { ifx time >= #run mul_f64_s64(9.995, SECONDS_IN_DAY) then 1 else 2; print_padding(left_padding); - TUI.tui_print("%d", FormatFloat.{value = value, trailing_width=decimals, width=4}); + print("%d", FormatFloat.{value = value, trailing_width=decimals, width=4}); print_padding(right_padding); return 0; } @@ -268,13 +273,13 @@ print_time :: (y: int, x: int, time: s64, space: int) -> int { ifx time >= #run mul_f64_s64(9.995, SECONDS_IN_YEAR) then 1 else 2; print_padding(left_padding); - TUI.tui_print("%y", FormatFloat.{value = value, trailing_width=decimals, width=4}); + print("%y", FormatFloat.{value = value, trailing_width=decimals, width=4}); print_padding(right_padding); return 0; } else { print_padding(left_padding); - TUI.tui_write_string(" ∞ "); + write_string(" ∞ "); print_padding(right_padding); return 0; } @@ -907,10 +912,16 @@ draw_user_interface :: (db: *Database, layout: *Layout) { empty_line := talloc_string(size_x); memset(empty_line.data, #char " ", size_x); + /* TODO + It's not safe to use temporary memory here because the console resolution may increase and use more than what we have in temporary memory. + And temporary memory is configured at compile time. + We should dynamically allocate memory with some headroom and, at beggining of function... adjust it if necessary. + // init_string_builder(*buffer, 100000); // builder := buffer; + */ builder := String_Builder.{ allocator = temporary_allocator }; - TUI.using_buffer(*builder); + TUI.using_builder_as_output(*builder); adjust_first_day_of_week := int.[ (0 + FIRST_DAY_OF_WEEK) % NUM_WEEK_DAYS, -- cgit v1.2.3 From a5e4f8725d74c2dbc3df35249b6dbdd061e44997 Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 14 May 2024 10:14:26 +0100 Subject: Added note/documentation about allowWindowOps on xterm. --- modules/TUI/module.jai | 1 + 1 file changed, 1 insertion(+) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 70675c7..fb742da 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -689,6 +689,7 @@ get_terminal_size :: () -> width: int, height: int { } // Some systems don't allow to query the terminal size directly... or the answer takes too much time. // In such cases, measure it indirectly by the maximum possible cursor position. + // (e.g.: allowWindowOps/disallowedWindowOps properties in xterm) else { x, y := get_cursor_position(); defer set_cursor_position(x, y); -- cgit v1.2.3 From af1317ed93f7d5fc16baceaadb0da17aa17591be Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 18 May 2024 01:30:01 +0100 Subject: Updated modules. --- modules/Saturation/module.jai | 26 +-- modules/Saturation/tests.jai | 397 +++++++++++++++++++++++++++++++++++++++--- modules/TUI/module.jai | 17 +- modules/TUI/snake.jai | 165 ++++++++++++++++++ modules/UTF8/module.jai | 29 ++- modules/UTF8/tests.jai | 159 ++++++++++++++++- snake.jai | 165 ------------------ ttt.jai | 2 +- 8 files changed, 748 insertions(+), 212 deletions(-) create mode 100644 modules/TUI/snake.jai delete mode 100644 snake.jai (limited to 'modules/TUI/module.jai') diff --git a/modules/Saturation/module.jai b/modules/Saturation/module.jai index 74643e0..50c9b3c 100644 --- a/modules/Saturation/module.jai +++ b/modules/Saturation/module.jai @@ -1,5 +1,7 @@ // Integer saturating arighmetic (with assembly branch-free procedures for x64 - expecting signed values in two's complement). +#module_parameters(PREFER_BRANCH_FREE_CODE := false); + #import "Basic"; #import "Math"; #import "String"; @@ -37,11 +39,11 @@ INTEGER_ARITHMETIC_TYPES_CHECK :: #string DONE return true; DONE -add :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +add :: (x: $Tx, y: $Ty, $prefer_branch_free_code := PREFER_BRANCH_FREE_CODE) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } { + + #if !(prefer_branch_free_code && CPU == .X64) { - #if USE_GENERIC || CPU != .X64 { - #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { #if Tr == s8 { MAX :: S8_MAX; MIN :: S8_MIN; } @@ -66,7 +68,7 @@ add :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: b return x + y, false; } else { - + result: Tr = ---; saturated: bool = ---; @@ -118,10 +120,10 @@ add :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: b } } -sub :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +sub :: (x: $Tx, y: $Ty, $prefer_branch_free_code := PREFER_BRANCH_FREE_CODE) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } { - #if USE_GENERIC || CPU != .X64 { + #if !(prefer_branch_free_code && CPU == .X64) { #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { @@ -195,10 +197,10 @@ sub :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: b } -mul :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +mul :: (x: $Tx, y: $Ty, $prefer_branch_free_code := PREFER_BRANCH_FREE_CODE) -> result: $Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } { - #if USE_GENERIC || CPU != .X64 { + #if !(prefer_branch_free_code && CPU == .X64) { #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { @@ -286,10 +288,10 @@ mul :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, saturated: b } } -div :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, remainder: Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } +div :: (x: $Tx, y: $Ty, $prefer_branch_free_code := PREFER_BRANCH_FREE_CODE) -> result: $Tr, remainder: Tr, saturated: bool #modify { #insert INTEGER_ARITHMETIC_TYPES_CHECK; } { - #if USE_GENERIC || CPU != .X64 { + #if !(prefer_branch_free_code && CPU == .X64) { #if Tr == s8 || Tr == s16 || Tr == s32 || Tr == s64 { @@ -380,11 +382,11 @@ div :: (x: $Tx, y: $Ty, $USE_GENERIC: bool = false) -> result: $Tr, remainder: T #asm { result === a; // Pin result to register A. remainder === d; // Pin remainder to register D. - + xor result, result; // Clear result. xor remainder, remainder; // Clear remainder (required when used as dividend's high bits). xor saturated, saturated; // Clear saturated (unsigned division never saturates). - mov result, x; // Copy x value to result. + mov.SIZE result, x; // Copy x value to result. DIVIDE_PLACEHOLDER } diff --git a/modules/Saturation/tests.jai b/modules/Saturation/tests.jai index 372d66d..2a82300 100644 --- a/modules/Saturation/tests.jai +++ b/modules/Saturation/tests.jai @@ -1,5 +1,7 @@ // Tests for integer saturating arighmetic procedures. +AVOID_INFINITE_FOR_LOOP :: true; + #import "Basic"; #import "Compiler"; #import "Math"; @@ -12,36 +14,41 @@ main :: () { "#=======================#\n", "# Basic tests #\n" ); + + + test_op :: ($operation: string, x: $Tx, y: $Ty, result: $Tr, type: Type, saturated: bool, remainder: Tr = 0) -> errors_found: int #expand { + + #insert #run () -> string { + // Build test call. + builder: String_Builder; + call := ifx operation == "div" + then "t_result, t_remainder, t_saturated := %(cast(Tx)x, cast(Ty)y);" + else "t_result, t_saturated := %(cast(Tx)x, cast(Ty)y);"; + + print(*builder, call, operation); - test_op :: (operation: string, x: $Tx, y: $Ty, result: $Tr, type: Type, saturated: bool, remainder: Tr = 0) -> errors_found: int #expand { - - print_test_call :: (operation: string) -> string { - str: string = ---; - if operation != "div" { - TEST_CALL :: #string DONE - t_result, t_saturated := OP(cast(Tx)x, cast(Ty)y); - if result != t_result print("%_%(%, %) = %0%0\n", operation, type, x, y, result, ifx saturated then " : saturated"); - DONE - str = replace(TEST_CALL, "OP", operation); - } else { - TEST_CALL :: #string DONE - t_result, t_remainder, t_saturated := OP(cast(Tx)x, cast(Ty)y); - if result != t_result print("%_%(%, %) = % + %0%0\n", operation, type, x, y, result, remainder, ifx saturated then " : saturated"); - DONE - str = replace(TEST_CALL, "OP", operation); - } - return str; - } - - #insert #run print_test_call(operation); + return builder_to_string(*builder); + }(); errors := 0; - if result != t_result { errors += 1; print(" > incorrect result value: got % expected %\n", t_result, result); }; - if type != type_of(t_result) { errors += 1; print(" > incorrect result type: got % expected %\n", type_of(t_result), type); }; - if saturated != t_saturated { errors += 1; print(" > incorrect saturated flag: got % expected %\n", t_saturated, saturated); }; + log: String_Builder; + if result != t_result { errors += 1; print(*log, " > incorrect result value: got % expected %\n", t_result, result); }; + if type != type_of(t_result) { errors += 1; print(*log, " > incorrect result type: got % expected %\n", type_of(t_result), type); }; + if saturated != t_saturated { errors += 1; print(*log, " > incorrect saturated flag: got % expected %\n", t_saturated, saturated); }; #if operation == "div" { - if remainder != t_remainder { errors += 1; print(" > incorrect remainder value: got % expected %\n", t_remainder, remainder); }; + if remainder != t_remainder { errors += 1; print(*log, " > incorrect remainder value: got % expected %\n", t_remainder, remainder); }; } + + if errors > 0 { + #if operation == "div" { + print("%_%(%, %) = % + %0%0\n", operation, type, x, y, result, remainder, ifx saturated then " : saturated"); + } + else { + print("%_%(%, %) = %0%0\n", operation, type, x, y, result, ifx saturated then " : saturated"); + } + write_builder(*log); + } + return errors; } @@ -157,6 +164,342 @@ main :: () { if errors > 0 print("# Found % %!\n", errors, ifx errors == 1 then "error" else "errors"); else print(" No errors found.\n"); + + // Test generic agains branch-free alternative. + write_strings( + "#=======================#\n", + "# generic == x64 asm ? #\n" + ); + + + full_test :: ($type: Type, test: (a: type, b: type)) { + #if type == { + case u8; + min :u8 = 0; + max :u8 = U8_MAX; + + case u16; + min :u16 = 0; + max :u16 = U16_MAX; + + case s8; + min :s8 = S8_MIN; + max :s8 = S8_MAX; + + case s16; + min :s16 = S16_MIN; + max :s16 = S16_MAX; + + case; + assert(false, "This will take way too long."); + } + + #if !AVOID_INFINITE_FOR_LOOP { + for a : min..max { + for b : min..max { + test(a, b); + } + } + } + else { + a :type = min; + b :type = min; + while loop_a := true { + while loop_b := true { + test(a, b); + if b == max then break loop_b; else b += 1; + } + if a == max then break loop_a; else a += 1; + } + } + } + + partial_test :: ($type: Type, test: (a: type, b: type)) { + min, max: type; + #if type == { + case u8; + min = 0; + max = U8_MAX; + + case u16; + min = 0; + max = U16_MAX; + + case u32; + min = 0; + max = U32_MAX; + + case s8; + min = S8_MIN; + max = S8_MAX; + + case s16; + min = S16_MIN; + max = S16_MAX; + + case s32; + min = S32_MIN; + max = S32_MAX; + + case; + assert(false, "This will take way too long."); + } + + #if !AVOID_INFINITE_FOR_LOOP { + for a: min..max { + b := a; + c := max - a + min; + test(a, b); + test(a, c); + } + } + else { + a := min; + while loop := true { + b := a; + c := max - a + min; + test(a, b); + test(a, c); + if a == max then break loop; else a += 1; + } + } + } + + minimal_test :: ($type: Type, test: (a: type, b: type)) { + #if type == { + case u32; + min :u32 = 0; + mid :u32 = U32_MAX / 2; + max :u32 = U32_MAX; + range :u32 = cast(u32)U16_MAX * 2048; + + case u64; + min :u64 = 0; + mid :u64 = U64_MAX / 2; + max :u64 = U64_MAX; + range :u64 = cast(u64)U16_MAX * 2048; + + case s32; + min :s32 = S32_MIN; + mid :s32 = (S32_MIN / 2) + (S32_MAX / 2); + max :s32 = S32_MAX; + range :s32 = cast(s32)S16_MAX * 2048; + + case s64; + min :s64 = S64_MIN; + mid :s64 = (S64_MIN / 2) + (S64_MAX / 2); + max :s64 = S64_MAX; + range :s64 = cast(s64)S16_MAX * 2048; + + case; + assert(false, "Invalid type % given.", type); + } + + #if !AVOID_INFINITE_FOR_LOOP { + start, end : type; + + start = min; + end = min+range; + for a: start..end { + b := a; + c := end - a + start; + test(a, b); + test(a, c); + } + + start = mid-range; + end = mid+range; + for a: start..end { + b := a; + c := end - a + start; + test(a, b); + test(a, c); + } + + start = max-range; + end = max; + for a: start..end { + b := a; + c := end - a + start; + test(a, b); + test(a, c); + } + } + else { + start, end, a : type; + + start = min; + end = min + range; + a = start; + while loop := true { + b := a; + c := end - a + start; + test(a, b); + test(a, c); + if a == end then break loop; else a += 1; + } + + start = mid - range; + end = mid + range; + a = start; + while loop := true { + b := a; + c := end - a + start; + test(a, b); + test(a, c); + if a == end then break loop; else a += 1; + } + + start = max - range; + end = max; + a = start; + while loop := true { + b := a; + c := end - a + start; + test(a, b); + test(a, c); + if a == end then break loop; else a += 1; + } + } + } + + + // add + + ADD_TEST_TEMPLATE :: (a: $T, b: T) { + rT, sT := add(a, b, true); + rF, sF := add(a, b, false); + assert(rT == rF && sT == sF, "> add(%1, %2, true) = %3,%4 != add(%1, %2, false) = %5,%6\n", a, b, rT, sT, rF, sF); + } + + write_string("# testing add,u8 #\n"); + full_test(u8, ADD_TEST_TEMPLATE); + + write_string("# testing add,u16 #\n"); + full_test(u16, ADD_TEST_TEMPLATE); + + write_string("# testing add,u32 #\n"); + partial_test(u32, ADD_TEST_TEMPLATE); + + write_string("# testing add,u64 #\n"); + minimal_test(u64, ADD_TEST_TEMPLATE); + + write_string("# testing add,s8 #\n"); + full_test(s8, ADD_TEST_TEMPLATE); + + write_string("# testing add,s16 #\n"); + full_test(s16, ADD_TEST_TEMPLATE); + + write_string("# testing add,s32 #\n"); + partial_test(s32, ADD_TEST_TEMPLATE); + + write_string("# testing add,s64 #\n"); + minimal_test(s64, ADD_TEST_TEMPLATE); + + + // sub + + SUB_TEST_TEMPLATE :: (a: $T, b: T) { + rT, sT := sub(a, b, true); + rF, sF := sub(a, b, false); + assert(rT == rF && sT == sF, "> sub(%1, %2, true) = %3,%4 != sub(%1, %2, false) = %5,%6\n", a, b, rT, sT, rF, sF); + } + + write_string("# testing sub,u8 #\n"); + full_test(u8, SUB_TEST_TEMPLATE); + + write_string("# testing sub,u16 #\n"); + full_test(u16, SUB_TEST_TEMPLATE); + + write_string("# testing sub,u32 #\n"); + partial_test(u32, SUB_TEST_TEMPLATE); + + write_string("# testing sub,u64 #\n"); + minimal_test(u64, SUB_TEST_TEMPLATE); + + write_string("# testing sub,s8 #\n"); + full_test(s8, SUB_TEST_TEMPLATE); + + write_string("# testing sub,s16 #\n"); + full_test(s16, SUB_TEST_TEMPLATE); + + write_string("# testing sub,s32 #\n"); + partial_test(s32, SUB_TEST_TEMPLATE); + + write_string("# testing sub,s64 #\n"); + minimal_test(s64, SUB_TEST_TEMPLATE); + + + // mul + + MUL_TEST_TEMPLATE :: (a: $T, b: T) { + rT, sT := mul(a, b, true); + rF, sF := mul(a, b, false); + assert(rT == rF && sT == sF, "> mul(%1, %2, true) = %3,%4 != mul(%1, %2, false) = %5,%6\n", a, b, rT, sT, rF, sF); + } + + write_string("# testing mul,u8 #\n"); + full_test(u8, MUL_TEST_TEMPLATE); + + write_string("# testing mul,u16 #\n"); + full_test(u16, MUL_TEST_TEMPLATE); + + write_string("# testing mul,u32 #\n"); + partial_test(u32, MUL_TEST_TEMPLATE); + + write_string("# testing mul,u64 #\n"); + minimal_test(u64, MUL_TEST_TEMPLATE); + + write_string("# testing mul,s8 #\n"); + full_test(s8, MUL_TEST_TEMPLATE); + + write_string("# testing mul,s16 #\n"); + full_test(s16, MUL_TEST_TEMPLATE); + + write_string("# testing mul,s32 #\n"); + partial_test(s32, MUL_TEST_TEMPLATE); + + write_string("# testing mul,s64 #\n"); + minimal_test(s64, MUL_TEST_TEMPLATE); + + + // div + + DIV_TEST_TEMPLATE :: (a: $T, b: T) { + if b == 0 then return; + rT, remT, sT := div(a, b, true); + rF, remF, sF := div(a, b, false); + assert(rT == rF && sT == sF, "> mul(%1, %2, true) = %3,%4,%5 != mul(%1, %2, false) = %6,%7,%8\n", a, b, rT, remT, sT, rF, remF, sF); + } + + write_string("# testing div,u8 #\n"); + full_test(u8, DIV_TEST_TEMPLATE); + + write_string("# testing div,u16 #\n"); + full_test(u16, DIV_TEST_TEMPLATE); + + write_string("# testing div,u32 #\n"); + partial_test(u32, DIV_TEST_TEMPLATE); + + write_string("# testing div,u64 #\n"); + minimal_test(u64, DIV_TEST_TEMPLATE); + + write_string("# testing div,s8 #\n"); + full_test(s8, DIV_TEST_TEMPLATE); + + write_string("# testing div,s16 #\n"); + full_test(s16, DIV_TEST_TEMPLATE); + + write_string("# testing div,s32 #\n"); + partial_test(s32, DIV_TEST_TEMPLATE); + + write_string("# testing div,s64 #\n"); + minimal_test(s64, DIV_TEST_TEMPLATE); + + + write_string(" No errors found.\n"); + + write_strings( "#=======================#\n", "# Benchmarks #\n" @@ -212,11 +555,11 @@ main :: () { r_asm: type = 0; time_gen := current_time_monotonic(); - for idx: 0..DATA_SIZE-1 #insert #run replace("r_gen ^= OP(numbers_x[idx], numbers_y[idx], true);", "OP", operation); + for idx: 0..DATA_SIZE-1 #insert #run replace("r_gen ^= OP(numbers_x[idx], numbers_y[idx], false);", "OP", operation); time_gen = current_time_monotonic() - time_gen; time_asm := current_time_monotonic(); - for idx: 0..DATA_SIZE-1 #insert #run replace("r_asm ^= OP(numbers_x[idx], numbers_y[idx]);", "OP", operation); + for idx: 0..DATA_SIZE-1 #insert #run replace("r_asm ^= OP(numbers_x[idx], numbers_y[idx], true);", "OP", operation); time_asm = current_time_monotonic() - time_asm; assert(r_gen == r_asm); diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index fb742da..d0373f9 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,3 +1,16 @@ +/* + A simple terminal user interface module that provides basic functionalities similar to the [ncurses library](https://en.wikipedia.org/wiki/Ncurses). + Usefull for creating simple terminal-based apps that require user input. + View `snake.jai` for an example. + It has been tested on the following terminal emulators: + - [GNOME Terminal](https://en.wikipedia.org/wiki/GNOME_Terminal) + - [kitty](https://en.wikipedia.org/wiki/Kitty_(terminal_emulator)) + - [Konsole](https://en.wikipedia.org/wiki/Konsole) + - [Linux console](https://en.wikipedia.org/wiki/Linux_console) + - [xterm](https://en.wikipedia.org/wiki/Xterm) + - [Windows Terminal](https://en.wikipedia.org/wiki/Windows_Terminal) +*/ + #module_parameters(COLOR_MODE_BITS := 24); @@ -32,11 +45,11 @@ #import "UTF8"; #load "key_map.jai"; -#add_context tui_style : Style; // This contains the last style applied by the module. +#add_context tui_style : Style; // This contains the last style applied by the module. #add_context tui_output_builder : *String_Builder; // If set, this will serve as an output buffer for this module procedures. KEY_SIZE :: #run type_info(Key).runtime_size; -#assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key. +#assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key. active := false; input_override : Key; diff --git a/modules/TUI/snake.jai b/modules/TUI/snake.jai new file mode 100644 index 0000000..b35c0fb --- /dev/null +++ b/modules/TUI/snake.jai @@ -0,0 +1,165 @@ +#import "Basic"; +#import "Random"; +TUI :: #import "TUI"(COLOR_MODE_BITS = 8); + +Vec2D :: struct { + x: int; + y: int; +} + +operator == :: (a: Vec2D, b: Vec2D) -> bool { + return a.x == b.x && a.y == b.y; +} + +screen_size_x: int = ---; +screen_size_y: int = ---; +player_name: string = ---; + +main :: () { + + game_loop :: () { + + LOOP_PERIOD_MS :: 30; + + score := 0; + dir := Vec2D.{1, 0}; + food := Vec2D.{5, 5}; + + snake_parts: [..] Vec2D; + for 0..13 array_add(*snake_parts, Vec2D.{3, 3}); + snake_parts[0].x += 1; + + TUI.flush_input(); + TUI.set_next_key(TUI.Keys.Resize); + timer := current_time_monotonic(); + while main_loop := true { + + timestamp := current_time_monotonic(); + key := TUI.get_key(LOOP_PERIOD_MS); + + if key == { + case TUI.Keys.Resize; + TUI.clear_terminal(); + screen_size_x, screen_size_y = TUI.get_terminal_size(); + TUI.draw_box(1, 1, screen_size_x, screen_size_y); + TUI.set_cursor_position(3, screen_size_y); + write_strings(" ", player_name, " "); + + case #char "q"; #through; + case #char "Q"; #through; + case TUI.Keys.Escape; + break main_loop; + + case TUI.Keys.Up; + if dir != Vec2D.{0, 1} then dir = Vec2D.{0, -1}; + + case TUI.Keys.Down; + if dir != Vec2D.{0, -1} then dir = Vec2D.{0, 1}; + + case TUI.Keys.Left; + if dir != Vec2D.{1, 0} then dir = Vec2D.{-1, 0}; + + case TUI.Keys.Right; + if dir != Vec2D.{-1, 0} then dir = Vec2D.{1, 0}; + } + + if screen_size_x < 15 || screen_size_y < 15 { + TUI.clear_terminal(); + TUI.set_cursor_position(1,1); + write_string("~ paused : increase window size ~"); + continue; + } + + last_pos := snake_parts[snake_parts.count-1]; + + // Update position. + for < snake_parts.count-1..1 { + if snake_parts[it] != snake_parts[it-1] { + snake_parts[it] = snake_parts[it-1]; + } + } + snake_parts[0].x += dir.x; + snake_parts[0].y += dir.y; + + // Teleport on borders. + if snake_parts[0].x < 2 then snake_parts[0].x = screen_size_x - 1; + if snake_parts[0].x >= screen_size_x then snake_parts[0].x = 2; + if snake_parts[0].y < 2 then snake_parts[0].y = screen_size_y - 1; + if snake_parts[0].y >= screen_size_y then snake_parts[0].y = 2; + food.x = clamp(food.x, 2, screen_size_x-1); + food.y = clamp(food.y, 2, screen_size_y-1); + + // Check for game-over. + for 1..snake_parts.count-1 { + if snake_parts[it] == snake_parts[0] { + break main_loop; + } + } + + // Check for food. + if snake_parts[0] == food { + score += 1; + array_add(*snake_parts, snake_parts[snake_parts.count-1]); + food = Vec2D.{ + cast(int)(random_get_zero_to_one_open() * (screen_size_x-3) + 2), + cast(int)(random_get_zero_to_one_open() * (screen_size_y-3) + 2) + }; + } + + // Wait to match game loop time. + delta := to_milliseconds(current_time_monotonic() - timestamp); + if delta < LOOP_PERIOD_MS { + sleep_milliseconds(xx (LOOP_PERIOD_MS - delta)); + } + + // Draw snake. + write_string(TUI.Commands.DrawingMode); + TUI.set_cursor_position(last_pos.x, last_pos.y); + write_string(TUI.Drawings.Blank); + for snake_parts { + TUI.set_cursor_position(it.x, it.y); + write_string(TUI.Drawings.Checkerboard); + } + // Draw food. + { + TUI.using_style(TUI.Style.{ foreground = TUI.Palette.RED, bold = true, }); + TUI.set_cursor_position(food.x, food.y); + write_string(TUI.Drawings.Diamond); + } + write_string(TUI.Commands.TextMode); + + // Set score + TUI.set_cursor_position(3, 1); + print(" % ", score); + } + } + + GAME_OVER_TEXT :: "~ game over ~"; + INSTRUCTIONS_TEXT :: "(esc to exit)"; + + seed: u64 = xx to_milliseconds(current_time_monotonic()) | 0x01; // Seed must be odd. + random_seed(seed); + + assert(TUI.setup_terminal(), "Failed to setup TUI."); + TUI.set_cursor_position(1, 1); + + write_string("Please enter player name: "); + player_name = TUI.read_input_line(64); + + while true { + game_loop(); + + // Game over screen. + box_size := Vec2D.{19, 4}; + TUI.draw_box((screen_size_x-box_size.x)/2, (screen_size_y-box_size.y)/2, box_size.x, box_size.y); + TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, (screen_size_y-box_size.y)/2 + 1); + write_string(GAME_OVER_TEXT); + TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, (screen_size_y-box_size.y)/2 + 2); + write_string(INSTRUCTIONS_TEXT); + sleep_milliseconds(100); + TUI.flush_input(); + if TUI.get_key() == TUI.Keys.Escape then break; + } + + assert(TUI.reset_terminal(), "Failed to reset TUI."); +} diff --git a/modules/UTF8/module.jai b/modules/UTF8/module.jai index 72d3d75..5e6fd65 100644 --- a/modules/UTF8/module.jai +++ b/modules/UTF8/module.jai @@ -8,15 +8,15 @@ is_continuation_byte :: inline (byte: u8) -> bool { } // Given a leading_byte, returns the number of bytes on the character. -count_character_bytes :: inline (leading_byte: u8) -> int { +count_character_bytes :: inline (character_leading_byte: u8) -> int { // BBBB BBBB & 1110 0000 == 110X XXXX -> 1 initial + 1 continuation byte - if (leading_byte & 0xE0) == 0xC0 return 1+1; + if (character_leading_byte & 0xE0) == 0xC0 return 1+1; // BBBB BBBB & 1111 0000 == 1110 XXXX -> 1 initial + 2 continuation byte - if (leading_byte & 0xF0) == 0xE0 return 1+2; + if (character_leading_byte & 0xF0) == 0xE0 return 1+2; // BBBB BBBB & 1111 1000 == 1111 0XXX -> 1 initial + 3 continuation byte - if (leading_byte & 0xF8) == 0xF0 return 1+3; + if (character_leading_byte & 0xF8) == 0xF0 return 1+3; return 1; } @@ -126,3 +126,24 @@ get_byte_index :: (str: string, character_index: int) -> buffer_index: int, succ } return -1, false; } + +// Scans the string for UTF8 encoding errors. +is_valid :: (str: string) -> is_valid := true, error_index: int = -1 { + idx := 0; + remainig_bytes := 0; + while idx < str.count { + defer idx += 1; + + is_continuation := is_continuation_byte(str[idx]); + + if (is_continuation && remainig_bytes == 0) || (!is_continuation && remainig_bytes > 0) then return false, idx; + + if is_continuation { + remainig_bytes -= 1; + continue; + } + + remainig_bytes = count_character_bytes(str[idx]) - 1; + } + return; +} diff --git a/modules/UTF8/tests.jai b/modules/UTF8/tests.jai index aff1d36..b7e3579 100644 --- a/modules/UTF8/tests.jai +++ b/modules/UTF8/tests.jai @@ -1,5 +1,162 @@ #import "Basic"; +#import "String"; +#import "UTF8"; main :: () { - assert(false, "TODO"); // TODO + write_strings( + "#=======================#\n", + "# Basic tests #\n" + ); + + tmp_str: string; + tmp_bool: bool; + tmp_int: int; + + assert(is_continuation_byte("0€1"[0]) == false); + assert(is_continuation_byte("0€1"[1]) == false); + assert(is_continuation_byte("0€1"[2]) == true); + assert(is_continuation_byte("0€1"[3]) == true); + assert(is_continuation_byte("0€1"[4]) == false); + + + write_strings("# count_character_bytes #\n"); + + assert(count_character_bytes("0£€𐍈1"[0]) == 1); + assert(count_character_bytes("0£€𐍈1"[1]) == 2); + assert(count_character_bytes("0£€𐍈1"[2]) == 1); + assert(count_character_bytes("0£€𐍈1"[3]) == 3); + assert(count_character_bytes("0£€𐍈1"[4]) == 1); + assert(count_character_bytes("0£€𐍈1"[5]) == 1); + assert(count_character_bytes("0£€𐍈1"[6]) == 4); + assert(count_character_bytes("0£€𐍈1"[7]) == 1); + assert(count_character_bytes("0£€𐍈1"[8]) == 1); + assert(count_character_bytes("0£€𐍈1"[9]) == 1); + assert(count_character_bytes("0£€𐍈1"[10]) == 1); + + + write_strings("# truncate #\n"); + + assert(compare(truncate("0£€𐍈1", 0), "") == 0); + assert(compare(truncate("0£€𐍈1", 1), "0") == 0); + assert(compare(truncate("0£€𐍈1", 2), "0") == 0); + assert(compare(truncate("0£€𐍈1", 3), "0£") == 0); + assert(compare(truncate("0£€𐍈1", 4), "0£") == 0); + assert(compare(truncate("0£€𐍈1", 5), "0£") == 0); + assert(compare(truncate("0£€𐍈1", 6), "0£€") == 0); + assert(compare(truncate("0£€𐍈1", 7), "0£€") == 0); + assert(compare(truncate("0£€𐍈1", 8), "0£€") == 0); + assert(compare(truncate("0£€𐍈1", 9), "0£€") == 0); + assert(compare(truncate("0£€𐍈1", 10), "0£€𐍈") == 0); + assert(compare(truncate("0£€𐍈1", 11), "0£€𐍈1") == 0); + assert(compare(truncate("0£€𐍈1", 12), "0£€𐍈1") == 0); + + + write_strings("# is_empty #\n"); + + assert(is_empty("")); + assert(is_empty("\0")); + assert(is_empty("\0\t")); + assert(is_empty("\0\t\n")); + assert(is_empty("\0\t\n\x0B")); + assert(is_empty("\0\t\n\x0B\x0C")); + assert(is_empty("\0\t\n\x0B\x0C\r")); + assert(is_empty("\0\t\n\x0B\x0C\r ")); + assert(is_empty("\0\t\n\x0B\x0C\r .") == false); + assert(is_empty("| B A Z € N G A |") == false); + + + write_strings("# delete_character #\n"); + + tmp_str = copy_string("",, temporary_allocator); + assert(delete_character(*tmp_str, 0) == false); + + tmp_str = copy_string("12£45€78𐍈",, temporary_allocator); + assert(delete_character(*tmp_str, -1) == false); + assert(delete_character(*tmp_str, 99999) == false); + assert(delete_character(*tmp_str, 7) == true); + assert(compare(tmp_str, "12£45€7𐍈") == 0); + assert(delete_character(*tmp_str, 2) == true); + assert(compare(tmp_str, "1245€7𐍈") == 0); + assert(delete_character(*tmp_str, 4) == true); + assert(compare(tmp_str, "12457𐍈") == 0); + assert(delete_character(*tmp_str, 3) == true); + assert(compare(tmp_str, "1247𐍈") == 0); + + + write_strings("# get_byte_index #\n"); + + tmp_str = copy_string("12£45€78𐍈X",, temporary_allocator); + + tmp_int, tmp_bool = get_byte_index("", 0); + assert(tmp_int == -1 && tmp_bool == false, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, -1); + assert(tmp_int == -1 && tmp_bool == false, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, -99999); + assert(tmp_int == -1 && tmp_bool == false, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 99999); + assert(tmp_int == -1 && tmp_bool == false, "(%, %)", tmp_int, tmp_bool); + + tmp_int, tmp_bool = get_byte_index(tmp_str, 0); + assert(tmp_int == 0 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 1); + assert(tmp_int == 1 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 2); + assert(tmp_int == 2 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 3); + assert(tmp_int == 4 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 4); + assert(tmp_int == 5 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 5); + assert(tmp_int == 6 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 6); + assert(tmp_int == 9 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 7); + assert(tmp_int == 10 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 8); + assert(tmp_int == 11 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + tmp_int, tmp_bool = get_byte_index(tmp_str, 9); + assert(tmp_int == 15 && tmp_bool == true, "(%, %)", tmp_int, tmp_bool); + + + write_strings("# count_characters #\n"); + + assert(count_characters("") == 0); + assert(count_characters("0") == 1); + assert(count_characters("0£") == 2); + assert(count_characters("0£€") == 3); + assert(count_characters("0£€𐍈") == 4); + assert(count_characters("0£€𐍈1") == 5); + + tmp_str = copy_string("123€DELETE",, temporary_allocator); + tmp_str[6] = 0; + assert(count_characters(tmp_str) == 10); + assert(count_characters(tmp_str, true) == 4); + + + write_strings("# is_valid #\n"); + + assert(is_valid("")); + + tmp_str = copy_string("123€DELETE",, temporary_allocator); + tmp_str[6] = 0; + tmp_bool, tmp_int = is_valid(tmp_str); + assert(tmp_bool == true && tmp_int == -1, "(%, %)", tmp_bool, tmp_int); + + tmp_str = copy_string("123€DELETE",, temporary_allocator); + tmp_str[3] = 0; // Cut € at start. + tmp_bool, tmp_int = is_valid(tmp_str); + assert(tmp_bool == false && tmp_int == 4, "(%, %)", tmp_bool, tmp_int); + + tmp_str = copy_string("123€DELETE",, temporary_allocator); + tmp_str[4] = 0; // Cut € at middle. + tmp_bool, tmp_int = is_valid(tmp_str); + assert(tmp_bool == false && tmp_int == 4, "(%, %)", tmp_bool, tmp_int); + + tmp_str = copy_string("123€DELETE",, temporary_allocator); + tmp_str[5] = 0; // Cut € at end. + tmp_bool, tmp_int = is_valid(tmp_str); + assert(tmp_bool == false && tmp_int == 5, "(%, %)", tmp_bool, tmp_int); + + + write_strings(" No errors found.\n"); } diff --git a/snake.jai b/snake.jai deleted file mode 100644 index b35c0fb..0000000 --- a/snake.jai +++ /dev/null @@ -1,165 +0,0 @@ -#import "Basic"; -#import "Random"; -TUI :: #import "TUI"(COLOR_MODE_BITS = 8); - -Vec2D :: struct { - x: int; - y: int; -} - -operator == :: (a: Vec2D, b: Vec2D) -> bool { - return a.x == b.x && a.y == b.y; -} - -screen_size_x: int = ---; -screen_size_y: int = ---; -player_name: string = ---; - -main :: () { - - game_loop :: () { - - LOOP_PERIOD_MS :: 30; - - score := 0; - dir := Vec2D.{1, 0}; - food := Vec2D.{5, 5}; - - snake_parts: [..] Vec2D; - for 0..13 array_add(*snake_parts, Vec2D.{3, 3}); - snake_parts[0].x += 1; - - TUI.flush_input(); - TUI.set_next_key(TUI.Keys.Resize); - timer := current_time_monotonic(); - while main_loop := true { - - timestamp := current_time_monotonic(); - key := TUI.get_key(LOOP_PERIOD_MS); - - if key == { - case TUI.Keys.Resize; - TUI.clear_terminal(); - screen_size_x, screen_size_y = TUI.get_terminal_size(); - TUI.draw_box(1, 1, screen_size_x, screen_size_y); - TUI.set_cursor_position(3, screen_size_y); - write_strings(" ", player_name, " "); - - case #char "q"; #through; - case #char "Q"; #through; - case TUI.Keys.Escape; - break main_loop; - - case TUI.Keys.Up; - if dir != Vec2D.{0, 1} then dir = Vec2D.{0, -1}; - - case TUI.Keys.Down; - if dir != Vec2D.{0, -1} then dir = Vec2D.{0, 1}; - - case TUI.Keys.Left; - if dir != Vec2D.{1, 0} then dir = Vec2D.{-1, 0}; - - case TUI.Keys.Right; - if dir != Vec2D.{-1, 0} then dir = Vec2D.{1, 0}; - } - - if screen_size_x < 15 || screen_size_y < 15 { - TUI.clear_terminal(); - TUI.set_cursor_position(1,1); - write_string("~ paused : increase window size ~"); - continue; - } - - last_pos := snake_parts[snake_parts.count-1]; - - // Update position. - for < snake_parts.count-1..1 { - if snake_parts[it] != snake_parts[it-1] { - snake_parts[it] = snake_parts[it-1]; - } - } - snake_parts[0].x += dir.x; - snake_parts[0].y += dir.y; - - // Teleport on borders. - if snake_parts[0].x < 2 then snake_parts[0].x = screen_size_x - 1; - if snake_parts[0].x >= screen_size_x then snake_parts[0].x = 2; - if snake_parts[0].y < 2 then snake_parts[0].y = screen_size_y - 1; - if snake_parts[0].y >= screen_size_y then snake_parts[0].y = 2; - food.x = clamp(food.x, 2, screen_size_x-1); - food.y = clamp(food.y, 2, screen_size_y-1); - - // Check for game-over. - for 1..snake_parts.count-1 { - if snake_parts[it] == snake_parts[0] { - break main_loop; - } - } - - // Check for food. - if snake_parts[0] == food { - score += 1; - array_add(*snake_parts, snake_parts[snake_parts.count-1]); - food = Vec2D.{ - cast(int)(random_get_zero_to_one_open() * (screen_size_x-3) + 2), - cast(int)(random_get_zero_to_one_open() * (screen_size_y-3) + 2) - }; - } - - // Wait to match game loop time. - delta := to_milliseconds(current_time_monotonic() - timestamp); - if delta < LOOP_PERIOD_MS { - sleep_milliseconds(xx (LOOP_PERIOD_MS - delta)); - } - - // Draw snake. - write_string(TUI.Commands.DrawingMode); - TUI.set_cursor_position(last_pos.x, last_pos.y); - write_string(TUI.Drawings.Blank); - for snake_parts { - TUI.set_cursor_position(it.x, it.y); - write_string(TUI.Drawings.Checkerboard); - } - // Draw food. - { - TUI.using_style(TUI.Style.{ foreground = TUI.Palette.RED, bold = true, }); - TUI.set_cursor_position(food.x, food.y); - write_string(TUI.Drawings.Diamond); - } - write_string(TUI.Commands.TextMode); - - // Set score - TUI.set_cursor_position(3, 1); - print(" % ", score); - } - } - - GAME_OVER_TEXT :: "~ game over ~"; - INSTRUCTIONS_TEXT :: "(esc to exit)"; - - seed: u64 = xx to_milliseconds(current_time_monotonic()) | 0x01; // Seed must be odd. - random_seed(seed); - - assert(TUI.setup_terminal(), "Failed to setup TUI."); - TUI.set_cursor_position(1, 1); - - write_string("Please enter player name: "); - player_name = TUI.read_input_line(64); - - while true { - game_loop(); - - // Game over screen. - box_size := Vec2D.{19, 4}; - TUI.draw_box((screen_size_x-box_size.x)/2, (screen_size_y-box_size.y)/2, box_size.x, box_size.y); - TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, (screen_size_y-box_size.y)/2 + 1); - write_string(GAME_OVER_TEXT); - TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, (screen_size_y-box_size.y)/2 + 2); - write_string(INSTRUCTIONS_TEXT); - sleep_milliseconds(100); - TUI.flush_input(); - if TUI.get_key() == TUI.Keys.Escape then break; - } - - assert(TUI.reset_terminal(), "Failed to reset TUI."); -} diff --git a/ttt.jai b/ttt.jai index b0d60d0..d5d6e4e 100644 --- a/ttt.jai +++ b/ttt.jai @@ -7,7 +7,7 @@ DEBUG :: false; #import "File"; #import "File_Utilities"; #import "String"; -#import "Saturation"; +#import "Saturation"(PREFER_BRANCH_FREE_CODE=true); #import "UTF8"; TUI :: #import "TUI"(COLOR_MODE_BITS=4); -- cgit v1.2.3 From 22e3b95ce23808ee08a5fa3b0481c17c2e5506d6 Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 20 May 2024 23:19:55 +0100 Subject: Updated TUI module to clear inside error message box. --- modules/TUI/module.jai | 26 ++++++-- modules/TUI/snake.jai | 165 ------------------------------------------------- ttt.jai | 2 +- 3 files changed, 21 insertions(+), 172 deletions(-) delete mode 100644 modules/TUI/snake.jai (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index d0373f9..2fb1233 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -125,10 +125,21 @@ Commands :: struct #type_info_none { // Draw/text. DrawingMode :: "\e(0"; TextMode :: "\e(B"; - ClearScreen :: "\e[2J"; - ClearLine :: "\e[2K"; + ClearToEndOfScreen :: "\e[0J"; // From current cursor position (inclusive) to end of screen. + ClearFromStartOfScreen :: "\e[1J"; // From start of screen to current cursor position. + ClearScreen :: "\e[2J"; // Leaves cursor in top left corner position. ClearScrollBack :: "\e[3J"; + ClearToEndOfLine :: "\e[0K"; // From current cursor position (inclusive) to end of line. + ClearFromStartOfLine :: "\e[1K"; // From start of line to current cursor position. + ClearLine :: "\e[2K"; SetGraphicsRendition :: "\e[%m"; + + // Text Modification. + InsertCharacters :: "\e[%@"; // Insert % spaces at curret cursor position (shifts existing text to the right). + DeleteCharacters :: "\e[%P"; // Delete % characters at the current cursor position (inserts space characters from the right). + EraseCharacters :: "\e[%X"; // Erase % characters from the current cursor position by overwriting them with space characters. + InsertLines :: "\e[%L"; // Insert % lines into the buffer at the current cursor position. + DeleteLines :: "\e[%M"; // Deletes % lines from the buffer, starting with the row the cursor is on. // Character encoding. EncodingIEC2022 :: "\e%@"; @@ -536,12 +547,12 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { if is_visible { print_to_builder(*builder, Commands.SetCursorPosition, y, x); append(*builder, str); - for chars_count..count_limit-1 append(*builder, " "); + if count_limit > chars_count then print_to_builder(*builder, Commands.EraseCharacters, count_limit-chars_count); } else { print_to_builder(*builder, Commands.SetCursorPosition, y, x); for 1..chars_count append(*builder, "*"); - for chars_count..count_limit-1 append(*builder, " "); + if count_limit > chars_count print_to_builder(*builder, Commands.EraseCharacters, count_limit-chars_count); } print_to_builder(*builder, Commands.SetCursorPosition, y, x+idx); write_builder(*builder); @@ -615,7 +626,7 @@ flush_input :: () { input_string.count = 0; } -draw_box :: (x: int, y: int, width: int, height: int) { +draw_box :: (x: int, y: int, width: int, height: int, clear_inside := false) { assert_is_active(); assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): 'x' and 'y' must be greater-than 0; 'width' and 'height' must be greater-than 1."); @@ -641,10 +652,13 @@ draw_box :: (x: int, y: int, width: int, height: int) { for idx: y+1..y+height-2 { print_to_builder(builder, Commands.SetCursorPosition, idx, x); append(builder, Drawings.LineV); + if clear_inside { + print_to_builder(builder, Commands.EraseCharacters, width-2); + } print_to_builder(builder, Commands.SetCursorPosition, idx, x+width-1); append(builder, Drawings.LineV); } - + // Draw bottom line. print_to_builder(builder, Commands.SetCursorPosition, y+height-1, x); append(builder, Drawings.CornerBL); diff --git a/modules/TUI/snake.jai b/modules/TUI/snake.jai deleted file mode 100644 index b35c0fb..0000000 --- a/modules/TUI/snake.jai +++ /dev/null @@ -1,165 +0,0 @@ -#import "Basic"; -#import "Random"; -TUI :: #import "TUI"(COLOR_MODE_BITS = 8); - -Vec2D :: struct { - x: int; - y: int; -} - -operator == :: (a: Vec2D, b: Vec2D) -> bool { - return a.x == b.x && a.y == b.y; -} - -screen_size_x: int = ---; -screen_size_y: int = ---; -player_name: string = ---; - -main :: () { - - game_loop :: () { - - LOOP_PERIOD_MS :: 30; - - score := 0; - dir := Vec2D.{1, 0}; - food := Vec2D.{5, 5}; - - snake_parts: [..] Vec2D; - for 0..13 array_add(*snake_parts, Vec2D.{3, 3}); - snake_parts[0].x += 1; - - TUI.flush_input(); - TUI.set_next_key(TUI.Keys.Resize); - timer := current_time_monotonic(); - while main_loop := true { - - timestamp := current_time_monotonic(); - key := TUI.get_key(LOOP_PERIOD_MS); - - if key == { - case TUI.Keys.Resize; - TUI.clear_terminal(); - screen_size_x, screen_size_y = TUI.get_terminal_size(); - TUI.draw_box(1, 1, screen_size_x, screen_size_y); - TUI.set_cursor_position(3, screen_size_y); - write_strings(" ", player_name, " "); - - case #char "q"; #through; - case #char "Q"; #through; - case TUI.Keys.Escape; - break main_loop; - - case TUI.Keys.Up; - if dir != Vec2D.{0, 1} then dir = Vec2D.{0, -1}; - - case TUI.Keys.Down; - if dir != Vec2D.{0, -1} then dir = Vec2D.{0, 1}; - - case TUI.Keys.Left; - if dir != Vec2D.{1, 0} then dir = Vec2D.{-1, 0}; - - case TUI.Keys.Right; - if dir != Vec2D.{-1, 0} then dir = Vec2D.{1, 0}; - } - - if screen_size_x < 15 || screen_size_y < 15 { - TUI.clear_terminal(); - TUI.set_cursor_position(1,1); - write_string("~ paused : increase window size ~"); - continue; - } - - last_pos := snake_parts[snake_parts.count-1]; - - // Update position. - for < snake_parts.count-1..1 { - if snake_parts[it] != snake_parts[it-1] { - snake_parts[it] = snake_parts[it-1]; - } - } - snake_parts[0].x += dir.x; - snake_parts[0].y += dir.y; - - // Teleport on borders. - if snake_parts[0].x < 2 then snake_parts[0].x = screen_size_x - 1; - if snake_parts[0].x >= screen_size_x then snake_parts[0].x = 2; - if snake_parts[0].y < 2 then snake_parts[0].y = screen_size_y - 1; - if snake_parts[0].y >= screen_size_y then snake_parts[0].y = 2; - food.x = clamp(food.x, 2, screen_size_x-1); - food.y = clamp(food.y, 2, screen_size_y-1); - - // Check for game-over. - for 1..snake_parts.count-1 { - if snake_parts[it] == snake_parts[0] { - break main_loop; - } - } - - // Check for food. - if snake_parts[0] == food { - score += 1; - array_add(*snake_parts, snake_parts[snake_parts.count-1]); - food = Vec2D.{ - cast(int)(random_get_zero_to_one_open() * (screen_size_x-3) + 2), - cast(int)(random_get_zero_to_one_open() * (screen_size_y-3) + 2) - }; - } - - // Wait to match game loop time. - delta := to_milliseconds(current_time_monotonic() - timestamp); - if delta < LOOP_PERIOD_MS { - sleep_milliseconds(xx (LOOP_PERIOD_MS - delta)); - } - - // Draw snake. - write_string(TUI.Commands.DrawingMode); - TUI.set_cursor_position(last_pos.x, last_pos.y); - write_string(TUI.Drawings.Blank); - for snake_parts { - TUI.set_cursor_position(it.x, it.y); - write_string(TUI.Drawings.Checkerboard); - } - // Draw food. - { - TUI.using_style(TUI.Style.{ foreground = TUI.Palette.RED, bold = true, }); - TUI.set_cursor_position(food.x, food.y); - write_string(TUI.Drawings.Diamond); - } - write_string(TUI.Commands.TextMode); - - // Set score - TUI.set_cursor_position(3, 1); - print(" % ", score); - } - } - - GAME_OVER_TEXT :: "~ game over ~"; - INSTRUCTIONS_TEXT :: "(esc to exit)"; - - seed: u64 = xx to_milliseconds(current_time_monotonic()) | 0x01; // Seed must be odd. - random_seed(seed); - - assert(TUI.setup_terminal(), "Failed to setup TUI."); - TUI.set_cursor_position(1, 1); - - write_string("Please enter player name: "); - player_name = TUI.read_input_line(64); - - while true { - game_loop(); - - // Game over screen. - box_size := Vec2D.{19, 4}; - TUI.draw_box((screen_size_x-box_size.x)/2, (screen_size_y-box_size.y)/2, box_size.x, box_size.y); - TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, (screen_size_y-box_size.y)/2 + 1); - write_string(GAME_OVER_TEXT); - TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, (screen_size_y-box_size.y)/2 + 2); - write_string(INSTRUCTIONS_TEXT); - sleep_milliseconds(100); - TUI.flush_input(); - if TUI.get_key() == TUI.Keys.Escape then break; - } - - assert(TUI.reset_terminal(), "Failed to reset TUI."); -} diff --git a/ttt.jai b/ttt.jai index d5d6e4e..1bfabd0 100644 --- a/ttt.jai +++ b/ttt.jai @@ -141,7 +141,7 @@ draw_error_window :: () { pos_y := 1 + (size_y - w_size_y) / 2; TUI.using_style(style_error); - TUI.draw_box(pos_x, pos_y, w_size_x, w_size_y); + TUI.draw_box(pos_x, pos_y, w_size_x, w_size_y, true); TUI.set_cursor_position(pos_x + 1, pos_y); write_string(" Error "); TUI.set_cursor_position(pos_x + 1, pos_y + 1); -- cgit v1.2.3 From 4468717c64d0fcac52f00326cff9e6b0b15917fc Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 26 May 2024 01:26:52 +0100 Subject: Patched TUI module. --- modules/TUI/module.jai | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 2fb1233..3c40ee1 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -342,6 +342,7 @@ is_active :: inline () -> bool { return active; } +// Prepares the terminal to be used by the module. setup_terminal :: () -> success := true #must { if active == true return; @@ -367,6 +368,7 @@ setup_terminal :: () -> success := true #must { return; } +// Restores the initial terminal settings. reset_terminal :: () -> success := true #must { if active == false return; @@ -390,6 +392,12 @@ set_next_key :: inline (key: Key) { input_override = key; } +// Returns, with the following priority: +// - last key passed to set_next_key; +// - Keys.Resize if terminal was resized; +// - key pressed by user; +// - Keys.None if everything else fails after the given timeout. +// If timeout is set to -1, it will wait indefinitely by the user input. get_key :: (timeout_milliseconds: s32 = -1) -> Key { assert_is_active(); @@ -689,7 +697,7 @@ get_terminal_size :: () -> width: int, height: int { write_string(Commands.QueryWindowSizeInChars); rows, columns: int = ---; - if OS_wait_for_input(0) { + if OS_wait_for_input(1) { // Expected response format: \e[8;;t // where is the number of rows and of columns. @@ -718,16 +726,17 @@ get_terminal_size :: () -> width: int, height: int { // In such cases, measure it indirectly by the maximum possible cursor position. // (e.g.: allowWindowOps/disallowedWindowOps properties in xterm) else { - x, y := get_cursor_position(); - defer set_cursor_position(x, y); - - set_cursor_position(0xFFFF, 0xFFFF); - columns, rows = get_cursor_position(); + write_string(Commands.SaveCursorPosition); + defer write_string(Commands.RestoreCursorPosition); + + set_cursor_position(0xFFFF, 0xFFFF,, tui_output_builder = null); + columns, rows = get_cursor_position(); } return columns, rows; } +// Range between 1 and terminal size. set_cursor_position :: inline (x: int, y: int) { assert_is_active(); if context.tui_output_builder == null { @@ -738,6 +747,7 @@ set_cursor_position :: inline (x: int, y: int) { } } +// Range between 1 and terminal size. get_cursor_position :: () -> x: int, y: int { assert_is_active(); @@ -777,12 +787,14 @@ set_terminal_title :: inline (title: string) { print(Commands.SetWindowTitle, title); } +// Set the module's context string builder in the current scope context. using_builder_as_output :: (builder: *String_Builder) #expand { __builder := context.tui_output_builder; context.tui_output_builder = builder; `defer context.tui_output_builder = __builder; } +// Helper to use the module's context string builder. tui_print :: inline (format_string: string, args: .. Any) { if context.tui_output_builder == null { print(format_string, ..args, to_standard_error = false); @@ -792,6 +804,7 @@ tui_print :: inline (format_string: string, args: .. Any) { } } +// Helper to use the module's context string builder. tui_write_string :: inline (s: string) { if context.tui_output_builder == null { write_string(s, to_standard_error = false); -- cgit v1.2.3 From 7f8a90efed56db9f49e9d7e5167372ddd7aa1df2 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 26 May 2024 11:54:44 +0100 Subject: Patched TUI module. --- modules/TUI/module.jai | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) (limited to 'modules/TUI/module.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 3c40ee1..124b906 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -693,10 +693,12 @@ get_terminal_size :: () -> width: int, height: int { auto_release_temp(); + rows, columns: int = ---; + flush_input(); write_string(Commands.QueryWindowSizeInChars); - - rows, columns: int = ---; + + // Wait a bit for a response to QueryWindowSizeInChars... if OS_wait_for_input(1) { // Expected response format: \e[8;;t @@ -704,15 +706,9 @@ get_terminal_size :: () -> width: int, height: int { FORMAT :: "\e[8;;t"; input := read_input(64, #char "t",, allocator = temporary_allocator); - // Discard head noise. - while input.count >= 3 && (input[0] != FORMAT[0] || input[1] != FORMAT[1] || input[2] != FORMAT[2]) { - advance(*input); - } - - // Discard tail noise. - while input.count >= 3 && input[input.count-1] != FORMAT[FORMAT.count-1] { - input.count -= 1; - } + // Discard head noise, assuming that the response is at the end of the input (because we used a terminator character during read). + start_idx := find_index_from_right(input, #char "\e"); + if start_idx > 0 then advance(*input, start_idx); assert(input.count >= 3 && input[0] == FORMAT[0] && input[1] == FORMAT[1] && input[2] == FORMAT[2] && input[input.count-1] == FORMAT[FORMAT.count-1], @@ -761,20 +757,14 @@ get_cursor_position :: () -> x: int, y: int { FORMAT :: "\e[;R"; input := read_input(64, #char "R",, allocator = temporary_allocator); - // Discard head noise. - while input.count >= 2 && (input[0] != FORMAT[0] || input[1] != FORMAT[1]) { - advance(*input); - } - - // Discard tail noise. - while input.count >= 2 && input[input.count-1] != FORMAT[FORMAT.count-1] { - input.count -= 1; - } + // Discard head noise, assuming that the response is at the end of the input (because we used a terminator character during read). + start_idx := find_index_from_right(input, #char "\e"); + if start_idx > 0 then advance(*input, start_idx); assert(input.count >= 2 && input[0] == FORMAT[0] && input[1] == FORMAT[1] && input[input.count-1] == FORMAT[FORMAT.count-1], "Failed to query cursor position: invalid response."); - + advance(*input, 2); parts := split(input, ";",, allocator = temporary_allocator); row := parse_int(*parts[0]); -- cgit v1.2.3