libs/justui: add a demo of the JustUI widget library

This commit is contained in:
Lephenixnoir 2021-03-11 18:17:15 +01:00
parent ca2d731f39
commit 9a9c366821
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
11 changed files with 912 additions and 1 deletions

View File

@ -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"

View File

@ -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

BIN
assets-fx/fonts/title.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -20,4 +20,7 @@ void gintctl_libs_openlibm(void);
/* gintctl_libs_libimg(): libimg-based rendering and image transform */
void gintctl_libs_libimg(void);
/* gintctl_libs_justui(): Just User Interfaces */
void gintctl_libs_justui(void);
#endif /* GINTCTL_LIBS */

View File

@ -0,0 +1,92 @@
//---
// gintctl.widgets.gscreen: gintctl's extended standard scene
//---
#ifndef _GINTCTL_WIDGETS_GSCREEN
#define _GINTCTL_WIDGETS_GSCREEN
#include <justui/jscene.h>
#include <justui/jlabel.h>
#include <justui/jfkeys.h>
#include <gintctl/util.h>
#include <gint/display.h>
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 */

View File

@ -0,0 +1,125 @@
//---
// gintctl.widgets.gtable: gintctl's scrolling tables
//---
#ifndef _GINTCTL_WIDGETS_GTABLE
#define _GINTCTL_WIDGETS_GTABLE
#include <justui/jwidget.h>
#include <gint/display.h>
/* 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 [, <an type of j_arg_t>])
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 */

View File

@ -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 },
}};

175
src/libs/justui.c Normal file
View File

@ -0,0 +1,175 @@
#include <gint/keyboard.h>
#include <gint/display.h>
#include <gint/std/stdio.h>
#include <gintctl/libs.h>
#include <gintctl/widgets/gscreen.h>
#include <gintctl/widgets/gtable.h>
#include <gintctl/util.h>
#include <gintctl/assets.h>
#include <justui/jscene.h>
#include <justui/jwidget.h>
#include <justui/jlabel.h>
#include <justui/jinput.h>
#include <justui/jpainted.h>
#include <justui/jfkeys.h>
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);
}

147
src/widgets/gscreen.c Normal file
View File

@ -0,0 +1,147 @@
#include <gintctl/widgets/gscreen.h>
#include <gintctl/assets.h>
#include <gintctl/util.h>
#include <gint/std/stdlib.h>
#include <justui/jscene.h>
#include <justui/jlabel.h>
#include <justui/jfkeys.h>
#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);
}

354
src/widgets/gtable.c Normal file
View File

@ -0,0 +1,354 @@
#include <gintctl/widgets/gtable.h>
#include <justui/jwidget-api.h>
#include <gint/std/stdlib.h>
#include <gintctl/util.h>
#include <stdarg.h>
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");
}