678 lines
15 KiB
C
678 lines
15 KiB
C
|
#include <justui/jwidget.h>
|
||
|
#include <justui/jwidget-api.h>
|
||
|
#include <justui/jscene.h>
|
||
|
#include <justui/jevent.h>
|
||
|
#include "jlayout_p.h"
|
||
|
#include "util.h"
|
||
|
|
||
|
#include <gint/display.h>
|
||
|
#include <gint/std/stdlib.h>
|
||
|
#include <gint/std/string.h>
|
||
|
|
||
|
#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 */
|
||
|
static jwidget_poly type_jwidget = {
|
||
|
.name = "jwidget",
|
||
|
.csize = jwidget_poly_csize,
|
||
|
.layout = NULL,
|
||
|
.render = jwidget_poly_render,
|
||
|
.event = NULL,
|
||
|
.destroy = NULL,
|
||
|
};
|
||
|
|
||
|
/* List of registered widget types */
|
||
|
static jwidget_poly *widget_types[WIDGET_TYPES_MAX] = {
|
||
|
&type_jwidget,
|
||
|
NULL,
|
||
|
};
|
||
|
|
||
|
/* Events */
|
||
|
uint16_t JWIDGET_KEY;
|
||
|
uint16_t JWIDGET_FOCUS_IN;
|
||
|
uint16_t JWIDGET_FOCUS_OUT;
|
||
|
|
||
|
//---
|
||
|
// Polymorphic functions for widgets
|
||
|
//---
|
||
|
|
||
|
static void jwidget_poly_render(void *w0, int x, int y)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
jlayout_stack *l;
|
||
|
|
||
|
/* If there is a stack layout, render active child */
|
||
|
if((l = jlayout_get_stack(w)) && l->active >= 0) {
|
||
|
jwidget *child = w->children[l->active];
|
||
|
if(child->visible) jwidget_render(child, x+child->x, y+child->y);
|
||
|
}
|
||
|
/* Otherwise, simply render all children */
|
||
|
else {
|
||
|
for(int k = 0; k < w->child_count; k++) {
|
||
|
jwidget *child = w->children[k];
|
||
|
if(child->visible) jwidget_render(child, x+child->x, y+child->y);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void jwidget_poly_csize(void *w0)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
|
||
|
/* The content box of this children is the union of the border boxes of the
|
||
|
children. This function is called only for widgets without a layout, in
|
||
|
which case the children must have their positions set. */
|
||
|
w->w = 0;
|
||
|
w->h = 0;
|
||
|
|
||
|
for(int k = 0; k < w->child_count; k++) {
|
||
|
jwidget *child = w->children[k];
|
||
|
if(!child->visible) continue;
|
||
|
jwidget_msize(child);
|
||
|
|
||
|
w->w = max(w->w, child->x + child->w);
|
||
|
w->h = max(w->h, child->y + child->h);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//---
|
||
|
// Initialization
|
||
|
//---
|
||
|
|
||
|
jwidget *jwidget_create(void *parent)
|
||
|
{
|
||
|
jwidget *w = malloc(sizeof *w);
|
||
|
if(!w) return NULL;
|
||
|
|
||
|
/* Type ID 0 is for jwidget */
|
||
|
jwidget_init(w, 0, parent);
|
||
|
return w;
|
||
|
}
|
||
|
|
||
|
void jwidget_init(jwidget *w, int type, void *parent)
|
||
|
{
|
||
|
w->parent = NULL;
|
||
|
w->children = NULL;
|
||
|
w->child_count = 0;
|
||
|
w->child_alloc = 0;
|
||
|
w->dirty = 1;
|
||
|
w->visible = 1;
|
||
|
|
||
|
w->type = type;
|
||
|
w->geometry = NULL;
|
||
|
|
||
|
w->x = 0;
|
||
|
w->y = 0;
|
||
|
w->w = 0;
|
||
|
w->h = 0;
|
||
|
w->min_w = 0;
|
||
|
w->min_h = 0;
|
||
|
w->max_w = 0x7fff;
|
||
|
w->max_h = 0x7fff;
|
||
|
|
||
|
w->layout = J_LAYOUT_NONE;
|
||
|
w->stretch_x = 0;
|
||
|
w->stretch_y = 0;
|
||
|
w->stretch_force = 0;
|
||
|
|
||
|
jwidget_set_parent(w, parent);
|
||
|
}
|
||
|
|
||
|
void jwidget_destroy(void *w0)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
jwidget_set_parent(w, NULL);
|
||
|
|
||
|
/* Run the custom destructor */
|
||
|
jwidget_poly const *poly = widget_types[w->type];
|
||
|
if(poly->destroy) poly->destroy(w);
|
||
|
|
||
|
for(int i = 0; i < w->child_count; i++) {
|
||
|
/* This will prevent us from unregistering the child from w in the
|
||
|
recursive call, which saves some time in pointer manipulation */
|
||
|
w->children[i]->parent = NULL;
|
||
|
jwidget_destroy(w->children[i]);
|
||
|
}
|
||
|
|
||
|
if(w->children) free(w->children);
|
||
|
if(w->geometry) free(w->geometry);
|
||
|
free(w);
|
||
|
}
|
||
|
|
||
|
//---
|
||
|
// Ownership
|
||
|
//---
|
||
|
|
||
|
void jwidget_set_parent(void *w0, void *parent0)
|
||
|
{
|
||
|
J_CAST(w, parent)
|
||
|
if(w->parent == parent) return;
|
||
|
|
||
|
if(w->parent != NULL)
|
||
|
jwidget_remove_child(w->parent, w);
|
||
|
if(parent != NULL)
|
||
|
jwidget_add_child(parent, w);
|
||
|
}
|
||
|
|
||
|
void jwidget_add_child(void *w0, void *child0)
|
||
|
{
|
||
|
J_CAST(w, child)
|
||
|
jwidget_insert_child(w, child, w->child_count);
|
||
|
}
|
||
|
|
||
|
void jwidget_insert_child(void *w0, void *child0, int position)
|
||
|
{
|
||
|
J_CAST(w, child)
|
||
|
if(child->parent == w) return;
|
||
|
if(position < 0 || position > w->child_count) return;
|
||
|
|
||
|
/* Don't overflow the child_count and child_alloc fields! */
|
||
|
if(w->child_count >= 256 - 4) return;
|
||
|
|
||
|
/* Make room for a new child if needed */
|
||
|
if(w->child_count + 1 > w->child_alloc) {
|
||
|
size_t new_size = (w->child_alloc + 4) * sizeof(jwidget *);
|
||
|
jwidget **new_children = realloc(w->children, new_size);
|
||
|
if(!new_children) return;
|
||
|
|
||
|
w->children = new_children;
|
||
|
w->child_alloc += 4;
|
||
|
}
|
||
|
|
||
|
/* Insert new child */
|
||
|
for(int k = w->child_count; k > position; k--)
|
||
|
w->children[k] = w->children[k - 1];
|
||
|
|
||
|
w->children[position] = child;
|
||
|
w->child_count++;
|
||
|
|
||
|
/* Remove the existing parent at the last moment, in order to keep the tree
|
||
|
in a safe state if allocations fail or parameters are invalid */
|
||
|
if(child->parent != NULL)
|
||
|
jwidget_remove_child(child->parent, child);
|
||
|
|
||
|
child->parent = w;
|
||
|
|
||
|
/* Update stack layouts to keep showing the same child */
|
||
|
if(w->layout == J_LAYOUT_STACK && w->layout_stack.active >= position) {
|
||
|
w->layout_stack.active++;
|
||
|
}
|
||
|
/* Update stack layout to show the first child if none was visible yet */
|
||
|
if(w->layout == J_LAYOUT_STACK && w->layout_stack.active == -1) {
|
||
|
w->layout_stack.active = 0;
|
||
|
}
|
||
|
|
||
|
/* Force a later recomputation of the layout */
|
||
|
w->dirty = 1;
|
||
|
}
|
||
|
|
||
|
void jwidget_remove_child(void *w0, void *child0)
|
||
|
{
|
||
|
J_CAST(w, child)
|
||
|
if(child->parent != w) return;
|
||
|
|
||
|
int write = 0;
|
||
|
int index = -1;
|
||
|
|
||
|
/* Remove all occurrences of (child) from (w->children) */
|
||
|
for(int read = 0; read < w->child_count; read++) {
|
||
|
if(w->children[read] != child) {
|
||
|
w->children[write++] = w->children[read];
|
||
|
}
|
||
|
else if(index < 0) {
|
||
|
index = read;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Remove the parent from the child */
|
||
|
child->parent = NULL;
|
||
|
|
||
|
/* Update stack layouts to not show the removed child */
|
||
|
if(w->layout == J_LAYOUT_STACK && w->layout_stack.active == index) {
|
||
|
w->layout_stack.active = -1;
|
||
|
}
|
||
|
|
||
|
/* Force a later recomputation of the layout */
|
||
|
w->dirty = 1;
|
||
|
}
|
||
|
|
||
|
//---
|
||
|
// Sizing and stretching
|
||
|
//---
|
||
|
|
||
|
void jwidget_set_minimum_width(void *w0, int min_width)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
w->min_w = clamp(min_width, 0, 0x7fff);
|
||
|
w->dirty = 1;
|
||
|
}
|
||
|
|
||
|
void jwidget_set_minimum_height(void *w0, int min_height)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
w->min_h = clamp(min_height, 0, 0x7fff);
|
||
|
w->dirty = 1;
|
||
|
}
|
||
|
|
||
|
void jwidget_set_minimum_size(void *w, int min_width, int min_height)
|
||
|
{
|
||
|
jwidget_set_minimum_width(w, min_width);
|
||
|
jwidget_set_minimum_height(w, min_height);
|
||
|
}
|
||
|
|
||
|
void jwidget_set_maximum_width(void *w0, int max_width)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
w->max_w = clamp(max_width, 0, 0x7fff);
|
||
|
w->dirty = 1;
|
||
|
}
|
||
|
|
||
|
void jwidget_set_maximum_height(void *w0, int max_height)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
w->max_h = clamp(max_height, 0, 0x7fff);
|
||
|
w->dirty = 1;
|
||
|
}
|
||
|
|
||
|
void jwidget_set_maximum_size(void *w, int max_width, int max_height)
|
||
|
{
|
||
|
jwidget_set_maximum_width(w, max_width);
|
||
|
jwidget_set_maximum_height(w, max_height);
|
||
|
}
|
||
|
|
||
|
void jwidget_set_fixed_width(void *w, int width)
|
||
|
{
|
||
|
jwidget_set_minimum_width(w, width);
|
||
|
jwidget_set_maximum_width(w, width);
|
||
|
}
|
||
|
|
||
|
void jwidget_set_fixed_height(void *w, int height)
|
||
|
{
|
||
|
jwidget_set_minimum_height(w, height);
|
||
|
jwidget_set_maximum_height(w, height);
|
||
|
}
|
||
|
|
||
|
void jwidget_set_fixed_size(void *w, int width, int height)
|
||
|
{
|
||
|
jwidget_set_minimum_size(w, width, height);
|
||
|
jwidget_set_maximum_size(w, width, height);
|
||
|
}
|
||
|
|
||
|
void jwidget_set_stretch(void *w0, int stretch_x, int stretch_y, bool force)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
w->stretch_x = clamp(stretch_x, 0, 15);
|
||
|
w->stretch_y = clamp(stretch_y, 0, 15);
|
||
|
w->stretch_force = force;
|
||
|
w->dirty = 1;
|
||
|
}
|
||
|
|
||
|
//---
|
||
|
// Geometry
|
||
|
//---
|
||
|
|
||
|
static jwidget_geometry default_geometry = {
|
||
|
.margins = { 0, 0, 0, 0 },
|
||
|
.borders = { 0, 0, 0, 0 },
|
||
|
.paddings = { 0, 0, 0, 0 },
|
||
|
.border_color = C_NONE,
|
||
|
.border_style = J_BORDER_NONE,
|
||
|
.background_color = C_NONE,
|
||
|
};
|
||
|
|
||
|
jwidget_geometry const *jwidget_geometry_r(void *w0)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
return (w->geometry == NULL) ? &default_geometry : w->geometry;
|
||
|
}
|
||
|
|
||
|
jwidget_geometry *jwidget_geometry_rw(void *w0)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
|
||
|
/* Duplicate default geometry as a copy-on-write tactic to save memory */
|
||
|
if(w->geometry == NULL) {
|
||
|
w->geometry = malloc(sizeof *w->geometry);
|
||
|
if(!w->geometry) return NULL;
|
||
|
*w->geometry = default_geometry;
|
||
|
}
|
||
|
|
||
|
/* Assume layout will need to be recomputed */
|
||
|
w->dirty = 1;
|
||
|
|
||
|
return w->geometry;
|
||
|
}
|
||
|
|
||
|
void jwidget_set_border(void *w, jwidget_border_style s, int width, int color)
|
||
|
{
|
||
|
jwidget_geometry *g = jwidget_geometry_rw(w);
|
||
|
|
||
|
g->border_style = s;
|
||
|
g->border_color = color;
|
||
|
for(int i = 0; i < 4; i++) g->borders[i] = width;
|
||
|
}
|
||
|
|
||
|
void jwidget_set_padding(void *w, int top, int right, int bottom, int left)
|
||
|
{
|
||
|
jwidget_geometry *g = jwidget_geometry_rw(w);
|
||
|
g->padding.top = top;
|
||
|
g->padding.right = right;
|
||
|
g->padding.bottom = bottom;
|
||
|
g->padding.left = left;
|
||
|
}
|
||
|
|
||
|
void jwidget_set_margin(void *w, int top, int right, int bottom, int left)
|
||
|
{
|
||
|
jwidget_geometry *g = jwidget_geometry_rw(w);
|
||
|
g->margin.top = top;
|
||
|
g->margin.right = right;
|
||
|
g->margin.bottom = bottom;
|
||
|
g->margin.left = left;
|
||
|
}
|
||
|
|
||
|
void jwidget_set_background(void *w, int color)
|
||
|
{
|
||
|
jwidget_geometry *g = jwidget_geometry_rw(w);
|
||
|
g->background_color = color;
|
||
|
}
|
||
|
|
||
|
//---
|
||
|
// Layout
|
||
|
//---
|
||
|
|
||
|
void jlayout_set_manual(void *w0)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
w->layout = J_LAYOUT_NONE;
|
||
|
w->dirty = 1;
|
||
|
}
|
||
|
|
||
|
void jwidget_msize(void *w0)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
int t = w->layout;
|
||
|
|
||
|
/* Size of contents */
|
||
|
if(t == J_LAYOUT_NONE) {
|
||
|
jwidget_poly const *poly = widget_types[w->type];
|
||
|
if(poly->csize) poly->csize(w);
|
||
|
}
|
||
|
|
||
|
else if(t == J_LAYOUT_HBOX || t == J_LAYOUT_VBOX)
|
||
|
jlayout_box_csize(w);
|
||
|
|
||
|
else if(t == J_LAYOUT_STACK)
|
||
|
jlayout_stack_csize(w);
|
||
|
|
||
|
else if(t == J_LAYOUT_GRID)
|
||
|
jlayout_grid_csize(w);
|
||
|
|
||
|
/* Add the size of the geometry */
|
||
|
jwidget_geometry const *g = jwidget_geometry_r(w);
|
||
|
|
||
|
w->w += g->margin.left + g->border.left + g->padding.left;
|
||
|
w->w += g->margin.right + g->border.right + g->padding.right;
|
||
|
w->h += g->margin.top + g->border.top + g->padding.top;
|
||
|
w->h += g->margin.bottom + g->border.bottom + g->padding.bottom;
|
||
|
|
||
|
w->dirty = 1;
|
||
|
}
|
||
|
|
||
|
bool jwidget_layout_dirty(void *w0)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
if(w->dirty) return true;
|
||
|
|
||
|
for(int k = 0; k < w->child_count; k++) {
|
||
|
if(!w->children[k]->visible) continue;
|
||
|
if(jwidget_layout_dirty(w->children[k])) return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool jwidget_visible(void *w0)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
return w->visible;
|
||
|
}
|
||
|
|
||
|
void jwidget_set_visible(void *w0, bool visible)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
if(w->visible == visible) return;
|
||
|
|
||
|
w->visible = visible;
|
||
|
if(w->parent) w->parent->dirty = 1;
|
||
|
}
|
||
|
|
||
|
bool jwidget_needs_update(void *w0)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
if(w->update) return true;
|
||
|
|
||
|
jlayout_stack *l = jlayout_get_stack(w);
|
||
|
|
||
|
/* Ignore invisible children, because their update bits are not reset after
|
||
|
a repaint, resulting in infinite REPAINT events */
|
||
|
for(int k = 0; k < w->child_count; k++) {
|
||
|
if(l && l->active != k) continue;
|
||
|
if(!w->children[k]->visible) continue;
|
||
|
if(jwidget_needs_update(w->children[k])) return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/* Apply layout recursively on this widget and its children */
|
||
|
static void jwidget_layout_apply(void *w0)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
if(!w->visible) return;
|
||
|
int t = w->layout;
|
||
|
|
||
|
if(t == J_LAYOUT_NONE) {
|
||
|
jwidget_poly const *poly = widget_types[w->type];
|
||
|
if(poly->layout) poly->layout(w);
|
||
|
}
|
||
|
else if(t == J_LAYOUT_HBOX || t == J_LAYOUT_VBOX) {
|
||
|
jlayout_box_apply(w);
|
||
|
}
|
||
|
else if(t == J_LAYOUT_STACK) {
|
||
|
jlayout_stack_apply(w);
|
||
|
}
|
||
|
else if(t == J_LAYOUT_GRID) {
|
||
|
jlayout_grid_apply(w);
|
||
|
}
|
||
|
|
||
|
/* The layout is now up-to-date and will not be recomputed until a widget
|
||
|
in the hierarchy requires it */
|
||
|
w->dirty = 0;
|
||
|
|
||
|
for(int k = 0; k < w->child_count; k++)
|
||
|
jwidget_layout_apply(w->children[k]);
|
||
|
}
|
||
|
|
||
|
void jwidget_layout(void *root0)
|
||
|
{
|
||
|
J_CAST(root)
|
||
|
if(!jwidget_layout_dirty(root)) return;
|
||
|
|
||
|
/* Phase 1: Compute the natural margin size of every widget (bottom-up) */
|
||
|
jwidget_msize(root);
|
||
|
|
||
|
/* Decide on the size of the root; first make sure it's acceptable */
|
||
|
root->w = clamp(root->w, root->min_w, root->max_w);
|
||
|
root->h = clamp(root->h, root->min_h, root->max_h);
|
||
|
/* Now if there is stretch and a maximum size, stretch */
|
||
|
if(root->stretch_x && root->max_w != 0x7fff)
|
||
|
root->w = root->max_w;
|
||
|
if(root->stretch_y && root->max_h != 0x7fff)
|
||
|
root->h = root->max_h;
|
||
|
|
||
|
/* Phase 2: Distribute space recursively (top-down) */
|
||
|
jwidget_layout_apply(root);
|
||
|
}
|
||
|
|
||
|
int jwidget_content_width(void *w0)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
jwidget_geometry const *g = jwidget_geometry_r(w);
|
||
|
|
||
|
return w->w - g->margin.left - g->border.left - g->padding.left
|
||
|
- g->margin.right - g->border.right - g->padding.right;
|
||
|
}
|
||
|
|
||
|
int jwidget_content_height(void *w0)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
jwidget_geometry const *g = jwidget_geometry_r(w);
|
||
|
|
||
|
return w->h - g->margin.top - g->border.top - g->padding.top
|
||
|
- g->margin.bottom - g->border.bottom - g->padding.bottom;
|
||
|
}
|
||
|
|
||
|
int jwidget_full_width(void *w0)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
return w->w;
|
||
|
}
|
||
|
|
||
|
int jwidget_full_height(void *w0)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
return w->h;
|
||
|
}
|
||
|
|
||
|
//---
|
||
|
// Rendering
|
||
|
//---
|
||
|
|
||
|
void jwidget_render(void *w0, int x, int y)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
if(!w->visible) return;
|
||
|
|
||
|
/* Render widget border */
|
||
|
jwidget_geometry const *g = jwidget_geometry_r(w);
|
||
|
jdirs b = g->border;
|
||
|
int color = g->border_color;
|
||
|
|
||
|
int cw = jwidget_content_width(w);
|
||
|
int ch = jwidget_content_height(w);
|
||
|
|
||
|
int x1 = x + g->margin.left;
|
||
|
int y1 = y + g->margin.top;
|
||
|
int x2 = x1 + b.left + g->padding.left + cw + g->padding.right;
|
||
|
int y2 = y1 + b.top + g->padding.top + ch + g->padding.bottom;
|
||
|
|
||
|
if(g->border_style == J_BORDER_NONE || color == C_NONE) {
|
||
|
}
|
||
|
else if(g->border_style == J_BORDER_SOLID) {
|
||
|
drect(x1, y1, x2 + b.right - 1, y1 + b.top - 1, color);
|
||
|
drect(x1, y2, x2 + b.right - 1, y2 + b.bottom - 1, color);
|
||
|
|
||
|
drect(x1, y1 + b.top, x1 + b.left - 1, y2 - 1, color);
|
||
|
drect(x2, y1 + b.top, x2 + b.right - 1, y2 - 1, color);
|
||
|
}
|
||
|
/* TODO: jwidget_render(): More border types */
|
||
|
|
||
|
if(g->background_color != C_NONE) {
|
||
|
drect(x1 + b.left, y1 + b.top, x2, y2, g->background_color);
|
||
|
}
|
||
|
|
||
|
/* Call the polymorphic render function at the top-left content point */
|
||
|
x += g->margin.left + b.left + g->padding.left;
|
||
|
y += g->margin.top + b.top + g->padding.top;
|
||
|
|
||
|
jwidget_poly const *poly = widget_types[w->type];
|
||
|
if(poly->render) poly->render(w, x, y);
|
||
|
|
||
|
w->update = 0;
|
||
|
}
|
||
|
|
||
|
//---
|
||
|
// Event management
|
||
|
//---
|
||
|
|
||
|
bool jwidget_event(void *w0, jevent e)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
jwidget_poly const *poly = widget_types[w->type];
|
||
|
if(poly->event) return poly->event(w, e);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void jwidget_emit(void *w0, jevent e)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
if(!w) return;
|
||
|
|
||
|
if(e.source == NULL) e.source = w;
|
||
|
|
||
|
if(!strcmp(jwidget_type(w), "jscene")) {
|
||
|
jscene_queue_event((jscene *)w, e);
|
||
|
}
|
||
|
else {
|
||
|
jwidget_emit(w->parent, e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//---
|
||
|
// Extension API
|
||
|
//---
|
||
|
|
||
|
char const *jwidget_type(void *w0)
|
||
|
{
|
||
|
J_CAST(w)
|
||
|
return widget_types[w->type]->name;
|
||
|
}
|
||
|
|
||
|
int j_register_widget(jwidget_poly *poly, char const *inherits)
|
||
|
{
|
||
|
/* 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;
|
||
|
}
|
||
|
|
||
|
for(int i = 0; i < WIDGET_TYPES_MAX; i++) {
|
||
|
if(widget_types[i] == NULL) {
|
||
|
widget_types[i] = poly;
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int j_register_event(void)
|
||
|
{
|
||
|
static int event_id = 0;
|
||
|
event_id++;
|
||
|
return event_id;
|
||
|
}
|
||
|
|
||
|
__attribute__((constructor(1000)))
|
||
|
static void j_register_jwidget(void)
|
||
|
{
|
||
|
JWIDGET_KEY = j_register_event();
|
||
|
JWIDGET_FOCUS_IN = j_register_event();
|
||
|
JWIDGET_FOCUS_OUT = j_register_event();
|
||
|
}
|