jpainted: try out a macro-based widget definition scheme

This commit is contained in:
Lephenixnoir 2024-09-06 09:39:22 +02:00
parent 683e89d725
commit 626da6f378
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
4 changed files with 104 additions and 27 deletions

View file

@ -7,7 +7,6 @@
#include <justui/defs.h> #include <justui/defs.h>
#include <justui/jwidget.h> #include <justui/jwidget.h>
#include <justui/jwidget-api.h>
/* jpainted: Simple widget designed to integrate low-effort rendering /* jpainted: Simple widget designed to integrate low-effort rendering

View file

@ -6,6 +6,7 @@
#define _J_JWIDGET_API #define _J_JWIDGET_API
#include <justui/defs.h> #include <justui/defs.h>
#include <justui/p/preproc.h>
#include <justui/jevent.h> #include <justui/jevent.h>
#include <gint/keyboard.h> #include <gint/keyboard.h>
@ -186,4 +187,38 @@ void jwidget_emit(void *w, jevent e);
notify it of the specified event. */ notify it of the specified event. */
bool jwidget_event(void *w, jevent e); 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 `<NAME>_poly_<METHOD>`
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 */ #endif /* _J_JWIDGET_API */

View file

@ -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 N8. */
#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 */

View file

@ -1,8 +1,8 @@
#include <justui/jpainted.h> #include <justui/jpainted.h>
#include <gint/std/stdlib.h> #include <justui/jwidget-api.h>
#include <stdlib.h>
/* Type identifier for jpainted */ J_DEFINE_WIDGET(jpainted, csize, render)
static int jpainted_type_id = -1;
jpainted *jpainted_create(void *function, j_arg_t arg, int natural_w, jpainted *jpainted_create(void *function, j_arg_t arg, int natural_w,
int natural_h, void *parent) int natural_h, void *parent)
@ -20,36 +20,15 @@ jpainted *jpainted_create(void *function, j_arg_t arg, int natural_w,
return p; return p;
} }
//--- void jpainted_poly_csize(void *p0)
// Polymorphic widget operations
//---
static void jpainted_poly_csize(void *p0)
{ {
jpainted *p = p0; jpainted *p = p0;
p->widget.w = p->natural_w; p->widget.w = p->natural_w;
p->widget.h = p->natural_h; 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; jpainted *p = p0;
p->paint(x, y, p->arg); 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);
}