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(-) 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