Skip to content

Instantly share code, notes, and snippets.

@lamarqua
Forked from vurtun/gui.c
Created October 9, 2017 16:54
Show Gist options
  • Select an option

  • Save lamarqua/620065e605547f45592bd4c9c3f7ce08 to your computer and use it in GitHub Desktop.

Select an option

Save lamarqua/620065e605547f45592bd4c9c3f7ce08 to your computer and use it in GitHub Desktop.
/* ===========================================================================
*
* LIBRARY
*
* =========================================================================== */
/* Proof of Concept GUI:
* - PoC UI implementation in ~2kLOC 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
* => Simplistic 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 */
#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))
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, long 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 (64*1024)
struct allocator {
void *usr;
alloc_f alloc;
dealloc_f dealloc;
};
struct memory_block {
struct list_hook hook;
struct memory_block *prev;
long 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;
long 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 {
uiid box_id, widget_id, type_id;
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 activated:1;
unsigned deactivated: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 links;
struct list_hook node;
struct list_hook hook;
struct box *parent;
void *widget;
};
/* panel */
struct widget_builder {
uiid type_id, widget_id;
int stkcnt, cnt;
struct box *stk[16];
struct panel_state *state;
struct memory_arena *arena;
void *widget, *widget_last;
};
struct panel_state {
struct memory_arena *arena;
struct context *ctx;
struct panel *panel;
struct repository *repo;
struct widget_builder *builder;
struct box *stk[16];
int stkcnt;
};
struct table {
int cnt;
uiid *keys;
struct box **boxes;
};
struct repository {
struct memory_arena arena;
int cnt, depth;
struct list_hook widgets;
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;
};
/* event */
enum event_type {
EVT_ACTIVATED,
EVT_DEACTIVATED,
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_entered_exited {
struct box *last;
struct box *cur;
};
struct event_int2 {int x, y;};
struct event_key {
int code;
unsigned pressed:1;
unsigned released:1;
unsigned ctrl:1;
unsigned shift:1;
unsigned alt:1;
unsigned super:1;
};
struct event_text {
char *buf;
int len;
unsigned ctrl:1;
unsigned shift:1;
unsigned alt:1;
unsigned super:1;
unsigned resized:1;
};
union event_data {
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;
};
struct event {
enum event_type type;
int cap, cnt;
struct box *origin;
struct box **boxes;
struct list_hook hook;
union event_data with;
};
/* operation */
enum operation_type {
OP_BLUEPRINT,
OP_LAYOUTING,
OP_TRANSFORM,
OP_INPUT,
OP_PAINT,
OP_CLEANUP,
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_INPUT = PROCESS_COMMIT|flag(OP_INPUT),
PROCESS_PAINT = PROCESS_COMMIT|flag(OP_PAINT),
PROCESS_EVERYTHING = 0xFFF
};
struct operation {
/* common */
enum operation_type type;
struct context *ctx;
struct memory_arena *arena;
struct temp_memory tmp;
/* layout + blueprint + cleanup */
struct panel *panel;
struct repository *repo;
int begin, end, inc;
struct box **boxes; /* layout + blueprint */
struct list_hook widgets; /* cleanup */
/* input */
struct list_hook evts;
struct input *in;
/* paint */
struct box **surfaces;
int cnt;
};
/* context */
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,
};
enum state_machine_state {
STATE_PREPROCESS,
STATE_GC,
STATE_CLEANUP,
STATE_BLUEPRINT,
STATE_LAYOUTING,
STATE_TRANSFORM,
STATE_INPUT,
STATE_PAINT,
STATE_POSTPROCESS
};
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 */
static struct context *create(const struct allocator *a);
static void destroy(struct context *ctx);
/* input */
static void input_char(struct context *ctx, char c);
static void input_resize(struct context *ctx, int w, int h);
static void input_scroll(struct context *ctx, int x, int y);
static void input_motion(struct context *ctx, int x, int y);
static void input_rune(struct context *ctx, unsigned long r);
static void input_key(struct context *ctx, int key, int down);
static void input_shortcut(struct context *ctx, int id, int down);
static void input_text(struct context *ctx, const char *t, int len);
static void input_button(struct context *ctx, enum mouse_button btn, int down);
/* process */
static struct operation *process_begin(struct context *ctx, unsigned int flags);
static void blueprint(struct operation *op, struct box *b);
static void layout(struct operation *op, struct box *b);
static void process_end(struct operation *op);
/* panel */
static struct panel_state *begin(struct context *ctx, uiid id);
static struct box *find(struct panel_state *p, uiid type_id, uiid widget_id);
static void end(struct panel_state *s);
static void clear(struct context *ctx, uiid pid);
static struct box *query(struct context *ctx, uiid pid, uiid type_id, uiid widget_id);
/* widgets */
static struct widget_builder *widget_begin(struct panel_state *s, uiid type_id, uiid widget_id, int size, int alignment);
static struct box *widget_add_box_begin(struct widget_builder *w, uiid box_id);
static void widget_add_box_end(struct widget_builder *w);
static struct box *widget_add_box(struct widget_builder *w, uiid box_id);
static void widget_end(struct widget_builder *w);
/* stack */
static void push(struct panel_state *s, struct box *b);
static struct box *peek(struct panel_state *s);
static void pop(struct panel_state *s);
static void reset(struct panel_state *s);
/* box */
static void box_add_property(struct box *b, enum property_type type);
static void box_shrink(struct box *d, const struct box *s, int pad);
static int box_intersect(const struct box *a, const struct box *b);
static void measure(struct box *b, int pad);
static void compute(struct box *b, int pad);
/* arena */
static void *arena_push(struct memory_arena *a, long size, long align);
#define arena_push_type(a,type) (type*)arena_push(a, szof(type), alignof(type))
#define arena_push_array(a,n,type) (type*)arena_push(a, (n)*szof(type), alignof(type))
static void arena_clear(struct memory_arena *a);
/* utf-8 */
static int utf_decode(unsigned long *rune, const char *str, int len);
static int utf_encode(char *s, int cap, unsigned long u);
static int utf_len(const char *s, int len);
static 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, long 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)
static 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;
}
static int
floori(float x)
{
x = cast(float,(cast(int,x) - ((x < 0.0f) ? 1 : 0)));
return cast(int,x);
}
static 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;
}
}
static 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;
}
static 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;
}
static 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;
}
static char
utf_encode_byte(unsigned long u, int i)
{
return (char)((utfbyte[i]) | ((unsigned char)u & ~utfmask[i]));
}
static 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;
}
static 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;
}
static 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;
}
static void
transform_init(struct transform *t)
{
t->x = t->y = 0;
t->sx = t->sy = 1;
}
static void
list_init(struct list_hook *list)
{
list->next = list->prev = list;
}
static 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;
}
static void
list_add_head(struct list_hook *list, struct list_hook *n)
{
list__add(n, list, list->next);
}
static void
list_add_tail(struct list_hook *list, struct list_hook *n)
{
list__add(n, list->prev, list);
}
static void
list__del(struct list_hook *prev, struct list_hook *next)
{
next->prev = prev;
prev->next = next;
}
static void
list_del(struct list_hook *entry)
{
list__del(entry->prev, entry->next);
entry->next = entry;
entry->prev = entry;
}
static void
list_move_head(struct list_hook *list, struct list_hook *entry)
{
list_del(entry);
list_add_head(list, entry);
}
static void
list_move_tail(struct list_hook *entry, struct list_hook *list)
{
list_del(entry);
list_add_tail(list, entry);
}
static 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;
}
static 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);
}
}
static 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);
}
}
static void
block_alloc_init(struct block_allocator *a)
{
list_init(&a->blks);
list_init(&a->freelist);
}
static struct memory_block*
block_alloc(struct block_allocator *a, long 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;
}
static void
block_dealloc(struct block_allocator *a, struct memory_block *blk)
{
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--;
}
static 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;
}
static void*
arena_push(struct memory_arena *a, long size, long align)
{
assert(a);
if (!a || !size) return 0;
align = max(align,1);
if (!a->blk || (a->blk->used + size + align) > a->blk->size) {
long minsz = size + szof(struct memory_block) + align;
long 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 += size + (res - raw);
zero(res, size);
return res;}
}
static 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--;
}
static void
arena_clear(struct memory_arena *a)
{
while (a->blk)
arena_free_last_blk(a);
}
static 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;
}
static 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--;
}
static void
insert(struct table *tbl, uiid id, struct box *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->boxes[idx] = b; return;
} while ((idx = ((idx+1) & (cnt-1))) != begin);
}
static struct box*
lookup(struct table *tbl, uiid id)
{
uiid key = 0;
uiid 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->boxes[idx];
} while ((idx = ((idx+1) & (cnt-1))) != begin);
return 0;
}
static int
box_equal(const struct box *a, const struct box *b)
{
if (!a || !b) return 0;
if (a->type_id != b->type_id)
return 0;
if (a->widget_id != b->widget_id)
return 0;
if (a->box_id != b->box_id)
return 0;
return 1;
}
static void
box_shrink(struct box *d, const struct box *s, int pad)
{
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);
}
static 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;}
}
static 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);
}
static void
input_resize(struct context *ctx, int w, int h)
{
struct input *in;
assert(ctx);
if (!ctx) return;
in = &ctx->input;
in->resized = 1;
in->width = w;
in->height = h;
}
static void
input_motion(struct context *ctx, int x, int y)
{
struct input *in;
assert(ctx);
if (!ctx) return;
in = &ctx->input;
in->mouse.x = x;
in->mouse.y = y;
in->mouse.dx = in->mouse.x - in->mouse.lx;
in->mouse.dy = in->mouse.y - in->mouse.ly;
}
static void
input_key(struct context *ctx, int key, int down)
{
struct input *in;
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;
}
static void
input_button(struct context *ctx, enum mouse_button idx, int down)
{
struct input *in;
struct key *btn;
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++;
}
static void
input_shortcut(struct context *ctx, int shortcut, int down)
{
struct input *in;
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;
}
static void
input_scroll(struct context *ctx, int x, int y)
{
struct input *in;
assert(ctx);
if (!ctx) return;
in = &ctx->input;
in->mouse.wheelx += x;
in->mouse.wheely += y;
}
static void
input_text(struct context *ctx, const char *buf, int len)
{
struct input *in;
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;
}
static void
input_char(struct context *ctx, char c)
{
input_text(ctx, &c, 1);
}
static void
input_rune(struct context *ctx, unsigned long r)
{
int len = 0;
char buf[UTF_SIZE];
len = utf_encode(buf, UTF_SIZE, r);
input_text(ctx, buf, len);
}
static void
reset(struct panel_state *s)
{
assert(s);
if (!s) return;
s->stkcnt = 0;
}
static 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;
}
static struct box*
peek(struct panel_state *s)
{
struct repository *r;
assert(s);
if (!s) return 0;
r = s->repo;
if (!s->stkcnt) return r->tree;
return s->stk[s->stkcnt-1];
}
static void
pop(struct panel_state *s)
{
assert(s);
if (!s) return;
assert(s->stkcnt);
--s->stkcnt;
}
static void
repository_init(struct repository *r, struct memory_arena *a)
{
zero(r, szof(*r));
list_init(&r->widgets);
r->arena.mem = a->mem;
}
static struct panel*
panel_create(struct memory_arena *a, uiid id,
struct list_hook *freelist, struct list_hook *list)
{
int i = 0;
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);
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);
list_add_tail(list, &p->hook);
for (i = 0; i < cntof(p->repo); ++i) {
struct repository *r = p->repo + i;
repository_init(r, a);
r->tree = &p->root;
} return p;
}
static void
panel_destroy(struct panel *p, struct list_hook *garbage,
struct list_hook *list)
{
struct list_hook *i, *n;
struct list_hook tmp;
list_init(&tmp);
/* unlink panel */
list_del(&p->root.node);
list_move_tail(&tmp, &p->hook);
/* remove sub-tree */
list_foreach_s(i,n,list) {
struct list_hook *k;
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(garbage, &tmp);
}
static struct panel*
panel_find(struct context *ctx, uiid id)
{
struct list_hook *i = 0;
list_foreach(i, &ctx->pan) {
struct panel *p;
p = list_entry(i, struct panel, hook);
if (p->id == id) return p;
} return 0;
}
static struct box*
find(struct panel_state *s, uiid type_id, uiid id)
{
struct repository *r;
struct panel *p;
struct box *b;
assert(s);
if (!s) return 0;
p = s->panel;
r = &p->repo[p->act];
if (!r->cnt || !r->tbl.cnt)
return 0;
b = lookup(&r->tbl, id);
if (b && b->type_id == type_id)
return b;
else return 0;
}
static struct box*
query(struct context *ctx, uiid pid, uiid type_id, uiid id)
{
struct panel *p;
assert(ctx);
if (!ctx) return 0;
p = panel_find(ctx, pid);
if (!p) return 0;
p->state.panel = p;
return find(&p->state, type_id, id);
}
static struct box*
widget_get_parent(struct widget_builder *w)
{
struct panel_state *s;
struct repository *r;
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];
}
static struct widget_builder*
widget_begin(struct panel_state *s, uiid type_id, uiid widget_id,
int size, int alignment)
{
struct box *last;
struct widget_builder *w;
assert(s);
if (!s) return 0;
w = s->builder;
zero(w, szof(*w));
last = find(s, type_id, widget_id);
if (last) {
last->alive = 1;
w->widget_last = last->widget;
} else w->widget_last = 0;
w->widget = arena_push(s->arena, size, alignment);
w->stk[w->stkcnt++] = peek(s);
w->widget_id = widget_id;
w->type_id = type_id;
w->arena = s->arena;
w->state = s;
return w;
}
static struct box*
widget_add_box(struct widget_builder *w, uiid box_id)
{
struct context *ctx;
struct panel_state *st;
struct repository *repo;
struct box *p, *b;
assert(w);
if (!w) return 0;
st = w->state;
ctx = st->ctx;
repo = st->repo;
/* allocate and setup box */
b = arena_push_type(w->arena, struct box);
b->widget_id = w->widget_id;
b->type_id = w->type_id;
b->widget = w->widget;
b->box_id = box_id;
list_init(&b->hook);
list_init(&b->node);
list_init(&b->links);
transform_init(&b->local_transform);
transform_init(&b->screen_transform);
if (box_equal(ctx->active, b))
ctx->active = b;
if (box_equal(ctx->origin, b))
ctx->origin = b;
if (box_equal(ctx->hot, b))
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) {
list_add_head(&repo->widgets, &b->hook);
repo->tbl.cnt++;
} w->cnt++;
return b;
}
static struct box*
widget_add_box_begin(struct widget_builder *w, uiid id)
{
struct box* b;
assert(w);
if (!w) return 0;
b = widget_add_box(w, id);
assert(w->stkcnt < cntof(w->stk));
w->stk[w->stkcnt++] = b;
return b;
}
static void
widget_add_box_end(struct widget_builder *w)
{
assert(w);
if (!w) return;
assert(w->stkcnt);
--w->stkcnt;
}
static void
widget_end(struct widget_builder *w)
{
struct panel_state *st;
struct repository *repo;
assert(w);
if (!w) return;
st = w->state;
repo = st->repo;
repo->cnt += w->cnt;
assert(!(--w->stkcnt));
}
static struct panel_state*
begin(struct context *ctx, uiid id)
{
struct panel_state *s = 0;
struct panel *parent = 0;
struct panel *p = 0;
struct box *root = 0;
assert(ctx);
if (!ctx) return 0;
/* find or create panel */
p = panel_find(ctx, id);
if (!p) {
p = panel_create(&ctx->arena, id, &ctx->freelist, &ctx->pan);
if (!p) return 0;
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->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->widget_id = id;
root->box_id = WIDGET_PANEL;
root->type_id = 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->widget_id = WIDGET_ROOT;
root->type_id = WIDGET_ROOT;
root->box_id = WIDGET_ROOT;
root->widget = ctx;
ctx->tree = root;
} push(s, root);
return s;
}
static void
end(struct panel_state *s)
{
struct context *ctx;
assert(s);
if (!s) return;
ctx = s->ctx;
assert(ctx->stkcnt > 0);
ctx->stkcnt--;
}
static 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(p, &ctx->garbage, &ctx->pan);
}
static struct panel_state*
popup_begin(struct context *ctx, uiid id)
{
struct panel_state *s = 0;
struct panel *root = 0;
struct box *blk = 0;
assert(ctx);
if (!ctx) return 0;
root = ctx->root;
s = &root->state;
blk = ctx->blocking;
ctx->stk[ctx->stkcnt++] = root;
push(s, ctx->popup);
blk->interactive = 0;
return begin(ctx, id);
}
static void
popup_close(struct panel_state *s)
{
struct context *ctx = 0;
struct list_hook *i, *n;
struct box *blk = 0;
assert(s && s->ctx);
if (!s) return;
ctx = s->ctx;
blk = ctx->blocking;
list_foreach_s(i, n, &blk->links) {
/* closes all popups */
struct box *b = list_entry(i,struct box,node);
struct panel *p = b->widget;
if (b == ctx->ui) continue;
panel_destroy(p, &ctx->garbage, &ctx->pan);
}
}
static void
popup_end(struct panel_state *s)
{
struct panel_state *p = 0;
struct context *ctx = 0;
struct panel *root = 0;
assert(s);
if (!s) return;
end(s);
ctx = s->ctx;
root = ctx->root;
p = &root->state;
assert(ctx->stkcnt > 0);
ctx->stkcnt--;
pop(p);
}
static struct context*
create(const struct allocator *a)
{
struct context *ctx = 0;
struct panel_state *s = 0;
struct widget_builder *w = 0;
a = (a) ? a: &default_allocator;
ctx = qalloc(a, szof(*ctx));
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, WIDGET_ROOT, 0,0);
ctx->overlay = widget_add_box_begin(w, WIDGET_OVERLAY);
box_add_property(ctx->overlay, PROPERTY_INTERACTIVE);
box_add_property(ctx->overlay, PROPERTY_UNIFORM);
ctx->popup = widget_add_box_begin(w, WIDGET_POPUP);
box_add_property(ctx->popup, PROPERTY_INTERACTIVE);
box_add_property(ctx->popup, PROPERTY_UNIFORM);
ctx->contextual = widget_add_box_begin(w, WIDGET_CONTEXTUAL);
box_add_property(ctx->contextual, PROPERTY_INTERACTIVE);
box_add_property(ctx->contextual, PROPERTY_UNIFORM);
ctx->unblocking = widget_add_box_begin(w, WIDGET_UNBLOCKING);
box_add_property(ctx->unblocking, PROPERTY_INTERACTIVE);
box_add_property(ctx->unblocking, PROPERTY_UNIFORM);
ctx->blocking = widget_add_box_begin(w, WIDGET_BLOCKING);
box_add_property(ctx->blocking, PROPERTY_INTERACTIVE);
box_add_property(ctx->blocking, PROPERTY_UNIFORM);
ctx->ui = widget_add_box(w, WIDGET_UI);
box_add_property(ctx->ui, PROPERTY_INTERACTIVE);
box_add_property(ctx->ui, PROPERTY_BACKGROUND);
widget_add_box_end(w);
widget_add_box_end(w);
widget_add_box_end(w);
widget_add_box_end(w);
widget_add_box_end(w);
widget_end(w);
push(s, ctx->ui);
end(s);
ctx->hot = ctx->tree;
ctx->root = s->panel;
ctx->active = ctx->tree;
ctx->origin = ctx->tree;
return ctx;
}
static void
destroy(struct context *ctx)
{
assert(ctx);
if (!ctx) return;
free_blocks(&ctx->blkmem);
qdealloc(&ctx->mem, ctx);
}
static 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;
}
static void
operation_end(struct operation *op)
{
assert(op);
if (!op) return;
temp_memory_end(op->tmp);
}
static struct box**
bfs(struct box **buf, struct box *root)
{
struct list_hook *i;
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;
}
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;
}
static void
reorder(struct box *b)
{
while (b->parent) {
struct list_hook *i;
struct box *p = b->parent;
if (p->uniform) goto nxt;
list_foreach(i, &p->links) {
struct box *n = list_entry(i, struct box, node);
if (n != b || n->background) break;
list_move_tail(&n->node, &p->links);
break;
} nxt: b = p;
} return;
}
static struct box*
at(struct box *b, int mx, int my)
{
struct list_hook *i;
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;
}
static void
event_add_box(struct event *evt, struct box *box)
{
assert(evt->cnt < evt->cap);
if (evt->cnt >= evt->cap) return;
evt->boxes[evt->cnt++] = box;
}
static struct event*
event_begin(struct operation *op, enum event_type type, struct box *orig)
{
struct event *res;
res = arena_push_type(op->arena, struct event);
list_init(&res->hook);
list_add_tail(&op->evts, &res->hook);
res->type = type;
res->origin = orig;
res->cap = orig->depth + 1;
res->boxes = arena_push_array(op->arena, res->cap, struct box*);
event_add_box(res, orig);
return res;
}
static void
event_end(struct event *evt)
{
unused(evt);
}
static 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;
}
static 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;
}
static void
swap_buffers(struct panel *p, struct repository *r,
struct memory_arena *a)
{
arena_clear(&r->arena);
repository_init(r, a);
p->act = !p->act;
}
static struct operation*
process_begin(struct context *ctx, unsigned int flags)
{
int i;
#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;
flags = (!flags) ? PROCESS_INPUT: flags;
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;
b->activated = b->deactivated = 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.boxes = arena_push_array(&r->arena, r->tbl.cnt, struct box*);}
r->cnt++;
/* insert each widget root box into table */
{struct list_hook *bi = 0;
list_foreach(bi, &r->widgets) {
struct box *w = list_entry(bi, struct box, hook);
insert(&r->tbl, w->widget_id, w);
}}
/* 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_GC);
}
case STATE_GC: {
struct panel *p = 0;
struct repository *r = 0;
struct operation *op = 0;
while (1) {
do {sm->iter = it(&ctx->pan, sm->iter);
if (!sm->iter) jmpto(sm, STATE_CLEANUP);
p = list_entry(sm->iter, struct panel, hook);
r = &p->repo[p->act];
} while (!p->repo[!p->act].cnt);
if (flags & PROCESS_CLEANUP) break;
swap_buffers(p,r,&ctx->arena);
}
op = operation_begin(OP_CLEANUP, ctx, &ctx->arena);
op->panel = p;
op->repo = r;
/* gather up all "dead" widget boxes */
{struct list_hook *x, *n;
list_foreach_s(x, n, &r->widgets) {
struct box *b = 0;
b = list_entry(x, struct box, hook);
if (b->alive) continue;
list_move_tail(&op->widgets, &b->hook);
}} return op;
}
case STATE_CLEANUP: {
struct panel *p = 0;
struct repository *r = 0;
struct operation *op = 0;
struct list_hook *freelist = &ctx->freelist;
if (!(flags & PROCESS_CLEANUP))
list_splice_head(freelist, &ctx->garbage);
if (list_empty(&ctx->garbage)) {
/* state transition table */
if (sm->unbalanced && (flags & PROCESS_BLUEPRINT))
jmpto(sm, STATE_BLUEPRINT);
else if (flags & flag(OP_INPUT))
jmpto(sm, STATE_INPUT);
else if (flags & flag(OP_PAINT))
jmpto(sm, STATE_PAINT);
else return 0;
}
sm->iter = ctx->garbage.next;
p = list_entry(sm->iter, struct panel, hook);
r = &p->repo[ p->act];
if (!p->repo[!p->act].cnt)
list_move_tail(freelist, &p->hook);
op = operation_begin(OP_CLEANUP, ctx, &r->arena);
op->widgets = r->widgets;
op->panel = p;
op->repo = r;
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];
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 return 0;
}
p = list_entry(sm->iter, struct panel, hook);
r = &p->repo[p->act];
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 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];
for (i = 0; i < r->cnt; ++i) {
struct list_hook *bi;
struct box *b = r->bfs[i];
struct transform *tb = &b->screen_transform;
list_foreach(bi, &b->links) {
struct box *sub = list_entry(bi, struct box, node);
const struct transform *lt = &sub->local_transform;
struct transform *st = &sub->screen_transform;
struct rect *l = &sub->local;
struct rect *s = &sub->screen;
/* screen transformations */
st->x = tb->x + lt->x;
st->y = tb->y + lt->y;
st->sx = tb->sx * lt->sx;
st->sy = tb->sy * lt->sy;
/* screen coordinates */
s->x = floori(cast(float,(l->x + st->x))*st->sx);
s->y = floori(cast(float,(l->y + st->y))*st->sy);
s->w = ceili(cast(float,l->w) * st->sx);
s->h = ceili(cast(float,l->h) * st->sy);
}
}
}
/* 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 return 0;
} break;
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;
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) {
struct event *evt;
struct box *prev = last;
struct box *cur = ctx->hot;
cur->entered = !inbox(lx, ly, cur->screen.x, cur->screen.y, cur->screen.w, cur->screen.h);
prev->exited = !inbox(mx, my, prev->screen.x, prev->screen.y, prev->screen.w, prev->screen.h);
/* exited */
evt = event_begin(op, EVT_EXITED, prev);
evt->with.entered.cur = ctx->hot;
evt->with.entered.last = last;
while ((prev = prev->parent)) {
int was = inbox(lx, ly, prev->screen.x, prev->screen.y, prev->screen.w, prev->screen.h);
int isnt = !inbox(mx, my, prev->screen.x, prev->screen.y, prev->screen.w, prev->screen.h);
prev->exited = was && isnt;
event_add_box(evt, prev);
} event_end(evt);
/* entered */
evt = event_begin(op, EVT_ENTERED, cur);
evt->with.exited.cur = ctx->hot;
evt->with.exited.last = last;
while ((cur = cur->parent)) {
int wasnt = !inbox(lx, ly, cur->screen.x, cur->screen.y, cur->screen.w, cur->screen.h);
int is = inbox(mx, my, cur->screen.x, cur->screen.y, cur->screen.w, cur->screen.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;
struct event *evt;
struct box *act;
/* moved */
if (a->movable_x || a->movable_y) {
evt = event_begin(op, EVT_EXITED, act = a);
if (a->movable_x)
a->local.x += evt->with.moved.x = in->mouse.dx;
if (a->movable_y)
a->local.y += evt->with.moved.y = in->mouse.dy;
a->moved = 1;
while ((act = act->parent))
event_add_box(evt, act);
event_end(evt);
sm->unbalanced = 1;
}
/* dragged */
evt = event_begin(op, EVT_DRAGGED, act = a);
evt->with.dragged.x = in->mouse.dx;
evt->with.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 *h = ctx->hot;
struct event *evt = 0;
struct box *act = 0;
if (!btn->transitions) continue;
ctx->active = (btn->down && btn->transitions) ? ctx->hot: ctx->active;
ctx->origin = (btn->down && btn->transitions) ? ctx->hot: ctx->tree;
if (btn->down && btn->transitions)
reorder(a);
/* pressed */
h->pressed = btn->down && btn->transitions;
if (h->pressed) {
evt = event_begin(op, EVT_PRESSED, act = a);
evt->with.pressed.x = in->mouse.x;
evt->with.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 = a);
evt->with.drag_begin.x = in->mouse.x;
evt->with.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->with.released.x = in->mouse.x;
evt->with.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 */
evt = event_begin(op, EVT_CLICKED, act = a);
evt->with.clicked.x = in->mouse.x;
evt->with.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 = a);
evt->with.drag_end.x = in->mouse.x;
evt->with.drag_end.y = in->mouse.y;
while ((act = act->parent))
event_add_box(evt, act);
event_end(evt);
a->drag_end = 1;
} btn->transitions = 0;
}
if (in->mouse.wheelx || in->mouse.wheely) {
/* scroll */
struct event *evt = 0;
struct box *a = ctx->active;
evt = event_begin(op, EVT_KEY, a);
evt->with.scroll.x = in->mouse.wheelx;
evt->with.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 */
struct event *evt = 0;
struct box *a = ctx->active;
if (!in->keys[i].transitions)
continue;
evt = event_begin(op, EVT_KEY, a);
evt->with.key.pressed = in->keys[i].down && in->keys[i].transitions;
evt->with.key.released = !in->keys[i].down && in->keys[i].transitions;
evt->with.key.shift = in->shift;
evt->with.key.super = in->super;
evt->with.key.ctrl = in->ctrl;
evt->with.key.alt = in->alt;
evt->with.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;
struct event *evt = event_begin(op, EVT_TEXT, a);
evt->with.text.buf = in->text;
evt->with.text.len = in->text_len;
evt->with.text.shift = in->shift;
evt->with.text.super = in->super;
evt->with.text.ctrl = in->ctrl;
evt->with.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_POSTPROCESS;
return op;
}
case STATE_PAINT: {
int depth = 0;
struct box **stk = 0;
struct temp_memory tmp;
struct list_hook *pi = 0;
struct operation *op;
op = operation_begin(OP_PAINT, 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);
sm->state = STATE_POSTPROCESS;
return op;
}
case STATE_POSTPROCESS: {
struct box *blk = ctx->blocking;
if (blk->links.prev == blk->links.next)
blk->interactive = 1;
sm->state = STATE_PREPROCESS;
return 0;
}} return 0;
#undef call
}
static void
process_end(struct operation *op)
{
switch (op->type) {
case OP_INPUT: {
struct context *ctx = op->ctx;
struct input *in = &ctx->input;
in->text_len = 0;
} break;
case OP_CLEANUP: {
struct context *ctx = op->ctx;
struct panel *p = op->panel;
struct repository *r = op->repo;
/* swap buffers */
arena_clear(&r->arena);
repository_init(r, &ctx->arena);
p->act = !p->act;
} break;}
operation_end(op);
}
static void
measure(struct box *b, int pad)
{
struct list_hook *i;
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);
}
}
static void
blueprint(struct operation *op, struct box *b)
{
unused(op);
if (b->type_id & WIDGET_INTERNAL_BEGIN) {
switch (b->type_id) {
case WIDGET_ROOT: {
switch (b->box_id) {
case WIDGET_OVERLAY:
case WIDGET_POPUP:
case WIDGET_CONTEXTUAL:
case WIDGET_UNBLOCKING:
case WIDGET_BLOCKING:
case WIDGET_UI: break;
case WIDGET_ROOT: {
struct context *ctx = op->ctx;
b->dw = ctx->input.width;
b->dh = ctx->input.height;
} break;}
} break;
case WIDGET_PANEL: {
measure(b,0);
} break;}
} else measure(b,0);
}
static void
compute(struct box *b, int pad)
{
struct list_hook *i;
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);
}
}
static void
layout_copy(struct box *b)
{
struct list_hook *i;
list_foreach(i, &b->links) {
struct box *n = 0;
n = list_entry(i, struct box, node);
n->local.w = b->local.w;
n->local.h = b->local.h;
}
}
static void
layout_default(struct box *b)
{
struct list_hook *i;
list_foreach(i, &b->links) {
struct box *n = list_entry(i, struct box, node);
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);
}
}
static void
layout(struct operation *op, struct box *b)
{
unused(op);
if ((b->type_id & WIDGET_INTERNAL_BEGIN)) {
switch (b->type_id) {
case WIDGET_PANEL:
layout_default(b); break;
case WIDGET_ROOT: {
switch (b->box_id) {
case WIDGET_ROOT: {
struct context *ctx = op->ctx;
b->local.w = ctx->input.width;
b->local.h = ctx->input.height;
layout_copy(b);
} break;
case WIDGET_OVERLAY:
case WIDGET_POPUP:
case WIDGET_CONTEXTUAL:
case WIDGET_UNBLOCKING:
case WIDGET_BLOCKING:
layout_copy(b); break;
case WIDGET_UI:
layout_default(b); break;
}
} break;}
return;
} else compute(b, 0);
}
/* ===========================================================================
*
*
* WIDGETS
*
*
* =========================================================================== */
enum widget_type {
WIDGET_LABEL,
WIDGET_CHECKBOX,
WIDGET_BUTTON,
WIDGET_SCROLL,
WIDGET_HBOX
};
/* ---------------------------------------------------------------------------
* LABEL
* --------------------------------------------------------------------------- */
struct label {
struct box *bounds;
int ubegin, ulen;
int selb, sele, len;
char buf[1];
};
static struct label*
label(struct panel_state *ctx, const char *txt, int len, uiid id)
{
static const int alignment = alignof(struct label);
static const int size = szof(struct label);
struct widget_builder *w;
struct label *lbl;
assert(ctx);
if (!ctx) return 0;
w = widget_begin(ctx, WIDGET_LABEL, id, size + max(len-1,0), alignment);
lbl = w->widget;
copy(lbl->buf, txt, len);
lbl->len = len;
lbl->bounds = widget_add_box(w, 0);
box_add_property(lbl->bounds, PROPERTY_INTERACTIVE);
widget_end(w);
return lbl;
}
/* ---------------------------------------------------------------------------
* CHECKBOX
* --------------------------------------------------------------------------- */
enum checkbox_component {
CHECKBOX_BOUNDS,
CHECKBOX_CURSOR
};
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;
unsigned activated:1;
unsigned deactivated:1;
};
static void
checkbox_poll(struct checkbox *chk, const struct box *b)
{
chk->exited = b->exited;
chk->entered = b->entered;
chk->pressed = b->pressed;
chk->clicked = b->clicked;
chk->released = b->released;
chk->activated = b->activated;
chk->deactivated = b->deactivated;
chk->toggled = b->clicked;
}
static struct checkbox*
checkbox_begin(struct panel_state *ctx, uiid id)
{
static const int alignment = alignof(struct label);
static const int size = szof(struct label);
static const int pad = 3;
struct checkbox *chk = 0;
assert(ctx);
if (!ctx) return 0;
{struct widget_builder *w;
w = widget_begin(ctx, WIDGET_CHECKBOX, id, size, alignment);
chk = w->widget;
if (w->widget_last) {
struct checkbox *prev = w->widget_last;
copy(chk, prev, szof(*chk));
checkbox_poll(chk, prev->bounds);
} else chk->padding = pad;
chk->bounds = widget_add_box_begin(w, CHECKBOX_BOUNDS);
box_add_property(chk->bounds, PROPERTY_INTERACTIVE);
chk->cursor = widget_add_box(w, CHECKBOX_CURSOR);
box_add_property(chk->cursor, PROPERTY_INTERACTIVE);
widget_add_box_end(w);
widget_end(w);}
push(ctx, chk->cursor);
return chk;
}
static void
checkbox_end(struct panel_state *ctx)
{
assert(ctx);
if (!ctx) return;
pop(ctx);
}
static struct checkbox*
checkbox(struct panel_state *ctx, uiid id)
{
struct checkbox *chk;
chk = checkbox_begin(ctx, id);
checkbox_end(ctx);
return chk;
}
/* ---------------------------------------------------------------------------
* BUTTON
* --------------------------------------------------------------------------- */
enum button_component {
BUTTON_BOUNDS,
BUTTON_CONTENT
};
struct button {
int padding;
struct box *bounds;
struct box *content;
unsigned hovered:1;
unsigned entered:1;
unsigned exited:1;
unsigned pressed:1;
unsigned released:1;
unsigned clicked:1;
unsigned activated:1;
unsigned deactivated:1;
};
static void
button_poll(struct button *btn, const struct box *b)
{
btn->exited = b->exited;
btn->entered = b->entered;
btn->pressed = b->pressed;
btn->clicked = b->clicked;
btn->released = b->released;
btn->activated = b->activated;
btn->deactivated = b->deactivated;
}
static struct button*
button_begin(struct panel_state *ctx, uiid id)
{
static const int alignment = alignof(struct button);
static const int size = szof(struct button);
static const int pad = 4;
struct button *btn;
assert(ctx);
if (!ctx) return 0;
{struct widget_builder *w;
w = widget_begin(ctx, WIDGET_BUTTON, id, size, alignment);
btn = w->widget;
if (w->widget_last) {
struct button *prev = w->widget_last;
copy(btn, prev, szof(*btn));
button_poll(btn, prev->bounds);
} else btn->padding = pad;
btn->bounds = widget_add_box_begin(w, BUTTON_BOUNDS);
box_add_property(btn->bounds, PROPERTY_INTERACTIVE);
btn->content = widget_add_box(w, BUTTON_CONTENT);
box_add_property(btn->content, PROPERTY_INTERACTIVE);
widget_add_box_end(w);
widget_end(w);}
push(ctx, btn->content);
return btn;
}
static const struct button*
button_query(struct context *ctx, uiid pid, uiid widget_id)
{
struct box *b = 0;
struct button *btn = 0;
if ((b = query(ctx, pid, WIDGET_BUTTON, widget_id)))
button_poll(b->widget, b);
return btn;
}
static void
button_end(struct panel_state *ctx)
{
assert(ctx);
if (!ctx) return;
pop(ctx);
}
/* ---------------------------------------------------------------------------
* HORIZONTAL BOX
* --------------------------------------------------------------------------- */
enum hbox_slot_type {
HBOX_SLOT_DYNAMIC,
HBOX_SLOT_STATIC,
HBOX_SLOT_VARIABLE,
HBOX_SLOT_FITTING
};
struct hbox_slot {
enum hbox_slot_type type;
int pixel;
struct box *bounds;
};
struct hbox {
unsigned char padding;
unsigned char spacing;
struct box *bounds;
int cnt;
struct hbox_slot slots[1];
};
static struct hbox*
hbox_begin(struct panel_state *ctx, uiid id, int cnt)
{
static const int alignment = alignof(struct hbox);
static const int size = szof(struct hbox);
static const int slot_size = szof(struct hbox_slot);
static const unsigned char spacing = 4;
static const unsigned char pad = 4;
struct hbox *h = 0;
int i;
assert(ctx);
assert(cnt > 0);
if (!ctx) return 0;
cnt = max(cnt, 1);
{struct widget_builder *w;
w = widget_begin(ctx, WIDGET_HBOX, id, size+max(cnt-1,0)*slot_size, alignment);
h = w->widget;
if (!w->widget_last)
h->spacing = spacing, h->padding = pad;
else copy(h, w->widget_last, size);
h->cnt = cnt;
h->bounds = widget_add_box_begin(w, 0);
box_add_property(h->bounds, PROPERTY_INTERACTIVE);
for (i = 0; i < cnt; ++i) {
struct hbox_slot *s = h->slots + i;
s->bounds = widget_add_box(w, cast(uiid,i+1));
box_add_property(s->bounds, PROPERTY_INTERACTIVE);
s->type = HBOX_SLOT_DYNAMIC;
s->pixel = 0;
} widget_add_box_end(w);
widget_end(w);}
push(ctx, h->bounds);
return h;
}
static void
hbox_slot_begin(struct panel_state *ctx,
enum hbox_slot_type type,
int pixel, int idx)
{
struct hbox *h = 0;
struct box *bounds = 0;
struct hbox_slot *slot = 0;
assert(ctx);
assert(idx >= 0);
if (!ctx || idx < 0)
return;
bounds = peek(ctx);
h = bounds->widget;
assert(idx < h->cnt);
slot = h->slots + idx;
slot->type = type;
slot->pixel = pixel;
push(ctx, slot->bounds);
}
static void
hbox_slot_dynamic_begin(struct panel_state *ctx, int idx)
{
hbox_slot_begin(ctx, HBOX_SLOT_DYNAMIC, 0, idx);
}
static void
hbox_slot_static_begin(struct panel_state *ctx, int pixel_width, int idx)
{
hbox_slot_begin(ctx, HBOX_SLOT_STATIC, pixel_width, idx);
}
static void
hbox_slot_variable_begin(struct panel_state *ctx,
int min_pixel_width, int idx)
{
hbox_slot_begin(ctx, HBOX_SLOT_VARIABLE, min_pixel_width, idx);
}
static void
hbox_slot_fitting_begin(struct panel_state *ctx, int idx)
{
hbox_slot_begin(ctx, HBOX_SLOT_FITTING, 0, idx);
}
static void
hbox_slot_end(struct panel_state *ctx)
{
assert(ctx);
if (!ctx) return;
pop(ctx);
}
static void
hbox_end(struct panel_state *ctx)
{
assert(ctx);
if (!ctx) return;
pop(ctx);
}
/* ===========================================================================
* APP
* =========================================================================== */
#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) ((unsigned long)(h16(s,0,i)^(h16(s,0,i)>>16)))
#define idx(s,i) hash(s,(unsigned long)i)
#define id(s) idx(s,0)
#define lit(s) s,len(s)
#define txt(s) s,((int)strlen(s))
enum shortcut {
KEY_COPY,
KEY_CUT,
KEY_PASTE,
KEY_SCROLL_START,
KEY_SCROLL_END,
KEY_SCROLL_DOWN,
KEY_SCROLL_UP,
KEY_COUNT
};
struct color {unsigned char r,g,b,a;};
struct canvas {SDL_Renderer *ren; int penx, peny;};
static 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;
}
static 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);
}
static 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);
}
static 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);
}
static 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;
}
static void
canvas_move_to(struct canvas *c, int x, int y)
{
c->penx = x;
c->peny = y;
}
static void
canvas_reset(struct canvas *c)
{
canvas_move_to(c, 0, 0);
}
int main(void)
{
SDL_Window *win;
struct canvas can;
struct context *ctx = create(DEFAULT_ALLOCATOR);
SDL_Init(SDL_INIT_VIDEO);
win = SDL_CreateWindow("GUI", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
800, 600, SDL_WINDOW_SHOWN);
can = canvas_create(win);
input_resize(ctx, 800, 600);
{int quit = 0;
while (!quit) {
/* ----------------------------- UI ------------------------------------*/
{struct panel_state *s = 0;
if ((s = begin(ctx, id("panel")))) {
struct hbox *h = 0;
if ((h = hbox_begin(s, id("hbox"), 3))) {
/* button */
{struct button *b = 0;
hbox_slot_static_begin(s, 120, 0);
if ((b = button_begin(s, id("#button")))) {
if (b->clicked) printf("button clicked!\n");
label(s, lit("label"), id("#label0"));
button_end(s);
} hbox_slot_end(s);}
/* checkbox */
{struct hbox *hb = 0;
hbox_slot_fitting_begin(s, 1);
if ((hb = hbox_begin(s, id("#hbox0"), 2))) {
struct checkbox *chk = 0;
hb->padding = 0, hb->spacing = 2;
hbox_slot_fitting_begin(s, 0);
chk = checkbox(s, id("#checkbox0"));
if (chk->toggled) printf("checkbox: %d\n", chk->checked);
hbox_slot_end(s);
hbox_slot_fitting_begin(s, 1);
label(s, lit("checkbox"), id("#label1"));
hbox_slot_end(s);
hbox_end(s);
} hbox_slot_end(s);}
/* checkbox-button */
{struct button *b = 0;
hbox_slot_static_begin(s, 150, 2);
if ((b = button_begin(s, id("#button2")))) {
struct hbox *hb = 0;
if ((hb = hbox_begin(s, id("#hbox1"), 2))) {
hb->padding = 0;
{struct checkbox *chk = 0;
hbox_slot_fitting_begin(s, 0);
chk = checkbox(s, id("#checkbox1"));
if (b->clicked && !chk->toggled)
chk->checked = !chk->checked;
hbox_slot_end(s);}
hbox_slot_fitting_begin(s, 1);
label(s, lit("Label"), id("#label2"));
hbox_slot_end(s);
hbox_end(s);
} button_end(s);
} hbox_slot_end(s);}
hbox_end(s);
} end(s);
}}
/* ---------------------------------------------------------------------*/
/* input */
{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;}
}}
/* process: input */
{struct operation *op = 0; int i;
while ((op = process_begin(ctx, PROCESS_INPUT))) {
switch (op->type) {
default: break;
case OP_BLUEPRINT: {
for (i = op->begin; i != op->end; i += op->inc) {
struct box *b = op->boxes[i];
switch (b->type_id) {
default: blueprint(op, b); break;
case WIDGET_LABEL: {
struct label *lbl = b->widget;
b->dw = lbl->len * 8 + max(lbl->len-1,0) + 2;
b->dh = 16 + 2;
} break;
case WIDGET_CHECKBOX: {
struct checkbox *chk = b->widget;
if (b->box_id == CHECKBOX_CURSOR)
b->dw = b->dh = 16 + 2;
else measure(b, chk->padding);
} break;
case WIDGET_BUTTON: {
struct button *btn = b->widget;
if (b->box_id == BUTTON_BOUNDS)
measure(b, btn->padding);
else if (b->box_id == BUTTON_CONTENT)
measure(b, 0);
} break;
case WIDGET_HBOX: {
int si = 0; struct hbox *h = b->widget;
if (b->box_id != 0) {measure(b, 0); break;}
for (si = 0; si < h->cnt; ++si) {
struct hbox_slot *s = h->slots + si;
struct box *sb = s->bounds;
switch (s->type) {
case HBOX_SLOT_DYNAMIC:
b->dw += sb->dw; break;
case HBOX_SLOT_STATIC:
b->dw += s->pixel; break;
case HBOX_SLOT_FITTING:
case HBOX_SLOT_VARIABLE:
b->dw += max(sb->dw, s->pixel); break;
} b->dh = max(sb->dh + 2*h->padding, b->dh);
} b->dw += h->spacing*(h->cnt-1) + h->padding*2;
} break;}
}
} break;
case OP_LAYOUTING: {
for (i = op->begin; i != op->end; i += op->inc) {
struct box *b = op->boxes[i];
switch (b->type_id) {
default: layout(op, b); break;
case WIDGET_CHECKBOX: {
struct checkbox *chk = b->widget;
if (b->box_id == BUTTON_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);
} break;
case WIDGET_BUTTON: {
struct button *btn = b->widget;
if (b->box_id == BUTTON_BOUNDS)
box_shrink(btn->content, b, btn->padding);
else if (b->box_id == BUTTON_CONTENT)
compute(b, 0);
} break;
case WIDGET_HBOX: {
int si; struct hbox *h = b->widget;
if (b->box_id != 0)
{compute(b, h->padding); break;}
{int enough_space, x = 0;
int var_cnt = 0, var_width = 0;
int min_var_cnt = 0, min_fix_w = 0;
int total_fix_w = 0, max_var_w = 0;
int space = max(b->local.w - (h->cnt-1) * h->spacing, 0);
space = max(space - h->padding, 0);
/* calculate metrics */
for (si = 0; si < h->cnt; ++si) {
struct hbox_slot *s = h->slots + si;
struct box *sb = s->bounds;
sb->local.y = b->local.y + h->padding;
sb->local.h = b->local.h;
switch (s->type) {
case HBOX_SLOT_DYNAMIC: {
min_var_cnt++;
var_cnt++;
} break;
case HBOX_SLOT_STATIC: {
total_fix_w += s->pixel;
min_fix_w += s->pixel;
} break;
case HBOX_SLOT_FITTING: {
s->pixel = max(sb->dh, sb->dw);
} /* fall through */
case HBOX_SLOT_VARIABLE: {
total_fix_w += s->pixel;
max_var_w = max(max_var_w, s->pixel);
var_cnt++;
} break; }
}
var_width = max(space-min_fix_w,0) / max(var_cnt,1);
enough_space = var_width >= max_var_w;
if (!enough_space)
var_width = max(space-total_fix_w,0) / max(min_var_cnt,1);
/* set position and width */
x = b->local.x + h->padding;
for (si = 0; si < h->cnt; ++si) {
struct hbox_slot *s = h->slots + si;
struct box *sb = s->bounds;
sb->local.x = x;
switch (s->type) {
case HBOX_SLOT_DYNAMIC:
sb->local.w = var_width; break;
case HBOX_SLOT_STATIC:
sb->local.w = s->pixel; break;
case HBOX_SLOT_FITTING:
sb->local.w = s->pixel; break;
case HBOX_SLOT_VARIABLE:
sb->local.w = (enough_space) ? var_width: s->pixel; break;
} x += sb->local.w + h->spacing;
}}
} break;}
}
} break;
case OP_INPUT: {
struct list_hook *it = 0;
list_foreach(it, &op->evts) {
struct event *evt = list_entry(it, struct event, hook);
for (i = 0; i < evt->cnt; i++) {
struct box *b = evt->boxes[i];
switch (b->type_id) {
case WIDGET_CHECKBOX: {
struct checkbox *chk = b->widget;
if (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;
} break;
case WIDGET_BUTTON: {
struct button *btn = b->widget;
b = btn->bounds;
if (b->entered && evt->type == EVT_ENTERED)
btn->hovered = 1;
if (b->exited && evt->type == EVT_EXITED)
btn->hovered = 0;
} break;}
}
}
} break;}
process_end(op);
}}
/* query */
{const struct button *b = 0;
if ((b = button_query(ctx, id("panel"), id("#button")))) {
if (b->entered) printf("Button: entered!\n");
if (b->exited) printf("Button: exited!\n");
if (b->pressed) printf("Button: pressed!\n");
if (b->released) printf("Button: released\n");
if (b->clicked) printf("Button: clicked!\n");
if (b->activated) printf("Button: activated!\n");
if (b->deactivated) printf("Button: deactivated!\n");
}}
/* 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];
switch (b->type_id) {
default: break;
case WIDGET_LABEL: {
int j = 0, x = 0;
struct label *lbl = b->widget;
const struct color c = rgb(0,0,0);
canvas_move_to(&can, b->screen.x+1, b->screen.y+1);
for (j = 0; j < lbl->len; ++j, x+=9)
stroke_rect(&can, x, 0, 8, 16, c);
canvas_reset(&can);
} break;
case WIDGET_CHECKBOX: {
struct checkbox *chk = b->widget;
canvas_move_to(&can, b->screen.x, b->screen.y);
if (b->box_id == CHECKBOX_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);
} break;
case WIDGET_BUTTON: {
if (b->box_id != BUTTON_BOUNDS) break;
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);
} break;}
} process_end(op);
}}
SDL_RenderPresent(can.ren);
SDL_Delay(33);
}}
destroy(ctx);
SDL_DestroyRenderer(can.ren);
SDL_DestroyWindow(win);
SDL_Quit();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment