Skip to content

Instantly share code, notes, and snippets.

@vurtun
Last active June 13, 2023 21:31
Show Gist options
  • Star 22 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save vurtun/61b6dbf21ef060bcbbd8d1faa88350d9 to your computer and use it in GitHub Desktop.
Save vurtun/61b6dbf21ef060bcbbd8d1faa88350d9 to your computer and use it in GitHub Desktop.
/* ===========================================================================
*
* LIBRARY
*
* =========================================================================== */
/* Proof of Concept GUI:
* - PoC UI implementation in ~2.5kLOC of C89 (ANSI C)
* => Core solutions has no external dependencies (removing standard library dependency is trival)
* => Does not use or require any special language constructs or macro constructs just pointers and enums
* - Combines both "retained" and "immediate mode" UI by providing control over update frequency
* => Does not require frame based updates while avoiding state mutability complexity found in retain mode GUIs
* - Tree node processor: Revolves around tree datastructure operations
* => Simple design composed around tree with rectangle nodes. Even "widgets" or "panels" are only sub-trees
* - Does not use any callbacks for tree operations like layouting, drawing or input handling.
* => Everything can be implemented using simple loops and conditions
* => Callbacks can be implemented by dispatch table
* - Event handling supports both polling as well as events
* => Both polling directly in code while constructing tree (dear imgui) and outside
* - Keeps full main application/game loop control including input handling in hand of user
* => Does not directly enforce any framework styled control flow
* => Supports both frame based updates as well as more energy conserving update on event
* - No widget inheriting commonly found in frameworks. Instead everything is just composited
* => Everything can be fit into each other. Want a checkbox inside button? Just combine them
* - Supports full layouting with desired size calculation
* => Avoids layouting problems commonly found in "pure" immediate mode GUIs
* - Does not specify anything rendering related. Just another tree traversal operation
* => Real production implementation would need a number of default backends (vertexes, rendering library bindings,...)
* - Provides solution for persistent state management
* => Widget state can be mananaged in library or outside by user
* => Special care taken for garbage collected language bindings
* - Provides callbacks for memory allocations and deallocations
* => Implements block and linear allocator for better memory allocation patterns
* - Simplifies both UI tree creation as well as commonly less focused tree traversing
* => No blackbox single "update" function call. Extending the library should be as simple as using it
* => Production level code would need to provide default widget implementations handled in switch default case
* - Core implementation can be extended to keep track of UI state over time (debugging)
* => Library internally keeps track of current and last frame which could be extended
* - Does not specify any font handling or text layouting scheme
* => Production level code would need default implementation (stb_truetype, freetype, slug, harfbuzz,...)
*
* Problems:
* - Basic UI unit "box" is memory intensive (~170 bytes per box)
* - In general more memory intensive than "pure" immediate mode UIs
* - Frame based in-place polling always has *one* frame of latency
* - Core is, compared to "pure" immediate mode UIs like nuklear/dear imgui, harder to implement
* - Tree creation still needs some work for multithreading (spinlock for panel list operations)
* - Processing steps are sequential so cannot be separated for multithreading, only complete process
*/
#include <assert.h> /* assert */
#include <stdlib.h> /* malloc, free */
#include <string.h> /* memcpy, memset */
#include <stdint.h> /* uintptr_t, intptr_t */
#include <limits.h> /* INT_MAX */
#undef min
#undef max
#define api static
#define intern static
#define unused(a) ((void)a)
#define cast(t,p) ((t)(p))
#define flag(n) ((1u)<<(n))
#define szof(a) ((int)sizeof(a))
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
#define clamp(a,v,b) (max(min(b,v),a))
#define zero(d,sz) memset(d,0,(size_t)(sz))
#define copy(d,s,sz) memcpy(d,s,(size_t)(sz))
#define cntof(a) ((int)(sizeof(a)/sizeof((a)[0])))
#define offsof(st,m) ((int)((uintptr_t)&(((st*)0)->m)))
#define containerof(ptr,type,member) (type*)((void*)((char*)(1?(ptr):&((type*)0)->member)-offsof(type, member)))
#define alignof(t) ((int)((char*)(&((struct {char c; t _h;}*)0)->_h) - (char*)0))
#define align(x,mask) ((void*)(((intptr_t)((const char*)(x)+(mask-1))&~(mask-1))))
#define between(x,a,b) ((a)<=(x) && (x)<=(b))
#define inbox(px,py,x,y,w,h) (between(px,x,x+w) && between(py,y,y+h))
#define intersect(x,y,w,h,X,Y,W,H) ((x)<(X)+(W) && (x)+(w)>(X) && (y)<(Y)+(H) && (y)+(h)>(Y))
#define strjoini(a, b) a ## b
#define strjoind(a, b) strjoini(a,b)
#define strjoin(a, b) strjoind(a,b)
#define uniqid(name) strjoin(name,__LINE__)
#define compiler_assert(exp) typedef char uniqid(_compile_assert_array)[(exp)?1:-1]
struct panel;
struct widget;
struct context;
struct repository;
struct panel_state;
struct memory_arena;
typedef uintptr_t uiid;
typedef void(*dealloc_f)(void *usr, void *data, const char *file, int line);
typedef void*(*alloc_f)(void *usr, int size, const char *file, int line);
/* list */
struct list_hook {
struct list_hook *next;
struct list_hook *prev;
};
/* memory */
#define DEFAULT_ALLOCATOR 0
#define DEFAULT_MEMORY_BLOCK_SIZE (32*1024)
struct allocator {
void *usr;
alloc_f alloc;
dealloc_f dealloc;
};
struct memory_block {
struct list_hook hook;
struct memory_block *prev;
int size, used;
unsigned char *base;
};
struct block_allocator {
const struct allocator *mem;
struct list_hook freelist;
struct list_hook blks;
int blkcnt;
};
struct temp_memory {
struct memory_arena *arena;
struct memory_block *blk;
int used;
};
struct memory_arena {
struct block_allocator *mem;
struct memory_block *blk;
int blkcnt, tmpcnt;
};
/* input */
enum mouse_button {
MOUSE_BUTTON_LEFT,
MOUSE_BUTTON_RIGHT,
MOUSE_BUTTON_COUNT
};
struct key {
unsigned char down;
unsigned char transitions;
};
struct mouse {
int x, y;
int lx, ly;
int dx, dy;
int wheelx, wheely;
struct key btn[MOUSE_BUTTON_COUNT];
};
struct input {
struct mouse mouse;
char text[32];
int text_len;
int width, height;
unsigned resized:1;
unsigned ctrl:1;
unsigned shift:1;
unsigned alt:1;
unsigned super:1;
struct key shortcuts[256];
struct key keys[512];
};
/* box */
struct rect {int x,y,w,h;};
struct transform {int x,y; float sx,sy;};
enum property_type {
PROPERTY_INTERACTIVE,
PROPERTY_MOVABLE_X,
PROPERTY_MOVABLE_Y,
PROPERTY_BACKGROUND,
PROPERTY_UNIFORM,
PROPERTY_SELECTABLE,
PROPERTY_COUNT
};
struct box {
int type;
struct transform local_transform;
struct transform screen_transform;
struct rect local, screen;
int dw, dh;
unsigned alive:1;
/* properties */
unsigned interactive:1;
unsigned movable_x:1;
unsigned movable_y:1;
unsigned background:1;
unsigned uniform:1;
unsigned selectable:1;
/* state */
unsigned hidden:1;
unsigned clicked:1;
unsigned pressed:1;
unsigned released:1;
unsigned entered:1;
unsigned exited:1;
unsigned drag_begin:1;
unsigned dragged:1;
unsigned drag_end:1;
unsigned moved:1;
unsigned scrolled:1;
short depth, cnt;
struct list_hook node;
struct list_hook links;
struct list_hook hook;
struct box *parent;
void *widget;
};
/* panel */
struct widget_builder {
int type;
int stkcnt, cnt;
struct box *stk[16];
struct panel_state *state;
struct memory_arena *arena;
const void *widget_last;
void *widget;
unsigned init:1;
};
struct panel_state {
struct memory_arena *arena;
struct context *ctx;
struct panel *panel;
struct repository *repo;
struct widget_builder *builder;
struct box *root;
struct box *stk[16];
int stkcnt;
};
struct state {
void *ptr;
uiid id;
struct list_hook hook;
};
struct table {
int cnt;
uiid *keys;
void **vals;
};
struct repository {
struct memory_arena arena;
int cnt, depth;
struct list_hook widgets;
struct list_hook state;
struct box *tree;
struct table tbl;
struct box **bfs;
};
struct panel {
uiid id;
struct panel_state state;
struct widget_builder builder;
struct repository repo[2];
struct box root;
struct panel *parent;
struct list_hook hook;
unsigned act:1;
};
/* event */
enum event_type {
EVT_CLICKED,
EVT_PRESSED,
EVT_RELEASED,
EVT_ENTERED,
EVT_EXITED,
EVT_DRAG_BEGIN,
EVT_DRAGGED,
EVT_DRAG_END,
EVT_MOVED,
EVT_KEY,
EVT_TEXT,
EVT_SCROLLED,
EVT_SHORTCUT,
EVT_COUNT
};
struct event_header {
enum event_type type;
int cap, cnt;
struct box *origin;
struct box **boxes;
struct list_hook hook;
};
struct event_entered_exited {
struct event_header hdr;
struct box *last;
struct box *cur;
};
struct event_int2 {
struct event_header hdr;
int x, y;
};
struct event_key {
struct event_header hdr;
int code;
unsigned pressed:1;
unsigned released:1;
unsigned ctrl:1;
unsigned shift:1;
unsigned alt:1;
unsigned super:1;
};
struct event_text {
struct event_header hdr;
char *buf;
int len;
unsigned ctrl:1;
unsigned shift:1;
unsigned alt:1;
unsigned super:1;
unsigned resized:1;
};
union event {
enum event_type type;
struct event_header hdr;
struct event_entered_exited entered;
struct event_entered_exited exited;
struct event_int2 moved;
struct event_int2 scroll;
struct event_int2 clicked;
struct event_int2 drag_begin;
struct event_int2 dragged;
struct event_int2 drag_end;
struct event_int2 pressed;
struct event_int2 released;
struct event_text text;
struct event_key key;
struct event_key shortcut;
};
/* operation */
enum operation_type {
OP_BLUEPRINT,
OP_LAYOUTING,
OP_TRANSFORM,
OP_INPUT,
OP_PAINT,
OP_CLEANUP,
OP_SERIALIZE,
OP_COUNT
};
enum process {
PROCESS_BLUEPRINT = flag(OP_BLUEPRINT),
PROCESS_LAYOUTING = flag(OP_LAYOUTING),
PROCESS_TRANSFORM = flag(OP_TRANSFORM),
PROCESS_CLEANUP = flag(OP_CLEANUP),
PROCESS_COMMIT = PROCESS_TRANSFORM|PROCESS_BLUEPRINT|PROCESS_LAYOUTING|PROCESS_CLEANUP,
PROCESS_INPUT = PROCESS_COMMIT|flag(OP_INPUT),
PROCESS_PAINT = PROCESS_COMMIT|flag(OP_PAINT),
PROCESS_SERIALIZE = PROCESS_COMMIT|flag(OP_SERIALIZE),
PROCESS_EVERYTHING = 0xFFF
};
struct operation {
/* common */
enum operation_type type;
struct context *ctx;
struct memory_arena *arena;
struct temp_memory tmp;
/* transform + layout + blueprint + cleanup */
struct panel *panel;
struct repository *repo;
int begin, end, inc;
struct box **boxes;
struct list_hook widgets; /* cleanup only */
/* input */
struct list_hook evts;
struct input *in;
/* paint + serialize */
struct box **surfaces;
int cnt;
};
/* context */
enum visibility {HIDDEN, VISIBLE};
enum popup_type {
POPUP_BLOCKING,
POPUP_NON_BLOCKING,
POPUP_CNT
};
enum widget_internal {
WIDGET_INTERNAL_BEGIN = 0x100000,
/* layers */
WIDGET_ROOT,
WIDGET_OVERLAY,
WIDGET_POPUP,
WIDGET_CONTEXTUAL,
WIDGET_UNBLOCKING,
WIDGET_BLOCKING,
WIDGET_UI,
/* widgets */
WIDGET_PANEL,
WIDGET_POPUP_PANEL,
WIDGET_CONTEXTUAL_PANEL
};
enum state_machine_state {
STATE_PREPROCESS,
STATE_GC,
STATE_CLEANUP,
STATE_BLUEPRINT,
STATE_LAYOUTING,
STATE_TRANSFORM,
STATE_INPUT,
STATE_PAINT,
STATE_SERIALIZE,
STATE_DONE
};
struct state_machine {
enum state_machine_state state;
unsigned unbalanced:1;
struct list_hook *iter;
int cnt;
};
struct context {
struct input input;
struct state_machine state_machine;
/* memory */
struct allocator mem;
struct block_allocator blkmem;
struct memory_arena arena;
/* panel */
struct list_hook pan;
struct list_hook garbage;
struct list_hook freelist;
/* tree */
struct box *tree;
struct box *overlay;
struct box *popup;
struct box *contextual;
struct box *unblocking;
struct box *blocking;
struct box *ui;
struct box *active;
struct box *origin;
struct box *hot;
/* misc */
struct panel_state *panel_state;
struct panel *root;
struct panel *stk[16];
int stkcnt;
};
/* context */
api struct context *create(const struct allocator *a);
api void init(struct context *ctx, const struct allocator *a);
api void destroy(struct context *ctx);
api void term(struct context *ctx);
/* input */
api void input_char(struct context *ctx, char c);
api void input_resize(struct context *ctx, int w, int h);
api void input_scroll(struct context *ctx, int x, int y);
api void input_motion(struct context *ctx, int x, int y);
api void input_rune(struct context *ctx, unsigned long r);
api void input_key(struct context *ctx, int key, int down);
api void input_shortcut(struct context *ctx, int id, int down);
api void input_text(struct context *ctx, const char *t, int len);
api void input_button(struct context *ctx, enum mouse_button btn, int down);
/* process */
api struct operation *process_begin(struct context *ctx, unsigned int flags);
api void blueprint(struct operation *op, struct box *b);
api void layout(struct operation *op, struct box *b);
api void transform(struct operation *op, struct box *b);
api void input(struct operation *op, union event *evt, struct box *b);
api void process_end(struct operation *op);
/* panel */
api struct panel_state *begin(struct context *ctx, uiid id);
api void end(struct panel_state *s);
api void clear(struct context *ctx, uiid pid);
api struct panel *panel_init(struct panel *p, uiid id);
api struct panel_state *panel_begin(struct context *ctx, struct panel *p);
api void panel_end(struct panel_state *s);
api struct panel *panel_find(struct context *ctx, uiid id);
/* popup */
api struct panel_state *popup_begin(struct context *ctx, struct panel *pan, uiid id, enum popup_type type);
api void popup_show(struct context *ctx, struct box *popup, enum visibility vis);
api void popup_toggle(struct context *ctx, struct box *popup);
api void popup_close(struct context *ctx, struct panel *pan);
api void popup_end(struct panel_state *s);
/* hash table */
api void store(struct panel_state *s, void *state, uiid id);
api void *load(struct panel_state *s, uiid id);
api void *query(struct context *ctx, uiid pid, uiid widget_id);
/* widgets */
api struct widget_builder *widget_begin(struct panel_state *s, uiid type, const void *prev, int size, int alignment);
api struct box *widget_node_push(struct widget_builder *w, struct box *prev);
api struct box *widget_box_push(struct widget_builder *w, struct box *prev);
api void widget_node_add(struct widget_builder *w, struct box *b, struct box *prev);
api void widget_box_add(struct widget_builder *w, struct box *b, struct box *prev);
api void widget_node_pop(struct widget_builder *w);
api void widget_end(struct widget_builder *w);
/* stack */
api void push(struct panel_state *s, struct box *b);
api struct box *peek(struct panel_state *s);
api void pop(struct panel_state *s);
api void reset(struct panel_state *s);
/* box */
api void box_add_property(struct box *b, enum property_type type);
api void box_shrink(struct box *d, const struct box *s, int pad);
api void measure(struct box *b, int pad);
api void compute(struct box *b, int pad);
/* arena */
api void *arena_push(struct memory_arena *a, int cnt, int size, int align);
#define arena_push_type(a,type) (type*)arena_push(a, 1, szof(type), alignof(type))
#define arena_push_array(a,n,type) (type*)arena_push(a, (n), szof(type), alignof(type))
api void arena_clear(struct memory_arena *a);
/* utf-8 */
api int utf_decode(unsigned long *rune, const char *str, int len);
api int utf_encode(char *s, int cap, unsigned long u);
api int utf_len(const char *s, int len);
api const char* utf_at(unsigned long *rune, int *rune_len, const char *s, int len, int idx);
/* ---------------------------------------------------------------------------
* IMPLEMENTATION
* --------------------------------------------------------------------------- */
#define UTF_SIZE 4
#define UTF_INVALID 0xFFFD
static const unsigned char utfbyte[UTF_SIZE+1] = {0x80,0,0xC0,0xE0,0xF0};
static const unsigned char utfmask[UTF_SIZE+1] = {0xC0,0x80,0xE0,0xF0,0xF8};
static const unsigned long utfmin[UTF_SIZE+1] = {0,0,0x80,0x800,0x10000};
static const unsigned long utfmax[UTF_SIZE+1] = {0x10FFFF,0x7F,0x7FF,0xFFFF,0x10FFFF};
#define qalloc(a,sz) (a)->alloc((a)->usr, sz, __FILE__, __LINE__)
#define qdealloc(a,ptr) (a)->dealloc((a)->usr, ptr, __FILE__, __LINE__)
static void *dalloc(void *usr, int s, const char *file, int line){return malloc((size_t)s);}
static void dfree(void *usr, void *d, const char *file, int line){free(d);}
static const struct allocator default_allocator = {0,dalloc,dfree};
#define list_empty(l) ((l)->next == (l))
#define list_entry(ptr,type,member) containerof(ptr,type,member)
#define list_foreach(i,l) for ((i)=(l)->next; (i)!=(l); (i)=(i)->next)
#define list_foreach_rev(i,l) for ((i)=(l)->prev; (i)!=(l); (i)=(i)->prev)
#define list_foreach_s(i,n,l) for ((i)=(l)->next,(n)=(i)->next;(i)!=(l);(i)=(n),(n)=(i)->next)
#define list_foreach_rev_s(i,n,l) for ((i)=(l)->prev,(n)=(i)->prev;(i)!=(l);(i)=(n),(n)=(i)->prev)
intern int
size_add_valid(int a, int b)
{
if (a < 0 || b < 0) return 0;
return a <= (INT_MAX-b);
}
intern int
size_add2_valid(int a, int b, int c)
{
return size_add_valid(a, b) && size_add_valid(a+b, c);
}
intern int
size_mul_valid(int a, int b)
{
if (a < 0 || b < 0) return 0;
if (b == 0) return 1;
return a <= (INT_MAX/b);
}
intern int
size_add_mul2_valid(int mula, int a, int mulb, int b)
{
/* mula * a + mulb * b */
return size_mul_valid(mula, a) && size_mul_valid(mulb, b) && size_add_valid(mula*a,mulb*b);
}
intern int
size_mul_add2_valid(int mul, int a, int b, int c)
{
/* mul*a + b + c */
return size_mul_valid(a,mul) && size_add2_valid(a*mul,b,c);
}
intern int
size_mul2_add_valid(int mula, int a, int mulb, int b, int c)
{
/* mulb*a + mulb*b + c */
return size_add_mul2_valid(mula,a,mulb,b) && size_add_valid(mula*a+mulb*b,c);
}
intern int
size_mul2_add2_valid(int mula, int a, int mulb, int b, int c, int d)
{
/* mulb*a + mulb*b + c + d */
return size_mul2_add_valid(mula,a,mulb,b,c) && size_add_valid(mula*a+mulb*b+c,d);
}
intern unsigned long
next_pow2(unsigned long n)
{
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
return ++n;
}
intern int
floori(float x)
{
x = cast(float,(cast(int,x) - ((x < 0.0f) ? 1 : 0)));
return cast(int,x);
}
intern int
ceili(float x)
{
if (x < 0) {
int t = cast(int,x);
float r = x - cast(float,t);
return (r > 0.0f) ? (t+1): t;
} else {
int i = cast(int,x);
return (x > i) ? (i+1): i;
}
}
intern float
roundf(float x)
{
int e = 0;
float y = 0;
static const float toint = 1.0f/(1.1920928955078125e-07F);
union {float f; unsigned long i;} u;
u.f = x;
e = (u.i >> 23) & 0xff;
if (e >= 0x7f+23) return x;
if (u.i >> 31) x = -x;
if (e < 0x7f-1)
return 0*u.f;
y = x + toint - toint - x;
if (y > 0.5f)
y = y + x - 1;
else if (y <= -0.5f)
y = y + x + 1;
else y = y + x;
if (u.i >> 31)
y = -y;
return y;
}
intern int
roundi(float x)
{
return cast(int, roundf(x));
}
intern unsigned long
hash(const char *str, unsigned long x)
{
int i = 0;
int len = cast(int,strlen(str));
unsigned long h = x;
for(i = 0; i < len; ++i)
h = 65599lu * h + cast(unsigned char,str[i]);
return h^(h>>16);
}
intern int
utf_validate(unsigned long *u, int i)
{
assert(u);
if (!u) return 0;
if (!between(*u, utfmin[i], utfmax[i]) ||
between(*u, 0xD800, 0xDFFF))
*u = UTF_INVALID;
for (i = 1; *u > utfmax[i]; ++i);
return i;
}
intern unsigned long
utf_decode_byte(char c, int *i)
{
assert(i);
if (!i) return 0;
for(*i = 0; *i < cntof(utfmask); ++(*i)) {
if (((unsigned char)c & utfmask[*i]) == utfbyte[*i])
return (unsigned char)(c & ~utfmask[*i]);
} return 0;
}
api int
utf_decode(unsigned long *u, const char *s, int slen)
{
int i,j, len, type = 0;
unsigned long udecoded;
assert(s); assert(u);
if (!s || !u || !slen)
return 0;
*u = UTF_INVALID;
udecoded = utf_decode_byte(s[0], &len);
if (!between(len, 1, UTF_SIZE))
return 1;
for (i = 1, j = 1; i < slen && j < len; ++i, ++j) {
udecoded = (udecoded << 6) | utf_decode_byte(s[i], &type);
if (type != 0) return j;
} if (j < len) return 0;
*u = udecoded;
utf_validate(u, len);
return len;
}
intern char
utf_encode_byte(unsigned long u, int i)
{
return (char)((utfbyte[i]) | ((unsigned char)u & ~utfmask[i]));
}
api int
utf_encode(char *s, int cap, unsigned long u)
{
int len, i;
len = utf_validate(&u, 0);
if (cap < len || !len || len > UTF_SIZE)
return 0;
for (i = len - 1; i != 0; --i) {
s[i] = utf_encode_byte(u, 0);
u >>= 6;
} s[0] = utf_encode_byte(u, len);
return len;
}
api int
utf_len(const char *s, int len)
{
int result = 0;
int rune_len = 0;
unsigned long rune = 0;
if (!s) return 0;
while ((rune_len = utf_decode(&rune, s, len))) {
len = max(0,len-rune_len);
s += rune_len;
result++;
} return result;
}
api const char*
utf_at(unsigned long *rune, int *rune_len,
const char *s, int len, int idx)
{
int runes = 0;
if (!s) return 0;
while ((*rune_len = utf_decode(rune, s, len))) {
if (runes++ == idx) return s;
len = max(0,len-*rune_len);
s += *rune_len;
} return 0;
}
intern void
transform_init(struct transform *t)
{
assert(t);
if (!t) return;
t->x = t->y = 0;
t->sx = t->sy = 1;
}
intern void
transform_concat(struct transform *d,
const struct transform *a,
const struct transform *b)
{
assert(d && a && b);
if (!d || !a || !b) return;
d->x = a->x + b->x;
d->y = a->y + b->y;
d->sx = a->sx * b->sx;
d->sy = a->sy * b->sy;
}
intern void
transform_rect(struct rect *d, const struct rect *s, const struct transform *t)
{
assert(d && s && t);
if (!d || !s || !t) return;
d->x = floori(cast(float,(s->x + t->x))*t->sx);
d->y = floori(cast(float,(s->y + t->y))*t->sy);
d->w = ceili(cast(float,s->w) * t->sx);
d->h = ceili(cast(float,s->h) * t->sy);
}
intern void
list_init(struct list_hook *list)
{
list->next = list->prev = list;
}
intern void
list__add(struct list_hook *n,
struct list_hook *prev, struct list_hook *next)
{
next->prev = n;
n->next = next;
n->prev = prev;
prev->next = n;
}
intern void
list_add_head(struct list_hook *list, struct list_hook *n)
{
list__add(n, list, list->next);
}
intern void
list_add_tail(struct list_hook *list, struct list_hook *n)
{
list__add(n, list->prev, list);
}
intern void
list__del(struct list_hook *prev, struct list_hook *next)
{
next->prev = prev;
prev->next = next;
}
intern void
list_del(struct list_hook *entry)
{
list__del(entry->prev, entry->next);
entry->next = entry;
entry->prev = entry;
}
intern void
list_move_head(struct list_hook *list, struct list_hook *entry)
{
list_del(entry);
list_add_head(list, entry);
}
intern void
list_move_tail(struct list_hook *list, struct list_hook *entry)
{
list_del(entry);
list_add_tail(list, entry);
}
intern void
list__splice(const struct list_hook *list,
struct list_hook *prev, struct list_hook *next)
{
struct list_hook *first = list->next;
struct list_hook *last = list->prev;
first->prev = prev;
prev->next = first;
last->next = next;
next->prev = last;
}
intern void
list_splice_head(struct list_hook *dst,
struct list_hook *list)
{
if (!list_empty(list)) {
list__splice(list, dst, dst->next);
list_init(list);
}
}
intern void
list_splice_tail(struct list_hook *dst,
struct list_hook *list)
{
if (!list_empty(list)) {
list__splice(list, dst->prev, dst);
list_init(list);
}
}
intern void
block_alloc_init(struct block_allocator *a)
{
list_init(&a->blks);
list_init(&a->freelist);
}
intern struct memory_block*
block_alloc(struct block_allocator *a, int blksz)
{
struct memory_block *blk = 0;
blksz = max(blksz, DEFAULT_MEMORY_BLOCK_SIZE);
if (blksz == DEFAULT_MEMORY_BLOCK_SIZE && !list_empty(&a->freelist)) {
/* allocate from freelist */
blk = list_entry(a->freelist.next, struct memory_block, hook);
list_del(&blk->hook);
} else blk = qalloc(a->mem, blksz);
/* setup block */
zero(blk, szof(*blk));
blk->size = blksz - szof(*blk);
blk->base = cast(unsigned char*, (blk+1));
/* add block into list */
a->blkcnt++;
list_init(&blk->hook);
list_add_head(&a->blks, &blk->hook);
return blk;
}
intern void
block_dealloc(struct block_allocator *a, struct memory_block *blk)
{
assert(a && blk);
if (!a || !blk) return;
list_del(&blk->hook);
if ((blk->size + szof(*blk)) == DEFAULT_MEMORY_BLOCK_SIZE)
list_add_head(&a->freelist, &blk->hook);
else qdealloc(a->mem, blk);
a->blkcnt--;
}
intern void
free_blocks(struct block_allocator *a)
{
struct list_hook *i, *n = 0;
list_foreach_s(i, n, &a->freelist) {
struct memory_block *blk = 0;
blk = list_entry(i, struct memory_block, hook);
list_del(&blk->hook);
qdealloc(a->mem, blk);
}
list_foreach_s(i, n, &a->blks) {
struct memory_block *blk = 0;
blk = list_entry(i, struct memory_block, hook);
list_del(&blk->hook);
qdealloc(a->mem, blk);
} a->blkcnt = 0;
}
api void*
arena_push(struct memory_arena *a, int cnt, int size, int align)
{
int valid = 0;
assert(a);
if (!a) return 0;
align = max(align,1);
valid = a->blk && size_mul_add2_valid(size, cnt, a->blk->used, align);
if (!valid || ((cnt*size) + a->blk->used + align) > a->blk->size) {
if (!size_mul_add2_valid(size, cnt, szof(struct memory_block), align))
return 0;
{int minsz = cnt*size + szof(struct memory_block) + align;
int blksz = max(minsz, DEFAULT_MEMORY_BLOCK_SIZE);
struct memory_block *blk = block_alloc(a->mem, blksz);
blk->prev = a->blk;
a->blk = blk;
a->blkcnt++;}
}
{struct memory_block *blk = a->blk;
unsigned char *raw = blk->base + blk->used;
unsigned char *res = align(raw, align);
blk->used += (cnt*size) + (res-raw);
zero(res, (cnt*size));
return res;}
}
intern void
arena_free_last_blk(struct memory_arena *a)
{
struct memory_block *blk = a->blk;
a->blk = blk->prev;
block_dealloc(a->mem, blk);
a->blkcnt--;
}
api void
arena_clear(struct memory_arena *a)
{
assert(a);
if (!a) return;
while (a->blk)
arena_free_last_blk(a);
}
intern struct temp_memory
temp_memory_begin(struct memory_arena *a)
{
struct temp_memory res;
res.used = a->blk ? a->blk->used: 0;
res.blk = a->blk;
res.arena = a;
a->tmpcnt++;
return res;
}
intern void
temp_memory_end(struct temp_memory tmp)
{
struct memory_arena *a = tmp.arena;
while (a->blk != tmp.blk)
arena_free_last_blk(a);
if (a->blk) a->blk->used = tmp.used;
a->tmpcnt--;
}
intern void
insert(struct table *tbl, uiid id, void *b)
{
uiid cnt = cast(uiid, tbl->cnt);
uiid idx = id & (cnt-1), begin = idx;
do {uiid key = tbl->keys[idx];
if (key) continue;
tbl->keys[idx] = id;
tbl->vals[idx] = b; return;
} while ((idx = ((idx+1) & (cnt-1))) != begin);
}
intern struct box*
lookup(struct table *tbl, uiid id)
{
uiid key, cnt = cast(uiid, tbl->cnt);
uiid idx = id & (cnt-1), begin = idx;
do {if (!(key = tbl->keys[idx])) return 0;
if (key == id) return tbl->vals[idx];
} while ((idx = ((idx+1) & (cnt-1))) != begin);
return 0;
}
api void
box_shrink(struct box *d, const struct box *s, int pad)
{
assert(d && s);
if (!d || !s) return;
d->local.x = s->local.x + pad;
d->local.y = s->local.y + pad;
d->local.w = max(0, s->local.w - 2*pad);
d->local.h = max(0, s->local.h - 2*pad);
}
api void
box_add_property(struct box *b, enum property_type type)
{
assert(b);
if (!b) return;
switch (type) {
case PROPERTY_INTERACTIVE: b->interactive=1; break;
case PROPERTY_MOVABLE_X: b->movable_x=1; break;
case PROPERTY_MOVABLE_Y: b->movable_y=1; break;
case PROPERTY_BACKGROUND: b->background=1; break;
case PROPERTY_UNIFORM: b->uniform=1; break;
case PROPERTY_SELECTABLE: b->selectable=1; break;}
}
intern int
box_intersect(const struct box *f, const struct box *s)
{
const struct rect *a = &f->screen;
const struct rect *b = &s->screen;
return intersect(a->x, a->y, a->w, a->h, b->x, b->y, b->w, b->h);
}
api void
input_resize(struct context *ctx, int w, int h)
{
struct input *in = 0;
assert(ctx);
if (!ctx) return;
in = &ctx->input;
in->resized = 1;
in->width = w;
in->height = h;
}
api void
input_motion(struct context *ctx, int x, int y)
{
struct input *in = 0;
assert(ctx);
if (!ctx) return;
in = &ctx->input;
in->mouse.x = x;
in->mouse.y = y;
}
api void
input_key(struct context *ctx, int key, int down)
{
struct input *in = 0;
assert(ctx);
if (!ctx) return;
in = &ctx->input;
if (key < 0 || key >= cntof(in->keys) || (in->keys[key].down == down))
return;
in->keys[key].transitions++;
in->keys[key].down = !!down;
}
api void
input_button(struct context *ctx, enum mouse_button idx, int down)
{
struct input *in = 0;
struct key *btn = 0;
assert(ctx);
if (!ctx) return;
in = &ctx->input;
if (in->mouse.btn[idx].down == down)
return;
btn = in->mouse.btn + idx;
btn->down = !!down;
btn->transitions++;
}
api void
input_shortcut(struct context *ctx, int shortcut, int down)
{
struct input *in = 0;
assert(ctx);
if (!ctx) return;
in = &ctx->input;
assert(shortcut < cntof(in->shortcuts));
if (in->shortcuts[shortcut].down == down)
return;
in->shortcuts[shortcut].transitions++;
in->shortcuts[shortcut].down = !!down;
}
api void
input_scroll(struct context *ctx, int x, int y)
{
struct input *in = 0;
assert(ctx);
if (!ctx) return;
in = &ctx->input;
in->mouse.wheelx += x;
in->mouse.wheely += y;
}
api void
input_text(struct context *ctx, const char *buf, int len)
{
struct input *in = 0;
assert(ctx);
if (!ctx) return;
in = &ctx->input;
if (in->text_len + len + 1 >= cntof(in->text))
return;
copy(in->text + in->text_len, buf, len);
in->text_len += len;
in->text[in->text_len] = 0;
}
api void
input_char(struct context *ctx, char c)
{
input_text(ctx, &c, 1);
}
api void
input_rune(struct context *ctx, unsigned long r)
{
int len = 0;
char buf[UTF_SIZE];
assert(ctx);
if (!ctx) return;
len = utf_encode(buf, UTF_SIZE, r);
input_text(ctx, buf, len);
}
api void
reset(struct panel_state *s)
{
assert(s);
if (!s) return;
s->stkcnt = 0;
}
api void
push(struct panel_state *s, struct box *b)
{
assert(s);
if (!s) return;
assert(s->stkcnt < cntof(s->stk));
s->stk[s->stkcnt++] = b;
}
api struct box*
peek(struct panel_state *s)
{
struct repository *r = 0;
assert(s);
if (!s) return 0;
r = s->repo;
if (!s->stkcnt) return r->tree;
return s->stk[s->stkcnt-1];
}
api void
pop(struct panel_state *s)
{
assert(s);
if (!s) return;
assert(s->stkcnt);
--s->stkcnt;
}
intern struct panel*
panel_create(struct memory_arena *a,
struct list_hook *freelist)
{
struct panel *p = 0;
if (!list_empty(freelist)) {
/* allocate from freelist */
struct list_hook *h = freelist->next;
list_del(freelist->next);
p = list_entry(h, struct panel, hook);
zero(p, szof(*p));
} else p = arena_push_type(a, struct panel);
return p;
}
api struct panel*
panel_init(struct panel *p, uiid id)
{
int i = 0;
assert(p);
if (!p) return 0;
p->id = id;
list_init(&p->hook);
list_init(&p->root.node);
list_init(&p->root.hook);
list_init(&p->root.links);
transform_init(&p->root.local_transform);
transform_init(&p->root.screen_transform);
for (i = 0; i < cntof(p->repo); ++i) {
struct repository *r = p->repo + i;
zero(r, szof(*r));
list_init(&r->widgets);
list_init(&r->state);
r->tree = &p->root;
} return p;
}
intern void
panel_destroy(struct context *ctx,
struct panel *p)
{
struct list_hook *i, *n;
struct list_hook tmp;
list_init(&tmp);
/* unlink panel */
if (p->parent)
p->parent->root.cnt--;
list_del(&p->root.node);
list_move_tail(&tmp, &p->hook);
/* remove sub-tree */
list_foreach_s(i,n, &ctx->pan) {
struct list_hook *k = 0;
struct panel *sub = list_entry(i, struct panel, hook);
list_foreach(k, &tmp) {
struct panel *gb = list_entry(k, struct panel, hook);
if (sub->parent == gb) {
list_del(&sub->root.node);
list_move_tail(&tmp, &sub->hook);
break;
}
}
} list_splice_tail(&ctx->garbage, &tmp);
}
api struct panel*
panel_find(struct context *ctx, uiid id)
{
struct list_hook *i = 0;
assert(ctx);
if (!ctx) return 0;
list_foreach(i, &ctx->pan) {
struct panel *p = 0;
p = list_entry(i, struct panel, hook);
if (p->id == id) return p;
} return 0;
}
api void
store(struct panel_state *s, void *state, uiid id)
{
static const int size = szof(struct state);
static const int align = alignof(struct state);
struct repository *r = 0;
struct state *st = 0;
assert(s);
if(!s) return;
r = s->repo;
st = arena_push(s->arena, 1, size, align);
list_init(&st->hook);
list_add_tail(&r->state, &st->hook);
st->ptr = state;
st->id = id;
r->tbl.cnt++;
}
api void*
query(struct context *ctx, uiid pid, uiid id)
{
struct repository *r = 0;
struct panel *p = 0;
assert(ctx);
if (!ctx) return 0;
p = panel_find(ctx, pid);
if (!p) return 0;
r = &p->repo[p->act];
if (!r->cnt || !r->tbl.cnt)
return 0;
return lookup(&r->tbl, id);
}
api void*
load(struct panel_state *s, uiid id)
{
struct repository *r = 0;
struct panel *p = 0;
assert(s);
if (!s) return 0;
p = s->panel;
r = &p->repo[p->act];
if (!r->cnt || !r->tbl.cnt)
return 0;
return lookup(&r->tbl, id);
}
intern struct box*
widget_get_parent(struct widget_builder *w)
{
struct panel_state *s = 0;
struct repository *r = 0;
assert(w);
assert(w->state);
assert(w->state->repo);
if (!w || !w->state || !w->state->repo)
return 0;
s = w->state;
r = s->repo;
if (!w->stkcnt) return r->tree;
return w->stk[w->stkcnt-1];
}
api struct widget_builder*
widget_begin(struct panel_state *s, uiid type, const void *prev,
int size, int alignment)
{
struct widget_builder *w = 0;
assert(s);
if (!s) return 0;
w = s->builder;
zero(w, szof(*w));
w->widget = arena_push(s->arena, 1, size, alignment);
w->widget_last = (prev) ? prev : w->widget;
w->stk[w->stkcnt++] = peek(s);
w->init = w->widget_last == prev;
w->type = type;
w->arena = s->arena;
w->state = s;
return w;
}
api void
widget_box_add(struct widget_builder *w, struct box *b, struct box *prev)
{
struct context *ctx = 0;
struct panel_state *st = 0;
struct repository *repo = 0;
struct box *p = 0;
assert(w);
if (!w) return;
st = w->state;
ctx = st->ctx;
repo = st->repo;
b->type = w->type;
b->widget = w->widget;
list_init(&b->hook);
list_init(&b->node);
list_init(&b->links);
transform_init(&b->local_transform);
transform_init(&b->screen_transform);
if (ctx->active == prev)
ctx->active = b;
if (ctx->origin == prev)
ctx->origin = b;
if (ctx->hot == prev)
ctx->hot = b;
/* link box into parent node */
p = b->parent = widget_get_parent(w);
list_add_tail(&p->links, &b->node);
b->depth = p->depth + 1;
repo->depth = max(repo->depth, b->depth);
/* add widget box into list */
if (!w->cnt && w->widget) {
list_add_head(&repo->widgets, &b->hook);
if (prev) prev->alive = 1;
} w->cnt++;
}
api void
widget_node_add(struct widget_builder *w, struct box *b, struct box *prev)
{
assert(w);
if (!w) return;
widget_box_add(w, b, prev);
assert(w->stkcnt < cntof(w->stk));
w->stk[w->stkcnt++] = b;
}
api struct box*
widget_box_push(struct widget_builder *w, struct box *prev)
{
struct box *b = arena_push_type(w->arena, struct box);
widget_box_add(w, b, prev);
return b;
}
api struct box*
widget_node_push(struct widget_builder *w, struct box *prev)
{
struct box *b = arena_push_type(w->arena, struct box);
widget_node_add(w, b, prev);
return b;
}
api void
widget_node_pop(struct widget_builder *w)
{
assert(w);
if (!w) return;
assert(w->stkcnt);
--w->stkcnt;
}
api void
widget_end(struct widget_builder *w)
{
struct panel_state *st = 0;
struct repository *repo = 0;
assert(w);
if (!w) return;
st = w->state;
repo = st->repo;
repo->cnt += w->cnt;
assert(!(--w->stkcnt));
}
api struct panel_state*
panel_begin(struct context *ctx, struct panel *p)
{
struct panel_state *s = 0;
struct panel *parent = 0;
struct box *root = 0;
assert(ctx && p);
if (!ctx || !p) return 0;
if (list_empty(&p->hook)) {
list_add_tail(&ctx->pan, &p->hook);
assert(ctx->stkcnt < cntof(ctx->stk));
parent = (ctx->stkcnt) ? ctx->stk[ctx->stkcnt-1]: ctx->root;
if (parent) {
/* link panel into parent node */
struct box *pb = peek(&parent->state);
list_add_tail(&pb->links, &p->root.node);
p->root.parent = pb;
p->parent = parent;
}
} else parent = p->parent;
ctx->stk[ctx->stkcnt++] = p;
/* setup panel state */
s = &p->state;
zero(s, szof(*s));
s->root = &p->root;
s->repo = &p->repo[!p->act];
s->arena = &s->repo->arena;
s->arena->mem = &ctx->blkmem;
s->builder = &p->builder;
s->panel = p;
s->ctx = ctx;
/* setup root node */
root = &p->root;
root->uniform = 1;
root->interactive = 1;
s->repo->tree = root;
if (parent) {
/* reset node */
struct panel_state *ps = &parent->state;
struct box *pb = peek(ps);
root->cnt = 0;
root->widget = p;
root->type = WIDGET_PANEL;
root->depth = pb->depth + 1;
s->repo->depth = root->depth;
list_init(&root->links);
list_init(&root->hook);
} else {
/* init tree root node */
root->type = WIDGET_ROOT;
root->widget = ctx;
ctx->tree = root;
} push(s, root);
return s;
}
api void
panel_end(struct panel_state *s)
{
struct context *ctx = 0;
assert(s);
if (!s) return;
ctx = s->ctx;
assert(ctx->stkcnt > 0);
ctx->stkcnt--;
}
api struct panel_state*
begin(struct context *ctx, uiid id)
{
struct panel *p = 0;
assert(ctx);
if (!ctx) return 0;
/* find or create panel */
p = panel_find(ctx, id);
if (!p) {
p = panel_create(&ctx->arena, &ctx->freelist);
if (!p) return 0;
panel_init(p, id);
} return panel_begin(ctx, p);
}
api void
end(struct panel_state *s)
{
panel_end(s);
}
api void
clear(struct context *ctx, uiid pid)
{
struct panel *p = 0;
assert(ctx);
if (!ctx) return;
p = panel_find(ctx, pid);
if (p) panel_destroy(ctx, p);
}
intern int
popup_is_active(struct context *ctx, enum popup_type type)
{
int active = 0;
struct list_hook *i = 0;
struct box *layer = 0;
struct box *skip = 0;
switch (type) {
default: assert(layer != 0); break;
case POPUP_BLOCKING:
layer = ctx->popup;
skip = ctx->contextual; break;
case POPUP_NON_BLOCKING:
layer = ctx->contextual;
skip = ctx->unblocking; break;}
assert(layer);
list_foreach(i, &layer->links) {
struct box *b = list_entry(i,struct box,node);
if (b == skip) continue;
if (!b->hidden) return 1;
} return active;
}
api void
popup_close(struct context *ctx, struct panel *pan)
{
panel_destroy(ctx, pan);
}
api void
popup_show(struct context *ctx,
struct box *popup, enum visibility vis)
{
assert(popup);
if (!popup || !popup->parent)
return;
ctx->blocking->interactive = 0;
popup->hidden = !vis;
}
api void
popup_toggle(struct context *ctx, struct box *popup)
{
assert(popup);
if (!popup || !popup->parent) return;
popup_show(ctx, popup, popup->hidden ? VISIBLE: HIDDEN);
}
api struct panel_state*
popup_begin(struct context *ctx, struct panel *pan, uiid id,
enum popup_type type)
{
int init = 0;
struct box *popup = 0;
struct panel_state *ps = 0;
assert(ctx);
if (!ctx) return 0;
/* push root panel and popup box */
ctx->stk[ctx->stkcnt++] = ctx->root;
switch (type) {
case POPUP_BLOCKING:
push(&ctx->root->state, ctx->popup); break;
case POPUP_NON_BLOCKING:
push(&ctx->root->state, ctx->contextual); break;
}
init = !panel_find(ctx, id);
if (!pan) {
ps = begin(ctx, id);
pan = ps->panel;
} else ps = panel_begin(ctx, pan);
popup = &pan->root;
popup->interactive = 1;
popup->uniform = 0;
popup->hidden = (!init)?popup->hidden:1;
return &pan->state;
}
api void
popup_end(struct panel_state *s)
{
struct context *ctx = 0;
assert(s);
if (!s) return;
ctx = s->ctx;
panel_end(s);
pop(&ctx->root->state);
assert(ctx->stkcnt > 0);
ctx->stkcnt--;
}
api void
init(struct context *ctx, const struct allocator *a)
{
struct panel_state *s = 0;
struct widget_builder *w = 0;
assert(ctx);
if (!ctx) return;
a = (a) ? a: &default_allocator;
zero(ctx, szof(*ctx));
ctx->mem = *a;
ctx->blkmem.mem = &ctx->mem;
ctx->arena.mem = &ctx->blkmem;
block_alloc_init(&ctx->blkmem);
list_init(&ctx->pan);
list_init(&ctx->garbage);
list_init(&ctx->freelist);
s = begin(ctx, 0);
w = widget_begin(s, WIDGET_ROOT, 0,0,0);
ctx->overlay = widget_node_push(w, ctx->overlay);
box_add_property(ctx->overlay, PROPERTY_INTERACTIVE);
box_add_property(ctx->overlay, PROPERTY_UNIFORM);
ctx->popup = widget_node_push(w, ctx->popup);
box_add_property(ctx->popup, PROPERTY_INTERACTIVE);
box_add_property(ctx->popup, PROPERTY_UNIFORM);
ctx->contextual = widget_node_push(w, ctx->contextual);
box_add_property(ctx->contextual, PROPERTY_INTERACTIVE);
box_add_property(ctx->contextual, PROPERTY_UNIFORM);
ctx->unblocking = widget_node_push(w, ctx->unblocking);
box_add_property(ctx->unblocking, PROPERTY_INTERACTIVE);
box_add_property(ctx->unblocking, PROPERTY_UNIFORM);
ctx->blocking = widget_node_push(w, ctx->blocking);
box_add_property(ctx->blocking, PROPERTY_INTERACTIVE);
box_add_property(ctx->blocking, PROPERTY_UNIFORM);
ctx->ui = widget_box_push(w, ctx->ui);
box_add_property(ctx->ui, PROPERTY_INTERACTIVE);
box_add_property(ctx->ui, PROPERTY_BACKGROUND);
widget_node_pop(w);
widget_node_pop(w);
widget_node_pop(w);
widget_node_pop(w);
widget_node_pop(w);
widget_end(w);
push(s, ctx->ui);
end(s);
{struct operation *op = 0;
while ((op = process_begin(ctx, 0)))
process_end(op);}
ctx->hot = ctx->tree;
ctx->root = s->panel;
ctx->active = ctx->tree;
ctx->origin = ctx->tree;
}
api struct context*
create(const struct allocator *a)
{
struct context *ctx = 0;
a = (a) ? a: &default_allocator;
ctx = qalloc(a, szof(*ctx));
init(ctx, a);
return ctx;
}
api void
term(struct context *ctx)
{
assert(ctx);
if (!ctx) return;
free_blocks(&ctx->blkmem);
}
api void
destroy(struct context *ctx)
{
assert(ctx);
if (!ctx) return;
term(ctx);
qdealloc(&ctx->mem, ctx);
}
intern struct operation*
operation_begin(enum operation_type type,
struct context *ctx, struct memory_arena *arena)
{
struct temp_memory tmp;
struct operation *res = 0;
tmp = temp_memory_begin(arena);
res = arena_push_type(arena, struct operation);
res->arena = arena;
res->type = type;
res->ctx = ctx;
res->tmp = tmp;
list_init(&res->evts);
list_init(&res->widgets);
return res;
}
intern void
operation_end(struct operation *op)
{
assert(op);
if (!op) return;
temp_memory_end(op->tmp);
}
intern struct box**
bfs(struct box **buf, struct box *root)
{
struct list_hook *i = 0;
unsigned long head = 0, tail = 1;
struct box **que = buf; que[tail] = root;
while (head < tail) {
struct box *b = que[++head];
list_foreach(i, &b->links)
que[++tail] = list_entry(i,struct box,node);
} return que+1;
}
intern int
dfs(struct box **buf, struct box **stk, struct box *root)
{
int tail = 0;
unsigned long head = 0;
struct list_hook *i = 0;
stk[head++] = root;
while (head > 0) {
struct box *b = stk[--head];
buf[tail++] = b;
list_foreach_rev(i, &b->links) {
struct box *s = list_entry(i,struct box,node);
if (s->hidden || !box_intersect(b, s)) continue;
stk[head++] = s;
}
} return tail;
}
intern void
reorder(struct box *b)
{
while (b->parent) {
struct list_hook *i = 0;
struct box *p = b->parent;
if (p->uniform) goto nxt;
list_foreach(i, &p->links) {
struct box *s = list_entry(i, struct box, node);
if (s != b || s->background) continue;
list_move_tail(&p->links, &s->node);
break;
} nxt: b = p;
} return;
}
intern struct box*
at(struct box *b, int mx, int my)
{
struct list_hook *i = 0;
rn: list_foreach_rev(i, &b->links) {
struct box *sub = list_entry(i, struct box, node);
if (!sub->interactive || sub->hidden) continue;
if (inbox(mx, my, sub->screen.x, sub->screen.y, sub->screen.w, sub->screen.h))
{b = sub; goto rn;}
} return b;
}
intern void
event_add_box(union event *evt, struct box *box)
{
assert(evt->hdr.cnt < evt->hdr.cap);
if (evt->hdr.cnt >= evt->hdr.cap) return;
evt->hdr.boxes[evt->hdr.cnt++] = box;
}
intern union event*
event_begin(struct operation *op, enum event_type type, struct box *orig)
{
union event *res = 0;
res = arena_push_type(op->arena, union event);
list_init(&res->hdr.hook);
list_add_tail(&op->evts, &res->hdr.hook);
res->type = type;
res->hdr.origin = orig;
res->hdr.cap = orig->depth + 1;
res->hdr.boxes = arena_push_array(op->arena, res->hdr.cap, struct box*);
event_add_box(res, orig);
return res;
}
intern void
event_end(union event *evt)
{
unused(evt);
}
intern struct list_hook*
it(struct list_hook *list, struct list_hook *iter)
{
if (iter && iter->next == list)
iter = 0;
else if (!iter)
iter = list->next;
else iter = iter->next;
return iter;
}
intern struct list_hook*
itr(struct list_hook *list, struct list_hook *iter)
{
if (iter && iter->prev == list)
iter = 0;
else if (!iter)
iter = list->prev;
else iter = iter->prev;
return iter;
}
intern void
swap_buffers(struct panel *p, struct repository *r)
{
arena_clear(&r->arena);
zero(r, szof(*r));
list_init(&r->widgets);
list_init(&r->state);
p->act = !p->act;
}
api struct operation*
process_begin(struct context *ctx, unsigned int flags)
{
int i = 0;
#define jmpto(sm,s) do {(sm)->state = s; goto run;} while(0)
struct state_machine *sm = 0;
assert(ctx);
assert(!ctx->stkcnt);
if (!ctx) return 0;
sm = &ctx->state_machine;
run: switch(sm->state) {
case STATE_PREPROCESS: {
struct list_hook *pi = 0;
list_foreach(pi, &ctx->pan) {
struct panel *p = list_entry(pi, struct panel, hook);
struct repository *r = &p->repo[!p->act];
if (!r->cnt) {
/* explicitly reset input state */
if ((flags & flag(OP_INPUT))) {
r = &p->repo[p->act];
for (i = 0; i < r->cnt; ++i) {
struct box *b = r->bfs[i];
b->entered = b->exited = 0;
b->drag_end = b->moved = 0;
b->pressed = b->released = 0;
b->clicked = b->scrolled = 0;
b->drag_begin = b->dragged = 0;
}
} continue;
} sm->unbalanced = 1;
/* allocate hash table */
{unsigned long tblcnt = 0;
tblcnt = cast(unsigned long, (cast(float,r->tbl.cnt)*1.3f));
tblcnt = next_pow2(tblcnt);
r->tbl.cnt = cast(int, tblcnt);
r->tbl.keys = arena_push_array(&r->arena, r->tbl.cnt, uiid);
r->tbl.vals = arena_push_array(&r->arena, r->tbl.cnt, void*);}
r->cnt++;
/* insert each widget root box into table */
{struct list_hook *bi = 0;
list_foreach(bi, &r->state) {
struct state *st = list_entry(bi, struct state, hook);
if (!st->ptr || !st->id) continue;
insert(&r->tbl, st->id, st->ptr);
}}
/* generate list of boxes in bfs order */
r->bfs = arena_push_array(&r->arena, r->cnt+1, struct box*);
r->bfs = bfs(r->bfs, r->tree);
} jmpto(sm, STATE_CLEANUP);
}
case STATE_CLEANUP: {
struct panel *p = 0;
struct repository *r = 0;
struct operation *op = 0;
if (list_empty(&ctx->garbage))
jmpto(sm, STATE_GC);
sm->iter = ctx->garbage.next;
p = list_entry(sm->iter, struct panel, hook);
r = &p->repo[p->act];
list_del(&p->hook);
/* setup operation */
op = operation_begin(OP_CLEANUP, ctx, &r->arena);
list_splice_head(&op->widgets, &r->widgets);
op->panel = p;
op->repo = r;
op->boxes = r->bfs;
op->end = r->cnt;
op->begin = 0;
op->inc = 1;
op->panel = p;
op->repo = r;
return op;
}
case STATE_GC: {
struct panel *p = 0;
struct repository *r = 0;
struct operation *op = 0;
do {sm->iter = it(&ctx->pan, sm->iter);
if (!sm->iter) {
/* state transition table */
if (sm->unbalanced && (flags & PROCESS_BLUEPRINT))
jmpto(sm, STATE_BLUEPRINT);
else if (flags & flag(OP_SERIALIZE))
jmpto(sm, STATE_SERIALIZE);
else if (flags & flag(OP_INPUT))
jmpto(sm, STATE_INPUT);
else if (flags & flag(OP_PAINT))
jmpto(sm, STATE_PAINT);
else jmpto(sm, STATE_DONE);
}
p = list_entry(sm->iter, struct panel, hook);
r = &p->repo[p->act];
} while (!p->repo[!p->act].cnt);
/* setup operation */
op = operation_begin(OP_CLEANUP, ctx, &ctx->arena);
op->boxes = r->bfs;
op->end = r->cnt;
op->begin = 0;
op->inc = 1;
op->panel = p;
op->repo = r;
/* gather up all "dead" widget boxes */
{struct list_hook *n, *x;
list_foreach_s(n, x, &r->widgets) {
struct box *b = 0;
b = list_entry(n, struct box, hook);
if (b->alive) continue;
list_move_tail(&op->widgets, &b->hook);
}} return op;
}
case STATE_BLUEPRINT: {
struct panel *p = 0;
struct repository *r = 0;
struct operation *op = 0;
sm->iter = itr(&ctx->pan, sm->iter);
if (!sm->iter) jmpto(sm, STATE_LAYOUTING);
p = list_entry(sm->iter, struct panel, hook);
r = &p->repo[p->act];
/* setup operation */
op = operation_begin(OP_BLUEPRINT, ctx, &r->arena);
op->end = op->inc = -1;
op->begin = r->cnt-1;
op->boxes = r->bfs;
op->panel = p;
op->repo = r;
return op;
}
case STATE_LAYOUTING: {
struct panel *p = 0;
struct repository *r = 0;
struct operation *op = 0;
sm->iter = it(&ctx->pan, sm->iter);
if (!sm->iter) {
sm->unbalanced = 0;
/* state transition table */
if (flags & PROCESS_TRANSFORM)
jmpto(sm, STATE_TRANSFORM);
else if (flags & PROCESS_PAINT)
jmpto(sm, STATE_PAINT);
else jmpto(sm, STATE_DONE);
}
p = list_entry(sm->iter, struct panel, hook);
r = &p->repo[p->act];
/* setup operation */
op = operation_begin(OP_LAYOUTING, ctx, &r->arena);
op->begin = 0, op->inc = 1;
op->end = max(0,r->cnt);
op->boxes = r->bfs;
op->panel = p;
op->repo = r;
return op;
}
case STATE_TRANSFORM: {
/* calculate all box screen coordinates and transformations */
struct panel *p = 0;
struct repository *r = 0;
struct operation *op = 0;
sm->iter = it(&ctx->pan, sm->iter);
if (!sm->iter) {
/* state transition table */
if (sm->unbalanced && (flags & PROCESS_BLUEPRINT))
jmpto(sm, STATE_BLUEPRINT);
else if (flags & PROCESS_INPUT)
jmpto(sm, STATE_INPUT);
else if (flags & PROCESS_PAINT)
jmpto(sm, STATE_PAINT);
else jmpto(sm, STATE_DONE);
}
p = list_entry(sm->iter, struct panel, hook);
r = &p->repo[p->act];
/* setup operation */
op = operation_begin(OP_TRANSFORM, ctx, &r->arena);
op->begin = 0, op->inc = 1;
op->end = max(0,r->cnt);
op->boxes = r->bfs;
op->panel = p;
op->repo = r;
return op;
}
case STATE_INPUT: {
struct operation *op = 0;
struct input *in = &ctx->input;
if (in->resized) {
struct box *root = ctx->tree;
in->resized = 0;
root->local.w = root->screen.w = in->width;
root->local.h = root->screen.h = in->height;
if (root->local.w != in->width || root->local.h != in->height)
jmpto(sm, STATE_BLUEPRINT);
}
op = operation_begin(OP_INPUT, ctx, &ctx->arena);
op->in = in;
in->mouse.dx = in->mouse.x - in->mouse.lx;
in->mouse.dy = in->mouse.y - in->mouse.ly;
if (in->mouse.dx || in->mouse.dy) {
/* motion - entered, exited, dragged, moved */
int mx = in->mouse.x, my = in->mouse.y;
int lx = in->mouse.lx, ly = in->mouse.ly;
struct box *last = ctx->hot;
ctx->hot = at(ctx->tree, mx, my);
if (ctx->hot != last) {
union event *evt;
struct box *prev = last;
struct box *cur = ctx->hot;
{const struct rect *c = &cur->screen, *p = &prev->screen;
cur->entered = !inbox(lx, ly, c->x, c->y, c->w, c->h);
prev->exited = !inbox(mx, my, p->x, p->y, p->w, p->h);}
/* exited */
evt = event_begin(op, EVT_EXITED, prev);
evt->entered.cur = ctx->hot;
evt->entered.last = last;
while ((prev = prev->parent)) {
const struct rect *p = &prev->screen;
int was = inbox(lx, ly, p->x, p->y, p->w, p->h);
int isnt = !inbox(mx, my, p->x, p->y, p->w, p->h);
prev->exited = was && isnt;
event_add_box(evt, prev);
} event_end(evt);
/* entered */
evt = event_begin(op, EVT_ENTERED, cur);
evt->exited.cur = ctx->hot;
evt->exited.last = last;
while ((cur = cur->parent)) {
const struct rect *c = &cur->screen;
int wasnt = !inbox(lx, ly, c->x, c->y, c->w, c->h);
int is = inbox(mx, my, c->x, c->y, c->w, c->h);
cur->entered = wasnt && is;
event_add_box(evt, cur);
} event_end(evt);
}
if (ctx->active == ctx->origin) {
/* dragged, moved */
struct box *a = ctx->active;
union event *evt = 0;
struct box *act = 0;
/* moved */
if (a->movable_x || a->movable_y) {
a->moved = 1;
if (a->movable_x)
a->local.x += in->mouse.dx;
if (a->movable_y)
a->local.y += in->mouse.dy;
sm->unbalanced = 1;
evt = event_begin(op, EVT_MOVED, act = a);
evt->moved.x = in->mouse.dx;
evt->moved.y = in->mouse.dy;
while ((act = act->parent))
event_add_box(evt, act);
event_end(evt);
}
/* dragged */
evt = event_begin(op, EVT_DRAGGED, act = a);
evt->dragged.x = in->mouse.dx;
evt->dragged.y = in->mouse.dy;
while ((act = act->parent))
event_add_box(evt, act);
event_end(evt);
a->dragged = 1;
}
/* reset */
in->mouse.dx = in->mouse.dy = 0;
in->mouse.lx = in->mouse.x;
in->mouse.ly = in->mouse.y;
}
for (i = 0; i < cntof(in->mouse.btn); ++i) {
/* button - pressed, released, clicked */
struct key *btn = in->mouse.btn + i;
struct box *a = ctx->active;
struct box *o = ctx->origin;
struct box *h = ctx->hot;
union event *evt = 0;
struct box *act = 0;
if (!btn->transitions) continue;
ctx->active = (btn->down && btn->transitions) ? ctx->hot: ctx->active;
ctx->origin = o = (btn->down && btn->transitions) ? ctx->hot: ctx->tree;
if (ctx->active != a && (btn->down && btn->transitions))
reorder(a);
/* pressed */
h->pressed = btn->down && btn->transitions;
if (h->pressed) {
evt = event_begin(op, EVT_PRESSED, act = h);
evt->pressed.x = in->mouse.x;
evt->pressed.y = in->mouse.y;
while ((act = act->parent)) {
event_add_box(evt, act);
act->pressed = 1;
} event_end(evt);
/* drag_begin */
evt = event_begin(op, EVT_DRAG_BEGIN, act = h);
evt->drag_begin.x = in->mouse.x;
evt->drag_begin.y = in->mouse.y;
while ((act = act->parent))
event_add_box(evt, act);
event_end(evt);
a->drag_begin = 1;
}
/* released */
h->released = !btn->down && btn->transitions;
if (h->released) {
evt = event_begin(op, EVT_RELEASED, act = a);
evt->released.x = in->mouse.x;
evt->released.y = in->mouse.y;
while ((act = act->parent)) {
event_add_box(evt, act);
act->released = 1;
} event_end(evt);
if (ctx->hot == ctx->active) {
/* clicked */
a->clicked = 1;
evt = event_begin(op, EVT_CLICKED, act = a);
evt->clicked.x = in->mouse.x;
evt->clicked.y = in->mouse.y;
while ((act = act->parent)) {
event_add_box(evt, act);
act->clicked = 1;
} event_end(evt);
}
/* drag_end */
evt = event_begin(op, EVT_DRAG_END, act = o);
evt->drag_end.x = in->mouse.x;
evt->drag_end.y = in->mouse.y;
while ((act = act->parent))
event_add_box(evt, act);
event_end(evt);
o->drag_end = 1;
} btn->transitions = 0;
}
if (in->mouse.wheelx || in->mouse.wheely) {
/* scroll */
union event *evt = 0;
struct box *a = ctx->active;
evt = event_begin(op, EVT_KEY, a);
evt->scroll.x = in->mouse.wheelx;
evt->scroll.y = in->mouse.wheely;
while ((a = a->parent)) {
event_add_box(evt, a);
a->scrolled = 1;
} event_end(evt);
in->mouse.wheelx = 0;
in->mouse.wheely = 0;
}
for (i = 0; i < cntof(in->keys); ++i) {
/* key - up, down */
union event *evt = 0;
struct box *a = ctx->active;
if (!in->keys[i].transitions)
continue;
evt = event_begin(op, EVT_KEY, a);
evt->key.pressed = in->keys[i].down && in->keys[i].transitions;
evt->key.released = !in->keys[i].down && in->keys[i].transitions;
evt->key.shift = in->shift;
evt->key.super = in->super;
evt->key.ctrl = in->ctrl;
evt->key.alt = in->alt;
evt->key.code = i;
while ((a = a->parent))
event_add_box(evt, a);
event_end(evt);
in->keys[i].transitions = 0;
}
for (i = 0; i < cntof(in->shortcuts); ++i) {
struct key *key = in->shortcuts + i;
key->transitions = 0;
}
if (in->text_len) {
/* text */
struct box *a = ctx->active;
union event *evt = event_begin(op, EVT_TEXT, a);
evt->text.buf = in->text;
evt->text.len = in->text_len;
evt->text.shift = in->shift;
evt->text.super = in->super;
evt->text.ctrl = in->ctrl;
evt->text.alt = in->alt;
while ((a = a->parent))
event_add_box(evt, a);
event_end(evt);
}
/* state transition table */
if (sm->unbalanced && (flags & PROCESS_BLUEPRINT))
sm->state = STATE_BLUEPRINT;
else if (flags & flag(OP_PAINT)) {
if (list_empty(&op->evts)) {
operation_end(op);
jmpto(sm, STATE_PAINT);
} sm->state = STATE_PAINT;
} else sm->state = STATE_DONE;
return op;
}
case STATE_SERIALIZE:
case STATE_PAINT: {
int depth = 0;
struct box **stk = 0;
struct temp_memory tmp;
struct list_hook *pi = 0;
struct operation *op;
if (sm->state == STATE_PAINT)
op = operation_begin(OP_PAINT, ctx, &ctx->arena);
else op = operation_begin(OP_SERIALIZE, ctx, &ctx->arena);
/* caculate tree depth and number of boxes */
op->cnt = depth = 0;
list_foreach(pi, &ctx->pan) {
struct panel *p = list_entry(pi, struct panel, hook);
struct repository *r = &p->repo[p->act];
depth = max(depth, r->depth+1);
op->cnt += r->cnt;
}
/* generate list of boxes in dfs order */
op->surfaces = arena_push_array(&ctx->arena, op->cnt+1, struct box*);
tmp = temp_memory_begin(&ctx->arena);
stk = arena_push_array(&ctx->arena, depth + 1, struct box*);
op->cnt = dfs(op->surfaces, stk, ctx->tree);
temp_memory_end(tmp);
/* state transition table */
if (sm->state == STATE_SERIALIZE) {
if (flags & flag(OP_INPUT))
sm->state = STATE_INPUT;
else if (flags & flag(OP_PAINT))
sm->state = STATE_PAINT;
else sm->state = STATE_DONE;
} else sm->state = STATE_DONE;
return op;
}
case STATE_DONE: {
sm->state = STATE_PREPROCESS;
return 0;
}} return 0;
#undef call
}
api void
process_end(struct operation *op)
{
switch (op->type) {
case OP_INPUT: {
struct context *ctx = op->ctx;
struct input *in = &ctx->input;
int blk = popup_is_active(ctx, POPUP_BLOCKING);
int nblk = popup_is_active(ctx, POPUP_NON_BLOCKING);
ctx->blocking->interactive = !blk && !nblk;
in->text_len = 0;
} break;
case OP_CLEANUP: {
struct context *ctx = op->ctx;
struct panel *p = op->panel;
struct repository *r = op->repo;
if (!p) break;
switch (ctx->state_machine.state) {
default: assert(0); break;
case STATE_CLEANUP:
if (!p->repo[!p->act].cnt)
list_add_head(&ctx->freelist, &p->hook);
break;
case STATE_GC:
swap_buffers(p, r);
break;
}
} break;}
operation_end(op);
}
api void
measure(struct box *b, int pad)
{
struct list_hook *i = 0;
list_foreach(i, &b->links) {
const struct box *n = list_entry(i, struct box, node);
b->dw = max(b->dw, n->dw + 2*pad);
b->dh = max(b->dh, n->dh + 2*pad);
}
}
api void
compute(struct box *b, int pad)
{
struct list_hook *i = 0;
list_foreach(i, &b->links) {
struct box *n = list_entry(i, struct box, node);
n->local.x = b->local.x + pad;
n->local.y = b->local.y + pad;
n->local.w = max(b->local.w - 2*pad, 0);
n->local.h = max(b->local.h - 2*pad, 0);
}
}
intern void
layout_copy(struct box *b)
{
struct list_hook *i = 0;
list_foreach(i, &b->links) {
struct box *n = 0;
n = list_entry(i, struct box, node);
n->local.x = b->local.x;
n->local.y = b->local.y;
n->local.w = b->local.w;
n->local.h = b->local.h;
}
}
intern void
layout_default(struct box *b)
{
struct list_hook *i = 0;
list_foreach(i, &b->links) {
struct box *n = list_entry(i, struct box, node);
n->local.x = max(b->local.x, n->local.x);
n->local.y = max(b->local.y, n->local.y);
n->local.w = min(n->dw, (b->local.x+b->local.w)-n->local.x);
n->local.h = min(n->dh, (b->local.y+b->local.h)-n->local.y);
}
}
api void
blueprint(struct operation *op, struct box *b)
{
struct context *ctx = op->ctx;
if (b->type & WIDGET_INTERNAL_BEGIN) {
switch (b->type) {
case WIDGET_ROOT: {
if (b == ctx->tree) {
b->dw = ctx->input.width;
b->dh = ctx->input.height;
}
} break;
case WIDGET_PANEL: {
measure(b,0);
} break;}
} else measure(b,0);
}
api void
layout(struct operation *op, struct box *b)
{
struct context *ctx = op->ctx;
if ((b->type & WIDGET_INTERNAL_BEGIN)) {
switch (b->type) {
case WIDGET_PANEL:
layout_default(b); break;
case WIDGET_ROOT: {
if (b == ctx->tree) {
b->local.w = ctx->input.width;
b->local.h = ctx->input.height;
layout_copy(b);
} else if (b == ctx->popup || b == ctx->contextual) {
struct list_hook *i = 0;
list_foreach(i, &b->links) {
struct box *n = list_entry(i, struct box, node);
if (n == ctx->contextual || n == ctx->unblocking) {
n->local.w = b->local.w;
n->local.h = b->local.h;
} else {
n->local.w = min(n->dw, (b->local.x+b->local.w)-n->local.x);
n->local.h = min(n->dh, (b->local.y+b->local.h)-n->local.y);
}
}
} else if (b == ctx->overlay || b == ctx->unblocking || b == ctx->blocking)
layout_copy(b);
else if (b == ctx->ui)
layout_default(b);
} break;}
} else compute(b, 0);
}
api void
transform(struct operation *op, struct box *b)
{
struct list_hook *bi;
struct transform *tb = &b->screen_transform;
list_foreach(bi, &b->links) {
struct box *sub = list_entry(bi, struct box, node);
transform_concat(&sub->screen_transform, tb, &sub->local_transform);
transform_rect(&sub->screen, &sub->local, &sub->screen_transform);
}
}
api void
input(struct operation *op, union event *evt, struct box *b)
{
struct context *ctx = op->ctx;
if (!(b->type & WIDGET_INTERNAL_BEGIN)) return;
switch (b->type) {
case WIDGET_ROOT: {
if (b != ctx->unblocking) return;
{struct list_hook *i = 0;
struct box *contextual = 0;
if (evt->hdr.origin != b) break;
if (!b->clicked || evt->type == EVT_CLICKED) break;
ctx = op->ctx;
/* hide all contextual menus */
contextual = ctx->contextual;
list_foreach(i, &contextual->links) {
struct box *c = list_entry(i,struct box,node);
if (c == b) continue;
c->hidden = 1;
}}
} break;}
}
/* ===========================================================================
*
*
*
* WIDGETS
*
*
*
* =========================================================================== */
enum widget_type {
WIDGET_LABEL,
WIDGET_ICON,
WIDGET_CHECKBOX,
WIDGET_BUTTON,
WIDGET_SLIDER,
WIDGET_SBOX,
WIDGET_FLEX_BOX,
WIDGET_BUTTON_LABEL,
WIDGET_CHECKBOX_LABEL,
WIDGET_COMBO,
WIDGET_COMBO_BOX,
WIDGET_COMBO_BOX_POPUP,
WIDGET_COUNT
};
/* ---------------------------------------------------------------------------
* LABEL
* --------------------------------------------------------------------------- */
struct label {
struct box *bounds;
int ubegin, ulen;
unsigned int clicked:1;
const char *str;
int len;
char buf[1];
};
api void
label_blueprint(struct label *lbl, struct box *b)
{
b->dw = lbl->len * 8 + max(lbl->len-1,0) + 2;
b->dh = 16 + 2;
}
api struct label*
text(struct panel_state *s, const struct label *prev,
const char *txt, int len)
{
static const int align = alignof(struct label);
static const int size = szof(struct label);
struct widget_builder *w = 0;
struct label *lbl = 0;
assert(s);
if (!s) return 0;
w = widget_begin(s, WIDGET_LABEL, prev, size + max(len-1,0), align);
lbl = w->widget;
prev = w->widget_last;
copy(lbl->buf, txt, len);
if (w->init) {
struct box *p = prev->bounds;
lbl->clicked = p->clicked;
}
lbl->str = lbl->buf;
lbl->len = len;
lbl->bounds = widget_box_push(w, prev->bounds);
box_add_property(lbl->bounds, PROPERTY_INTERACTIVE);
widget_end(w);
return lbl;
}
api struct label*
label(struct panel_state *s, const struct label *prev,
const char *txt)
{
struct label *lbl;
lbl = text(s, prev, 0, 0);
lbl->len = strlen(txt);
lbl->str = txt;
return lbl;
}
api struct label*
text_id(struct panel_state *s, const char *txt, int len, uiid id)
{
const struct label *prev = load(s, id);
struct label *lbl = text(s,prev,txt,len);
store(s, lbl, id);
return lbl;
}
api struct label*
label_id(struct panel_state *s, const char *txt, uiid id)
{
const struct label *prev = load(s, id);
struct label *lbl = label(s,prev,txt);
store(s, lbl, id);
return lbl;
}
/* ---------------------------------------------------------------------------
* CHECKBOX
* --------------------------------------------------------------------------- */
struct checkbox {
int padding;
struct box *bounds;
struct box *cursor;
unsigned checked:1;
unsigned hovered:1;
unsigned entered:1;
unsigned exited:1;
unsigned pressed:1;
unsigned released:1;
unsigned clicked:1;
unsigned toggled:1;
};
api void
checkbox_poll(struct checkbox *chk)
{
const struct box *b = chk->bounds;
if (!b) return;
chk->exited = b->exited;
chk->entered = b->entered;
chk->pressed = b->pressed;
chk->clicked = b->clicked;
chk->released = b->released;
chk->toggled = b->clicked;
}
api void
checkbox_blueprint(struct checkbox *chk, struct box *b)
{
if (b == chk->cursor)
b->dw = b->dh = 16 + 2;
else measure(b, chk->padding);
}
api void
checkbox_layout(struct checkbox *chk, struct box *b)
{
if (b == chk->bounds) {
if (b->local.w > b->local.h) {
b->local.x += (b->local.w - b->local.h) / 2;
b->local.w = b->local.h;
} else if (b->local.h > b->local.w) {
b->local.y += (b->local.h - b->local.w) / 2;
b->local.h = b->local.w;
} box_shrink(chk->cursor, b, chk->padding);
} else compute(b, 0);
}
api void
checkbox_input(struct checkbox *chk, struct box *b, const union event *evt)
{
if (b == chk->bounds && b->clicked && evt->type == EVT_CLICKED)
{chk->toggled = 1; chk->checked = !chk->checked;}
b = chk->bounds;
if (b->entered && evt->type == EVT_ENTERED)
chk->hovered = 1;
if (b->exited && evt->type == EVT_EXITED)
chk->hovered = 0;
}
api struct checkbox*
checkbox_query(struct context *ctx, uiid pid, uiid id)
{
struct checkbox *chk = query(ctx, pid, id);
if (chk) checkbox_poll(chk);
return chk;
}
api struct checkbox*
checkbox_begin(struct panel_state *s, const struct checkbox *prev)
{
static const int align = alignof(struct label);
static const int size = szof(struct label);
struct checkbox *chk = 0;
assert(s);
if (!s) return 0;
{struct widget_builder *w;
w = widget_begin(s, WIDGET_CHECKBOX, prev, size, align);
chk = w->widget;
prev = w->widget_last;
copy(chk, prev, szof(*chk));
if (w->init) chk->padding = 3;
checkbox_poll(chk);
chk->bounds = widget_node_push(w, prev->bounds);
box_add_property(chk->bounds, PROPERTY_INTERACTIVE);
chk->cursor = widget_box_push(w, prev->cursor);
box_add_property(chk->cursor, PROPERTY_INTERACTIVE);
widget_node_pop(w);
widget_end(w);}
push(s, chk->cursor);
return chk;
}
api struct checkbox*
checkbox_begin_id(struct panel_state *s, uiid id)
{
const struct checkbox *prev = load(s, id);
struct checkbox *chk = checkbox_begin(s, prev);
store(s, chk, id);
return chk;
}
api void
checkbox_end(struct panel_state *s)
{
assert(s);
if (!s) return;
pop(s);
}
api struct checkbox*
checkbox(struct panel_state *s, const struct checkbox *prev)
{
struct checkbox *chk = 0;
chk = checkbox_begin(s, prev);
checkbox_end(s);
return chk;
}
api struct checkbox*
checkbox_id(struct panel_state *s, uiid id)
{
struct checkbox *chk = 0;
chk = checkbox_begin_id(s, id);
checkbox_end(s);
return chk;
}
/* ---------------------------------------------------------------------------
* ICON
* --------------------------------------------------------------------------- */
enum icon_symbol {
ICON_CIRCLE_SOLID,
ICON_CIRCLE_OUTLINE,
ICON_RECT_SOLID,
ICON_RECT_OUTLINE,
ICON_TRIANGLE_UP,
ICON_TRIANGLE_DOWN,
ICON_TRIANGLE_LEFT,
ICON_TRIANGLE_RIGHT,
ICON_COUNT
};
struct icon {
enum icon_symbol sym;
struct box *bounds;
};
api void
icon_layout(struct icon *ico, struct box *b)
{
/* make sure same with and height for size */
if (b->local.w > b->local.h) {
b->local.x += (b->local.w - b->local.h) / 2;
b->local.w = b->local.h;
} else if (b->local.h > b->local.w) {
b->local.y += (b->local.h - b->local.w) / 2;
b->local.h = b->local.w;
}
}
api struct icon*
icon(struct panel_state *s, const struct icon *prev)
{
static const int align = alignof(struct icon);
static const int size = szof(struct icon);
struct icon *ico = 0;
assert(s);
if (!s) return ico;
{struct widget_builder *w;
w = widget_begin(s, WIDGET_ICON, prev, size, align);
ico = w->widget;
prev = w->widget_last;
ico->sym = ICON_TRIANGLE_DOWN;
ico->bounds = widget_box_push(w, prev->bounds);
box_add_property(ico->bounds, PROPERTY_INTERACTIVE);
widget_end(w);}
return ico;
}
api struct icon*
icon_id(struct panel_state *s, uiid id)
{
const struct icon *prev = load(s, id);
struct icon *ico = icon(s, prev);
store(s, ico, id);
return ico;
}
/* ---------------------------------------------------------------------------
* BUTTON
* --------------------------------------------------------------------------- */
struct button {
struct box *bounds;
unsigned hovered:1;
unsigned entered:1;
unsigned exited:1;
unsigned pressed:1;
unsigned released:1;
unsigned clicked:1;
};
api void
button_input(struct button *btn, struct box *b, const union event *evt)
{
b = btn->bounds;
if (b->entered && evt->type == EVT_ENTERED)
btn->hovered = 1;
if (b->exited && evt->type == EVT_EXITED)
btn->hovered = 0;
}
api void
button_poll(struct button *btn)
{
struct box *b = btn->bounds;
if (!b) return;
btn->exited = b->exited;
btn->entered = b->entered;
btn->pressed = b->pressed;
btn->clicked = b->clicked;
btn->released = b->released;
}
api struct button*
button_query(struct context *ctx, uiid pid, uiid widget_id)
{
struct button *btn = query(ctx, pid, widget_id);
if (btn) button_poll(btn);
return btn;
}
api struct button*
button_begin(struct panel_state *s, const struct button *prev)
{
static const int align = alignof(struct button);
static const int size = szof(struct button);
struct button *btn = 0;
assert(s);
if (!s) return 0;
{struct widget_builder *w = 0;
w = widget_begin(s, WIDGET_BUTTON, prev, size, align);
btn = w->widget;
prev = w->widget_last;
copy(btn, prev, sizeof(*btn));
button_poll(btn);
btn->bounds = widget_box_push(w, prev->bounds);
box_add_property(btn->bounds, PROPERTY_INTERACTIVE);
widget_end(w);}
push(s, btn->bounds);
return btn;
}
api struct button*
button_begin_id(struct panel_state *s, uiid id)
{
struct button *prev = load(s, id);
struct button *btn = button_begin(s, prev);
store(s, btn, id);
return btn;
}
static void
button_end(struct panel_state *ctx)
{
assert(ctx);
if (!ctx) return;
pop(ctx);
}
/* ---------------------------------------------------------------------------
* SLIDER
* --------------------------------------------------------------------------- */
struct slider {
struct box *bounds;
struct box *cursor;
float lo, val, hi;
int cursor_size;
unsigned hovered:1;
unsigned entered:1;
unsigned exited:1;
unsigned drag_begin:1;
unsigned dragged:1;
unsigned drag_end:1;
unsigned value_changed:1;
};
api void
slider_input(struct slider *sld, struct box *b, const union event *evt)
{
struct box *p = sld->bounds;
if (b != sld->cursor) return;
if (b->moved && evt->type == EVT_MOVED) {
float sld_min = min(sld->lo, sld->hi);
float sld_max = max(sld->lo, sld->hi);
b->local.x = min(p->local.x+p->local.w-b->local.w, b->local.x);
b->local.x = max(p->local.x, b->local.x);
{int x = p->local.x;
int w = p->local.w - sld->cursor_size;
sld->val = cast(float,(b->local.x - x)) / cast(float,w);
sld->val = (sld->val * sld_max) + sld_min;
sld->value_changed = p->moved = 1;}
}
}
api void
slider_blueprint(struct slider *sld, struct box *b)
{
if (b == sld->bounds)
measure(b, 0);
else if (b == sld->cursor) {
measure(b, 0);
b->dw = max(b->dw, sld->cursor_size);
b->dh = max(b->dh, 16);
}
}
api void
slider_layout(struct slider *sld, struct box *b)
{
if (b == sld->bounds)
{compute(b, 0); return;}
{const struct box *p = b->parent;
float sld_min = min(sld->lo, sld->hi);
float sld_max = max(sld->lo, sld->hi);
float sld_val = clamp(sld_min, sld->val, sld_max);
float cur_off = (sld_val - sld_min) / sld_max;
int x = p->local.x + sld->cursor_size/2;
int w = p->local.w - sld->cursor_size;
b->local.h = 16;
b->local.w = sld->cursor_size;
b->local.x = x + roundi(w*cur_off) - sld->cursor_size/2;
b->local.y = p->local.y + p->local.h/2 - b->local.h/2;}
}
api void
slider_poll(struct slider *sld)
{
const struct box *b = sld->bounds;
if (!b) return;
sld->exited = b->exited;
sld->entered = b->entered;
sld->drag_begin = b->drag_begin;
sld->dragged = b->dragged;
sld->drag_end = b->drag_end;
sld->value_changed = sld->value_changed && b->moved;
}
api struct slider*
slider_query(struct context *ctx, uiid pid, uiid id)
{
struct slider *sld = query(ctx, pid, id);
if (sld) slider_poll(sld);
return sld;
}
api struct slider*
slider_begin(struct panel_state *s, const struct slider *prev)
{
static const int align = alignof(struct slider);
static const int size = szof(struct slider);
struct slider *sld = 0;
assert(s);
if (!s) return 0;
{struct widget_builder *w = 0;
w = widget_begin(s, WIDGET_SLIDER, prev, size, align);
sld = w->widget;
prev = w->widget_last;
copy(sld, prev, szof(*sld));
if (w->init) sld->cursor_size = 8;
slider_poll(sld);
sld->bounds = widget_node_push(w, sld->bounds);
box_add_property(sld->bounds, PROPERTY_INTERACTIVE);
sld->cursor = widget_box_push(w, sld->cursor);
box_add_property(sld->cursor, PROPERTY_INTERACTIVE);
box_add_property(sld->cursor, PROPERTY_MOVABLE_X);
widget_node_pop(w);
widget_end(w);}
push(s, sld->cursor);
return sld;
}
api struct slider*
slider_begin_id(struct panel_state *s, uiid id)
{
struct slider *prv = load(s, id);
struct slider *sld = slider_begin(s, prv);
store(s, sld, id);
return sld;
}
api void
slider_end(struct panel_state *s)
{
assert(s);
if (!s) return;
pop(s);
}
api struct slider*
slider(struct panel_state *s, const struct slider *prev)
{
struct slider *sld = 0;
sld = slider_begin(s, prev);
slider_end(s);
return sld;
}
api struct slider*
slider_id(struct panel_state *s, uiid id)
{
struct slider *sld = 0;
sld = slider_begin_id(s, id);
slider_end(s);
return sld;
}
/* ---------------------------------------------------------------------------
* SIMPLE BOX
* --------------------------------------------------------------------------- */
enum sbox_vertical_alignment {
SBOX_VALIGN_TOP,
SBOX_VALIGN_CENTER,
SBOX_VALIGN_BOTTOM
};
enum sbox_horizontal_alignment {
SBOX_HALIGN_LEFT,
SBOX_HALIGN_CENTER,
SBOX_HALIGN_RIGHT
};
struct sbox {
unsigned char padding;
enum sbox_vertical_alignment valign;
enum sbox_horizontal_alignment halign;
struct box *bounds;
struct box *content;
};
api void
sbox_blueprint(struct sbox *sbx, struct box *b)
{
if (b == sbx->bounds)
measure(b, sbx->padding);
else measure(b, 0);
}
api void
sbox_layout(struct sbox *sbx, struct box *b)
{
struct list_hook *h = 0;
if (b == sbx->bounds)
{box_shrink(sbx->content, b, sbx->padding); return;}
switch (sbx->halign) {
case SBOX_HALIGN_LEFT:
list_foreach(h, &b->links) {
struct box *n = list_entry(h, struct box, node);
n->local.x = b->local.x;
n->local.w = b->local.w;
} break;
case SBOX_HALIGN_CENTER:
list_foreach(h, &b->links) {
struct box *n = list_entry(h, struct box, node);
n->local.x = max(b->local.x,(b->local.x + b->local.w/2) - n->dw/2);
n->local.w = max((b->local.x + b->local.w) - n->local.x, 0);
} break;
case SBOX_HALIGN_RIGHT:
list_foreach(h, &b->links) {
struct box *n = list_entry(h, struct box, node);
n->local.x = max(b->local.x, b->local.x + b->local.w - n->dw);
n->local.w = min(n->dw, n->local.w);
} break;}
switch (sbx->valign) {
case SBOX_VALIGN_TOP:
list_foreach(h, &b->links) {
struct box *n = list_entry(h, struct box, node);
n->local.y = b->local.y;
n->local.h = b->local.h;
} break;
case SBOX_VALIGN_CENTER:
list_foreach(h, &b->links) {
struct box *n = list_entry(h, struct box, node);
n->local.y = max(b->local.y,(b->local.y + b->local.h/2) - n->dh/2);
n->local.h = max((b->local.y + b->local.h) - n->local.y, 0);
} break;
case SBOX_VALIGN_BOTTOM:
list_foreach(h, &b->links) {
struct box *n = list_entry(h, struct box, node);
n->local.y = max(b->local.y,b->local.y + b->local.h - n->dh);
n->local.h = min(n->dh, n->local.h);
} break;}
}
api struct sbox*
sbox_begin(struct panel_state *s, const struct sbox *prev)
{
static const int align = alignof(struct sbox);
static const int size = szof(struct sbox);
static const int pad = 4;
struct sbox *sbx;
assert(s);
if (!s) return 0;
{struct widget_builder *w = 0;
w = widget_begin(s, WIDGET_SBOX, prev, size, align);
sbx = w->widget;
prev = w->widget_last;
copy(sbx, prev, szof(*sbx));
sbx->padding = max(sbx->padding, pad);
sbx->bounds = widget_node_push(w, prev->bounds);
box_add_property(sbx->bounds, PROPERTY_INTERACTIVE);
sbx->content = widget_box_push(w, prev->content);
box_add_property(sbx->content, PROPERTY_INTERACTIVE);
widget_node_pop(w);
widget_end(w);}
push(s, sbx->content);
return sbx;
}
api struct sbox*
sbox_begin_id(struct panel_state *s, uiid id)
{
struct sbox *prev = load(s, id);
struct sbox *sbx = sbox_begin(s, prev);
store(s, sbx, id);
return sbx;
}
api void
sbox_end(struct panel_state *s)
{
assert(s);
if (!s) return;
pop(s);
}
/* ---------------------------------------------------------------------------
* FLEX BOX
* --------------------------------------------------------------------------- */
enum flex_box_orientation {
FLEX_BOX_HORIZONTAL,
FLEX_BOX_VERTICAL
};
enum flex_box_slot_type {
FLEX_BOX_SLOT_DYNAMIC,
FLEX_BOX_SLOT_STATIC,
FLEX_BOX_SLOT_VARIABLE,
FLEX_BOX_SLOT_FITTING
};
struct flex_box_slot {
enum flex_box_slot_type type;
int pixel;
struct box *bounds;
};
struct flex_box {
enum flex_box_orientation orientation;
unsigned char padding;
unsigned char spacing;
struct box *bounds;
int cnt;
struct flex_box_slot slots[1];
};
api void
flex_box_blueprint(struct flex_box *f, struct box *b)
{
int i = 0;
if (b != f->bounds) {measure(b, 0); return;}
b->dw = b->dh = 0;
for (i = 0; i < f->cnt; ++i) {
struct flex_box_slot *s = f->slots + i;
struct box *sb = s->bounds;
switch (f->orientation) {
case FLEX_BOX_HORIZONTAL: {
switch (s->type) {
case FLEX_BOX_SLOT_DYNAMIC:
b->dw += sb->dw; break;
case FLEX_BOX_SLOT_STATIC:
b->dw += s->pixel; break;
case FLEX_BOX_SLOT_FITTING:
case FLEX_BOX_SLOT_VARIABLE:
b->dw += max(sb->dw, s->pixel); break;
} b->dh = max(sb->dh + 2*f->padding, b->dh);
} break;
case FLEX_BOX_VERTICAL: {
switch (s->type) {
case FLEX_BOX_SLOT_DYNAMIC:
b->dh += sb->dh; break;
case FLEX_BOX_SLOT_STATIC:
b->dh += s->pixel; break;
case FLEX_BOX_SLOT_FITTING:
case FLEX_BOX_SLOT_VARIABLE:
b->dh += max(sb->dh, s->pixel); break;
} b->dw = max(sb->dw + 2*f->padding, b->dw);
} break;}
}
{int pad = f->spacing*(f->cnt-1) + f->padding*2;
switch (f->orientation) {
case FLEX_BOX_HORIZONTAL: b->dw += pad; break;
case FLEX_BOX_VERTICAL: b->dh += pad; break;}}
}
api void
flex_box_layout(struct flex_box *f, struct box *b)
{
int i;
if (b != f->bounds)
{compute(b, 0); return;}
/* calculate metrics */
{int pos = 0, space = 0;
int varcnt = 0, staticsz = 0, fixsz = 0, maxvar = 0;
int dynsz = 0, varsz = 0, var = 0, dyncnt = 0;
if (f->orientation == FLEX_BOX_HORIZONTAL)
space = max(b->local.w - (f->cnt-1)*f->spacing, 0);
else space = max(b->local.h - (f->cnt-1)*f->spacing, 0);
space = max(0, space - 2*f->padding);
for (i = 0; i < f->cnt; ++i) {
struct flex_box_slot *s = f->slots + i;
struct box *sb = s->bounds;
if (f->orientation == FLEX_BOX_HORIZONTAL) {
sb->local.y = b->local.y + f->padding;
sb->local.h = max(0,b->local.h - 2*f->padding);
} else {
sb->local.x = b->local.x + f->padding;
sb->local.w = max(0,b->local.w - 2*f->padding);
}
switch (s->type) {
case FLEX_BOX_SLOT_DYNAMIC: {
varcnt++, dyncnt++;
} break;
case FLEX_BOX_SLOT_FITTING:
if (f->orientation == FLEX_BOX_HORIZONTAL)
s->pixel = max(s->pixel, sb->dw);
else s->pixel = max(s->pixel, sb->dh);
case FLEX_BOX_SLOT_STATIC: {
staticsz += s->pixel;
} break;
case FLEX_BOX_SLOT_VARIABLE: {
fixsz += s->pixel;
maxvar = max(s->pixel, maxvar);
varcnt++;
} break;}
}
/* calculate dynamic slot size */
dynsz = max(space - staticsz, 0);
varsz = max(dynsz - fixsz, 0);
if (varsz) {
if (varcnt) {
var = dynsz / max(varcnt,1);
if (maxvar > var) {
for (i = 0; i < f->cnt; ++i) {
struct flex_box_slot *s = f->slots + i;
if (s->pixel <= var) continue;
switch (s->type) {
case FLEX_BOX_SLOT_FITTING:
case FLEX_BOX_SLOT_VARIABLE: {
staticsz += s->pixel;
varcnt--;
} break;}
}
dynsz = max(space - staticsz, 0);
var = dynsz / max(varcnt,1);
}
} else var = dynsz / max(varcnt+dyncnt,1);
} else var = 0;
/* set position and width */
switch (f->orientation) {
case FLEX_BOX_HORIZONTAL: pos = b->local.x + f->padding; break;
case FLEX_BOX_VERTICAL: pos = b->local.y + f->padding; break;}
for (i = 0; i < f->cnt; ++i) {
struct flex_box_slot *s = f->slots + i;
struct box *sb = s->bounds;
switch (f->orientation) {
case FLEX_BOX_HORIZONTAL: {
sb->local.x = pos;
switch (s->type) {
case FLEX_BOX_SLOT_DYNAMIC: sb->local.w = var; break;
case FLEX_BOX_SLOT_STATIC: sb->local.w = s->pixel; break;
case FLEX_BOX_SLOT_FITTING: sb->local.w = s->pixel; break;
case FLEX_BOX_SLOT_VARIABLE:
sb->local.w = (s->pixel > var) ? s->pixel: var; break;
} pos += sb->local.w + f->spacing;
} break;
case FLEX_BOX_VERTICAL: {
sb->local.y = pos;
switch (s->type) {
case FLEX_BOX_SLOT_DYNAMIC: sb->local.h = var; break;
case FLEX_BOX_SLOT_STATIC: sb->local.h = s->pixel; break;
case FLEX_BOX_SLOT_FITTING: sb->local.h = s->pixel; break;
case FLEX_BOX_SLOT_VARIABLE:
sb->local.h = (s->pixel > var) ? s->pixel: var; break;
} pos += sb->local.h + f->spacing;
} break;}
}}
}
api struct flex_box*
flex_box_begin(struct panel_state *s, const struct flex_box *prev, int cnt)
{
static const int slot_size = szof(struct flex_box_slot);
static const int align = alignof(struct flex_box);
static const int size = szof(struct flex_box);
struct flex_box *h = 0;
int i = 0;
assert(s);
assert(cnt > 0);
if (!s) return 0;
cnt = max(cnt, 1);
{struct widget_builder *w = 0;
w = widget_begin(s, WIDGET_FLEX_BOX, prev, size+(cnt-1)*slot_size, align);
h = w->widget;
prev = w->widget_last;
copy(h, w->widget_last, size);
if (w->init) h->spacing = 8, h->padding = 4;
h->cnt = cnt;
h->bounds = widget_node_push(w, h->bounds);
box_add_property(h->bounds, PROPERTY_INTERACTIVE);
for (i = 0; i < cnt; ++i) {
struct flex_box_slot *st = h->slots + i;
st->bounds = widget_box_push(w, st->bounds);
box_add_property(st->bounds, PROPERTY_INTERACTIVE);
st->type = FLEX_BOX_SLOT_DYNAMIC;
st->pixel = 0;
} widget_node_pop(w);
widget_end(w);}
push(s, h->bounds);
return h;
}
api struct flex_box*
flex_box_begin_id(struct panel_state *s, uiid id, int cnt)
{
struct flex_box *prv = load(s, id);
struct flex_box *fbx = flex_box_begin(s, prv, cnt);
store(s, fbx, id);
return fbx;
}
api void
flex_box_slot_begin(struct panel_state *s,
enum flex_box_slot_type type,
int pixel, int idx)
{
struct flex_box *h = 0;
struct box *bounds = 0;
struct flex_box_slot *slot = 0;
assert(s);
assert(idx >= 0);
if (!s || idx < 0)
return;
bounds = peek(s);
h = bounds->widget;
assert(idx < h->cnt);
slot = h->slots + idx;
slot->type = type;
slot->pixel = pixel;
push(s, slot->bounds);
}
api void
flex_box_slot_dynamic_begin(struct panel_state *s, int idx)
{
flex_box_slot_begin(s, FLEX_BOX_SLOT_DYNAMIC, 0, idx);
}
api void
flex_box_slot_static_begin(struct panel_state *s, int pixel_width, int idx)
{
flex_box_slot_begin(s, FLEX_BOX_SLOT_STATIC, pixel_width, idx);
}
api void
flex_box_slot_variable_begin(struct panel_state *s,
int min_pixel_width, int idx)
{
flex_box_slot_begin(s, FLEX_BOX_SLOT_VARIABLE, min_pixel_width, idx);
}
api void
flex_box_slot_fitting_begin(struct panel_state *s, int idx)
{
flex_box_slot_begin(s, FLEX_BOX_SLOT_FITTING, 0, idx);
}
api void
flex_box_slot_end(struct panel_state *s)
{
assert(s);
if (!s) return;
pop(s);
}
api void
flex_box_end(struct panel_state *s)
{
assert(s);
if (!s) return;
pop(s);
}
/* ---------------------------------------------------------------------------
* BUTTON_LABEL
* --------------------------------------------------------------------------- */
struct button_label {
struct box *bounds;
struct button *btn;
struct sbox *sbx;
struct label *lbl;
unsigned entered:1;
unsigned exited:1;
unsigned pressed:1;
unsigned released:1;
unsigned clicked:1;
};
api void
button_label_poll(struct button_label *bl)
{
const struct box *b = bl->bounds;
if (!b) return;
button_poll(bl->btn);
bl->clicked = b->clicked;
bl->pressed = b->pressed;
bl->released = b->released;
bl->entered = b->entered;
bl->exited = b->exited;
}
api struct button_label*
button_label_query(struct context *ctx, uiid pid, uiid widget_id)
{
struct button_label *bl = 0;
bl = query(ctx, pid, widget_id);
if (bl) button_label_poll(bl);
return bl;
}
api struct button_label*
button_label(struct panel_state *s, const struct button_label *prev,
const char *lbl)
{
static const int align = alignof(struct button_label);
static const int size = szof(struct button_label);
struct button_label *bl = 0;
{struct widget_builder *w = 0;
w = widget_begin(s, WIDGET_BUTTON_LABEL, prev, size, align);
bl = w->widget;
prev = w->widget_last;
copy(bl, w->widget_last, size);
button_label_poll(bl);
bl->bounds = widget_box_push(w, bl->bounds);
box_add_property(bl->bounds, PROPERTY_INTERACTIVE);
widget_end(w);}
push(s, bl->bounds);
if ((bl->btn = button_begin(s, bl->btn))) {
if ((bl->sbx = sbox_begin(s, bl->sbx))) {
struct sbox *x = bl->sbx;
x->halign = SBOX_HALIGN_CENTER;
x->valign = SBOX_VALIGN_CENTER;
bl->lbl = label(s, bl->lbl, lbl);
sbox_end(s);
} button_end(s);
} pop(s);
return bl;
}
api struct button_label*
button_label_id(struct panel_state *s, uiid id, const char *lbl)
{
struct button_label *prev = load(s, id);
struct button_label *bl = button_label(s, prev, lbl);
store(s, bl, id);
return bl;
}
/* ---------------------------------------------------------------------------
* CHECKBOX LABEL
* --------------------------------------------------------------------------- */
struct checkbox_label {
int padding;
struct box *bounds;
struct flex_box *fbx;
struct checkbox *chk;
struct sbox *sbx;
struct label *lbl;
unsigned checked:1;
unsigned entered:1;
unsigned exited:1;
unsigned pressed:1;
unsigned released:1;
unsigned clicked:1;
unsigned toggled:1;
};
api void
checkbox_label_poll(struct checkbox_label *cb)
{
const struct box *b = cb->bounds;
if (!b) return;
checkbox_poll(cb->chk);
cb->clicked = b->clicked;
cb->checked = cb->chk->checked;
cb->toggled = cb->chk->toggled;
cb->entered = b->entered;
cb->exited = b->exited;
if (cb->clicked && !cb->toggled) {
cb->checked = cb->chk->checked = !cb->checked;
cb->toggled = cb->chk->toggled = 1;
}
}
api struct checkbox_label*
checkbox_label_query(struct context *ctx, uiid pid, uiid widget_id)
{
struct checkbox_label *cl = 0;
cl = query(ctx, pid, widget_id);
if (cl) checkbox_label_poll(cl);
return cl;
}
api struct checkbox_label*
checkbox_label(struct panel_state *s, const struct checkbox_label *prev,
const char *lbl)
{
static const int align = alignof(struct checkbox_label);
static const int size = szof(struct checkbox_label);
struct checkbox_label *cl = 0;
{struct widget_builder *w = 0;
w = widget_begin(s, WIDGET_CHECKBOX_LABEL, prev, size, align);
cl = w->widget;
prev = w->widget_last;
copy(cl, w->widget_last, size);
checkbox_label_poll(cl);
cl->bounds = widget_box_push(w, cl->bounds);
box_add_property(cl->bounds, PROPERTY_INTERACTIVE);
widget_end(w);}
push(s, cl->bounds);
if ((cl->fbx = flex_box_begin(s, cl->fbx, 2))) {
flex_box_slot_fitting_begin(s, 0);
cl->chk = checkbox(s, cl->chk);
flex_box_slot_end(s);
flex_box_slot_fitting_begin(s, 1);
if ((cl->sbx = sbox_begin(s, cl->sbx))) {
struct sbox *sbx = cl->sbx;
sbx->padding = 0;
sbx->halign = SBOX_HALIGN_CENTER;
sbx->valign = SBOX_VALIGN_CENTER;
cl->lbl = label(s, cl->lbl, lbl);
sbox_end(s);
} flex_box_slot_end(s);
flex_box_end(s);
} pop(s);
return cl;
}
api struct checkbox_label*
checkbox_label_id(struct panel_state *s, uiid id, const char *lbl)
{
struct checkbox_label *prev = load(s, id);
struct checkbox_label *cl = checkbox_label(s, prev, lbl);
store(s, cl, id);
return cl;
}
/* ---------------------------------------------------------------------------
* COMBO
* --------------------------------------------------------------------------- */
enum combo_state {
COMBO_COLLAPSED,
COMBO_VISIBLE
};
struct combo {
enum combo_state state;
struct panel *panel;
struct box *bounds;
struct box *popup;
};
api void
combo_popup_toggle(struct context *ctx, struct combo *c)
{
struct box *pop = 0;
assert(c);
if (!c) return;
pop = c->popup;
popup_toggle(ctx, pop);
c->state = !c->state;
}
api void
combo_transform(struct combo *c, struct box *b)
{
struct box *p = c->popup;
transform_concat(&p->screen_transform, &b->screen_transform, &p->local_transform);
transform_rect(&p->screen, &p->local, &p->screen_transform);
}
api void
combo_layout(struct combo *c, struct box *b)
{
struct box *pop = c->popup;
pop->local.y = b->local.y + b->local.h - 1;
pop->local.x = b->local.x;
pop->local.w = max(b->local.w, pop->local.w);
compute(b, 0);
}
api void
combo_input(struct context *ctx, struct combo *combo,
struct box *b, const union event *evt)
{
struct combo *c = b->widget;
if (!b->clicked || evt->type == EVT_CLICKED) return;
combo_popup_toggle(ctx,c);
}
api struct combo*
combo_begin(struct panel_state *ctx, const struct combo *prev)
{
static const int align = alignof(struct combo);
static const int size = szof(struct combo);
struct combo *c = 0;
assert(ctx);
if (!ctx) return 0;
{struct widget_builder *w = 0;
w = widget_begin(ctx, WIDGET_COMBO, prev, size, align);
c = w->widget;
prev = w->widget_last;
c->state = prev->state;
c->bounds = widget_box_push(w, prev->bounds);
box_add_property(c->bounds, PROPERTY_INTERACTIVE);
widget_end(w);}
push(ctx, c->bounds);
return c;
}
api struct combo*
combo_begin_id(struct panel_state *s, uiid id)
{
struct combo *prev = load(s, id);
struct combo *c = combo_begin(s, prev);
store(s, c, id);
return c;
}
api struct panel_state*
combo_popup_begin(struct panel_state *s, uiid id)
{
struct box *b = 0;
struct combo *c = 0;
struct context *ctx = 0;
struct panel_state *res = 0;
assert(s);
if (!s) return 0;
ctx = s->ctx;
b = peek(s);
assert(b->widget);
c = b->widget;
res = popup_begin(ctx, c->panel, id, POPUP_NON_BLOCKING);
c->panel = res->panel;
c->popup = res->root;
c->state = !c->popup->hidden;
return res;
}
api void
combo_popup_end(struct panel_state *s)
{
assert(s);
if (!s) return;
popup_end(s);
}
api void
combo_end(struct panel_state *s)
{
assert(s);
if (!s) return;
pop(s);
}
api void
combo_popup_close(struct context *ctx, struct combo *c)
{
assert(c);
if (!c) return;
c->state = COMBO_COLLAPSED;
popup_close(ctx, c->panel);
}
/* ---------------------------------------------------------------------------
* COMBO_BOX
* --------------------------------------------------------------------------- */
struct combo_box {
int old, sel, cnt;
enum combo_state state;
struct combo *combo;
struct box *bounds;
struct box *popup;
struct flex_box *fbx;
struct sbox *sbx;
struct icon *ico;
struct label *lbl;
struct flex_box *fb;
unsigned entered:1;
unsigned exited:1;
unsigned pressed:1;
unsigned released:1;
unsigned clicked:1;
unsigned selection_changed:1;
struct label *elms[1];
};
api void
combo_box_input(struct context *ctx, struct combo_box *cb,
struct box *b, const union event *evt)
{
int i = 0, j = 0;
if (evt->type != EVT_CLICKED) return;
if (evt->hdr.origin == cb->bounds) return;
for (i = 0; i < evt->hdr.cnt; i++) {
struct box *n = evt->hdr.boxes[i];
if (n->type != WIDGET_LABEL) continue;
for (j = 0; j < cb->cnt; j++) {
struct label *elm = cb->elms[j];
if (n != elm->bounds) continue;
combo_popup_toggle(ctx, cb->combo);
cb->selection_changed = 1;
cb->lbl->str = elm->str;
cb->sel = j;
goto eol;
}
} eol:;
}
api void
combo_box_poll(struct combo_box *cb)
{
const struct box *b = cb->bounds;
if (!b) return;
cb->selection_changed = cb->old != cb->sel;
cb->state = cb->combo->state;
cb->clicked = b->clicked;
cb->pressed = b->pressed;
cb->released = b->released;
cb->entered = b->entered;
cb->exited = b->exited;
cb->old = cb->sel;
}
api struct combo_box*
combo_box_query(struct context *ctx, uiid pid, uiid widget_id)
{
struct combo_box *cb = 0;
cb = query(ctx, pid, widget_id);
if (cb) combo_box_poll(cb);
return cb;
}
api struct combo_box*
combo_box(struct panel_state *s, const struct combo_box *prev,
const char *list[], int cnt)
{
static const int align = alignof(struct combo_box);
static const int size = szof(struct combo_box);
struct combo_box *cb = 0;
{struct widget_builder *w = 0;
w = widget_begin(s, WIDGET_COMBO_BOX, prev, size + cnt * szof(void*), align);
cb = w->widget;
prev = w->widget_last;
copy(cb, w->widget_last, size);
cb->sel = clamp(0, cb->sel, cnt);
cb->cnt = cnt;
combo_box_poll(cb);
cb->bounds = widget_box_push(w, cb->bounds);
box_add_property(cb->bounds, PROPERTY_INTERACTIVE);
widget_end(w);}
push(s, cb->bounds);
if ((cb->combo = combo_begin(s, cb->combo))) {
/* combo header */
if ((cb->fbx = flex_box_begin(s, cb->fbx, 2))) {
flex_box_slot_dynamic_begin(s,0);
if ((cb->sbx = sbox_begin(s, cb->sbx))) {
struct sbox *x = cb->sbx;
x->padding = 0;
x->halign = SBOX_HALIGN_LEFT;
x->valign = SBOX_VALIGN_CENTER;
cb->lbl = label(s, cb->lbl, list[cb->sel]);
sbox_end(s);
} flex_box_slot_end(s);
flex_box_slot_static_begin(s, 16, 1);
cb->ico = icon(s, cb->ico);
cb->ico->sym = ICON_RECT_OUTLINE;
flex_box_slot_end(s);
flex_box_end(s);
}
/* combo popup */
{struct panel_state *ps = 0; int i = 0;
if ((ps = combo_popup_begin(s, cast(uiid,list)))) {
/* special layer to detect clicks on elements */
struct widget_builder *w = 0;
w = widget_begin(ps, WIDGET_COMBO_BOX_POPUP, 0, 0, 0);
w->widget = cb;
cb->popup = widget_box_push(w, cb->popup);
box_add_property(cb->popup, PROPERTY_INTERACTIVE);
widget_end(w);
push(ps, cb->popup);
/* combo elements */
if ((cb->fb = flex_box_begin(ps, cb->fb, cnt))) {
cb->fb->orientation = FLEX_BOX_VERTICAL;
for (i = 0; i < cnt; ++i) {
flex_box_slot_fitting_begin(ps, i);
cb->elms[i] = label_id(ps, list[i], cast(uiid,list[i]));
flex_box_slot_end(ps);
} flex_box_end(ps);
} combo_popup_end(ps);
pop(ps);
}} combo_end(s);
} pop(s);
return cb;
}
api struct combo_box*
combo_box_id(struct panel_state *s, uiid id, const char **list, int cnt)
{
struct combo_box *prev = load(s, id);
struct combo_box *bl = combo_box(s, prev, list, cnt);
store(s, bl, id);
return bl;
}
/* ===========================================================================
*
* CANVAS
*
* =========================================================================== */
#include <stdio.h>
#include "SDL2/SDL.h"
#define len(s) (cntof(s)-1)
#define h1(s,i,x) (x*65599lu+(unsigned char)(s)[(i)<len(s)?len(s)-1-(i):len(s)])
#define h4(s,i,x) h1(s,i,h1(s,i+1,h1(s,i+2,h1(s,i+3,x))))
#define h16(s,i,x) h4(s,i,h4(s,i+4,h4(s,i+8,h4(s,i+12,x))))
#define hash(s,i) ((uiid)(h16(s,0,i)^(h16(s,0,i)>>16)))
#define idx(s,i) hash(s,(uiid)i)
#define id(s) idx(s,0)
#define lit(s) s,len(s)
#define txt(s) s,((int)strlen(s))
struct color {unsigned char r,g,b,a;};
struct canvas {SDL_Renderer *ren; int penx, peny;};
intern struct color
rgb(int r, int g, int b)
{
struct color res;
res.r = cast(unsigned char, clamp(0,r,255));
res.g = cast(unsigned char, clamp(0,g,255));
res.b = cast(unsigned char, clamp(0,b,255));
res.a = 255;
return res;
}
intern void
fill_rect(struct canvas *c, int x, int y, int w, int h, struct color col)
{
SDL_Rect a;
a.w = w, a.h = h;
a.x = x+c->penx, a.y = y+c->peny;
SDL_SetRenderDrawColor(c->ren, col.r,col.g,col.b,col.a);
SDL_RenderFillRect(c->ren, &a);
}
intern void
stroke_line(struct canvas *c, int x0, int y0, int x1, int y1, struct color col)
{
SDL_SetRenderDrawColor(c->ren, col.r,col.g,col.b,col.a);
SDL_RenderDrawLine(c->ren, c->penx+x0, c->peny+y0, c->penx+x1, c->peny+y1);
}
intern void
stroke_rect(struct canvas *c, int x, int y, int w, int h, struct color col)
{
SDL_Rect a;
a.x = c->penx+x, a.y = c->peny+y;
a.w = w, a.h = h;
SDL_SetRenderDrawColor(c->ren, col.r,col.g,col.b,col.a);
SDL_RenderDrawRect(c->ren, &a);
}
intern struct canvas
canvas_create(SDL_Window *win)
{
struct canvas res;
zero(&res, szof(res));
res.ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED);
SDL_SetRenderDrawColor(res.ren, 255,255,255,255);
SDL_RenderClear(res.ren);
return res;
}
intern void
canvas_move_to(struct canvas *c, int x, int y)
{
c->penx = x;
c->peny = y;
}
intern void
canvas_reset(struct canvas *c)
{
canvas_move_to(c, 0, 0);
}
intern void
canvas_draw_label(struct canvas *can, struct box *b, struct label *lbl)
{
int j = 0, x = 0;
const struct color c = rgb(0,0,0);
canvas_move_to(can, b->screen.x+1, b->screen.y+1);
/* NOTE(micha): place-holder since SDL2 has no text rendering */
for (j = 0; j < lbl->len; ++j, x+=9*b->screen_transform.sx)
stroke_rect(can, x, 0, cast(int,8.0f*b->screen_transform.sx),
cast(int,16.0f*b->screen_transform.sy), c);
canvas_reset(can);
}
intern void
canvas_draw_icon(struct canvas *can, struct box *b, struct icon *ico)
{
canvas_move_to(can, b->screen.x, b->screen.y);
switch (ico->sym) {
case ICON_CIRCLE_SOLID:
case ICON_CIRCLE_OUTLINE:
case ICON_TRIANGLE_UP:
case ICON_TRIANGLE_DOWN:
case ICON_TRIANGLE_LEFT:
case ICON_TRIANGLE_RIGHT:
case ICON_RECT_OUTLINE:
/* NOTE(micha): place-holder since SDL2 has minimal shape rendering */
stroke_rect(can, 0,0, b->screen.w, b->screen.h, rgb(0,0,0));
break;
case ICON_RECT_SOLID:
fill_rect(can, 0,0, b->screen.w, b->screen.h, rgb(0,0,0));
break;}
canvas_reset(can);
}
intern void
canvas_draw_checkbox(struct canvas *can, struct box *b, struct checkbox *chk)
{
canvas_move_to(can, b->screen.x, b->screen.y);
if (b == chk->bounds)
stroke_rect(can, 0,0, b->screen.w, b->screen.h, rgb(0,0,0));
else if (chk->checked)
fill_rect(can, 0,0, b->screen.w, b->screen.h, rgb(0,0,0));
canvas_reset(can);
}
intern void
canvas_draw_button(struct canvas *can, struct box *b, struct button *btn)
{
canvas_move_to(can, b->screen.x, b->screen.y);
stroke_rect(can, 0,0, b->screen.w, b->screen.h, rgb(0,0,0));
canvas_reset(can);
}
intern void
canvas_draw_slider(struct canvas *can, struct box *b, struct slider *sld)
{
int off = 0;
struct box *cur = 0;
if (b != sld->bounds) return;
/* cursor */
cur = sld->cursor;
off = b->screen.y + b->screen.h/2;
stroke_rect(can, cur->screen.x, b->screen.y + b->screen.h/2-8,
cur->screen.w, cur->screen.h, rgb(0,0,0));
/* lines */
canvas_move_to(can, b->screen.x, cur->screen.y);
stroke_line(can, 0, 0, 0, cur->screen.h, rgb(0,0,0));
stroke_line(can, b->screen.w, 0, b->screen.w, cur->screen.h, rgb(0,0,0));
canvas_reset(can);
stroke_line(can, b->screen.x, off, cur->screen.x, off, rgb(0,0,0));
stroke_line(can, cur->screen.x + cur->screen.w, off,
b->screen.x + b->screen.w, off, rgb(0,0,0));
canvas_reset(can);
}
intern void
canvas_draw_combo(struct canvas *can, struct box *b, struct combo *c)
{
/* combo */
struct box *pop = c->popup;
canvas_move_to(can, b->screen.x, b->screen.y);
stroke_rect(can, 0,0, b->screen.w, b->screen.h, rgb(0,0,0));
canvas_reset(can);
/* popup */
if (pop->hidden) return;
canvas_move_to(can, pop->screen.x, pop->screen.y);
fill_rect(can, 0,0, pop->screen.w, pop->screen.h, rgb(255,255,255));
stroke_rect(can, 0,0, pop->screen.w, pop->screen.h, rgb(0,0,0));
canvas_reset(can);
}
intern void
canvas_draw_debug(struct context *ctx, struct canvas *can, struct box *b)
{
canvas_move_to(can, b->screen.x+1, b->screen.y+1);
if (b == ctx->active) {
stroke_rect(can, 0, 0, b->screen.w, b->screen.h, rgb(0,255,0));
} else if (b == ctx->hot) {
stroke_rect(can, 0, 0, b->screen.w, b->screen.h, rgb(255,0,0));
} else {
switch (b->type) {
default: stroke_rect(can,0,0,b->screen.w,b->screen.h,rgb(0,0,255)); break;
case WIDGET_LABEL: stroke_rect(can,0,0,b->screen.w,b->screen.h,rgb(178,34,34)); break;
case WIDGET_CHECKBOX: stroke_rect(can,0,0,b->screen.w,b->screen.h,rgb(0,255,255)); break;
case WIDGET_BUTTON: stroke_rect(can,0,0,b->screen.w,b->screen.h,rgb(255,168,0)); break;
case WIDGET_SLIDER: stroke_rect(can,0,0,b->screen.w,b->screen.h,rgb(85,107,47)); break;
case WIDGET_SBOX: stroke_rect(can,0,0,b->screen.w,b->screen.h,rgb(64,224,208)); break;
case WIDGET_FLEX_BOX: stroke_rect(can,0,0,b->screen.w,b->screen.h,rgb(0,128,128)); break;}
} canvas_reset(can);
}
/* ===========================================================================
*
* APP
*
* =========================================================================== */
int main(void)
{
struct canvas can;
SDL_Window *win = 0;
struct context *ctx = create(DEFAULT_ALLOCATOR);
SDL_Init(SDL_INIT_VIDEO);
win = SDL_CreateWindow("GUI", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
1000, 600, SDL_WINDOW_SHOWN);
can = canvas_create(win);
input_resize(ctx, 1000, 600);
{int quit = 0;
while (!quit) {
{SDL_Event evt;
while (SDL_PollEvent(&evt)) {
switch (evt.type) {
case SDL_QUIT: quit = 1; break;
case SDL_MOUSEMOTION: input_motion(ctx, evt.motion.x, evt.motion.y); break;
case SDL_MOUSEWHEEL: input_scroll(ctx, evt.wheel.x, evt.wheel.y); break;
case SDL_TEXTINPUT: input_text(ctx, txt(evt.text.text)); break;
case SDL_WINDOWEVENT: {
if (evt.window.event == SDL_WINDOWEVENT_RESIZED ||
evt.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
input_resize(ctx, evt.window.data1, evt.window.data2);
} break;
case SDL_MOUSEBUTTONUP:
case SDL_MOUSEBUTTONDOWN: {
int down = (evt.type == SDL_MOUSEBUTTONDOWN);
if (evt.button.button == SDL_BUTTON_LEFT)
input_button(ctx, MOUSE_BUTTON_LEFT, down);
else if (evt.button.button == SDL_BUTTON_RIGHT)
input_button(ctx, MOUSE_BUTTON_RIGHT, down);
} break;
case SDL_KEYUP:
case SDL_KEYDOWN: {
int down = (evt.type == SDL_KEYDOWN);
SDL_Keycode sym = evt.key.keysym.sym;
input_key(ctx, sym, down);
} break;}
}}
/* ----------------------------- UI ------------------------------------*/
{struct panel_state *s = 0;
if ((s = begin(ctx, id("panel")))) {
struct flex_box *f = 0;
if ((f = flex_box_begin_id(s, idx("flex_box",0), 4))) {
/* text button */
{struct button_label *bl = 0;
flex_box_slot_static_begin(s, 120, 0);
if ((bl = button_label_id(s, id("button_label"), "label")))
if (bl->clicked) printf("button clicked!\n");
flex_box_slot_end(s);}
/* slider */
{struct slider *sld = 0;
flex_box_slot_static_begin(s, 120, 1);
if ((sld = slider_id(s, id("slider")))) {
sld->lo = 0, sld->hi = 10;
if (sld->value_changed)
printf("slider: value changed: %.2f\n", sld->val);
} flex_box_slot_end(s);}
/* checkbox label */
{struct checkbox_label *cl = 0;
flex_box_slot_static_begin(s, 150, 2);
cl = checkbox_label_id(s, id("checkbox_label"), "checkbox");
if (cl->toggled) printf("checkbox toggled:\n");
flex_box_slot_end(s);}
/* text combo box */
{struct combo_box *cb = 0;
static const char *list[] = {"Pistol","Shotgun","Plasma","BFG"};
flex_box_slot_static_begin(s, 100, 3);
if ((cb = combo_box_id(s, id("combo_box"), list, cntof(list)))) {
if (cb->selection_changed)
printf("combo box: selection changed: %s\n", list[cb->sel]);
} flex_box_slot_end(s);}
flex_box_end(s);
} end(s);
}}
/* ---------------------------------------------------------------------*/
/* Process: Input */
/* ---------------------------------------------------------------------*/
{struct operation *op = 0; int i;
while ((op = process_begin(ctx, PROCESS_INPUT))) {
switch (op->type) {
default: break;
case OP_CLEANUP: {
struct list_hook *n = 0;
list_foreach(n, &op->widgets) {
struct box *b = list_entry(n, struct box, hook);
switch (b->type) {
case WIDGET_COMBO: combo_popup_close(ctx, b->widget); break;
default: break;}
}
} break;
case OP_BLUEPRINT: {
for (i = op->begin; i != op->end; i += op->inc) {
struct box *b = op->boxes[i];
switch (b->type) {
case WIDGET_LABEL: label_blueprint(b->widget, b); break;
case WIDGET_ICON: b->dw = b->dh = 16; break;
case WIDGET_CHECKBOX: checkbox_blueprint(b->widget, b); break;
case WIDGET_BUTTON: measure(b, 0); break;
case WIDGET_SLIDER: slider_blueprint(b->widget, b); break;
case WIDGET_SBOX: sbox_blueprint(b->widget, b); break;
case WIDGET_COMBO: measure(b, 0); break;
case WIDGET_FLEX_BOX: flex_box_blueprint(b->widget, b);break;
default: blueprint(op, b); break;}
}
} break;
case OP_LAYOUTING: {
for (i = op->begin; i != op->end; i += op->inc) {
struct box *b = op->boxes[i];
switch (b->type) {
case WIDGET_ICON: icon_layout(b->widget, b); break;
case WIDGET_CHECKBOX: checkbox_layout(b->widget, b); break;
case WIDGET_BUTTON: compute(b, 0); break;
case WIDGET_SLIDER: slider_layout(b->widget, b); break;
case WIDGET_SBOX: sbox_layout(b->widget, b); break;
case WIDGET_COMBO: combo_layout(b->widget, b); break;
case WIDGET_FLEX_BOX: flex_box_layout(b->widget, b); break;
default: layout(op, b); break;}
}
} break;
case OP_TRANSFORM: {
for (i = op->begin; i != op->end; i += op->inc) {
struct box *b = op->boxes[i];
switch (b->type) {
case WIDGET_COMBO:
combo_transform(b->widget, b);
default: transform(op, b); break;}
}
} break;
case OP_INPUT: {
struct list_hook *it = 0;
list_foreach(it, &op->evts) {
union event *evt = list_entry(it, union event, hdr.hook);
for (i = 0; i < evt->hdr.cnt; i++) {
struct box *b = evt->hdr.boxes[i];
switch (b->type) {
case WIDGET_CHECKBOX: checkbox_input(b->widget, b, evt); break;
case WIDGET_BUTTON: button_input(b->widget, b, evt); break;
case WIDGET_SLIDER: slider_input(b->widget, b, evt); break;
case WIDGET_COMBO: combo_input(ctx, b->widget, b, evt); break;
case WIDGET_COMBO_BOX_POPUP: combo_box_input(ctx, b->widget, b, evt); break;
default: input(op, evt, b); break;}
}
}
} break;}
process_end(op);
}}
/* ---------------------------------------------------------------------*/
/* Process: Paint */
/* ---------------------------------------------------------------------*/
{struct operation *op = 0; int i;
SDL_SetRenderDrawColor(can.ren, 255,255,255,255);
SDL_RenderClear(can.ren);
while ((op = process_begin(ctx, PROCESS_PAINT))) {
assert(op->type == OP_PAINT);
for (i = 0; i < op->cnt; ++i) {
struct box *b = op->surfaces[i];
#ifndef VISDBG
switch (b->type) {
default: break;
case WIDGET_LABEL: canvas_draw_label(&can, b, b->widget); break;
case WIDGET_ICON: canvas_draw_icon(&can, b, b->widget); break;
case WIDGET_CHECKBOX: canvas_draw_checkbox(&can, b, b->widget); break;
case WIDGET_BUTTON: canvas_draw_button(&can, b, b->widget); break;
case WIDGET_SLIDER: canvas_draw_slider(&can, b, b->widget); break;
case WIDGET_COMBO: canvas_draw_combo(&can, b, b->widget); break;}
#else
canvas_draw_debug(ctx, &can, b);
#endif
} process_end(op);
}}
SDL_RenderPresent(can.ren);
SDL_Delay(16);
}}
destroy(ctx);
SDL_DestroyRenderer(can.ren);
SDL_DestroyWindow(win);
SDL_Quit();
return 0;
}
@dumblob
Copy link

dumblob commented Oct 3, 2017

@dumblob
Copy link

dumblob commented Oct 7, 2017

Thanks @vurtun for the correction.

There is one more thing which seems important and is missing in the section * Problems:. According to my understanding support for the notion of different physical dimensions of different parts of the video buffer is not implemented (see the note at the bottom of vurtun/nuklear#283 (comment) ). Let me explain.

Let's assume I have 3 different physical LCD displays and want to use a Quarks application on all of them simultaneously (one application window spanning across all three displays). The LCDs are physically placed like this:

final_physical_displays_positioning
(note the bezels which make the physical displaying surface non-continuous)

This is how the physical surface itself looks like:
final_physical_displays_positioning_without_bezels

Graphic cards nowadays support more displays using a "virtual" huge "multibuffer canvas" where all the 3 different "framebuffers", each corresponding to one and only one LCD, must fit in. But because each LCD has not only different physical dimensions, they have also different resolutions and therefore the "multibuffer canvas" (yellow color) used by graphic cards to draw the actual pixels to looks like this:

multibuffer_canvas_after_applying_physical_dpi_of_lcds

The issue is, that all UIs known to me do not have the capability to support different resolutions in different areas of the "multibuffer canvas" and thus they just choose some "random" DPI (usually the DPI of the first physical display in the list of physical displays connected to the running system, which is of course unpredictable) with the following result:

multibuffer_canvas_after_applying_physical_dpi_of_lcds-bad_gui_toolkit

Which looks on the whole physical surface like this:

final_physical_displays_positioning_without_bezels-bad_gui_toolkit

@vurtun
Copy link
Author

vurtun commented Oct 7, 2017

If I understand your solution correctly:

The basic idea how to implement "multibuffer canvas" is to have a separate canvas for each of the different pixel sizes and draw everything to each of those canvases separately with the corresponding scale (thanks to clipping the double/triple/... drawing of certain windows/widgets would be in most cases eliminated like it's done now when a window/widget is half outside the canvas).

This should be possible. Just iterate over the generated "paint" surface boxes three times in a row with different scalers each time. Or am I missing something here.

@dumblob
Copy link

dumblob commented Oct 8, 2017

This should be possible. Just iterate over the generated "paint" surface boxes three times in a row with different scalers each time. Or am I missing something here.

Yep, that should do. But I'm not sure about the clipping (i.e. performance) in gui.c - how can I skip generation of unnecessary areas in those three different "paint" surface boxes?

@vurtun
Copy link
Author

vurtun commented Oct 8, 2017

Hm .., this implementation is composed as a rectangle tree (similar to a 2D quad tree just with a lot more child nodes per node) so you could check each sub-tree if it is inside that particular rectangle? The list of boxes to be painted is already build up each frame so you could basically do software clipping. In fact I already do some software clipping. Basically if any sub-node is not inside its parent node the sub-tree will be clipped. This would just add another clip rectangle to check against.

@dumblob
Copy link

dumblob commented Oct 8, 2017

Basically if any sub-node is not inside its parent node the sub-tree will be clipped. This would just add another clip rectangle to check against.

Sounds great. Do you think any modification of this experimental lib would be needed or would it be enough to implement this in the main loop?

@vurtun
Copy link
Author

vurtun commented Oct 8, 2017

All clipping is done inside:

static int
dfs(struct box **buf, struct box **stk, struct box *root)
{
    int tail = 0;
    struct list_hook *i;
    unsigned long head = 0;

    stk[head++] = root;
    while (head > 0) {
        struct box *b = stk[--head];
        buf[tail++] = b;
        list_foreach(i, &b->links) {
            struct box *s = list_entry(i,struct box,node);
            if (s->hidden || !box_intersect(b, s)) continue;
            stk[head++] = s;
        }
    } return tail;
}

Specifically this check:

if (s->hidden || !box_intersect(b, s)) continue;

So basically the library would need a global (inside struct context) clipping rectangle
that is changed for each screen rectangle:

if (s->hidden || !box_intersect(b, s) || !box_intersect(b, ctx->clip_rectangle)) continue;

Interestingly this could also be used to implement a dirty rectangle approach to only paint section of the
screen that need to be redrawn, but I wanted to keep this simple for now.

@dumblob
Copy link

dumblob commented Oct 8, 2017

Thanks for the precise explanation!

but I wanted to keep this simple for now.

Makes sense. Actually it sounds you have some bigger plans with it 😉. Any hints?

@vurtun
Copy link
Author

vurtun commented Oct 9, 2017

Actually it sounds you have some bigger plans with it 😉. Any hints?

Honestly after I am done writing popup handling and fixed PROCESS_CLEAR I will stop. The problem is getting this to
production level quality code would take me multiple years. The amount of work required is just not doable as a side project. GUIs are
a really big topic with tons of really nasty problems. Problems not so much in the core UI but just all the stuff around it like your example. If I had more experience and all the right equipment to test all these cases it probably would take way less time but even then it would be hard to defend working on it for such a long time.

So this is basically my final proof of concept solution for the core UI problem. It has everything I wanted when I started working on GUIs 3 years ago. So it is a good time to stop without leaving me with regrets that I stopped later.

@dumblob
Copy link

dumblob commented Oct 9, 2017

So it is a good time to stop without leaving me with regrets that I stopped later.

It's sad, but you're right. This is exactly why any more significant open source project can't live without a regular material support and without being part of (or at least deeply supported by) a commercial business.

Would you actually be interested in e.g. being a part-time/full-time team leader accountable for steering contributions from your team members and external contributors, reviewing this contributions and deciding architectural choices in gui.c? Or rather a full-time/part-time programmer working on gui.c and having few other mainly external contributors? Or would you rather run away from GUI/UI stuff completely?

@vurtun
Copy link
Author

vurtun commented Oct 10, 2017

Would you actually be interested in e.g. being a part-time/full-time team leader accountable for steering contributions from your team members and external contributors, reviewing this contributions and deciding architectural choices in gui.c? Or rather a full-time/part-time programmer working on gui.c and having few other mainly external contributors? Or would you rather run away from GUI/UI stuff completely?

At this point in time I prepared myself to get away from GUI/UI. The topic basically never left me the last three years and I was constantly thinking about it. With this "release" I am able to leave it behind me. Maybe not forever but at least in a way that I won't constantly think about it. This version is not perfect but close enough.

In a perfect world I would be able to make this into a commercial solutions and start a middleware company. In reality however the cost of first implementing a solutions and then afterwards breaking into the industry is close to impossible. Especially if you don't have a name or any references.

@dumblob
Copy link

dumblob commented Oct 11, 2017

Understood. I see some light at the end of the tunnel... I'll come later (in a year or two). Or as we say here "Ich komme auf dich zu später.".

@dumblob
Copy link

dumblob commented Oct 28, 2017

Hm, I noticed your comment on https://gist.github.com/vurtun/61b6dbf21ef060bcbbd8d1faa88350d9#file-gui-c-L3267 . I agree, that the solution with "objects" is the fitting way to solve this. But just to clarify - ANSI C supports variadic functions, which are in other words functions with variable number of arguments.

@dumblob
Copy link

dumblob commented Oct 28, 2017

https://gist.github.com/vurtun/61b6dbf21ef060bcbbd8d1faa88350d9/revisions#diff-729da247b80ee74140a7732c42a735c3R610

How does the - toint + toint look like in assembly with -O2 or -O3 optimizations of gcc? Can we somehow instruct the compiler to not optimize it?

@vurtun
Copy link
Author

vurtun commented Oct 29, 2017

ANSI C supports variadic functions, which are in other words functions with variable number of arguments.

I probably phrased it wrong. While I am not a big fan of variadic functions (outside printf) is my problem not really with variable number of arguments. Instead what interests me more is the optional default value. In other word in C++:

void function(int val = 5);

Personally I don't like the C++ version as well. Since on the calling side the function signature will change. For example in these calls:

function();
function(5);

you cannot tell by simply reading these two functions what language feature is being called. It could be function overloading or a function with default values but you cannot tell by simply reading the call.

In my previous iteration I used C99 designated initializer as named parameter in combination with clever use of zero values to get around the problem:

struct param {
    int  val;
};
void function(struct param p);
/* ... */
function((struct param){});

However I prefer this solution here of directly returning an object already filled out with default values. First you only have to fill our what you need. So in most cases the default value is sufficient and additional code is only required in special cases. Second is that it is easier to port an API to other language if everything is just pointers. Finally in this solution like I said since you also have return values it basically just makes more sense to use objects and direct variable manipulation.

How does the - toint + toint look like in assembly with -O2 or -O3 optimizations of gcc? Can we somehow instruct the compiler to not optimize it?

This whole function will be replaced. Since it only rounds from float to int and not float to float it can be simplified. I am currently finally finishing up the popup code which will also feature a different version for rounding.

@dumblob
Copy link

dumblob commented Nov 5, 2017

I am currently finally finishing up the popup code which will also feature a different version for rounding.

Good. My question though still holds as the - toint + toint issue is still present. How do we make sure it's not optimized away?

@dumblob
Copy link

dumblob commented Nov 25, 2017

@vurtun I stumbled upon the following special requirement on handling of a "cursor" on touch devices. Namely, that the position of the touch is just approximate and should automatically be "attracted" by all buttons/sliders/whatever based on the Euclidean metric and a previously defined priority of each button/slider/whatever.

The issue is, that this (a click somewhere between buttons/sliders/whatever, but not directly on them) requires 3 costly steps basically in each frame:

  1. evaluation of precise positions of all buttons/sliders/whatever
  2. evaluation of the metric and priority rules - the result is a set of buttons/sliders/whatever which are affected by the cursor position
  3. reaction of the affected buttons/sliders/whatever on the cursor position as well as chained reaction of other parts of the GUI to the changes

My understanding is, that step 1. and 3. are very similar or the same and thus a double-work. Would you also have fear, that it's too slow? There is also this "react first in the second frame" solution. But I wanted a same-frame solution. Any ideas?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment