diff --git a/CMakeLists.txt b/CMakeLists.txt index ae6998b..8b115c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ set(SOURCES src/widgets/flist.c src/widgets/flog.c src/widgets/gscreen.c + src/widgets/gtable.c # ctype src/ctype/charprops.c src/ctype/classes.c @@ -33,8 +34,11 @@ set(SOURCES src/setjmp/simple.c # signal src/signal/signal.c + # stdio + src/stdio/printf.c # stdlib src/stdlib/arith.c + src/stdlib/exit.c src/stdlib/fpconv.c src/stdlib/intconv.c src/stdlib/sizes.c diff --git a/include/ft/all-tests.h b/include/ft/all-tests.h index 78daeb9..d2f615c 100644 --- a/include/ft/all-tests.h +++ b/include/ft/all-tests.h @@ -23,12 +23,20 @@ extern ft_test ft_setjmp_interrupt; /* signal */ extern ft_test ft_signal_signal; +/* stdio */ +extern ft_test ft_stdio_printf_basics; +extern ft_test ft_stdio_printf_formats; +extern ft_test ft_stdio_printf_fp; + /* stdlib */ extern ft_test ft_stdlib_arith; extern ft_test ft_stdlib_sizes; extern ft_test ft_stdlib_llconv; extern ft_test ft_stdlib_lconv; extern ft_test ft_stdlib_fpconv; +extern ft_test ft_stdlib_exit; +extern ft_test ft_stdlib__Exit; +extern ft_test ft_stdlib_abort; /* string */ extern ft_test ft_string_memset; diff --git a/include/ft/widgets/gtable.h b/include/ft/widgets/gtable.h new file mode 100644 index 0000000..9a2a4ed --- /dev/null +++ b/include/ft/widgets/gtable.h @@ -0,0 +1,125 @@ +//--- +// gintctl.widgets.gtable: gintctl's scrolling tables +//--- + +#ifndef _GINTCTL_WIDGETS_GTABLE +#define _GINTCTL_WIDGETS_GTABLE + +#include +#include + +/* gtable: A dynamic scrolling table + + This widget is a table with a header and a set of rows, that scrolls + vertically to show more rows than there is space available on-screen. + + The columns all have their own width, label, and font. During rendering, the + data is provided to the table through a user-provided function that + generates strings for every column in a chosen row. */ +typedef struct gtable { + jwidget widget; + /* Column details */ + struct gtable_column *meta; + /* Function to generate the strings for a row */ + void (*generator)(struct gtable *g, int row, j_arg_t arg); + j_arg_t arg; + /* Number of columns, number of rows visible on-screen, number of rows, + and current top row */ + uint8_t columns; + uint8_t visible; + uint16_t rows; + uint16_t offset; + /* Row height */ + uint8_t row_height; + /* Additional row spacing */ + uint8_t row_spacing; + + /* Coordinates during rendering */ + int16_t x, y; + +} gtable; + +/* gtable_create(): Create a scrolling table + + The number of columns of the table must be fixed, while the number of rows + can be set and changed later with gtable_set_rows(). + + The generator is a function that will be called during rendering to generate + strings for each row. It should have the following prototype, where the last + argument is an optional object of a type listed in j_arg_t (essentially an + integer or a pointer). + + void generator(gtable *g, int row [, ]) + + The generator should return all the strings for the row by a call to + gtable_provide(). */ +gtable *gtable_create(int columns, void *generator, j_arg_t arg, void *parent); + +/* gtable_provide(): Pass strings to display on a given row + + The strings should be passed as variable arguments. A NULL terminator is + added automatically. The strings will not be used after the call finishes, + so they can be allocated statically or on the stack of the caller. */ +void gtable_provide(gtable *g, ...); +#define gtable_provide(...) gtable_provide(__VA_ARGS__, NULL) + +//--- +// Configuration of columns +//--- + +/* gtable_set_column_title(): Set the title of a column */ +void gtable_set_column_title(gtable *t, int column, char const *title); + +/* gtable_set_column_font(): Set the font of a column */ +void gtable_set_column_font(gtable *t, int column, font_t const *font); + +/* gtable_set_column_size(): Set the size of a column + This function sets the size of a column in relative units. During layout, + the width of the table will be split, and each column will receive space + proportional to their size setting. The default is 1 for all columns. */ +void gtable_set_column_size(gtable *t, int column, uint size); + +/* The previous functions have group-setting functions to set properties for + all columns at once. Simply list the headers/fonts/widths all at once; + columns 0 through the number of args will be assigned. */ + +void gtable_set_column_titles(gtable *t, ...); +#define gtable_set_column_titles(...) \ + gtable_set_column_titles(__VA_ARGS__, NULL) + +void gtable_set_column_sizes(gtable *t, ...); +#define gtable_set_column_sizes(...) \ + gtable_set_column_sizes(__VA_ARGS__, 0) + +void gtable_set_column_fonts(gtable *t, ...); +#define gtable_set_column_fonts(...) \ + gtable_set_column_fonts(__VA_ARGS__, NULL) + +/* gtable_set_font(): Set the font for all columns */ +void gtable_set_font(gtable *t, font_t const *font); + +//--- +// Configuration of rows +//--- + +/* gtable_set_rows(): Set the number of rows */ +void gtable_set_rows(gtable *t, int rows); + +/* gtable_set_row_height(): Fix row height instead of guessing from font + Setting 0 will reset to the default behavior. */ +void gtable_set_row_height(gtable *t, int row_height); + +/* gtable_set_row_spacing(): Set additional row spacing */ +void gtable_set_row_spacing(gtable *t, int row_spacing); + +//--- +// Movement +//--- + +/* gtable_scroll_to(): Scroll to the specified offset (if acceptable) */ +void gtable_scroll_to(gtable *t, int offset); + +/* gtable_end(): Offset of the end of the table */ +int gtable_end(gtable *t); + +#endif /* _GINTCTL_WIDGETS_GTABLE */ diff --git a/src/main.c b/src/main.c index 4ea3013..2bcf5d4 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,5 @@ #include #include -#include #include #include @@ -9,10 +8,12 @@ #include #include +#include + #include #include -/* We don't initialize the test result fields below */ +/* We don't initialize the test result fields in the ft_list objects below */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" @@ -38,13 +39,21 @@ ft_list headers_libc[] = { &ft_signal_signal, NULL, }}, - { "", NULL }, + { "", (ft_test*[]){ + &ft_stdio_printf_basics, + &ft_stdio_printf_formats, + &ft_stdio_printf_fp, + NULL, + }}, { "", (ft_test*[]){ &ft_stdlib_arith, &ft_stdlib_sizes, &ft_stdlib_llconv, &ft_stdlib_lconv, &ft_stdlib_fpconv, + &ft_stdlib_exit, + &ft_stdlib__Exit, + &ft_stdlib_abort, NULL, }}, { "", (ft_test*[]){ @@ -78,7 +87,8 @@ int main(void) if(isSH4()) __cpucap |= __CPUCAP_SH4ALDSP; - kprint_enable_fp(); + __printf_enable_fp(); + __printf_enable_fixed(); // Initialize test results for(int i = 0; headers_libc[i].name; i++) @@ -96,6 +106,7 @@ int main(void) fbrowser_set_headers(browser, headers_libc, false); jwidget *results = jwidget_create(NULL); + jlayout_set_stack(results); gscreen_add_tab(scr, results, NULL); gscreen_set_tab_fkeys_visible(scr, 1, false); diff --git a/src/stdio/printf.c b/src/stdio/printf.c new file mode 100644 index 0000000..6da25cb --- /dev/null +++ b/src/stdio/printf.c @@ -0,0 +1,340 @@ +#include +#include +#include +#include +#include +#include + +#include + +#define NAN __builtin_nan("") +#define INFINITY __builtin_inf() + +struct printf_test { + char const *format; + + /* Argument information is set by macros below */ + enum { + TYPE_I32, TYPE_U32, TYPE_STR, TYPE_PTR, TYPE_U64, TYPE_DBL + } type; + union { + int i32; + uint32_t u32; + char const *str; + void *ptr; + uint64_t u64; + double dbl; + }; + char const *argument_as_string; + char const *solution; +}; + +struct printf_result { + char answer[15]; + uint8_t passed; +}; + +#define I32(i) TYPE_I32, { .i32 = i }, #i +#define U32(u) TYPE_U32, { .u32 = u }, #u +#define STR(s) TYPE_STR, { .str = s }, #s +#define PTR(p) TYPE_PTR, { .ptr = (void *)p }, #p +#define U64(u) TYPE_U64, { .u64 = u }, #u +#define DBL(d) TYPE_DBL, { .dbl = d }, #d + +static void run_tests(ft_test *t0, struct printf_test const *tests, + struct printf_result *results) +{ + for(int i = 0; tests[i].format; i++) + { + struct printf_test const *t = &tests[i]; + struct printf_result *r = &results[i]; + int c = -1; + + #define run(TYPE, field) \ + case TYPE: \ + c = snprintf(r->answer, 16, t->format, t->field); \ + break; + + switch(t->type) + { + run(TYPE_I32, i32) + run(TYPE_U32, u32) + run(TYPE_STR, str) + run(TYPE_PTR, ptr) + run(TYPE_U64, u64) + run(TYPE_DBL, dbl) + } + + r->passed = (!strcmp(r->answer, t->solution) && + c == (int)strlen(t->solution)); + ft_assert(t0, r->passed == 1); + } +} + +struct table_params { + struct printf_test const *test; + struct printf_result const *result; +}; + +static void table_gen(gtable *t0, int row, void *params0) +{ + struct table_params *params = params0; + struct printf_test const *t = params->test; + struct printf_result const *r = params->result; + + char c1[16]; + sprintf(c1, "%d", row+1); + gtable_provide(t0, c1, t[row].format, t[row].solution, r[row].answer); +} + +static jwidget *widget_gen(struct printf_test const *tests, + struct printf_result const *results) +{ + if(!results) return NULL; + static struct table_params p; + p.test = tests; + p.result = results; + + int test_count = 0; + while(tests[test_count].format) test_count++; + + gtable *t = gtable_create(4, table_gen, &p, NULL); + gtable_set_rows(t, test_count); + gtable_set_column_titles(t, "ID", "Format", "Expected", "Got"); + gtable_set_column_sizes(t, 3, 8, 16, 16); + gtable_set_row_spacing(t, 2); + jwidget_set_stretch(t, 1, 1, false); + + return (void *)t; +} + +//--- +// General features +//--- + +static void _ft_stdio_printf_basics(ft_test *t) +{ + char str[32]; + int n; + + /* Basic output */ + n = snprintf(str, 32, "test"); + ft_assert(t, n == 4 && !strcmp(str, "test")); + + /* Truncated output */ + n = snprintf(str, 8, "12345678901234"); + ft_assert(t, n == 14 && !strcmp(str, "1234567")); + + /* Parameter size */ + n = snprintf(str, 32, "%hhx", 0x123); + ft_assert(t, n == 2 && !strcmp(str, "23")); + + /* Truncated output in the middle of a format */ + n = snprintf(str, 5, "%hd", 100000); + ft_assert(t, n == 6 && !strcmp(str, "-310")); + + /* Percent format */ + n = snprintf(str, 32, "%%d"); + ft_assert(t, n == 2 && !strcmp(str, "%d")); + + /* Nonexistent formats */ + n = snprintf(str, 32, "a%.7Z"); + ft_assert(t, n == 1 && !strcmp(str, "a")); + + /* Extension */ + n = snprintf(str, 32, "%.4D", 1234567); + ft_assert(t, n == 8 && !strcmp(str, "123.4567")); +} + +ft_test ft_stdio_printf_basics = { + .name = "printf: Basics options", + .function = _ft_stdio_printf_basics, +}; + +//--- +// Usual formats +//--- + +static struct printf_test const usual_tests[] = { + /* Base cases with length and precision */ + { "%d", I32(-849), "-849" }, + { "%7i", I32(78372), " 78372" }, + { "%3d", I32(65536), "65536" }, + { "%6.4d", I32(17), " 0017" }, + { "%6.3d", I32(-1876), " -1876" }, + { "%.0d", I32(0), "" }, + { "%.d", I32(0), "" }, + /* Sign */ + { "%+i", I32(15), "+15" }, + { "% 7i", I32(78372), " 78372" }, + { "% d", I32(65536), " 65536" }, + /* Alignment */ + { "%08d", I32(-839), "-0000839" }, + { "%-6.4d", I32(17), "0017 " }, + { "%-+6.4i", I32(17), "+0017 " }, + /* Bases */ + { "%d", I32(0xcb7), "3255" }, + { "%x", U32(0xcb7), "cb7" }, + { "%X", U32(0xcb7), "CB7" }, + { "%o", U32(0xcb7), "6267" }, + /* Argument size */ + { "%zu", U32(1000000000), "1000000000" }, + { "%td", I32(99999), "99999" }, + { "%lld", U64(10000000000ll), "10000000000" }, + { "%llx", U64(0x123456789abull), "123456789ab" }, + { "%jx", U64(0x123456789abull), "123456789ab" }, + /* Alternative prefixes */ + { "%#x", U32(0x7b), "0x7b" }, + { "%#X", U32(0x7b), "0X7B" }, + { "%#o", U32(255), "0377" }, + /* Pointers */ + { "%p", PTR(0xa44b0000), "0xa44b0000" }, + /* Characters and strings */ + { "%s", STR("HellWrld!"), "HellWrld!" }, + { "%-8.5s", STR("Hello, World!"), "Hello " }, + { "%c", I32(100), "d" }, + { "%6c", I32('#'), " #", }, + /* NULL terminator */ + { NULL } +}; + +static struct printf_result *usual_results = NULL; + +static void _ft_stdio_printf_formats(ft_test *t) +{ + int test_count = sizeof usual_tests / sizeof usual_tests[0] - 1; + + if(usual_results) free(usual_results); + usual_results = malloc(test_count * sizeof *usual_results); + if(!ft_assert(t, usual_results != NULL)) return; + + run_tests(t, usual_tests, usual_results); +} + + +static jwidget *_ft_stdio_printf_formats_widget(ft_test *test) +{ + (void)test; + return widget_gen(usual_tests, usual_results); +} + +ft_test ft_stdio_printf_formats = { + .name = "printf: Usual formats", + .function = _ft_stdio_printf_formats, + .widget = _ft_stdio_printf_formats_widget, +}; + +//--- +// Floating-point formats +//--- + +static struct printf_test const fp_tests[] = { + /* Floating-point special values */ + { "%f", DBL(NAN), "nan" }, + { "%3.5F", DBL(NAN), "NAN" }, + { "%+2F", DBL(-INFINITY), "-INF" }, + { "%10G", DBL(INFINITY), "INF" }, + { "%+g", DBL(INFINITY), "inf" }, + { "%7.3e", DBL(-INFINITY), "-inf" }, + { "%8E", DBL(NAN), "NAN" }, + /* Simple floating-point cases */ + { "%f", DBL(13e3), "13000.000000" }, + { "%.3f", DBL(-13e3), "-13000.000" }, + { "%.F", DBL(13e3), "13000" }, + { "%f", DBL(-12.42), "-12.420000" }, + { "%.0f", DBL(-12.42), "-12", }, + { "%.1f", DBL(12.42), "12.4", }, + { "%.8F", DBL(12.42), "12.42000000" }, + { "%F", DBL(0.0312), "0.031200" }, + { "%.4f", DBL(0.0312), "0.0312" }, + { "%.2f", DBL(-0.0312), "-0.03", }, + { "%.0f", DBL(0.0312), "0", }, + /* Floating-point rounding */ + { "%.f", DBL(1.75), "2", }, + { "%.1f", DBL(1.75), "1.8", }, + { "%.3F", DBL(0.0625), "0.063", }, + { "%.1F", DBL(-99.99), "-100.0" }, + { "%.2F", DBL(12999.992), "12999.99" }, + { "%.2f", DBL(12999.995), "13000.00" }, + /* General options with floating-point */ + { "%09.3F", DBL(123.4567), "00123.457" }, + { "%05.0f", DBL(99.99), "00100" }, + { "%+11f", DBL(0.0035678), " +0.003568" }, + { "%- 11F", DBL(0.0035678), " 0.003568 " }, + /* Simple exponent cases */ + { "%e", DBL(3.876), "3.876000e+00" }, + { "%E", DBL(-38473.34254), "-3.847334E+04" }, + { "%.2e", DBL(187.2), "1.87e+02" }, + { "%.1e", DBL(-18.27), "-1.8e+01" }, + { "%e", DBL(1e-10), "1.000000e-10" }, + { "%E", DBL(3.873e180), "3.873000E+180" }, + { "%.e", DBL(0.0005), "5e-04" }, + { "%.E", DBL(128.37), "1E+02" }, + { "%.5e", DBL(912.3), "9.12300e+02" }, + /* Exponent with rounding and general options */ + { "%11.3e", DBL(12.499), " 1.250e+01" }, + { "% -11.E", DBL(358.7), " 4E+02 " }, + { "%+e", DBL(14.99999), "+1.499999e+01" }, + { "%+e", DBL(14.999999), "+1.500000e+01" }, + /* Exponent sizes */ + { "%.2e", DBL(1e10), "1.00e+10" }, + { "%.2e", DBL(1e100), "1.00e+100" }, + { "%.2e", DBL(1e-10), "1.00e-10" }, + { "%.2e", DBL(1e-100), "1.00e-100" }, + /* Format selection and trailing zero elimination in %g */ + { "%g", DBL(124), "124" }, + { "%g", DBL(2.3), "2.3" }, + { "%g", DBL(0.0001), "0.0001" }, + { "%G", DBL(0.00001), "1E-05" }, + { "%g", DBL(834e-93), "8.34e-91" }, + { "%g", DBL(32842914732), "3.28429e+10" }, + { "%g", DBL(123456), "123456" }, + { "%G", DBL(1234567), "1.23457E+06" }, + { "%G", DBL(123000), "123000" }, + /* Rounding and general options in %g */ + { "%.3g", DBL(1278), "1.28e+03" }, + { "%+.8g", DBL(1.23e5), "+123000" }, + { "%- 12.8g", DBL(123000.01), " 123000.01 " }, + { "%0.8g", DBL(123000.001), "123000" }, + { "%08.8g", DBL(123000.001), "00123000" }, + { "%g", DBL(1.234567), "1.23457" }, + { "%.1g", DBL(1.8), "2" }, + /* Edge cases of significant digit count in %g */ + { "%.4g", DBL(999.93), "999.9" }, + { "%.4g", DBL(999.97), "1000" }, + { "%.5g", DBL(999.97), "999.97" }, + { "%.8G", DBL(999.97), "999.97" }, + { "%.4g", DBL(1.0001), "1", }, + /* Elimination of trailing zeros in %g */ + { "%.3g", DBL(3002), "3e+03" }, + { "%.5g", DBL(0.00000034), "3.4e-07" }, + { "%.6g", DBL(999.9997), "1000" }, + /* NULL terminator */ + { NULL } +}; + +static struct printf_result *fp_results = NULL; + +static void _ft_stdio_printf_fp(ft_test *t) +{ + int test_count = sizeof fp_tests / sizeof fp_tests[0] - 1; + + if(fp_results) free(fp_results); + fp_results = malloc(test_count * sizeof *fp_results); + if(!ft_assert(t, fp_results != NULL)) return; + + run_tests(t, fp_tests, fp_results); +} + + +static jwidget *_ft_stdio_printf_fp_widget(ft_test *test) +{ + (void)test; + return widget_gen(fp_tests, fp_results); +} + +ft_test ft_stdio_printf_fp = { + .name = "printf: Floating-point formats", + .function = _ft_stdio_printf_fp, + .widget = _ft_stdio_printf_fp_widget, +}; diff --git a/src/stdlib/exit.c b/src/stdlib/exit.c new file mode 100644 index 0000000..0873282 --- /dev/null +++ b/src/stdlib/exit.c @@ -0,0 +1,39 @@ +#include +#include +#include + +static jwidget *_ft_stdlib_exit(GUNUSED ft_test *t) +{ + exit(EXIT_SUCCESS); + return NULL; +} + +ft_test ft_stdlib_exit = { + .name = "Press F5: exit()", + .function = NULL, + .widget = _ft_stdlib_exit, +}; + +static jwidget *_ft_stdlib__Exit(GUNUSED ft_test *t) +{ + _Exit(EXIT_SUCCESS); + return NULL; +} + +ft_test ft_stdlib__Exit = { + .name = "Press F5: _Exit()", + .function = NULL, + .widget = _ft_stdlib__Exit, +}; + +static jwidget *_ft_stdlib_abort(GUNUSED ft_test *t) +{ + abort(); + return NULL; +} + +ft_test ft_stdlib_abort = { + .name = "Press F5: abort()", + .function = NULL, + .widget = _ft_stdlib_abort, +}; diff --git a/src/widgets/fbar.c b/src/widgets/fbar.c index 0974af6..4233e35 100644 --- a/src/widgets/fbar.c +++ b/src/widgets/fbar.c @@ -115,9 +115,15 @@ static void fbar_poly_render(void *b0, int x, int y) bool guaranteed = (w > guaranteeable_min); if(guaranteed) w -= guaranteeable_min; + px.done = (st.done * w) / main; px.pending = (st.pending * w) / main; px.empty = (st.empty * w) / main; - px.done = w - px.pending - px.empty; + int rest = w - px.done - px.pending - px.empty; + if(rest > 0) { + if(st.done) px.done += rest; + else if(st.pending) px.pending += rest; + else if(st.empty) px.empty += rest; + } if(guaranteed) { if(st.done) px.done += 10; if(st.pending) px.pending += 10; diff --git a/src/widgets/gtable.c b/src/widgets/gtable.c new file mode 100644 index 0000000..4c85dc2 --- /dev/null +++ b/src/widgets/gtable.c @@ -0,0 +1,352 @@ +#include +#include +#include +#include + +struct gtable_column { + char const *title; + font_t const *font; + uint16_t size; + uint16_t width; +}; + +/* Type identifier for gtable */ +static int gtable_type_id = -1; + +void update_visible(gtable *t); + +gtable *gtable_create(int columns, void *generator, j_arg_t arg, void *parent) +{ + if(gtable_type_id < 0) return NULL; + + gtable *t = malloc(sizeof *t); + if(!t) return NULL; + + t->meta = malloc(columns * sizeof *t->meta); + if(!t->meta) { free(t); return NULL; } + + jwidget_init(&t->widget, gtable_type_id, parent); + + t->generator = generator; + t->arg = arg; + + t->columns = columns; + t->rows = 0; + t->offset = 0; + t->visible = 0; + + t->row_height = 0; + t->row_spacing = 1; + + for(uint i = 0; i < t->columns; i++) { + t->meta[i].title = NULL; + t->meta[i].font = dfont_default(); + t->meta[i].width = 1; + } + + return t; +} + +//--- +// Configuration of columns +//--- + +void gtable_set_column_title(gtable *t, int column, char const *title) +{ + if(column < 0 || column >= t->columns) return; + t->meta[column].title = title; + t->widget.update = 1; +} + +void gtable_set_column_font(gtable *t, int column, font_t const *font) +{ + if(column < 0 || column >= t->columns) return; + t->meta[column].font = (font ? font : dfont_default()); + t->widget.update = 1; +} + +void gtable_set_column_size(gtable *t, int column, uint size) +{ + if(column < 0 || column >= t->columns) return; + t->meta[column].size = size; + t->widget.dirty = 1; +} + +#undef gtable_set_column_titles +void gtable_set_column_titles(gtable *t, ...) +{ + va_list args; + va_start(args, t); + + for(uint i = 0; i < t->columns; i++) + { + char const *title = va_arg(args, char const *); + if(!title) break; + gtable_set_column_title(t, i, title); + } + + va_end(args); +} + +#undef gtable_set_column_fonts +void gtable_set_column_fonts(gtable *t, ...) +{ + va_list args; + va_start(args, t); + + for(uint i = 0; i < t->columns; i++) + { + font_t const *font = va_arg(args, font_t const *); + if(!font) break; + gtable_set_column_font(t, i, font); + } + + va_end(args); +} + +#undef gtable_set_column_sizes +void gtable_set_column_sizes(gtable *t, ...) +{ + va_list args; + va_start(args, t); + + for(uint i = 0; i < t->columns; i++) + { + uint size = va_arg(args, uint); + if(!size) break; + gtable_set_column_size(t, i, size); + } + + va_end(args); +} + +void gtable_set_font(gtable *t, font_t const *font) +{ + for(uint i = 0; i < t->columns; i++) { + gtable_set_column_font(t, i, font); + } +} + +//--- +// Configuration of rows +//--- + +void gtable_set_rows(gtable *t, int rows) +{ + /* The re-layout will make sure we stay in the visible range */ + t->rows = max(rows, 0); + t->widget.dirty = 1; +} + +void gtable_set_row_height(gtable *t, int row_height) +{ + t->row_height = row_height; + t->visible = 0; + t->widget.dirty = 1; +} + +void gtable_set_row_spacing(gtable *t, int row_spacing) +{ + t->row_spacing = row_spacing; + t->visible = 0; + t->widget.dirty = 1; +} + +//--- +// Movement +//--- + +/* Guarantee that the current positioning is valid */ +static void bound(gtable *t) +{ + t->offset = min(t->offset, gtable_end(t)); +} + +void gtable_scroll_to(gtable *t, int offset) +{ + t->offset = offset; + bound(t); +} + +int gtable_end(gtable *t) +{ + update_visible(t); + return max((int)t->rows - (int)t->visible, 0); +} + +//--- +// Polymorphic widget operations +//--- + +int compute_row_height(gtable *t) +{ + int row_height = t->row_height; + if(row_height == 0) { + for(int i = 0; i < t->columns; i++) { + row_height = max(row_height, t->meta[i].font->line_height); + } + } + return row_height; +} + +/* Recompute (visible) based on the current size */ +void update_visible(gtable *t) +{ + if(t->visible == 0) { + int row_height = compute_row_height(t); + int header_height = row_height + t->row_spacing + 1; + + int space = jwidget_content_height(t) - header_height; + t->visible = max(0, space / (row_height + t->row_spacing)); + } +} + +static void gtable_poly_csize(void *t0) +{ + gtable *t = t0; + t->widget.w = 64; + t->widget.h = 32; +} + +static void gtable_poly_layout(void *t0) +{ + gtable *t = t0; + t->visible = 0; + update_visible(t); + bound(t); + + int cw = jwidget_content_width(t); + /* Leave space for a scrollbar */ + if(t->visible < t->rows) cw -= 4; + + /* Compute the width of each column */ + uint total_size = 0; + for(uint i = 0; i < t->columns; i++) { + total_size += t->meta[i].size; + } + + uint space_left = cw; + for(uint i = 0; i < t->columns; i++) { + t->meta[i].width = (t->meta[i].size * cw) / total_size; + space_left -= t->meta[i].width; + } + + /* Grant space left to the first columns */ + for(uint i = 0; i < t->columns && i < space_left; i++) { + t->meta[i].width++; + } +} + +static void gtable_poly_render(void *t0, int base_x, int base_y) +{ + gtable *t = t0; + int row_height = compute_row_height(t); + int cw = jwidget_content_width(t); + int y = base_y; + + for(uint i=0, x=base_x; i < t->columns; i++) { + font_t const *old_font = dfont(t->meta[i].font); + dtext(x, y, C_BLACK, t->meta[i].title); + dfont(old_font); + + x += t->meta[i].width; + } + + y += row_height + t->row_spacing; + dline(base_x, y, base_x + cw - 1, y, C_BLACK); + y += t->row_spacing + 1; + int rows_y = y; + + for(uint i = 0; t->offset + i < t->rows && i < t->visible; i++) { + t->x = base_x; + t->y = y; + + t->generator(t, t->offset + i, t->arg); + y += row_height + t->row_spacing; + } + + /* Scrollbar */ + if(t->visible < t->rows) { + /* Area where the scroll bar lives */ + int area_w = 2; + int area_x = base_x + cw - area_w; + int area_y = rows_y; + int area_h = jwidget_content_height(t) - (area_y - base_y); + + /* Position and size of scroll bar within the area */ + int bar_y = (t->offset * area_h + t->rows/2) / t->rows; + int bar_h = (t->visible * area_h + t->rows/2) / t->rows; + + drect(area_x, area_y + bar_y, area_x + area_w - 1, + area_y + bar_y + bar_h, C_BLACK); + } +} + +#undef gtable_provide +void gtable_provide(gtable *t, ...) +{ + va_list args; + va_start(args, t); + + for(uint i = 0; i < t->columns; i++) { + char const *str = va_arg(args, char const *); + if(!str) break; + + font_t const *old_font = dfont(t->meta[i].font); + dtext(t->x, t->y, C_BLACK, str); + dfont(old_font); + + t->x += t->meta[i].width; + } + + va_end(args); +} + +static bool gtable_poly_event(void *t0, jevent e) +{ + gtable *t = t0; + int end = gtable_end(t); + update_visible(t); + + if(e.type != JWIDGET_KEY) return false; + + if(e.key.type == KEYEV_UP) return false; + + if(e.key.key == KEY_DOWN && t->offset < end) { + if(e.key.shift) t->offset = end; + else t->offset++; + t->widget.update = 1; + return true; + } + if(e.key.key == KEY_UP && t->offset > 0) { + if(e.key.shift) t->offset = 0; + else t->offset--; + t->widget.update = 1; + return true; + } + + return false; +} + +static void gtable_poly_destroy(void *t0) +{ + gtable *t = t0; + free(t->meta); +} + +/* gtable type definition */ +static jwidget_poly type_gtable = { + .name = "gtable", + .csize = gtable_poly_csize, + .layout = gtable_poly_layout, + .render = gtable_poly_render, + .event = gtable_poly_event, + .destroy = gtable_poly_destroy, +}; + +/* Type registration */ +__attribute__((constructor(2000))) +static void j_register_gtable(void) +{ + gtable_type_id = j_register_widget(&type_gtable, "jwidget"); +}