diff options
| author | dam <dam@gudinoff> | 2023-09-20 18:00:20 +0100 |
|---|---|---|
| committer | dam <dam@gudinoff> | 2023-09-20 18:00:20 +0100 |
| commit | 65adffcf572c98c0198affcdf729d989fec253ae (patch) | |
| tree | 31f2c0fe7ca0cec7e7b932bb6c2f8126a7577bac /TUI | |
| parent | 9e2fc467ad0e779734d836656875cf92bcb5732a (diff) | |
| download | task-time-tracker-65adffcf572c98c0198affcdf729d989fec253ae.tar.zst task-time-tracker-65adffcf572c98c0198affcdf729d989fec253ae.zip | |
Moved TUI into a module with split OS-based implementations.
Diffstat (limited to 'TUI')
| -rw-r--r-- | TUI/module.jai | 194 | ||||
| -rw-r--r-- | TUI/unix.jai | 63 | ||||
| -rw-r--r-- | TUI/windows.jai | 111 |
3 files changed, 368 insertions, 0 deletions
diff --git a/TUI/module.jai b/TUI/module.jai new file mode 100644 index 0000000..67a9edd --- /dev/null +++ b/TUI/module.jai @@ -0,0 +1,194 @@ +#if OS == .WINDOWS { + #load "windows.jai"; +} else #if (OS == .LINUX) || (OS == .MACOS) { + #load "unix.jai"; +} else { + #assert(false, "Unsupported OS."); +} + +#import "String"; + +// https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences +// https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#designate-character-set +// https://github.com/MicrosoftDocs/Console-Docs/blob/main/docs/console-virtual-terminal-sequences.md + +isTUIActive := false; // TODO Rename this variable. + + +Drawings :: struct { + CornerBR :: "\x6A"; + CornerTR :: "\x6B"; + CornerTL :: "\x6C"; + CornerBL :: "\x6D"; + Cross :: "\x6E"; + LineH :: "\x71"; + TeeL :: "\x74"; + TeeR :: "\x75"; + TeeB :: "\x76"; + TeeT :: "\x77"; + LineV :: "\x78"; + + Blank :: "\x5F"; + Diamond :: "\x60"; + Checkerboard :: "\x61"; + PlusMinus :: "\x67"; + LessThanOrEqual :: "\x79"; + GreaterThanOrEqual :: "\x7A"; + Pi :: "\x7B"; + NotEqual :: "\x7C"; + CenteredDot :: "\x7E"; +} + +Commands :: struct { + EnterAlternateBuffer :: "\e[?1049h"; + EnterMainBuffer :: "\e[?1049l"; + + EnterDrawingMode :: "\e(0"; + EnterNormalMode :: "\e(B"; + ClearScreen :: "\e[2J"; + ClearLine :: "\e[2K"; + + RefreshWindow :: "\e[7t"; // TODO Not yet tested. + + SetUTF8 :: "\e%G"; // TODO TEST ME PLEASE + + // 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> ; <c> R" Where <r> = row and <c> = column. + QueryDeviceAttributes :: "\e[0c"; + QueryWindowSizeInChars :: "\e[18t"; // Emits the window size as: "ESC [ 8 <r> ; <c> t" Where <r> = row and <c> = column. + +} + +start :: () { + OS_prepare_terminal(); + write_strings(Commands.HideCursor, Commands.SaveCursorPosition, Commands.EnterAlternateBuffer, Commands.SetUTF8); + isTUIActive = true; +} + +stop :: () { + isTUIActive = false; + write_strings(Commands.EnterMainBuffer, Commands.RestoreCursorPosition, Commands.ShowCursor); + OS_reset_terminal(); +} + +draw_box :: (x: int, y: int, width: int, height: int, to_standard_error := false) { + + + // TODO Hardcoded box starting at 1,1... fix this! + + write_strings( + // Commands.EnterNormalMode, + Commands.EnterDrawingMode, + "\e[1;1H", // Move to position 1,1 + // TODO // Move pointer to top-left corner. + Drawings.CornerTL, + to_standard_error = to_standard_error); + + for 1..width-2 { + write_string(Drawings.LineH, to_standard_error = to_standard_error); + } + write_string(Drawings.CornerTR, to_standard_error = to_standard_error); + + + // TODO Take care of the temporary allocations. + for idx: 2..height-1 { + tmpL := tprint("\e[%;%H", idx, 1); + tmpR := tprint("\e[%;%H", idx, width); + write_strings( + tmpL, + Drawings.LineV, + tmpR, + Drawings.LineV, + to_standard_error = to_standard_error); + } + + tmpBL := tprint("\e[%;%H", height, 1); + write_strings( + tmpBL, + Drawings.CornerBL, + to_standard_error = to_standard_error); + for 1..width-2 { + write_string(Drawings.LineH, to_standard_error = to_standard_error); + } + write_string(Drawings.CornerBR, to_standard_error = to_standard_error); + + write_strings( + // TODO // print + Commands.EnterNormalMode, + to_standard_error = to_standard_error); +} + +clear_screen :: inline () { + write_string(Commands.ClearScreen); +} + +get_terminal_size :: () -> rows: int, columns: int { + rows, columns := OS_get_terminal_size(); + return rows, columns; +} + +// read_input: () -> string { + // return OS_read_input(); +// } + +// get_cursor_position :: () -> row: int, column: int { +// assert(isTUIActive, "TUI is not active."); // TODO +// write_string(TUI.Commands.QueryCursorPosition); // Returned "\e[21;1R" +// read_input() +// } + + +// read_input: () -> string; + +#if OS == .WINDOWS { + + // #run read_input = () -> string { + read_input :: () -> string { + MAX_BYTES_TO_READ :: 1024; + temp : [MAX_BYTES_TO_READ] u8; + result: string = ---; + bytes_read : s32; + + if !ReadConsoleA( stdin, temp.data, xx temp.count, *bytes_read ) + return ""; + + result.data = alloc(bytes_read); + result.count = bytes_read; + memcpy(result.data, temp.data, bytes_read); + return result; + }; +} +else #if OS == .LINUX || OS == .MACOS { + #import "Basic"; + #import "POSIX"; + + read_input :: () -> string { + buffer: [8192] u8; + bytes_read := read(STDIN_FILENO, buffer.data, buffer.count-1); + str := to_string(buffer.data, bytes_read); + return str; + }; +} diff --git a/TUI/unix.jai b/TUI/unix.jai new file mode 100644 index 0000000..439447d --- /dev/null +++ b/TUI/unix.jai @@ -0,0 +1,63 @@ +#import "POSIX"; + + + __term : My_Termios; + + My_Termios :: struct { + c_iflag : u32; // Input mode flags. + c_oflag : u32; // Output mode flags. + c_cflag : u32; // Control modes flags. + c_lflag : u32; // Local modes flags. + c_line : u8; // Line discipline. + c_cc : [32]u8; // Control characters. + c_ispeed : u32; // Input speed (baud rates). + c_ospeed : u32; // Output speed (baud rates). + } + + // 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 : *My_Termios) -> s32 #foreign libc; + + // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html + tcgetattr :: (fd : s32, termios_p : *My_Termios) -> s32 #foreign libc; + + +OS_prepare_terminal :: () { + // TODO Required to do unlocking input. + tcgetattr(STDIN_FILENO, *__term); + term_new := __term; + + term_new.c_iflag &= 0xFFFFFA14;// ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); + term_new.c_oflag &= 0xFFFFFFFE;// ~OPOST; + term_new.c_lflag &= 0xFFFF7FB4;// ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + term_new.c_cflag &= 0xFFFFFECF;// ~(CSIZE | PARENB); + term_new.c_cflag |= 0x00000030; + + tcsetattr(STDIN_FILENO, 0, *term_new); +} + +OS_reset_terminal :: () { + tcsetattr(STDIN_FILENO, 0, *__term); // return echo +} + +OS_get_terminal_size :: () -> rows: int, columns: int { + buffer: [512] u8; + write_string(Commands.QueryWindowSizeInChars); + bytes_read := read(STDIN_FILENO, buffer.data, buffer.count-1); + + str := to_string(buffer.data, bytes_read); + + // Result: [8;79;156t + assert( + buffer.data[0] == #char "\e" && + buffer.data[1] == #char "[" && + buffer.data[2] == #char "8", + "Query windows size in chars returned invalid response."); + + parts := split(str, ";"); + rows := parse_int(*parts[1]); + columns := parse_int(*parts[2]); + return rows, columns; +} diff --git a/TUI/windows.jai b/TUI/windows.jai new file mode 100644 index 0000000..ef0cfa8 --- /dev/null +++ b/TUI/windows.jai @@ -0,0 +1,111 @@ +#import "Windows"; + + + kernel32 :: #system_library "kernel32"; + + GetConsoleScreenBufferInfo :: (hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: *CONSOLE_SCREEN_BUFFER_INFO) -> bool #foreign kernel32; + ReadConsoleA :: (hConsoleHandle: HANDLE, buff : *u8, chars_to_read : s32, chars_read : *s32, lpInputControl := *void ) -> bool #foreign kernel32; + // ReadConsole :: (hConsoleInput: HANDLE, lpBuffer: *u8, nNumberOfCharsToRead: s32, lpNumberOfCharsRead: *s32, pInputControl := *void) -> bool #foreign kernel32; + GetConsoleMode :: (hConsoleHandle: HANDLE, lpMode: *u32) -> bool #foreign kernel32; + SetConsoleMode :: (hConsoleHandle: HANDLE, dwMode: u32) -> bool #foreign kernel32; + GetLastError :: () -> s32 #foreign kernel32; + + ENABLE_VIRTUAL_TERMINAL_INPUT :: 0x0200; + + ENABLE_PROCESSED_OUTPUT :: 0x0001; + ENABLE_WRAP_AT_EOL_OUTPUT :: 0x0002; + ENABLE_VIRTUAL_TERMINAL_PROCESSING :: 0x0004; + DISABLE_NEWLINE_AUTO_RETURN :: 0x0008; + ENABLE_LVB_GRID_WORLDWIDE :: 0x0010; + + SHORT :: s16; + WORD :: u16; + DWORD :: s32; + + COORD :: struct { + X : SHORT; + 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; + } + + + stdin: HANDLE; + initial_stdin_mode: u32; + stdout: HANDLE; + initial_stdout_mode: u32; + + +OS_prepare_terminal :: () { + print("TODO TUI\n", to_standard_error = true); + + + // stdin + stdin = GetStdHandle(STD_INPUT_HANDLE ); + if stdin == INVALID_HANDLE_VALUE { + print("Invalid input handler.", to_standard_error = true); + return; + } + if GetConsoleMode(stdin, *initial_stdin_mode) == false { + print("Failed to get input mode.", to_standard_error = true); + return; + } + if SetConsoleMode(stdin, initial_stdin_mode | ENABLE_VIRTUAL_TERMINAL_INPUT) == false { + print("Failed to set input mode: %.", GetLastError(), to_standard_error = true); + return; + } + + // stdout + stdout = GetStdHandle(STD_OUTPUT_HANDLE); + outMode: u32 = 0; + if stdout == INVALID_HANDLE_VALUE { + print("Invalid output handler.", to_standard_error = true); + return; + } + if GetConsoleMode(stdout, *initial_stdout_mode) == false { + print("Failed to get output mode.", to_standard_error = true); + return; + } + if SetConsoleMode(stdout, initial_stdout_mode | ENABLE_PROCESSED_OUTPUT| ENABLE_VIRTUAL_TERMINAL_PROCESSING) == false { + print("Failed to set output mode: %.", GetLastError(), to_standard_error = true); + return; + } +} + +OS_reset_terminal :: () { + print("TODO TUI\n", to_standard_error = true); + + if SetConsoleMode(stdin, initial_stdin_mode) == false { + print("Failed to reset input mode: %.", GetLastError(), to_standard_error = true); + return; + } + + if SetConsoleMode(stdout, initial_stdout_mode) == false { + print("Failed to reset output mode: %.", GetLastError(), to_standard_error = true); + return; + } +} + +OS_get_terminal_size :: () -> rows: int, columns: int { + + ScreenBufferInfo: CONSOLE_SCREEN_BUFFER_INFO; + GetConsoleScreenBufferInfo(stdout, *ScreenBufferInfo); + columns := ScreenBufferInfo.srWindow.Right - ScreenBufferInfo.srWindow.Left + 1; + rows := ScreenBufferInfo.srWindow.Bottom - ScreenBufferInfo.srWindow.Top + 1; + + return rows, columns; +} |
