From 9a9c366821204eb6c0a4923fa7faf9b955408ee9 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Thu, 11 Mar 2021 18:17:15 +0100 Subject: [PATCH] libs/justui: add a demo of the JustUI widget library --- CMakeLists.txt | 8 +- assets-fx/fonts/fxconv-metadata.txt | 7 + assets-fx/fonts/title.png | Bin 0 -> 17426 bytes assets-fx/img/opt_libs_jui.png | Bin 0 -> 1768 bytes include/gintctl/libs.h | 3 + include/gintctl/widgets/gscreen.h | 92 ++++++++ include/gintctl/widgets/gtable.h | 125 ++++++++++ src/gintctl.c | 2 + src/libs/justui.c | 175 ++++++++++++++ src/widgets/gscreen.c | 147 ++++++++++++ src/widgets/gtable.c | 354 ++++++++++++++++++++++++++++ 11 files changed, 912 insertions(+), 1 deletion(-) create mode 100644 assets-fx/fonts/title.png create mode 100644 assets-fx/img/opt_libs_jui.png create mode 100644 include/gintctl/widgets/gscreen.h create mode 100644 include/gintctl/widgets/gtable.h create mode 100644 src/libs/justui.c create mode 100644 src/widgets/gscreen.c create mode 100644 src/widgets/gtable.c diff --git a/CMakeLists.txt b/CMakeLists.txt index c007d94..5f98d48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ include(Fxconv) find_package(Gint 2.1 REQUIRED) find_package(LibProf 2.1 REQUIRED) find_package(LibImg 2.1 REQUIRED) +find_package(JustUI 1.0 REQUIRED) set(SOURCES src/gintctl.c @@ -32,6 +33,7 @@ set(SOURCES src/gint/timer_callbacks.c src/gint/tlb.c src/gint/topti.c + src/libs/justui.c src/libs/libimg.c src/libs/memory.c src/libs/openlibm.c @@ -46,10 +48,13 @@ set(SOURCES src/perf/memory.s src/perf/render.c src/regs/regs.c + src/widgets/gscreen.c + src/widgets/gtable.c ) set(ASSETS_fx assets-fx/fonts/hexa.png assets-fx/fonts/mini.png + assets-fx/fonts/title.png assets-fx/fonts/uf5x7 assets-fx/img/bopti_1col.png assets-fx/img/bopti_2col.png @@ -69,6 +74,7 @@ set(ASSETS_fx assets-fx/img/opt_gint_timer_callbacks.png assets-fx/img/opt_gint_timers.png assets-fx/img/opt_gint_tlb.png + assets-fx/img/opt_libs_jui.png assets-fx/img/opt_main.png assets-fx/img/opt_mem.png assets-fx/img/opt_perf_libprof.png @@ -117,7 +123,7 @@ target_include_directories(gintctl PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include" "${FXSDK_COMPILER_INSTALL}/include/openlibm") target_link_libraries(gintctl - Gint::Gint LibProf::LibProf LibImg::LibImg -lopenlibm) + Gint::Gint LibProf::LibProf LibImg::LibImg JustUI::JustUI -lopenlibm) if("${FXSDK_PLATFORM_LONG}" STREQUAL fx9860G) generate_g1a(TARGET gintctl OUTPUT "gintctl.g1a" diff --git a/assets-fx/fonts/fxconv-metadata.txt b/assets-fx/fonts/fxconv-metadata.txt index 1ada7a3..74437e4 100644 --- a/assets-fx/fonts/fxconv-metadata.txt +++ b/assets-fx/fonts/fxconv-metadata.txt @@ -14,6 +14,13 @@ mini.png: grid.size: 5x6 grid.padding: 1 +title.png: + type: font + name: font_title + charset: print + grid.size: 5x6 + grid.padding: 1 + uf5x7: type: font name: font_uf5x7 diff --git a/assets-fx/fonts/title.png b/assets-fx/fonts/title.png new file mode 100644 index 0000000000000000000000000000000000000000..edf81391d904d11a8e58f80e0bf9f5700644e28b GIT binary patch literal 17426 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

O4d`l$FJg2%}j$p-xM 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

#~eO@`-4A}J~+|5gH=yr zu8xT}>G2RQ-V$#@v3ddg1F+4&M$^w^RZJt+<;tifWpds2PoP7&!Hr72;hy?Ng(&U9cg;PNq}Aw3CP`n@cPVluKpOvJz*)$1(gr1LN1@P;e8I$%mT5Q+H{yJty;_ z!v%<)58XKQ=klSCCNjQ_;qX=>MhIc(c^&T`J!r(MKQ2o3^p@8jEHRDdXHH&EFR4hD zkwFNcKW%Yu-vOYc?aguCoJHxRB z17;El;skRTR`QuTK*_=n@&4HP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O1b@vgEo6{qI#|2?7L2%aIUNWd~V)j@)a<)frX! zscv-x4E8DNGsW{G3El@V1>MxFAsEKl>3QcAcX3RH+=#Aaeg1qFT@HM)EDM~0$1Cg5L@Y| zw&WFnV4ULm(HHr-tnsUoj=2@dkbr__BuYljnVqN$4-mAFIZNja21u0;lH@L8;ATKa zm>cgny<=JV^Bh(IKPn~;Zl)}d)w@DJ-Ul?aRJ5zAsq4_G>oKuzYG!WNbIZlMtDCzI zpT35SK6KddF~%Hg@)TH7r%j(@&bb!mT>V+P@bptX|frDf$cYS#K@?TgiaJO2yT=!-RenY!cr zn>9Rjzk%Kmbo-26&cGOV0^@!efS~zu)&}*?m$}PXn+#VdBT2p79BS(!1BE?_wdk9> zpUnMb-X386XT0%WVeYxq{U4a)C*=>k{f4!3J7K(xeO!1kZ36d!52`%C=Gl2ng38X? zwq~8_20Pm+xLPtWs-~l`fvDuD37$?_=7Kjy+q>Gbd7fk`j9CDL^Xw3>>+|8KT8U$t zQV5O_B!Uqc)$@w$(O2OPxJIG@G1TbGlnTXHWE8 zNECq(vssN(Z@pf9;@yT?zmi*$#|gR40CMoyO{#P$p9|nJbOa#HPNd!bloseA~PtVw@T{MgLbxY8A+<6C_M}} zYZ?HsTY4bdXU@C47!c)Hh!@jNC>#ZjDM}aCn+X^N0kLpVh17yxec)y*hoW8tKt>=< z*akFH^ESKdEIv@R$)GTqxdCB^UdnrJ0($5`=s48v7Wv8TDFdsaM;+@JxS0SLIPH1P zq`__#DKlVIUAgaz5Y>xfpv%00(0GC#OavhXU#hYA?i|PyvbiX1U{&wSyd%!W6cdg} zaPI<0kMHA9Two=5unK22Am8TE-REIQ8C9AfKS>2%2bgAV>>4CCx>a$X*$)&mA(~iM zH1WWCiAM7VxtckHFAs2TyzuozugKWhBcS(Tpw3&2>@DnO)M*_El3|0qLxes=1p>T0W4MhrelN<2YNZ1q_0=W*5GUj^&H)6vTTNSWF z4(LrWQMbZtF;Uza$uRTnY2OxsbViqJk(FN-fica0SY&-HawCL3*a#&o(n$73+29Ex zJaJWM*CTIt$^`C+-I2Q<0D#BaB`zm<$`uwt1BTwGj6GlpFTTr^p?r@e11ZR{Ey*9x zyfB+PLug@)@I99$}Y>+C@o!!Q8UDmO(IN~{q4xmhc zVsw=MY$@Hlp=I47AT9_AmUocfVqyW{wWMLqNvt#I)#t);uW}m?Fww- +#include +#include +#include +#include + +struct gscreen_tab; + +/* gscreen: gintctl's standard screen. + + The gscreen is a scene with a stylized title bar, an optional function key + bar at the bottom, and a stacked layout of tabs in the middle. Each tab can + have specific properties set, and the gscreen handles some of the focus + management caused by hiding and showing a lot of widgets in tabs. + + The scene of a gscreen can be accessed with (the_gscreen->scene). */ +typedef struct { + jscene *scene; + /* Number of tabs */ + int tab_count; + /* Information on tabs */ + struct gscreen_tab *tabs; + /* Fixed widets */ + jlabel *title; + jfkeys *fkeys; + +} gscreen; + +struct gscreen_tab { + /* TODO: gscreen: Hide title bar and/or status bar */ + bool title_visible, fkeys_visible; + /* Most recent focused widget one */ + jwidget *focus; +}; + +/* gscreen_create(): Set up a standard scene. + If (title = NULL), the scene has no title bar, if (fkeys = NULL) it has no + function bar. To show a title/function bar on some tabs but not all, create + one here and use gscreen_set_tab_{title,fkeys}_visible(). */ + +#ifdef FX9860G +gscreen *gscreen_create(char const *title, bopti_image_t const *fkeys); +#define gscreen_create2(short, img, long, fkeys) gscreen_create(short, img) +#endif + +#ifdef FXCG50 +gscreen *gscreen_create(char const *title, char const *fkeys); +#define gscreen_create2(short, img, long, fkeys) gscreen_create(long, fkeys) +#endif + +/* gscreen_add_tab(): Add a tab to the stacked layout + The child's parent will be changed. The widget will also have a stretch of + (1, 1, false) set by default. If not NULL, the last parameter indicates + which widget to focus when the tab is first shown. */ +void gscreen_add_tab(gscreen *scene, void *widget, void *focus); + +/* gcreen_add_tabs(): Add several tabs at once, with no special parameters */ +void gscreen_add_tabs(gscreen *s, ...); +#define gscreen_add_tabs(...) gscreen_add_tabs(__VA_ARGS__, NULL) + +/* gscreen_show_tab(): Show a tab from the stack + Returns true if the tab changed, false if it was already active or if it is + an invalid tab widget. */ +bool gscreen_show_tab(gscreen *s, int tab); + +/* gscreen_current_tab(): Give the currently visible tab */ +int gscreen_current_tab(gscreen *s); + +/* gscreen_in(): Check if we're in a specific tab */ +bool gscreen_in(gscreen *s, int tab); + +/* gscreen_set_tab_title_visible(): Set whether title bar is shown on a tab */ +void gscreen_set_tab_title_visible(gscreen *s, int tab, bool visible); + +/* gscreen_set_tab_fkeys_visible(): Set whether fkeys are shown on a tab */ +void gscreen_set_tab_fkeys_visible(gscreen *s, int tab, bool visible); + +//--- +// Focus management +//--- + +/* Set focus for the current tab */ +void gscreen_focus(gscreen *s, void *widget); + +#endif /* _GINTCTL_WIDGETS_GSCREEN */ diff --git a/include/gintctl/widgets/gtable.h b/include/gintctl/widgets/gtable.h new file mode 100644 index 0000000..9a2a4ed --- /dev/null +++ b/include/gintctl/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/gintctl.c b/src/gintctl.c index 71bc1ed..b521dbb 100644 --- a/src/gintctl.c +++ b/src/gintctl.c @@ -92,6 +92,8 @@ struct menu menu_libs = { gintctl_libs_openlibm, 0 }, { "libimg" _("",": Image transforms"), gintctl_libs_libimg, 0 }, + { "JustUI widgets", + gintctl_libs_justui, 0 }, { NULL, NULL, 0 }, }}; diff --git a/src/libs/justui.c b/src/libs/justui.c new file mode 100644 index 0000000..4144016 --- /dev/null +++ b/src/libs/justui.c @@ -0,0 +1,175 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static int recursive_widget_count(void *w0) +{ + J_CAST(w) + + int total = 1; + for(int k = 0; k < w->child_count; k++) + total += recursive_widget_count(w->children[k]); + + return total; +} + +static int nth_child(void *w0, int n, int l, jwidget **result, int *level) +{ + J_CAST(w) + if(n-- == 0) + { + *result = w; + *level = l; + return n; + } + + for(int k = 0; k < w->child_count && n >= 0; k++) + n = nth_child(w->children[k], n, l+1, result, level); + + return n; +} + +static void widget_tree_gen(gtable *t, int row, jwidget *root) +{ + jwidget *w = NULL; + int indent; + nth_child(root, row, 0, &w, &indent); + if(!w) return; + + char c1[32], c2[16], c3[16]; + for(int i = 0; i < indent; i++) c1[i] = ' '; + sprintf(c1 + indent, "%s", jwidget_type(w)); + sprintf(c2, "%dx%d", jwidget_full_width(w), jwidget_full_height(w)); + sprintf(c3, "%dx%d", jwidget_content_width(w), jwidget_content_height(w)); + + gtable_provide(t, c1, c2, c3); +} + +static void paint_pattern(int x, int y) +{ + for(int dx = 0; dx < 12; dx++) + for(int dy = 0; dy < 12; dy++) + { + if(((x + dx) ^ (y + dy)) & 1) dpixel(x + dx, y + dy, C_BLACK); + } +} + +static void table_gen(gtable *t, int row) +{ + char c1[16], c2[16], c3[16]; + sprintf(c1, "%d:1", row); + sprintf(c2, "%d:2", row); + sprintf(c3, "%d:%d", row, t->visible); + gtable_provide(t, c1, c2, c3); +} + +/* gintctl_libs_justui(): Just User Interfaces */ +void gintctl_libs_justui(void) +{ + gscreen *scr = gscreen_create2("JustUI Widgets", &img_opt_libs_jui, + "JustUI graphical interfaces", "/SCENE;/TREE;;;;"); + + // Sample GUI + + jwidget *tab1 = jwidget_create(NULL); + jlayout_set_vbox(tab1)->spacing = _(1,2); + + jwidget *c = jwidget_create(tab1); + jlabel *c1 = jlabel_create("", c); + jpainted_create(paint_pattern, NULL, 12, 12, c); + gtable *c3 = gtable_create(3, table_gen, NULL, c); + + jwidget_set_border(c, J_BORDER_SOLID, 1, C_BLACK); + jwidget_set_padding(c, 1, 1, 1, 1); + jwidget_set_stretch(c, 1, 1, false); + jlayout_set_hbox(c)->spacing = _(1,2); + + jlabel_set_font(c1, _(&font_uf5x7, dfont_default())); + jlabel_set_alignment(c1, J_ALIGN_CENTER); + jlabel_asprintf(c1, "Test\nlĪ±bel\nxy=%d,%d", 7, 8); + jwidget_set_border(c1, J_BORDER_SOLID, 1, C_BLACK); + jwidget_set_stretch(c1, 1, 1, false); + + jwidget_set_stretch(c3, 2, 0, false); + gtable_set_rows(c3, 5); + gtable_set_column_titles(c3, "C1", "C2", "Column 3"); + gtable_set_column_sizes(c3, 1, 1, 3); + gtable_set_font(c3, &font_mini); + + jinput *input = jinput_create("Prompt:" _(," "), 12, tab1); + jwidget_set_stretch(input, 1, 0, false); + jinput_set_font(input, _(&font_uf5x7, dfont_default())); + + // Widget tree visualisation + + gtable *tree = gtable_create(3, widget_tree_gen, scr->scene, NULL); + gtable_set_column_titles(tree, "Type", "Size", "Content"); + gtable_set_column_sizes(tree, 3, 2, 2); + gtable_set_row_spacing(tree, 2); + gtable_set_font(tree, &font_mini); + + // Scene setup + + gscreen_add_tab(scr, tab1, c3); + gscreen_add_tab(scr, tree, tree); + jscene_set_focused_widget(scr->scene, c3); + gtable_set_rows(tree, recursive_widget_count(scr->scene)); + gscreen_set_tab_title_visible(scr, 1, false); + + jevent e; + key_event_t k; + int key = 0; + + while(key != KEY_EXIT) + { + jscene_run(scr->scene, &e, &k); + + if(e.type == JSCENE_PAINT) + { + dclear(C_WHITE); + jscene_render(scr->scene); + dupdate(); + } + + if(e.type == JINPUT_VALIDATED && e.source == input) + { + gscreen_focus(scr, c3); + jlabel_snprintf(c1, 20, "New!\n%s", jinput_value(input)); + } + if(e.type == JINPUT_CANCELED && e.source == input) + { + gscreen_focus(scr, c3); + } + + if(k.type != KEYEV_DOWN) continue; + key = k.key; + + if(key == KEY_F3 && gscreen_in(scr, 0)) + { + bool input_focused = (jscene_focused_widget(scr->scene) == input); + gscreen_focus(scr, input_focused ? (void *)c3 : (void *)input); + } + + if(key == KEY_F1) gscreen_show_tab(scr, 0); + if(key == KEY_F2) gscreen_show_tab(scr, 1); + + #ifdef FX9860G + if(key == KEY_F6) screen_mono(u"\\\\fls0\\justui.bin"); + #endif + } + + jwidget_destroy(scr->scene); +} diff --git a/src/widgets/gscreen.c b/src/widgets/gscreen.c new file mode 100644 index 0000000..772fac1 --- /dev/null +++ b/src/widgets/gscreen.c @@ -0,0 +1,147 @@ +#include +#include +#include +#include + +#include +#include +#include + +#ifdef FX9860G +gscreen *gscreen_create(char const *name, bopti_image_t const *img) +#endif +#ifdef FXCG50 +gscreen *gscreen_create(char const *name, char const *labels) +#endif +{ + gscreen *g = malloc(sizeof *g); + if(!g) return NULL; + + jscene *s = jscene_create_fullscreen(NULL); + if(!s) { free(g); return NULL; } + + g->scene = s; + g->tabs = NULL; + g->tab_count = 0; + + jlabel *title = name ? jlabel_create(name, s) : NULL; + jwidget *stack = jwidget_create(s); + jfkeys *fkeys = _(img,labels) ? jfkeys_create(_(img,labels), s) : NULL; + + if((name && !title) || !stack || (_(img,labels) && !fkeys)) { + jwidget_destroy(s); + return NULL; + } + + g->title = title; + g->fkeys = fkeys; + + jlayout_set_vbox(s)->spacing = _(1,3); + jlayout_set_stack(stack); + + if(title) { + jwidget_set_background(title, C_BLACK); + jlabel_set_text_color(title, C_WHITE); + jlabel_set_font(title, _(&font_title, dfont_default())); + jwidget_set_stretch(title, 1, 0, false); + + #ifdef FX9860G + jwidget_set_padding(title, 1, 1, 0, 1); + jwidget_set_margin(title, 0, 0, 1, 0); + #endif + + #ifdef FXCG50 + jwidget_set_padding(title, 3, 6, 3, 6); + #endif + } + + #ifdef FXCG50 + jwidget_set_padding(stack, 1, 3, 1, 3); + #endif + + jwidget_set_stretch(stack, 1, 1, false); + return g; +} + +void gscreen_add_tab(gscreen *s, void *widget, void *focus) +{ + struct gscreen_tab *t = realloc(s->tabs, (s->tab_count+1) * sizeof *t); + if(!t) return; + + s->tabs = t; + s->tabs[s->tab_count].title_visible = (s->title != NULL); + s->tabs[s->tab_count].fkeys_visible = (s->fkeys != NULL); + s->tabs[s->tab_count].focus = focus; + s->tab_count++; + + jwidget *stack = s->scene->widget.children[1]; + jwidget_add_child(stack, widget); + jwidget_set_stretch(widget, 1, 1, false); +} + +#undef gscreen_add_tabs +void gscreen_add_tabs(gscreen *s, ...) +{ + va_list args; + va_start(args, s); + jwidget *w; + + while((w = va_arg(args, jwidget *))) + gscreen_add_tab(s, w, NULL); + + va_end(args); +} + +bool gscreen_show_tab(gscreen *s, int tab) +{ + jwidget *stack = s->scene->widget.children[1]; + jlayout_stack *l = jlayout_get_stack(stack); + + /* Find widget ID in the stack + int i = 0; + while(i < stack->child_count && stack->children[i] != widget) i++; + if(i >= stack->child_count || l->active == i) return false; */ + if(tab < 0 || tab >= stack->child_count) return false; + + /* Update keyboard focus */ + s->tabs[l->active].focus = jscene_focused_widget(s->scene); + jscene_set_focused_widget(s->scene, s->tabs[tab].focus); + + l->active = tab; + stack->update = 1; + + /* Hide or show title and function key bar as needed */ + jwidget_set_visible(s->title, s->tabs[tab].title_visible); + if(s->fkeys) jwidget_set_visible(s->fkeys, s->tabs[tab].fkeys_visible); + + return true; +} + +int gscreen_current_tab(gscreen *s) +{ + jwidget *stack = s->scene->widget.children[1]; + jlayout_stack *l = jlayout_get_stack(stack); + return l->active; +} + +bool gscreen_in(gscreen *s, int tab) +{ + return gscreen_current_tab(s) == tab; +} + +void gscreen_set_tab_title_visible(gscreen *s, int tab, bool visible) +{ + if(tab < 0 || tab >= s->tab_count) return; + s->tabs[tab].title_visible = visible; +} + +void gscreen_set_tab_fkeys_visible(gscreen *s, int tab, bool visible) +{ + if(tab < 0 || tab >= s->tab_count) return; + s->tabs[tab].fkeys_visible = visible; +} + +void gscreen_focus(gscreen *s, void *widget) +{ + return jscene_set_focused_widget(s->scene, widget); +} diff --git a/src/widgets/gtable.c b/src/widgets/gtable.c new file mode 100644 index 0000000..12b6330 --- /dev/null +++ b/src/widgets/gtable.c @@ -0,0 +1,354 @@ +#include +#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 -= _(2,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 = _(1,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; + + key_event_t kev = e.data.key; + if(kev.type == KEYEV_UP) return false; + + if(kev.key == KEY_DOWN && t->offset < end) { + if(kev.shift) t->offset = end; + else t->offset++; + t->widget.update = 1; + return true; + } + if(kev.key == KEY_UP && t->offset > 0) { + if(kev.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"); +}