From 626da6f378fd3763fe51dd9478f85d9f9ea4874c Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Fri, 6 Sep 2024 09:39:22 +0200 Subject: [PATCH] jpainted: try out a macro-based widget definition scheme --- include/justui/jpainted.h | 1 - include/justui/jwidget-api.h | 35 ++++++++++++++++++++ include/justui/p/preproc.h | 64 ++++++++++++++++++++++++++++++++++++ src/jpainted.c | 31 +++-------------- 4 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 include/justui/p/preproc.h diff --git a/include/justui/jpainted.h b/include/justui/jpainted.h index 5b545fe..c7724fd 100644 --- a/include/justui/jpainted.h +++ b/include/justui/jpainted.h @@ -7,7 +7,6 @@ #include #include -#include /* jpainted: Simple widget designed to integrate low-effort rendering diff --git a/include/justui/jwidget-api.h b/include/justui/jwidget-api.h index 149e427..968842f 100644 --- a/include/justui/jwidget-api.h +++ b/include/justui/jwidget-api.h @@ -6,6 +6,7 @@ #define _J_JWIDGET_API #include +#include #include #include @@ -186,4 +187,38 @@ void jwidget_emit(void *w, jevent e); notify it of the specified event. */ bool jwidget_event(void *w, jevent e); +/* Helper macro for defining a new widget. Parameters are: + - Widget NAME + - Variadic list of overridden widget poly METHODs, can be empty + - Implicity, non-static poly functions `_poly_` + And it defines: + - The `NAME_type_id` identifier for use with `jwidget_init()` + - A constructor that registers the widget and sets `NAME_type_id`. + + For example: + ``` + J_DEFINE_WIDGET(jpainted, csize, render) + ``` + + gets the functions `jpainted_poly_csize` and `jpainted_poly_render` from + context, registers the type at startup and provides `jpainted_type_id` for + the widget creation function. */ + +#define J_DEFINE_WIDGET(NAME, ...) \ + static int NAME##_type_id = -1; \ + J_REPEAT(J_DEFINE_WIDGET_POLY_PROTO, (NAME), __VA_ARGS__) \ + static jwidget_poly type_##NAME = { \ + .name = #NAME, \ + J_REPEAT(J_DEFINE_WIDGET_POLY, (NAME), __VA_ARGS__) \ + }; \ + __attribute__((constructor)) \ + static void j_register_##NAME(void) { \ + NAME##_type_id = j_register_widget(&type_##NAME); \ + } + +#define J_DEFINE_WIDGET_POLY(NAME, METHOD) \ + .METHOD = NAME##_poly_##METHOD, +#define J_DEFINE_WIDGET_POLY_PROTO(NAME, METHOD) \ + extern jwidget_poly_##METHOD##_t NAME##_poly_##METHOD; + #endif /* _J_JWIDGET_API */ diff --git a/include/justui/p/preproc.h b/include/justui/p/preproc.h new file mode 100644 index 0000000..a2cd84f --- /dev/null +++ b/include/justui/p/preproc.h @@ -0,0 +1,64 @@ +//--- +// JustUI.util.preproc: Preprocessor utilities +//--- + +#ifndef _J_UTIL_PREPROC +#define _J_UTIL_PREPROC + +/* J_REPEAT: Dispatch a partially-evaluated macro call on each arg in a list + The call `J_REPEAT(X, C, V1, ..., VN)` where `C = (C1, ..., CM)` (with the + parentheses) is equivalent to the series of calls + + ``` + X(C1, ..., CM, V1) + X(C1, ..., CM, V2) + ... + X(C1, ..., CM, VN) + ``` + + and is used to iterate on lists. There is a size limit of N≤8. */ +#define J_REPEAT1(X, C, V1, ...) \ + J_CALL(X, C, V1) __VA_OPT__(J_REPEAT2(X, C, __VA_ARGS__)) +#define J_REPEAT2(X, C, V2, ...) \ + J_CALL(X, C, V2) __VA_OPT__(J_REPEAT3(X, C, __VA_ARGS__)) +#define J_REPEAT3(X, C, V3, ...) \ + J_CALL(X, C, V3) __VA_OPT__(J_REPEAT4(X, C, __VA_ARGS__)) +#define J_REPEAT4(X, C, V4, ...) \ + J_CALL(X, C, V4) __VA_OPT__(J_REPEAT5(X, C, __VA_ARGS__)) +#define J_REPEAT5(X, C, V5, ...) \ + J_CALL(X, C, V5) __VA_OPT__(J_REPEAT6(X, C, __VA_ARGS__)) +#define J_REPEAT6(X, C, V6, ...) \ + J_CALL(X, C, V6) __VA_OPT__(J_REPEAT7(X, C, __VA_ARGS__)) +#define J_REPEAT7(X, C, V7, ...) \ + J_CALL(X, C, V7) __VA_OPT__(J_REPEAT8(X, C, __VA_ARGS__)) +#define J_REPEAT8(X, C, V8, ...) \ + ({ __VA_OPT__(_Static_assert(0, \ + "J_REPEAT: too many macro arguments (maximum 8)");) \ + J_CALL(X, C, V8); }) +#define J_REPEAT(X, C, ...) __VA_OPT__(J_REPEAT1(X, C, __VA_ARGS__)) + +/* J_CALL: Perform a call to a partially evaluated macro + The call `J_CALL(X, C, A1, ..., AN)` where `C = (C1, ..., CM)` (with the + parentheses) reduces to `X(C1, ..., CM, A1, ..., AN)`, i.e. it calls the + already-partially-applied `X(C)` with further arguments. Both M=0 (`C=()`) + and N=0 (no variadic arguments) are allowed. + + The main difficulty is "unfolding" `C` into the arguments of `X`. This + problem is dealt with by absorbing the parentheses into an ID-function macro + call: + + ``` + J_ID C + ~> J_ID (C1, ..., CM) + ~> , C1, ..., CM + ``` + + From there, the only subtletly is gobbling the commas. We gobble the comma + before `J_ID C` if M=0 and the comma before the other arguments if N=0. This + requires a few expansion stages. */ +#define J_ID(...) __VA_OPT__(,) __VA_ARGS__ +#define J_CALL3(X, ...) X(__VA_ARGS__) +#define J_CALL2(...) J_CALL3(__VA_ARGS__) +#define J_CALL(X, C, ...) J_CALL2(X J_ID C, ##__VA_ARGS__) + +#endif /* _J_UTIL_PREPROC */ diff --git a/src/jpainted.c b/src/jpainted.c index 21fe071..905a660 100644 --- a/src/jpainted.c +++ b/src/jpainted.c @@ -1,8 +1,8 @@ #include -#include +#include +#include -/* Type identifier for jpainted */ -static int jpainted_type_id = -1; +J_DEFINE_WIDGET(jpainted, csize, render) jpainted *jpainted_create(void *function, j_arg_t arg, int natural_w, int natural_h, void *parent) @@ -20,36 +20,15 @@ jpainted *jpainted_create(void *function, j_arg_t arg, int natural_w, return p; } -//--- -// Polymorphic widget operations -//--- - -static void jpainted_poly_csize(void *p0) +void jpainted_poly_csize(void *p0) { jpainted *p = p0; p->widget.w = p->natural_w; p->widget.h = p->natural_h; } -static void jpainted_poly_render(void *p0, int x, int y) +void jpainted_poly_render(void *p0, int x, int y) { jpainted *p = p0; p->paint(x, y, p->arg); } - -/* jpainted type definition */ -static jwidget_poly type_jpainted = { - .name = "jpainted", - .csize = jpainted_poly_csize, - .layout = NULL, - .render = jpainted_poly_render, - .event = NULL, - .destroy = NULL, -}; - -/* Type registration */ -__attribute__((constructor)) -static void j_register_jpainted(void) -{ - jpainted_type_id = j_register_widget(&type_jpainted); -}