JustUI/doc/widgets.md
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

6.1 KiB

JustUI: Widget types

General widgets and void types

JustUI defines a base structure jwidget useful for containers, as well as a number of derived structures for content widgets. Because C does not support any type of polymorphism, JustUI uses void * pointers when referring to "any widget", which accepts pointers to any base or derived structure.

This relaxed typing scheme mostly applies to the jwidget_*() functions that operate on the widget level. Widget-specific functions such as jinput_value() request exactly their type of widget as input (in this case, jinput). Essentially, this means that jwidget can be inherited from transparently, but the other types cannot (a structure attribute must be used). This is a compromise to work with polymorphic types in the C type system.

Widget collection

jwidget is the base type. It provides children/parent management (see Widget hierarchy), built-in layouts (see Space distribution and layout) and size constraints, geometry (margin, border, padding, background), and automatic repaint/relayout mechanisms.

jlabel is a content widget that shows a single-line or multi-line piece of text. It supports various text alignment options (left/right/center), line wrapping (letter and word level), and a couple of graphical options.

jscene is a special widget designed to be at the root of the widget tree. It provides event dispatching, automatic repaint and layout, keyboard input, and a generic input loop that is suitable for most GUI programs. See Scenes and events.

jinput is a one-line input field. It supports direct key input, delayed and instant SHIFT and ALPHA modifiers, as well as modifier locking, with visual hints.

jpainted is a very simple wigdet that paints itself with a user-provided function. It is intended to make custom widgets with very little effort. A jpainted can be positioned, sized and managed by the widget tree while the user only provides a drawing function.

jfkeys represents a function key bar, normally at the bottom of the screen. On fx-9860G, it uses an image to show keys; on fx-CG 50, it supports a string specification that looks like "@JUMP;;#ROM;#RAM;#ILRAM;#ADDIN". It can change options dynamically.

Custom widgets and polymorphic operations

For custom widgets that just have custom rendering a no event management, one can simply use a jpainted instance with well-chosen options. However, for reusable widgets that have internal state or event handling, a new widget type should be created.

A custom widget must be a structure that starts with a jwidget. The type itself should be register with j_register_widget(), and provide a couple of polymorphic functions. Here is an example with a very trivial widget that holds an integer counter.

typedef struct {
	jwidget widget;
	int counter;
} jcounter;

To be registered in JustUI, a custom widget type must provide a couple of functions, in the form of a jwidget_poly structure. All of the following functions are detailed in <justui/widget-api.h>, but here is a brief review of the requirements (the type_poly_ prefix is customary). All of the functions receive pointers to jcounter structures, but the type is void * because of the limitations mentioned earlier.

/* Receives a (jcounter *). Should set its (w) and (h) attributes to the
   natural dimensions of the widget. Cannot be NULL. */
void jcounter_poly_csize(void *counter);

/* Paint a jcounter at (x,y). */
void jcounter_poly_render(void *counter, int x, int y);

/* Destroy the resources of a jcounter. (If there are not malloc'd pointers in
   the structure, this is generally not needed.) */
void jcounter_poly_destroy(void *counter);

The following functions should be implemented if the custom widget needs to receive events (such as keyboard input). Events are defined in <justui/jevent.h>.

/* Handle event (e) sent to a jcounter. Should return true if the event was
   accepted/used, false if it was ignored/unused. */
bool jcounter_poly_event(void *counter, jevent e);

The following function is used if (1) an instance of the custom widget has children, and (2) this instance does not have a layout. This is rarely needed. See Space distribution and layout

/* Receives a (jcounter *) that has its full size set in the (w) and (h)
   attributes. Should determine the position and size of the children. */
void jcounter_poly_layout(void *counter);

In general, most of the work consists in specifying the csize() and render() functions. destroy() just has to release the resources allocated during widget creation, event() is straightforward, and layout() is very rarely needed at all.

Once the required functions are implemented, a polymorphic widget structure can be defined and registered as a new type. A good way to do this is to register the widget in a constructor located in the same file as the widget creation function, so that it runs automatically if the widget is used.

static jwidget_poly type_jcounter = {
	.name    = "jcounter",
	.csize   = jcounter_poly_csize,
	.layout  = NULL,
	.render  = jcounter_poly_render,
	.event   = NULL,
	.destroy = NULL,
};

static int jcounter_type_id;

__attribute__((constructor))
static void j_register_jcounter(void)
{
	jcounter_type_id = j_register_widget(&type_jcounter);
}

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 type ID. Note how the parent is also passed at creation time so that the new widget can automatically be attached to the tree.

jcounter *jcounter_create(int initial_value, void *parent)
{
	if(jcounter_type_id < 0) return NULL;

	jcounter *c = malloc(sizeof *c);
	jwidget_init(&c->widget, jcounter_type_id, parent);

	c->counter = initial_value;
	return c;
}

That's pretty much it. There is a fair amount of boilerplate, but this part is the same for every widget. See jpainted for a simple example and jinput for a full-featured one.