Compare commits

...

16 commits

Author SHA1 Message Date
Lephenixnoir
7a5101360a
add notes about keyboard focus system 2024-09-11 13:21:03 +02:00
Lephenixnoir
f28d7a9cb8
jfkeys: add event processing to jfkeys
This requires jfkeys to _get_ the events, which needs to be done
manually by its owner to some degree (in gintctl, gscreen handles it).

Currently there are just 6 values 0..5 for F1..F6, but future expansion
for rotating menus with more than 6 entries and nested menus might add
to this set.
2024-09-06 16:19:01 +02:00
Lephenixnoir
93eb0df38a
jinput: fix incorrect return value in new event() convention 2024-09-06 10:02:55 +02:00
Lephenixnoir
626da6f378
jpainted: try out a macro-based widget definition scheme 2024-09-06 09:58:33 +02:00
Lephenixnoir
683e89d725
update built-in widgets' event handlers to default to jwidget 2024-09-06 09:58:32 +02:00
Lephenixnoir
216918123f
remove inheritance from jwidget API
This is the first step in improving the widget definition process. By
forcing the default behavior to be the widget behavior (thus
discouraging inheritance that doesn't work in C anyway), the default of
jwidget_poly_event() is exposed to the contract. This will allow further
features in using events, specifically for more complex focus cases.
2024-09-06 09:58:32 +02:00
Lephenixnoir
a2129f1ed2
jscene: give a default layout 2024-09-05 09:03:03 +02:00
Lephenixnoir
7b8070f02c
jevent: conveniences for testing for keys 2024-09-04 14:45:27 +02:00
Lephenixnoir
f32dcc69ce
jwidget: use dclear() fror background when widget is full screen 2024-09-04 08:57:58 +02:00
Lephenixnoir
e12a58c1f0
jfkeys: parameterize width to better fit on fx-CP display 2024-09-04 08:56:04 +02:00
Lephenixnoir
7f2131d6a0
jscene: add autopaint option
This handles JSCENE_PAINT events in the usual manner. This requires that
no painting other than the scene is desired, which is generally the
case.
2024-09-04 08:55:56 +02:00
Lephenixnoir
7587dfa17c
jlist: add user data pointer
This allows the info and paint functions to access user data associated
with the list without going through globals.
2024-09-04 08:51:41 +02:00
Lephenixnoir
12b29f8223
jlist: autofill info structure to 0 before calling info function
This avoids having to specify all fields in the info function.
2024-09-04 08:50:38 +02:00
Lephenixnoir
ba7b0a02d0
jlist, jscrolledlist: make parent the last constructor argument
To stay in line with constructors for other widgets.
2024-09-04 08:49:52 +02:00
Lephenixnoir
3488c6515a
jlist: add background/inversion selection feature 2024-09-04 08:46:26 +02:00
Lephenixnoir
0c8371edce
jscene: add missing jscene_set_poweroff function 2024-09-03 17:00:53 +02:00
20 changed files with 472 additions and 147 deletions

View file

@ -1,3 +1,106 @@
# JustUI: Scenes and events
# JustUI: Scenes, events and keyboard focus
## Introduction
The _scene_ in JustUI is the component sitting at the root of the widget hierarchy. It handles all the dynamic aspects of the UI, including distributing/propagating events and managing keyboard focus among the widgets.
Ideas...
New keyboard focus system.
1. Principle and conforming states
a. Widgets have a focus policy of FOCUS_ACCEPT, FOCUS_SCOPE or FOCUS_REJECT,
and two status flags FOCUSED and ACTIVE_FOCUSED whose valid combinations
describe 3 focus levels: *no focus* (F=0), *inactive focus* (F=1, AF=0),
and *active focus* (F=1, AF=1).
b. Each focus scope defines a region consisting of its subtree but excluding
itself and any children of other focus scopes. Up to one widget of policy
FOCUS_ACCEPT or FOCUS_SCOPE in the region may have its FOCUSED flag set,
called the *focus target* of the scope. The ACTIVE_FOCUSED flag of the
target is equal to the scope's.
c. Every focus scope fs induces a *focus chain* fc(fs), where
fc(w) = [] if w is FOCUS_ACCEPT or w is FOCUS_SCOPE with no target;
fc(w) = fc(target) + [target] if w is a scope with the named target.
Note that as a result of 1.b all elements of the chain have their FOCUSED
flag set and their ACTIVE_FOCUSED flags equal to that of fs.
d. jscene is a focus scope defined to have its ACTIVE_FOCUSED always set,
the only such widget that doesn't inherit this flag from a scope parent.
As such, the ACTIVE_FOCUSED flag identifies the focus chain of the scene.
jscene offers keyboard events to its focus chain, propagating rejected
events in list order.
X. The first (deepest) widget in the scene's focus chain is said to have
*strong focus*. Other widgets this chain are said to have *weak focus*.
-> TODO: Strong/weak focus is an informal substate of active focus and
may not be kept in the future.
2. Operations
a. The core operation is a changing the target of a scope, which can be
initiated by any widget in the region through a widget-context call. The
consequences of this operation are as follow:
- The old target, if any, loses its FOCUSED and ACTIVE_FOCUSED flags. If
it's a scope and it had active focus, all widgets in its focus chain
also lose active focus.
- The new target, if any, gets its FOCUSED flag and the scope's value for
the ACTIVE_FOCUSED flag. If it's a focus scope and it gets active
focus, then its focus chain also gets active focus.
All widgets affected receive a FOCUS_CHANGED event and the scope itself
receives a FOCUS_TARGET_CHANGED event.
b. There is a widget-scene call to give a widget active focus. If the widget
has policy FOCUS_ACCEPT or FOCUS_SCOPE, this call walks up the scope
chain and assigns new targets until it reaches the scene. If the widget
has policy FOCUS_NONE, this call just removes the surrounding scope's
target.
X. There is a widget-context call to relinquish one's own focus within the
parent scope. This is intended to differ from 2.b in that scopes might
implement special logic for moving focus to a nearby widget.
-> TODO: Left unresolved for later
3. Key-listener pattern
a. The operations described in 2.a and 2.b allow a "key-listener" pattern
where a widget "klw" (not the scene) receives keyboard events from its
potentially-focused descendants (i.e. inserts itself in the focus chain)
without impacting the focus mechanics of its surrounding scope "ssw".
b. The pattern consists of making klw a focus scope, and whenever it
receives a FOCUS_TARGET_CHANGED event that assigns a target, make klw
grab focus within ssw. This way, with regards to ssw, when focus moves:
- From within klw to within klw:
the grab is a no-op: OK.
- From within klw to outside it:
outside widget gets focus within ssw and klw does nothing: OK.
- From outside klw to within it:
inside widget gets focus in klw, klw gets focus in ssw: OK.
- From outside klw to outside it:
normal retargeting within ssw.
X. Should this be a built-in behavior?
4. Behavior of built-in widgets
a. `jfileselect`, `jinput`, `jlist` have policy FOCUS_ACCEPT
b. `jscene` has policy FOCUS_SCOPE
TODO: What about jframe?
c. `jfkeys` has policy `FOCUS_REJECT` and must be given events manually
d. Widgets with a stack layout will try and give focus (within their
surrounding scope) to the current element. If the element is complex, it
should be a focus scope. Similarly, widgets with invisible children will
move focus around to their direct children as needed to make sure focus
remains on a visible child.
5. Implementation
a. Events are `FOCUS_CHANGED` when own FOCUSED and ACTIVE_FOCUSED flags have
changed, and `FOCUS_TARGET_CHANGED` for scopes when the target changes.
NOTES:
- Focus events don't propagate but are replicated, which adds some complexity
- Focus scopes could have background focus for jfkeys (left for later). Hard
part is this isn't specified by jfkeys.
Could tell users to add this to their main loop, or follow gscreen:
jevent e = jscene_run();
if(jwidget_event(fkeys, e)) continue;
- Is there any sensible definition to "relinquishing" focus? In gintctl if e.g.
an jinput validates we want to take its focus away and put it back somewhere
sensible (i.e. the gscreen, not NULL, otherwise we don't have F-keys anymore)
- Some functions from <jwidget-api.h> should go to <jwidget.h>
- Functions from <jwidget.h> that require context or operate on the whole tree
should **really** be identified clearly. Everything by default should just
treat the widget as a box in a void.
-> Previously this was in jscene. Why not keep it that way?
## Keyboard focus and event propagation
TODO.

View file

@ -125,20 +125,13 @@ static jwidget_poly type_jcounter = {
static int jcounter_type_id;
__attribute__((constructor(2001)))
__attribute__((constructor))
static void j_register_jcounter(void)
{
jcounter_type_id = j_register_widget(&type_jcounter, "jwidget");
jcounter_type_id = j_register_widget(&type_jcounter);
}
```
The second parameter to `j_register_widget()` specifies inheritance. `jcounter`
inherits from `jwidget`, which means that the unspecified polymorphic functions
(`layout`, `event` and `destroy`) will use the default behavior of `jwidget`.
This is mostly useful if you don't specify `csize` (the default behavior is to
select the smallest size where all children fit) or `render` (the default
behavior is to render all visible children).
The type ID returned by `j_register_widget()` is how JustUI differentiates
labels from input fields from custom counters. When creating the widget, you
should initialize the `jwidget` field with `jwidget_init()` and specify the

View file

@ -9,6 +9,7 @@
#include <justui/jwidget.h>
#include <gint/keyboard.h>
#include <gint/defs/attributes.h>
/* jevent: GUI event
@ -49,4 +50,20 @@ typedef struct {
} jevent;
/* Check if an event is a key press of the specified key with modifiers. */
GINLINE static bool jevent_is_press_mods(
jevent e, int key, bool shift, bool alpha) {
return e.type == JWIDGET_KEY
&& (e.key.type == KEYEV_DOWN || e.key.type == KEYEV_HOLD)
&& e.key.key == key
&& e.key.shift == shift
&& e.key.alpha == alpha;
}
#define jevent_is_press(E, KEY) jevent_is_press_mods(E, KEY, false, false)
#define jevent_is_shift_press(E, KEY) jevent_is_press_mods(E, KEY, true, false)
#define jevent_is_alpha_press(E, KEY) jevent_is_press_mods(E, KEY, false, true)
#define jevent_is_shift_alpha_press(E, KEY) \
jevent_is_press_mods(E, KEY, true, true)
#endif /* _J_EVENT */

View file

@ -39,7 +39,7 @@
* "/NAME" for a menu key;
* ".NAME" for an entry key;
* "@NAME" for an action key;
" "#NAME" for a special key.
* "#NAME" for a special key.
The names are separated by semicolons, eg. "/F1;;/F3;.F4;@F5;#F6". Several
sets of function keys can be defined if separated by a '|' character. For
@ -51,7 +51,11 @@
with its 128x64 resolution, the convention is that the image is 128x8, and
key #i is positioned at x = 21i+2 with width 19. The equivalent of "|"-
separated levels is allowed by stacking up rows of keys (in which case the
image is of height 9n-1 for n rows). */
image is of height 9n-1 for n rows).
jkfeys will gobble keyboard events for F1..F6 and emit JFKEYS_TRIGGERED
events instead. However, in general jfkeys doesn't have keyboard focus, so
you have to give the events manually. */
typedef struct {
jwidget widget;
int8_t level;
@ -72,6 +76,9 @@ typedef struct {
} jfkeys;
/* Events */
extern uint16_t JFKEYS_TRIGGERED;
/* jfkeys_create2(): Create a set of function keys
Both the image and text specification are provided; one of them should

View file

@ -9,10 +9,12 @@
#include <justui/jwidget.h>
typedef enum {
/* Selected item is styled by the paint function or delegate */
JLIST_SELECTION_MANUAL = 0,
/* Selected item is indicated by inverting its rendered area */
JLIST_SELECTION_INVERT = 0,
JLIST_SELECTION_INVERT = 1,
/* Selected item is indicated by applying a background color */
JLIST_SELECTION_BACKGROUND = 1,
JLIST_SELECTION_BACKGROUND = 2,
} jlist_selection_style;
@ -23,6 +25,10 @@ typedef struct {
bool selectable;
/* Whether item can be triggered */
bool triggerable;
/* Selection style for jlist to draw */
int8_t selection_style;
/* Selection background color for JLIST_SELECTION_BACKGROUND */
uint16_t selection_bg_color;
/* The following fields are only applicable if there is no delegate. */
@ -33,9 +39,16 @@ typedef struct {
struct jlist;
/* Info function: should fill `info` with the data related to list element
#index (starts at 0). `info` is guaranteed to be pre-initialized to 0. */
typedef void (*jlist_item_info_function)(struct jlist *list, int index,
jlist_item_info *info);
/* Paint function: should draw element #index on the rectangle of size `w×h`
at position `x,y`. If the item has a selection style that is not
JLIST_SELECTION_MANUAL, the selection effect is handled by jlist. Otherwise,
the paint function should check the `selected` parameter to apply any
relevant styling. */
typedef void (*jlist_item_paint_function)(int x, int y, int w, int h,
struct jlist *list, int index, bool selected);
@ -64,6 +77,8 @@ typedef struct jlist {
/* Currently selected item, -1 if none */
int cursor;
/* User data pointer */
void *user;
} jlist;
@ -73,13 +88,14 @@ extern uint16_t JLIST_SELECTION_MOVED;
extern uint16_t JLIST_MODEL_UPDATED;
/* jlist_create(): Create a new (empty) jlist. */
jlist *jlist_create(void *parent, jlist_item_info_function info_function,
jlist_item_paint_function paint_function);
jlist *jlist_create(jlist_item_info_function info_function,
jlist_item_paint_function paint_function, void *parent);
/* jlist_update_model(): Update jlists's information about the model
The new model size is passed as parameter. The model is refreshed by
repeatedly calling the info function. */
void jlist_update_model(jlist *l, int item_count);
repeatedly calling the info function. The user pointer is also updated. To
keep it unchanged, pass `l->user` as third parameter. */
void jlist_update_model(jlist *l, int item_count, void *user);
/* jlist_clear(): Remove all items */
void jlist_clear(jlist *l);

View file

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

View file

@ -35,6 +35,8 @@ typedef struct {
bool mainmenu;
/* Whether jscene_run() powers off */
bool poweroff;
/* Whether jscene_run() will autopaint */
bool autopaint;
} jscene;
@ -118,6 +120,17 @@ void jscene_set_mainmenu(jscene *scene, bool mainmenu);
will probably want to save important data before leaving anyway. */
void jscene_set_poweroff(jscene *scene, bool poweroff);
/* jscene_set_autopaint(): Set whether jscene_run() handles its own painting
This will automatically handle JSCENE_PAINT events by drawing the scene
widget and updating the screen. You should use this only if the scene is the
only thing to draw; don't overdraw after this. If you have things to draw
not handled by jscene, handle JSCENE_PAINT yourself.
When enabling autopaint, you should also set a background color for the
scene, otherwise frames will draw transparently on top of each other. */
void jscene_set_autopaint(jscene *scene, bool autopaint);
/* jscene_run(): Run a scene's main loop
This function implements a main control loop that sleeps when there is

View file

@ -25,9 +25,10 @@ typedef struct {
} jscrolledlist;
/* jscrolledlist_create(): Create a scrolled list */
jscrolledlist *jscrolledlist_create(void *parent,
/* Create a scrolled list; arguments are forwarded to the jlist. */
jscrolledlist *jscrolledlist_create(
jlist_item_info_function info_function,
jlist_item_paint_function paint_function);
jlist_item_paint_function paint_function,
void *parent);
#endif /* _J_JSCROLLEDLIST */

View file

@ -6,6 +6,7 @@
#define _J_JWIDGET_API
#include <justui/defs.h>
#include <justui/p/preproc.h>
#include <justui/jevent.h>
#include <gint/keyboard.h>
@ -28,7 +29,13 @@
Implementations of this function should use jwidget_msize() on the children
to position their margin box within the widget's content box. The size set
by this function needs not be in the minimum/maximum range of the widget. */
by this function needs not be in the minimum/maximum range of the widget,
which is handled later.
If not overloaded (i.e. NULL in the poly structure), the default behavior is
to compute the smallest size that fits all children (based on their own
natural content size), which only makes sense if the children are at fixed
positions. */
typedef void jwidget_poly_csize_t(void *w);
/* jwidget_poly_layout_t: Layout a widget after its size has been set
@ -37,25 +44,45 @@ typedef void jwidget_poly_csize_t(void *w);
the widget has no layout. The margin-box size allocated to the widget has
been set in (w->w) and (w->h); the widget must now position its contents and
children. If the widget has a layout, the layout's specialized function is
called instead of this one. */
called instead of this one.
Custom positioning for children is only relevant for widgets that use custom
layouts, which is fairly rare. Most often, this function is used to position
internal elements of the widget after the size has been set (e.g. jlabel
computes line breaks here).
If not overloaded (i.e. NULL in the poly structure), the default behavior is
to leave children's positions unchanged, assuming they are fixed. */
typedef void jwidget_poly_layout_t(void *w);
/* jwidget_poly_render_t: Render a widget
This function is called during rendering after the widget's geometry is
drawn. (x,y) are the coordinates of the content box. This function must
render widget-specific visuals; there is no clipping, so the widget should
honor its width and height.
render widget-specific visuals. If the widget is clipped (as specified by
`jwidget_set_clipped()`), this function can specify any drawing coordinates
and all drawing will automatically be restricted with the widget's box.
However, if the widget is not clipped, drawing beyond the widget's width and
height will overflow to other widgets.
This function should call jwidget_render() for all children that need to be
rendered. jwidget_render() handles the geometry and takes as parameters the
coordinates of the margin box, so it can be called as:
This function should render its children. In the simple case where all
children can be rendered at the same time, you can simply call
`jwidget_poly_render(w)` which will do that.
If for any reason children need to be rendered separately, this function can
also called `jwidget_render()` on individual children. `jwidget_render()`
handles the geometry and takes as parameters the coordinates of the margin
box, so it can be called as:
jwidget_render(child, x + child->x, y + child->y).
It will draw the geometry and call the polymorphic renderer of the child at
its content-box coordinates. Normally you can ignore geometry altogether. */
its content-box coordinates. Normally you can ignore geometry altogether.
If not overloaded, the behavior is `jwidget_poly_render(w)`, i.e. just
render the children if there are any. */
typedef void jwidget_poly_render_t(void *w, int x, int y);
extern jwidget_poly_render_t jwidget_poly_render;
/* jwidget_poly_event_t: Handle an event
@ -63,22 +90,32 @@ typedef void jwidget_poly_render_t(void *w, int x, int y);
key events. This function is somewhat of a catch-all function for dynamic
occurrences. The widget should either accept the event, do something with
it, and return true, or refuse the event, do nothing and return false. This
influences events that propagate, such as key events. */
influences events that propagate, such as key events.
This function should always default to
return jwidget_poly_event(w, e);
to allow default/generic behaviors to propagate, unless the widget
explicitly wants to interfere with them. */
typedef bool jwidget_poly_event_t(void *w, jevent e);
extern jwidget_poly_event_t jwidget_poly_event;
/* jwidget_poly_destroy_t: Destroy a widget's specific resources
This function must destroy the widget-specific resources. It is called by
jwidget_destroy(), which follows it by freeing the widget's standard data
and destroying the children. This function can be NULL if there are no
widget-specific resources to free. */
widget-specific resources to free. It should not call the "base" version of
the function. */
typedef void jwidget_poly_destroy_t(void *w);
/* jwidget_poly: Polymorphic interface for a widget type */
typedef struct {
/* Type name, used for display and inheritance */
char const *name;
/* Polymorphic functions */
/* Polymorphic functions. If unused for custom widgets, should be set to
NULL and the default behavior (described above) will apply. */
jwidget_poly_csize_t *csize;
jwidget_poly_layout_t *layout;
jwidget_poly_render_t *render;
@ -95,13 +132,8 @@ typedef struct {
This function returns a new widget type ID to pass to jwidget_init() when
creating widgets of the custom type. Returns -1 if registration fails. The
polymorphic structure must outlive all widgets of the custom type.
If (inherits) is non-NULL, the new type will inherit from the widget type
with the provided name. All NULL functions in (poly) will be replaced by the
parent type's functions. This mechanism only works if widgets are declared
in inheritance order, which is normally enforced by constructor priority. */
int j_register_widget(jwidget_poly *poly, char const *inherits);
polymorphic structure must outlive all widgets of the custom type. */
int j_register_widget(jwidget_poly *poly);
/* j_register_event(): Register a new event type
@ -155,4 +187,52 @@ 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 `<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;
/* Helper macro for defining new events. Each parameter is an event name to
define, e.g.
```
J_DEFINE_EVENTS(MYWIDGET_EVENT1, MYWIDGET_EVENT2, MYWIDGET_EVENT3)
``` */
#define J_DEFINE_EVENTS(...) \
uint16_t __VA_ARGS__; \
__attribute__((constructor)) \
static void J_DEFINE_EVENTS_NAME(__COUNTER__)(void) { \
J_REPEAT(J_DEFINE_EVENTS_INIT, (), __VA_ARGS__) \
}
#define J_DEFINE_EVENTS_NAME2(COUNTER) _j_init_##COUNTER
#define J_DEFINE_EVENTS_NAME(COUNTER) J_DEFINE_EVENTS_NAME2(COUNTER)
#define J_DEFINE_EVENTS_INIT(EVENT) EVENT = j_register_event();
#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

@ -567,7 +567,7 @@ static bool jfileselect_poly_event(void *fs0, jevent e)
}
}
return false;
return jwidget_poly_event(fs, e);
}
static void jfileselect_poly_destroy(void *fs0)
@ -589,10 +589,10 @@ static jwidget_poly type_jfileselect = {
.destroy = jfileselect_poly_destroy,
};
__attribute__((constructor(1003)))
__attribute__((constructor))
static void j_register_jfileselect(void)
{
jfileselect_type_id = j_register_widget(&type_jfileselect, "jwidget");
jfileselect_type_id = j_register_widget(&type_jfileselect);
JFILESELECT_LOADED = j_register_event();
JFILESELECT_VALIDATED = j_register_event();
JFILESELECT_CANCELED = j_register_event();

View file

@ -4,8 +4,8 @@
#include <gint/std/stdlib.h>
#include <gint/std/string.h>
/* Type identified for jfkeys */
static int jfkeys_type_id = -1;
J_DEFINE_WIDGET(jfkeys, csize, render, event)
J_DEFINE_EVENTS(JFKEYS_TRIGGERED)
extern font_t j_font_fkeys_fx;
@ -89,14 +89,14 @@ static char const *get_label(char const *level, int key, size_t *len)
// Polymorphic widget operations
//---
static void jfkeys_poly_csize(void *f0)
void jfkeys_poly_csize(void *f0)
{
jfkeys *f = f0;
f->widget.w = DWIDTH;
f->widget.h = JFKEYS_HEIGHT;
}
static void jfkeys_poly_render(void *f0, int base_x, int y)
void jfkeys_poly_render(void *f0, int base_x, int y)
{
jfkeys *f = f0;
@ -117,8 +117,11 @@ static void jfkeys_poly_render(void *f0, int base_x, int y)
if(!text || (*text != '.' && *text != '/' && *text != '@'
&& *text != '#')) continue;
int x = base_x + 4 + 65 * position;
int w = 63;
int fw = jwidget_full_width(f);
int margin = (fw >= 250 ? 4 : 2);
int spacing = 2;
int w = (fw - 2 * margin - 5 * spacing) / 6;
int x = base_x + margin + (w + spacing) * position;
int color = (text[0] == '#') ? f->text_special_color : f->text_color;
if(text[0] == '.') {
@ -153,6 +156,38 @@ static void jfkeys_poly_render(void *f0, int base_x, int y)
dfont(old_font);
}
bool jfkeys_poly_event(void *f0, jevent e)
{
jfkeys *f = f0;
jevent te;
te.source = f;
te.type = JFKEYS_TRIGGERED;
if(e.type == JWIDGET_KEY && e.key.type == KEYEV_DOWN) {
int fun = keycode_function(e.key.key);
if(fun >= 0) {
te.data = fun - 1;
jwidget_emit(f, te);
return true;
}
#if GINT_HW_CP
static uint8_t const CP_Fk[6] = {
KEY_EQUALS, KEY_X, KEY_Y, KEY_Z, KEY_CARET, KEY_DIV };
for(int i = 0; i < 6; i++) {
if(e.key.key == CP_Fk[i]) {
te.data = i;
jwidget_emit(f, te);
return true;
}
}
#endif
}
return jwidget_poly_event(f, e);
}
int jfkeys_level(jfkeys *f)
{
return f->level;
@ -190,20 +225,3 @@ void jfkeys_set_font(jfkeys *keys, font_t const *font)
{
keys->font = font;
}
/* jfkeys type definition */
static jwidget_poly type_jfkeys = {
.name = "jfkeys",
.csize = jfkeys_poly_csize,
.layout = NULL,
.render = jfkeys_poly_render,
.event = NULL,
.destroy = NULL,
};
/* Type registration */
__attribute__((constructor(1005)))
static void j_register_jfkeys(void)
{
jfkeys_type_id = j_register_widget(&type_jfkeys, "jwidget");
}

View file

@ -379,7 +379,7 @@ static bool jframe_poly_event(void *f0, jevent e)
return true;
}
return false;
return jwidget_poly_event(f, e);
}
/* jframe type definition */
@ -391,8 +391,8 @@ static jwidget_poly type_jframe = {
.event = jframe_poly_event,
};
__attribute__((constructor(1001)))
__attribute__((constructor))
static void j_register_jframe(void)
{
jframe_type_id = j_register_widget(&type_jframe, "jwidget");
jframe_type_id = j_register_widget(&type_jframe);
}

View file

@ -256,6 +256,7 @@ static bool jinput_poly_event(void *i0, jevent e)
if(e.type == JWIDGET_KEY) {
key_event_t ev = e.key;
bool handled = true;
/* Releasing modifiers */
if(ev.type == KEYEV_UP && ev.key == KEY_SHIFT) {
@ -326,13 +327,15 @@ static bool jinput_poly_event(void *i0, jevent e)
/* Remove modifiers otherwise */
else i->mode &= ~(JINPUT_SHIFT | JINPUT_ALPHA);
}
else return false;
else handled = false;
}
i->widget.update = 1;
if(handled)
return true;
}
return true;
return jwidget_poly_event(i, e);
}
static void jinput_poly_destroy(void *i0)
@ -351,10 +354,10 @@ static jwidget_poly type_jinput = {
.destroy = jinput_poly_destroy,
};
__attribute__((constructor(1003)))
__attribute__((constructor))
static void j_register_jinput(void)
{
jinput_type_id = j_register_widget(&type_jinput, "jwidget");
jinput_type_id = j_register_widget(&type_jinput);
JINPUT_VALIDATED = j_register_event();
JINPUT_CANCELED = j_register_event();
}

View file

@ -354,8 +354,8 @@ static jwidget_poly type_jlabel = {
};
/* Type registration */
__attribute__((constructor(1002)))
__attribute__((constructor))
static void j_register_jlabel(void)
{
jlabel_type_id = j_register_widget(&type_jlabel, "jwidget");
jlabel_type_id = j_register_widget(&type_jlabel);
}

View file

@ -5,6 +5,7 @@
#include <gint/display.h>
#include <stdlib.h>
#include <string.h>
/* Type identifier for jlist */
static int jlist_type_id = -1;
@ -19,8 +20,8 @@ struct jlist_item_info {
bool selectable;
};
jlist *jlist_create(void *parent, jlist_item_info_function info_function,
jlist_item_paint_function paint_function)
jlist *jlist_create(jlist_item_info_function info_function,
jlist_item_paint_function paint_function, void *parent)
{
if(jlist_type_id < 0)
return NULL;
@ -36,6 +37,7 @@ jlist *jlist_create(void *parent, jlist_item_info_function info_function,
l->info_function = info_function;
l->paint_function = paint_function;
l->cursor = -1;
l->user = NULL;
return l;
}
@ -112,8 +114,10 @@ int jlist_selected_item(jlist *l)
// Item management
//---
void jlist_update_model(jlist *l, int item_count)
void jlist_update_model(jlist *l, int item_count, void *user)
{
l->user = user;
if(l->item_count != item_count) {
l->items = realloc(l->items, item_count * sizeof *l->items);
if(!l->items) {
@ -126,6 +130,7 @@ void jlist_update_model(jlist *l, int item_count)
l->item_count = item_count;
for(int i = 0; i < item_count; i++) {
memset(&l->items[i], 0, sizeof l->items[i]);
l->info_function(l, i, &l->items[i]);
}
@ -136,7 +141,7 @@ void jlist_update_model(jlist *l, int item_count)
void jlist_clear(jlist *l)
{
jlist_update_model(l, 0);
jlist_update_model(l, 0, NULL);
}
jrect jlist_selected_region(jlist *l)
@ -193,16 +198,24 @@ static void jlist_poly_render(void *l0, int x, int y)
for(int i = 0; i < l->item_count; i++) {
jlist_item_info *info = &l->items[i];
bool selected = (l->cursor == i);
if(info->delegate) {
int h = info->delegate
? jwidget_full_height(info->delegate)
: info->natural_height;
if(selected && info->selection_style == JLIST_SELECTION_BACKGROUND)
drect(x1, y, x2, y + h - 1, info->selection_bg_color);
if(info->delegate)
jwidget_render(info->delegate, x1, y);
y += jwidget_full_height(info->delegate);
}
else {
l->paint_function(x1, y, x2-x1+1, info->natural_height, l, i,
l->cursor == i);
y += info->natural_height;
}
else
l->paint_function(x1, y, x2-x1+1, h, l, i, selected);
if(selected && info->selection_style == JLIST_SELECTION_INVERT)
drect(x1, y, x2, y + h - 1, C_INVERT);
y += h;
}
}
@ -246,7 +259,7 @@ static bool jlist_poly_event(void *l0, jevent e)
return true;
}
return false;
return jwidget_poly_event(l, e);
}
static void jlist_poly_destroy(void *l0)
@ -264,10 +277,10 @@ static jwidget_poly type_jlist = {
.destroy = jlist_poly_destroy,
};
__attribute__((constructor(1001)))
__attribute__((constructor))
static void j_register_jlist(void)
{
jlist_type_id = j_register_widget(&type_jlist, "jwidget");
jlist_type_id = j_register_widget(&type_jlist);
JLIST_ITEM_TRIGGERED = j_register_event();
JLIST_SELECTION_MOVED = j_register_event();
JLIST_MODEL_UPDATED = j_register_event();

View file

@ -1,8 +1,8 @@
#include <justui/jpainted.h>
#include <gint/std/stdlib.h>
#include <justui/jwidget-api.h>
#include <stdlib.h>
/* 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(1004)))
static void j_register_jpainted(void)
{
jpainted_type_id = j_register_widget(&type_jpainted, "jwidget");
}

View file

@ -45,6 +45,7 @@ jscene *jscene_create(int x, int y, int w, int h, void *parent)
jwidget_init(&s->widget, jscene_type_id, parent);
jwidget_set_fixed_size(s, w, h);
jlayout_set_vbox(s);
s->x = x;
s->y = y;
@ -55,6 +56,7 @@ jscene *jscene_create(int x, int y, int w, int h, void *parent)
s->lost_events = 0;
s->mainmenu = true;
s->poweroff = true;
s->autopaint = false;
/* Prepare first layout/paint operation */
s->widget.dirty = 1;
@ -209,6 +211,16 @@ void jscene_set_mainmenu(jscene *scene, bool mainmenu)
scene->mainmenu = mainmenu;
}
void jscene_set_poweroff(jscene *scene, bool poweroff)
{
scene->poweroff = poweroff;
}
void jscene_set_autopaint(jscene *scene, bool autopaint)
{
scene->autopaint = autopaint;
}
jevent jscene_run(jscene *s)
{
keydev_t *d = keydev_std();
@ -224,6 +236,11 @@ jevent jscene_run(jscene *s)
/* Queued GUI events */
e = jscene_read_event(s);
if(e.type == JSCENE_PAINT && s->autopaint) {
jscene_render(s);
dupdate();
continue;
}
if(e.type != JSCENE_NONE && !jscene_process_event(s, e)) break;
/* Queued keyboard events */
@ -280,10 +297,10 @@ static jwidget_poly type_jscene = {
.destroy = NULL,
};
__attribute__((constructor(1001)))
__attribute__((constructor))
static void j_register_jscene(void)
{
jscene_type_id = j_register_widget(&type_jscene, "jwidget");
jscene_type_id = j_register_widget(&type_jscene);
JSCENE_NONE = j_register_event();
JSCENE_PAINT = j_register_event();
JSCENE_KEY = JWIDGET_KEY;

View file

@ -8,9 +8,10 @@
/* Type identifier for jscrolledlist */
static int jscrolledlist_type_id = -1;
jscrolledlist *jscrolledlist_create(void *parent,
jscrolledlist *jscrolledlist_create(
jlist_item_info_function info_function,
jlist_item_paint_function paint_function)
jlist_item_paint_function paint_function,
void *parent)
{
if(jscrolledlist_type_id < 0)
return NULL;
@ -26,7 +27,7 @@ jscrolledlist *jscrolledlist_create(void *parent,
jwidget_set_stretch(l->frame, 1, 1, false);
jframe_set_align(l->frame, J_ALIGN_LEFT, J_ALIGN_TOP);
l->list = jlist_create(l->frame, info_function, paint_function);
l->list = jlist_create(info_function, paint_function, l->frame);
jwidget_set_stretch(l->list, 1, 1, false);
return l;
@ -67,8 +68,7 @@ static bool jscrolledlist_poly_event(void *l0, jevent e)
if(e.type == JLIST_MODEL_UPDATED && e.source == l->list)
shake_scroll(l, true);
/* Allow the evnts to bubble up */
return false;
return jwidget_poly_event(l, e);
}
/* jscrolledlist type definition */
@ -78,8 +78,8 @@ static jwidget_poly type_jscrolledlist = {
.event = jscrolledlist_poly_event,
};
__attribute__((constructor(1002)))
__attribute__((constructor))
static void j_register_jscrolledlist(void)
{
jscrolledlist_type_id = j_register_widget(&type_jscrolledlist, "jwidget");
jscrolledlist_type_id = j_register_widget(&type_jscrolledlist);
}

View file

@ -12,7 +12,6 @@
#define WIDGET_TYPES_MAX 32
/* Polymorphic functions for jwidget */
static jwidget_poly_render_t jwidget_poly_render;
static jwidget_poly_csize_t jwidget_poly_csize;
/* jwidget type definition */
@ -21,7 +20,7 @@ static jwidget_poly type_jwidget = {
.csize = jwidget_poly_csize,
.layout = NULL,
.render = jwidget_poly_render,
.event = NULL,
.event = jwidget_poly_event,
.destroy = NULL,
};
@ -40,7 +39,7 @@ uint16_t JWIDGET_FOCUS_OUT;
// Polymorphic functions for widgets
//---
static void jwidget_poly_render(void *w0, int x, int y)
void jwidget_poly_render(void *w0, int x, int y)
{
J_CAST(w)
jlayout_stack *l;
@ -86,6 +85,14 @@ static void jwidget_poly_csize(void *w0)
}
}
bool jwidget_poly_event(void *w0, jevent e)
{
J_CAST(w)
(void)w;
(void)e;
return false;
}
//---
// Initialization
//---
@ -651,7 +658,13 @@ void jwidget_render(void *w0, int x, int y)
/* TODO: jwidget_render(): More border types */
if(g->background_color != C_NONE) {
drect(x1 + b.left, y1 + b.top, x2-1, y2-1, g->background_color);
int bgx = x1 + b.left;
int bgy = y1 + b.top;
if(bgx == 0 && bgy == 0 && x2 == DWIDTH && y2 == DHEIGHT)
dclear(g->background_color);
else
drect(bgx, bgy, x2-1, y2-1, g->background_color);
}
/* Call the polymorphic render function at the top-left content point */
@ -715,25 +728,14 @@ char const *jwidget_type(void *w0)
return widget_types[w->type]->name;
}
int j_register_widget(jwidget_poly *poly, char const *inherits)
int j_register_widget(jwidget_poly *poly)
{
/* Resolve inheritance */
if(inherits) {
jwidget_poly const *base = NULL;
for(int i = 0; i < WIDGET_TYPES_MAX && !base; i++) {
if(widget_types[i] && !strcmp(widget_types[i]->name, inherits))
base = widget_types[i];
}
if(!base) return -1;
if(!poly->csize) poly->csize = base->csize;
if(!poly->layout) poly->layout = base->layout;
if(!poly->render) poly->render = base->render;
if(!poly->event) poly->event = base->event;
if(!poly->destroy) poly->destroy = base->destroy;
}
/* Resolve default behaviors */
if(!poly->csize) poly->csize = jwidget_poly_csize;
if(!poly->layout) poly->layout = NULL;
if(!poly->render) poly->render = jwidget_poly_render;
if(!poly->event) poly->event = jwidget_poly_event;
if(!poly->destroy) poly->destroy = NULL;
for(int i = 0; i < WIDGET_TYPES_MAX; i++) {
if(widget_types[i] == NULL) {