Skip to content

Instantly share code, notes, and snippets.

@vurtun
Last active December 9, 2023 12:03
Show Gist options
  • Star 52 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save vurtun/c5b0374c27d2f5e9905bfbe7431d9dc0 to your computer and use it in GitHub Desktop.
Save vurtun/c5b0374c27d2f5e9905bfbe7431d9dc0 to your computer and use it in GitHub Desktop.
Quarks: Graphical user interface
#include "qk.h"
/* system includes */
#include <assert.h> /* assert */
#include <stdlib.h> /* calloc, free */
#include <string.h> /* memcpy, memset */
#include <limits.h> /* INT_MAX */
#include <stdio.h> /* fprintf, fputc */
#include <emmintrin.h> /* _mm_pause */
/* constants */
#define VERSION 1
#define UTF_SIZE 4
#define UTF_INVALID 0xFFFD
/* utf-8 */
static const unsigned char utf_byte[UTF_SIZE+1] = {0x80,0,0xC0,0xE0,0xF0};
static const unsigned char utf_mask[UTF_SIZE+1] = {0xC0,0x80,0xE0,0xF0,0xF8};
static const unsigned long utf_min[UTF_SIZE+1] = {0,0,0x80,0x800,0x10000};
static const unsigned long utf_max[UTF_SIZE+1] = {0x10FFFF,0x7F,0x7FF,0xFFFF,0x10FFFF};
/* memory */
#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};
/* repository member object alignment */
static const int uint_align = alignof(unsigned);
static const int uiid_align = alignof(uiid);
static const int int_align = alignof(int);
static const int ptr_align = alignof(void*);
static const int box_align = alignof(struct box);
static const int param_align = alignof(union param);
static const int repo_align = alignof(struct repository);
/* repository member object size */
static const int int_size = szof(int);
static const int ptr_size = szof(void*);
static const int uint_size = szof(unsigned);
static const int uiid_size = szof(uiid);
static const int box_size = szof(struct box);
static const int param_size = szof(union param);
static const int repo_size = szof(struct repository);
/* root tables */
static const struct element g_root_elements[] = {
/* type id, parent, wid, depth,flags */
{WIDGET_ROOT, 0, 0, WIDGET_ROOT, 0,0},
{WIDGET_OVERLAY, 1, 0, WIDGET_OVERLAY, 1,0},
{WIDGET_POPUP, 2, 1, WIDGET_POPUP, 2,0},
{WIDGET_CONTEXTUAL, 3, 2, WIDGET_CONTEXTUAL, 3,0},
{WIDGET_UNBLOCKING, 4, 3, WIDGET_UNBLOCKING, 4,0},
{WIDGET_BLOCKING, 5, 4, WIDGET_BLOCKING, 5,0},
{WIDGET_UI, 6, 5, WIDGET_UI, 6,0},
};
static union param g_root_params[1];
static const unsigned char g_root_data[1];
static const uiid g_root_table_keys[] = {0,1,2,3,4,5,6,0};
static const int g_root_table_vals[] = {0,1,2,3,4,5,6,0};
static const struct component g_root = {
VERSION, 0, 0, 7,
g_root_elements, cntof(g_root_elements),
g_root_table_vals, g_root_table_keys, 8,
g_root_data, 0, g_root_params, 0,
0, 0, 0, 0
};
/* opcodes */
#define OPCODES(OP)\
OP(BUF_BEGIN, 1, MIDFMT)\
OP(BUF_END, 1, MIDFMT)\
OP(ULNK, 2, MIDFMT" "IDFMT)\
OP(DLNK, 1, MIDFMT)\
OP(CONCT, 2, MIDFMT" %d")\
OP(WIDGET_BEGIN, 2, "%d %d")\
OP(WIDGET_END, 0, "") \
OP(BOX_PUSH, 2, IDFMT" "IDFMT)\
OP(BOX_POP, 0, "")\
OP(PROPERTY_SET, 1, "%u")\
OP(PROPERTY_CLR, 1, "%u")\
OP(PUSH_FLOAT, 1, "%f")\
OP(PUSH_INT, 1, "%d")\
OP(PUSH_UINT, 1, "%u")\
OP(PUSH_ID, 1, IDFMT)\
OP(PUSH_MID, 1, MIDFMT)\
OP(PUSH_STR, 1, "%s")\
OP(NEXT_BUF, 1, "%p")\
OP(EOF, 0, "")
enum opcodes {
#define OP(a,b,c) OP_ ## a,
OPCODES(OP)
#undef OP
OPCNT
};
static const struct opdef {
enum opcodes type;
int argc;
const char *name;
const char *fmt;
const char *str;
} opdefs[] = {
#define OP(a,b,c) {OP_ ## a, b, #a, c, #a " " c},
OPCODES(OP)
#undef OP
{OPCNT,0,0}
};
/* ---------------------------------------------------------------------------
* Platform
* --------------------------------------------------------------------------- */
intern unsigned
cas(volatile unsigned *dst, unsigned swap, unsigned cmp)
{
#if defined(_WIN32) && !(defined(__MINGW32__) || defined(__MINGW64__))
#pragma intrinsic(_InterlockedCompareExchange);
return _InterlockedCompareExchange((volatile long*)dst, swap, cmp);
#else
return __sync_val_compare_and_swap(dst, cmp, swap);
#endif
}
intern void
spinlock_begin(volatile unsigned *lock)
{
int i, mask = 1;
static const int max = 64;
while (cas(lock, 1, 0) != 0) {
for (i = mask; i; --i)
_mm_pause();
mask = mask < max ? mask << 1: max;
}
}
intern void
spinlock_end(volatile unsigned *slock)
{
_mm_sfence();
*slock = 0;
}
/* ---------------------------------------------------------------------------
* Math
* --------------------------------------------------------------------------- */
api int
npow2(int n)
{
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
return ++n;
}
api int
floori(float x)
{
x = cast(float,(cast(int,x) - ((x < 0.0f) ? 1 : 0)));
return cast(int,x);
}
api 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;
}
}
api 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;
}
api int
roundi(float x)
{
return cast(int, roundf(x));
}
api int
strn(const char *s)
{
const char *a = s;
const unsigned *w;
if (!s) return 0;
#define UONES (((unsigned)-1)/UCHAR_MAX)
#define UHIGHS (UONES * (UCHAR_MAX/2+1))
#define UHASZERO(x) ((x)-UONES & ~(x) & UHIGHS)
for (;ucast(s)&3; ++s) if (!*s) return (int)(s-a);
for (w = (const void*)s; !UHASZERO(*w); ++w);
for (s = (const void*)w; *s; ++s);
return (int)(s-a);
#undef UONES
#undef UHIGHS
#undef UHASZERO
}
/* ---------------------------------------------------------------------------
* Overflow
* --------------------------------------------------------------------------- */
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)
{
/* mula*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)
{
/* mula*a + mulb*b + c + d */
return size_mul2_add_valid(mula,a,mulb,b,c) && size_add_valid(mula*a+mulb*b+c,d);
}
/* ---------------------------------------------------------------------------
* UTF-8
* --------------------------------------------------------------------------- */
intern int
utf_validate(unsigned long *u, int i)
{
assert(u);
if (!u) return 0;
if (!between(*u, utf_min[i], utf_max[i]) ||
between(*u, 0xD800, 0xDFFF))
*u = UTF_INVALID;
for (i = 1; *u > utf_max[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(utf_mask); ++(*i)) {
if (((unsigned char)c & utf_mask[*i]) == utf_byte[*i])
return (unsigned char)(c & ~utf_mask[*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)((utf_byte[i]) | ((unsigned char)u & ~utf_mask[i]));
}
api int
utf_encode(char *s, int cap, unsigned long u)
{
int n, i;
n = utf_validate(&u, 0);
if (cap < n || !n || n > UTF_SIZE)
return 0;
for (i = n - 1; i != 0; --i) {
s[i] = utf_encode_byte(u, 0);
u >>= 6;
} s[0] = utf_encode_byte(u, n);
return n;
}
api int
utf_len(const char *s, int n)
{
int result = 0;
int rune_len = 0;
unsigned long rune = 0;
if (!s) return 0;
while ((rune_len = utf_decode(&rune, s, n))) {
n = max(0, n-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;
assert(s);
assert(rune);
assert(rune_len);
if (!s || !rune || !rune_len) 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;
}
/* ---------------------------------------------------------------------------
* List
* --------------------------------------------------------------------------- */
api 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;
}
api void
list_add_head(struct list_hook *list, struct list_hook *n)
{
list__add(n, list, list->next);
}
api 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;
}
api void
list_del(struct list_hook *entry)
{
list__del(entry->prev, entry->next);
entry->next = entry;
entry->prev = entry;
}
api void
list_move_head(struct list_hook *list, struct list_hook *entry)
{
list_del(entry);
list_add_head(list, entry);
}
api 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);
}
}
/* ---------------------------------------------------------------------------
* Block-Alloator
* --------------------------------------------------------------------------- */
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;
assert(a);
assert(a->mem);
assert(a->mem->alloc);
spinlock_begin(&a->lock);
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);
assert(blk);
/* 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);
spinlock_end(&a->lock);
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;
assert(a);
assert(a->mem);
assert(a->mem->alloc);
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;
}
/* ---------------------------------------------------------------------------
* Arena
* --------------------------------------------------------------------------- */
intern void
arena_grow(struct memory_arena *a, int minsz)
{
/* allocate new memory block */
int blksz = max(minsz, DEFAULT_MEMORY_BLOCK_SIZE);
struct memory_block *blk = block_alloc(a->mem, blksz);
assert(blk);
/* link memory block into list */
blk->prev = a->blk;
a->blk = blk;
a->blkcnt++;
}
api void*
arena_push(struct memory_arena *a, int cnt, int size, int align)
{
int valid = 0;
assert(a);
if (!a) return 0;
/* validate enough space */
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;
arena_grow(a, cnt*size + szof(struct memory_block) + align);
}
/* allocate memory from block */
align = max(align,1);
{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--;
}
intern 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;
assert(a);
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;
assert(a);
while (a->blk != tmp.blk)
arena_free_last_blk(a);
if (a->blk) a->blk->used = tmp.used;
a->tmpcnt--;
}
/* ---------------------------------------------------------------------------
* Hash-Table
* --------------------------------------------------------------------------- */
intern void
insert(struct table *t, uiid key, int val)
{
uiid n = cast(uiid, t->cnt);
uiid i = key & (n-1), b = i;
do {if (t->keys[i]) continue;
t->keys[i] = key;
t->vals[i] = val; return;
} while ((i = ((i+1) & (n-1))) != b);
}
intern int
lookup(struct table *t, uiid key)
{
uiid k, n = cast(uiid, t->cnt);
uiid i = key & (n-1), b = i;
do {if (!(k = t->keys[i])) return 0;
if (k == key) return t->vals[i];
} while ((i = ((i+1) & (n-1))) != b);
return 0;
}
/* ---------------------------------------------------------------------------
* Box
* --------------------------------------------------------------------------- */
api void
box_shrink(struct box *d, const struct box *s, int pad)
{
assert(d && s);
if (!d || !s) return;
d->x = s->x + pad;
d->y = s->y + pad;
d->w = max(0, s->w - 2*pad);
d->h = max(0, s->h - 2*pad);
}
api void
box_pad(struct box *d, const struct box *s, int padx, int pady)
{
assert(d && s);
if (!d || !s) return;
d->x = s->x + padx;
d->y = s->y + pady;
d->w = max(0, s->w - 2*padx);
d->h = max(0, s->h - 2*pady);
}
api int
box_intersect(const struct box *a, const struct box *b)
{
return intersect(a->x, a->y, a->w, a->h, b->x, b->y, b->w, b->h);
}
/* ---------------------------------------------------------------------------
* Buffer
* --------------------------------------------------------------------------- */
intern union param*
op_push(struct state *s, int n)
{
union param *op;
assert(s);
assert(n > 0);
assert(n < MAX_OPS-2);
{struct param_buf *ob = s->opbuf;
if ((s->op_idx + n) >= (MAX_OPS-2)) {
/* allocate new param buffer */
struct param_buf *b = 0;
b = arena_push(&s->arena, 1, szof(*ob), 0);
ob->ops[s->op_idx + 0].op = OP_NEXT_BUF;
ob->ops[s->op_idx + 1].p = b;
s->opbuf = ob = b;
s->op_idx = 0;
}
assert(s->op_idx + n < (MAX_OPS-2));
op = ob->ops + s->op_idx;
s->op_idx += n;}
return op;
}
intern const char*
str_store(struct state *s, const char *str, int n)
{
assert(s && str);
assert(s->buf);
assert(n < MAX_STR_BUF-1);
{struct str_buf *ob = s->buf;
if ((s->buf_off + n) > MAX_STR_BUF-1) {
/* allocate new data buffer */
struct str_buf *b = arena_push(&s->arena, 1, szof(*ob), 0);
s->buf_off = 0;
ob->next = b;
s->buf = ob = b;
} assert((s->buf_off + n) <= MAX_STR_BUF-1);
copy(ob->buf + s->buf_off, str, n);
/* store zero-terminated string */
{int off = s->buf_off;
ob->buf[s->buf_off + n] = 0;
s->total_buf_size += n + 1;
s->buf_off += n + 1;
return ob->buf + off;}}
}
intern void
cmd_add(struct cmd_buf *s, struct memory_arena *a, const union cmd *cmd)
{
assert(s);
assert(s->list);
assert(s->buf);
spinlock_begin(&s->lock);
{
struct cmd_blk *blk = s->buf;
if (s->idx >= MAX_OPS) {
/* allocate new cmd buffer block */
struct cmd_blk *b = 0;
b = arena_push(a, 1, szof(*blk), 0);
blk->next = b;
s->buf = blk = b;
s->idx = 0;
}
/* push cmd into buffer */
assert(s->idx < MAX_OPS);
blk->cmds[s->idx++] = *cmd;
}
spinlock_end(&s->lock);
}
/* ---------------------------------------------------------------------------
* IDs
* --------------------------------------------------------------------------- */
api void
pushid(struct state *s, unsigned id)
{
struct idrange *idr = 0;
assert(s);
assert(s->stkcnt < cntof(s->idstk));
if (!s || s->stkcnt >= cntof(s->idstk))
return;
idr = &s->idstk[s->stkcnt++];
idr->base = id, idr->cnt = 0;
s->lastid = (idr->base & 0xFFFFFFFFlu) << 32lu;
}
api void
setid(struct state *s, uiid id)
{
assert(s);
assert(s->stkcnt < cntof(s->idstk));
if (!s) return;
/* set one time only ID */
s->idstate = ID_GEN_ONE_TIME;
s->otid = id;
}
api uiid
genwid(struct state *s)
{
uiid id = 0;
assert(s);
if (!s) return 0;
/* generate widget ID */
{int idx = max(0, s->stkcnt-1);
struct idrange *idr = &s->idstk[idx];
id |= (idr->base & 0xFFFFFFFFlu) << 32lu;
id |= (++idr->cnt);
s->lastid = id;
return id;}
}
api uiid
genbid(struct state *s)
{
/* generate box ID */
switch (s->idstate) {
case ID_GEN_DEFAULT:
return genwid(s);
case ID_GEN_ONE_TIME: {
s->idstate = ID_GEN_DEFAULT;
return s->otid;
}}
}
api void
popid(struct state *s)
{
assert(s);
assert(s->stkcnt);
if (!s || !s->stkcnt) return;
s->stkcnt--;
}
/* ---------------------------------------------------------------------------
* Repository
* --------------------------------------------------------------------------- */
api struct box*
find(struct repository *repo, uiid id)
{
int idx = lookup(&repo->tbl, id);
if (!idx) return 0;
return repo->boxes + idx;
}
/* ---------------------------------------------------------------------------
* State
* --------------------------------------------------------------------------- */
intern struct state*
state_find(struct context *ctx, uiid id)
{
struct list_hook *i = 0;
assert(ctx);
if (!ctx) return 0;
list_foreach(i, &ctx->states) {
struct state *s = list_entry(i, struct state, hook);
if (s->id == id) return s;
} return 0;
}
api struct box*
polls(struct state *s, uiid id)
{
struct repository *repo = 0;
repo = s->repo;
if (!repo) return 0;
return find(repo, id);
}
/* ---------------------------------------------------------------------------
* Module
* --------------------------------------------------------------------------- */
intern struct module*
module_find(struct context *ctx, mid id)
{
struct list_hook *i = 0;
assert(ctx);
if (!ctx) return 0;
list_foreach(i, &ctx->mod) {
struct module *m = list_entry(i, struct module, hook);
if (m->id == id) return m;
} return 0;
}
api struct state*
module_begin(struct context *ctx, mid id,
enum relationship rel, mid parent, mid owner, uiid bid)
{
struct state *s = 0;
assert(ctx);
if (!ctx) return 0;
/* try to pick up previous state if possible */
spinlock_begin(&ctx->module_lock);
s = state_find(ctx, id);
spinlock_end(&ctx->module_lock);
if (s) {s->op_idx -= 2; return s;}
/* allocate temporary state */
spinlock_begin(&ctx->mem_lock);
s = arena_push_type(&ctx->arena, struct state);
spinlock_end(&ctx->mem_lock);
assert(s);
if (!s) return 0;
/* setup state */
s->id = id;
s->ctx = ctx;
s->cfg = &ctx->cfg;
s->arena.mem = &ctx->blkmem;
s->param_list = s->opbuf = arena_push(&s->arena, 1, szof(struct param_buf), 0);
s->buf_list = s->buf = arena_push(&s->arena, 1, szof(struct str_buf), 0);
s->mod = module_find(ctx, id);
if (s->mod) s->repo = s->mod->repo[s->mod->repoid];
/* append state into list */
list_init(&s->hook);
spinlock_begin(&ctx->module_lock);
list_add_tail(&ctx->states, &s->hook);
spinlock_end(&ctx->module_lock);
pushid(s, id);
/* serialize api call */
{union param *p = op_push(s, 8);
p[0].op = OP_BUF_BEGIN;
p[1].mid = id;
p[2].op = OP_ULNK;
p[3].mid = parent;
p[4].id = bid;
p[5].op = OP_CONCT;
p[6].mid = owner;
p[7].i = (int)rel;}
return s;
}
api void
module_end(struct state *s)
{
assert(s);
if (!s) return;
{union param *p = op_push(s,2);
p[0].op = OP_BUF_END;
p[1].id = s->id;}
popid(s);
assert(!s->wtop);
assert(!s->depth);
}
api int
module_destroy(struct context *ctx, mid id)
{
struct list_hook tmp;
struct module *m = 0;
struct list_hook *i = 0;
struct list_hook *n = 0;
struct repository *repo = 0;
assert(ctx);
assert(id);
if (!ctx || !id)
return 0;
/* find and unlink module */
m = module_find(ctx, id);
if (!m) return 0;
repo = m->repo[m->repoid];
if (repo) {
/* unlink root box */
struct box *root = m->root;
list_del(&root->node);
}
list_del(&m->hook);
list_move_tail(&tmp, &m->hook);
/* remove sub-tree */
list_foreach_s(i, n, &ctx->mod) {
struct list_hook *k = 0;
struct module *sub = list_entry(i, struct module, hook);
list_foreach(k, &tmp) {
struct module *gb = list_entry(k, struct module, hook);
if (sub->parent == gb) {
list_move_tail(&tmp, &sub->hook);
break;
}
}
} list_splice_tail(&ctx->garbage, &tmp);
return 1;
}
api struct state*
section_begin(struct state *s, mid id)
{
assert(s);
assert(id && "Section are not allowed to overwrite root");
if (!s || !id) return 0;
return module_begin(s->ctx, id, RELATIONSHIP_INDEPENDENT, s->id, s->id, s->lastid);
}
api void
section_end(struct state *s)
{
module_end(s);
}
api void
link(struct state *s, mid id, enum relationship rel)
{
assert(s);
if (!s) return;
{union param *p = op_push(s, 5);
p[0].op = OP_DLNK;
p[1].mid = id;
p[2].op = OP_CONCT;
p[3].mid = id;
p[4].i = (int)rel;}
}
api struct state*
begin(struct context *ctx, mid id)
{
assert(ctx);
if (!ctx) return 0;
return module_begin(ctx, id, RELATIONSHIP_INDEPENDENT,
0, 0, WIDGET_UI - WIDGET_LAYER_BEGIN);
}
api void
end(struct state *s)
{
assert(s);
if (!s) return;
module_end(s);
}
api void
slot(struct state *s, uiid id)
{
assert(s);
assert(s->wtop > 0);
if (!s || s->wtop <= 0) return;
widget_begin(s, WIDGET_SLOT);
{union param *p = op_push(s, 4);
p[0].op = OP_BOX_PUSH;
p[1].id = id;
p[2].id = s->wstk[s->wtop-1].id;
p[3].op = OP_BOX_POP;
widget_end(s);}
}
/* ---------------------------------------------------------------------------
* Popup
* --------------------------------------------------------------------------- */
api struct state*
popup_begin(struct state *s, mid id, enum popup_type type)
{
unsigned bid = 0;
switch (type) {
case POPUP_BLOCKING:
bid = WIDGET_POPUP-WIDGET_LAYER_BEGIN; break;
case POPUP_NON_BLOCKING:
bid = WIDGET_CONTEXTUAL-WIDGET_LAYER_BEGIN; break;}
return module_begin(s->ctx, id, RELATIONSHIP_INDEPENDENT, 0, s->id, bid);
}
api void
popup_end(struct state *s)
{
union param *p = op_push(s, 2);
p[0].op = OP_BUF_END;
p[1].id = s->id;
}
api struct box*
popup_find(struct context *ctx, mid id)
{
struct module *m = module_find(ctx, id);
if (!m || !m->root) return 0;
return m->root;
}
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->lnks) {
struct box *b = list_entry(i, struct box, node);
if (b == skip) continue;
if (!(b->flags & BOX_HIDDEN))
return 1;
} return active;
}
api void
popup_show(struct context *ctx, mid id, enum visibility vis)
{
struct box *pop = popup_find(ctx, id);
if (!pop) return;
if (vis == VISIBLE) {
pop->flags &= ~(unsigned)BOX_HIDDEN;
} else pop->flags |= BOX_HIDDEN;
ctx->blocking->flags |= BOX_IMMUTABLE;
}
/* ---------------------------------------------------------------------------
* Widget
* --------------------------------------------------------------------------- */
intern void
wstk_push(struct state *s, int type, int *argc)
{
struct widget *w = 0;
assert(s->wtop < cntof(s->wstk));
w = s->wstk + s->wtop++;
w->id = genwid(s);
w->argc = argc;
w->type = type;
}
intern struct widget*
wstk_peek(struct state *s)
{
int i = max(0, s->wtop - 1);
assert(s->wtop > 0);
return &s->wstk[i];
}
intern void
wstk_pop(struct state *s)
{
assert(s->wtop > 0);
s->wtop = max(s->wtop-1, 0);
}
api void
widget_begin(struct state *s, int type)
{
assert(s);
if (!s) return;
{union param *p = op_push(s,3);
p[0].op = OP_WIDGET_BEGIN;
p[1].type = type;
p[2].i = 0;
wstk_push(s, type, &p[2].i);}
}
api void
widget_end(struct state *s)
{
assert(s);
if (!s || s->wtop <= 0) return;
{union param *p = op_push(s,1);
p[0].op = OP_WIDGET_END;
wstk_pop(s);}
}
api uiid
widget_box_push(struct state *s)
{
assert(s);
if (!s) return 0;
{union param *p = op_push(s,3);
p[0].op = OP_BOX_PUSH;
p[1].id = genbid(s);
p[2].id = s->wstk[s->wtop-1].id;
s->depth++;
s->boxcnt++;
s->tree_depth = max(s->depth, s->tree_depth);
return p[1].id;}
}
api uiid
widget_box(struct state *s)
{
uiid id = widget_box_push(s);
widget_box_pop(s);
return id;
}
api void
widget_box_pop(struct state *s)
{
assert(s);
if (!s) return;
{union param *p = op_push(s,1);
p[0].op = OP_BOX_POP;
assert(s->depth);
s->depth = max(s->depth-1, 0);}
}
api void
widget_box_property_set(struct state *s, enum properties prop)
{
union param *p = op_push(s,2);
p[0].op = OP_PROPERTY_SET;
p[1].u = prop;
}
api void
widget_box_property_clear(struct state *s, enum properties prop)
{
union param *p = op_push(s,2);
p[0].op = OP_PROPERTY_CLR;
p[1].u = prop;
}
/* ---------------------------------------------------------------------------
* Parameter
* --------------------------------------------------------------------------- */
intern union param*
widget_push_param(struct state *s)
{
struct widget *w = wstk_peek(s);
*w->argc +=1;
s->argcnt++;
return op_push(s, 2);
}
api float*
widget_param_float(struct state *s, float f)
{
assert(s);
if (!s) return 0;
{union param *p = widget_push_param(s);
p[0].op = OP_PUSH_FLOAT;
p[1].f = f;
return &p[1].f;}
}
api int*
widget_param_int(struct state *s, int i)
{
assert(s);
if (!s) return 0;
{union param *p = widget_push_param(s);
p[0].op = OP_PUSH_INT;
p[1].i = i;
return &p[1].i;}
}
api unsigned*
widget_param_uint(struct state *s, unsigned u)
{
assert(s);
if (!s) return 0;
{union param *p = widget_push_param(s);
p[0].op = OP_PUSH_UINT;
p[1].u = u;
return &p[1].u;}
}
api uiid*
widget_param_id(struct state *s, uiid id)
{
assert(s);
if (!s) return 0;
{union param *p = widget_push_param(s);
p[0].op = OP_PUSH_ID;
p[1].id = id;
return &p[1].id;}
}
api mid*
widget_param_mid(struct state *s, mid id)
{
assert(s);
if (!s) return 0;
{union param *p = widget_push_param(s);
p[0].op = OP_PUSH_MID;
p[1].mid = id;
return &p[1].mid;}
}
api const char*
widget_param_str(struct state *s, const char *str, int len)
{
assert(s);
if (!s) return 0;
{union param *p = widget_push_param(s);
p[0].op = OP_PUSH_STR;
p[1].i = len + 1;
return str_store(s, str, len);}
}
api float*
widget_modifier_float(struct state *s, float *f)
{
assert(s && f);
assert(s->ctx);
assert(s->ctx->active);
assert(s->wtop > 0);
assert(s->ctx);
if (!s || !f) return 0;
{struct repository *repo = s->repo;
if (repo) {
/* try to find previous state and set if box is active */
const struct context *ctx = s->ctx;
const struct box *act = ctx->active;
struct widget w = s->wstk[s->wtop-1];
if (act->wid == w.id && act->type == w.type)
*f = act->params[*w.argc].f;
} return widget_param_float(s, *f);}
}
api int*
widget_modifier_int(struct state *s, int *i)
{
assert(s && i);
assert(s->ctx);
assert(s->ctx->active);
assert(s->wtop > 0);
assert(s->ctx);
if (!s || !i) return 0;
{struct repository *repo = s->repo;
if (repo) {
/* try to find previous state and set if box is active */
const struct context *ctx = s->ctx;
const struct box *act = ctx->active;
struct widget w = s->wstk[s->wtop-1];
if (act->wid == w.id && act->type == w.type)
*i = act->params[*w.argc].i;
} return widget_param_int(s, *i);}
}
api unsigned*
widget_modifier_uint(struct state *s, unsigned *u)
{
assert(s && u);
assert(s->ctx);
assert(s->ctx->active);
assert(s->wtop > 0);
assert(s->ctx);
if (!s || !u) return 0;
{struct repository *repo = s->repo;
if (repo) {
/* try to find previous state and set if box is active */
const struct context *ctx = s->ctx;
const struct box *act = ctx->active;
struct widget w = s->wstk[s->wtop-1];
if (act->wid == w.id && act->type == w.type)
*u = act->params[*w.argc].u;
} return widget_param_uint(s, *u);}
}
api float*
widget_state_float(struct state *s, float f)
{
const struct box *b;
struct widget w;
assert(s);
assert(s->ctx);
assert(s->wtop > 0);
if (!s || s->wtop < 1) return 0;
/* try to find and set previous state */
w = s->wstk[s->wtop-1];
b = polls(s, w.id + 1);
if (!b || b->type != w.type)
return widget_param_float(s, f);
return widget_param_float(s, b->params[*w.argc].f);
}
api int*
widget_state_int(struct state *s, int i)
{
const struct box *b = 0;
struct widget w;
assert(s);
assert(s->ctx);
assert(s->wtop > 0);
if (!s || s->wtop < 1) return 0;
/* try to find and set previous state */
w = s->wstk[s->wtop-1];
b = polls(s, w.id + 1);
if (!b || b->type != w.type)
return widget_param_int(s, i);
return widget_param_int(s, b->params[*w.argc].i);
}
api unsigned*
widget_state_uint(struct state *s, unsigned u)
{
const struct box *b;
struct widget w;
assert(s);
assert(s->ctx);
assert(s->wtop > 0);
if (!s || s->wtop < 1) return 0;
/* try to find and set previous state */
w = s->wstk[s->wtop-1];
b = polls(s, w.id + 1);
if (!b || b->type != w.type)
return widget_param_uint(s, u);
return widget_param_uint(s, b->params[*w.argc].u);
}
api uiid*
widget_state_id(struct state *s, uiid u)
{
const struct box *b;
struct widget w;
assert(s);
assert(s->ctx);
assert(s->wtop > 0);
if (!s || s->wtop < 1) return 0;
/* try to find and set previous state */
w = s->wstk[s->wtop-1];
b = polls(s, w.id + 1);
if (!b || b->type != w.type)
return widget_param_id(s, u);
return widget_param_id(s, b->params[*w.argc].id);
}
api union param*
widget_get_param(struct box *b, int idx)
{
assert(b);
return b->params + idx;
}
api float*
widget_get_float(struct box *b, int idx)
{
union param *p = 0;
assert(b);
p = widget_get_param(b, idx);
return &p->f;
}
api int*
widget_get_int(struct box *b, int idx)
{
union param *p = 0;
assert(b);
p = widget_get_param(b, idx);
return &p->i;
}
api unsigned*
widget_get_uint(struct box *b, int idx)
{
union param *p = 0;
assert(b);
p = widget_get_param(b, idx);
return &p->u;
}
api uiid*
widget_get_id(struct box *b, int idx)
{
union param *p = 0;
assert(b);
p = widget_get_param(b, idx);
return &p->id;
}
api mid*
widget_get_mid(struct box *b, int idx)
{
union param *p = 0;
assert(b);
p = widget_get_param(b, idx);
return &p->mid;
}
api const char*
widget_get_str(struct box *b, int idx)
{
union param *p = 0;
assert(b);
p = widget_get_param(b, idx);
return b->buf + p[0].i;
}
/* ---------------------------------------------------------------------------
* Process
* --------------------------------------------------------------------------- */
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];
if (b != root && b->type == WIDGET_TREE_ROOT)
continue;
list_foreach(i, &b->lnks)
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->lnks) {
struct box *s = list_entry(i,struct box,node);
if (s->flags & BOX_HIDDEN /*|| !box_intersect(b, s) */) continue;
stk[head++] = s;
}
} return tail;
}
intern void
proc_begin(union process *p, enum process_type type,
struct context *ctx, struct memory_arena *arena)
{
assert(p);
assert(ctx);
assert(arena);
zero(p, szof(*p));
p->type = type;
p->hdr.tmp = temp_memory_begin(arena);
p->hdr.arena = arena;
p->hdr.ctx = ctx;
}
intern void
proc_end(union process *p)
{
assert(p);
if (!p) return;
temp_memory_end(p->hdr.tmp);
}
api void
box_blueprint(struct box *b, int padx, int pady)
{
assert(b);
if (!b) return;
{struct list_hook *i = 0;
list_foreach(i, &b->lnks) {
const struct box *n = list_entry(i, struct box, node);
b->dw = max(b->dw, n->dw + 2*padx);
b->dh = max(b->dh, n->dh + 2*pady);
}}
}
api void
box_layout(struct box *b, int pad)
{
assert(b);
if (!b) return;
{struct list_hook *i = 0;
list_foreach(i, &b->lnks) {
struct box *n = list_entry(i, struct box, node);
n->x = b->x + pad;
n->y = b->y + pad;
n->w = max(b->w - 2*pad, 0);
n->h = max(b->h - 2*pad, 0);
}}
}
intern struct box*
at(struct box *b, int mx, int my)
{
struct list_hook *i = b->lnks.prev;
while (1) {
while (&b->lnks != i) {
struct box *sub = list_entry(i, struct box, node);
if (!(sub->flags & BOX_IMMUTABLE) && !(sub->flags & BOX_HIDDEN)) {
if (inbox(mx, my, sub->x, sub->y, sub->w, sub->h)) {
b = sub;
i = b->lnks.prev;
continue;
}
} i = i->prev;
}
if (b->flags & BOX_UNSELECTABLE){
i = b->node.prev;
b = b->parent;
} else break;
} return b;
}
intern void
event_add_box(union event *evt, struct box *box)
{
assert(evt);
assert(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(union process *p, enum event_type type, struct box *orig)
{
union event *evt = 0;
assert(p);
assert(orig);
/* allocate event and link into list */
evt = arena_push_type(p->hdr.arena, union event);
assert(evt);
list_init(&evt->hdr.hook);
list_add_tail(&p->input.evts, &evt->hdr.hook);
/* setup event */
evt->type = type;
evt->hdr.input = p->input.state;
evt->hdr.origin = orig;
evt->hdr.cap = orig->tree_depth + 1;
evt->hdr.boxes = arena_push_array(p->hdr.arena, evt->hdr.cap, struct box*);
event_add_box(evt, orig);
return evt;
}
intern void
event_end(union event *evt)
{
unused(evt);
}
intern struct list_hook*
it(struct list_hook *list, struct list_hook *iter)
{
/* list iteration */
if (list_empty(list)) return 0;
else 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)
{
/* list reverse iteration */
if (list_empty(list)) return 0;
else if (iter && iter->prev == list)
iter = 0;
else if (!iter)
iter = list->prev;
else iter = iter->prev;
return iter;
}
api union process*
process_begin(struct context *ctx, unsigned flags)
{
#define jmpto(ctx,s) do{(ctx)->state = (s); goto r;}while(0)
enum processing_states {
STATE_DISPATCH,
STATE_COMMIT, STATE_BLUEPRINT, STATE_LAYOUTING,
STATE_INPUT, STATE_PAINT,
STATE_CLEAR, STATE_GC, STATE_CLEANUP, STATE_DONE
};
int i = 0;
assert(ctx);
if (!ctx) return 0;
r:switch (ctx->state) {
case STATE_DISPATCH: {
if (flags & flag(PROC_CLEANUP))
jmpto(ctx, STATE_CLEANUP);
else if (flags & flag(PROC_COMMIT))
jmpto(ctx, STATE_COMMIT);
else if (flags & flag(PROC_CLEAR))
jmpto(ctx, STATE_GC);
else jmpto(ctx, STATE_DONE);
}
case STATE_COMMIT: {
struct list_hook *si = 0;
union process *p = &ctx->proc;
proc_begin(p, PROC_COMMIT, ctx, &ctx->arena);
list_foreach(si, &ctx->states)
p->commit.cnt++;
if (p->commit.cnt == 0) {
/* state transition table */
if (flags & flag(PROC_BLUEPRINT))
jmpto(ctx, STATE_BLUEPRINT);
else if (flags & flag(PROC_CLEAR))
jmpto(ctx, STATE_GC);
else if (flags & flag(PROC_CLEANUP))
jmpto(ctx, STATE_CLEANUP);
else jmpto(ctx, STATE_DONE);
}
/* allocate memory for compilation objects and linking commands buffer */
p->commit.objs = arena_push_array(p->hdr.arena, p->commit.cnt, struct object);
{struct cmd_blk *cmds = arena_push(p->hdr.arena, 1, szof(struct cmd_blk), 0);
p->commit.lnks.buf = p->commit.lnks.list = cmds;}
/* setup compilation objects */
list_foreach(si, &ctx->states) {
struct object *obj = p->commit.objs + i++;
obj->in = list_entry(si, struct state, hook);
obj->state = 0, obj->out = 0;
obj->cmds = &p->commit.lnks;
obj->mem = p->hdr.arena;
obj->ctx = ctx;
} return p;
}
case STATE_BLUEPRINT: {
struct module *m = 0;
struct repository *r = 0;
union process *p = &ctx->proc;
ctx->iter = itr(&ctx->mod, ctx->iter);
if (!ctx->iter) {
/* state transition table */
if (flags & flag(PROC_LAYOUT))
jmpto(ctx, STATE_LAYOUTING);
else jmpto(ctx, STATE_DONE);
} m = list_entry(ctx->iter, struct module, hook);
assert(m);
r = m->repo[m->repoid];
assert(r);
/* blueprint request of current module repository */
proc_begin(p, PROC_BLUEPRINT, ctx, &ctx->arena);
p->blueprint.repo = r;
p->blueprint.end = p->blueprint.inc = -1;
p->blueprint.begin = r->boxcnt-1;
p->blueprint.boxes = r->bfs;
return p;
}
case STATE_LAYOUTING: {
struct module *m = 0;
struct repository *r = 0;
union process *p = &ctx->proc;
ctx->iter = it(&ctx->mod, ctx->iter);
if (!ctx->iter) {
ctx->unbalanced = 0;
/* state transition table */
if (flags & flag(PROC_INPUT))
jmpto(ctx, STATE_INPUT);
else if (flags & flag(PROC_PAINT))
jmpto(ctx, STATE_PAINT);
else jmpto(ctx, STATE_DONE);
} m = list_entry(ctx->iter, struct module, hook);
assert(m);
r = m->repo[m->repoid];
assert(r);
/* layout request of current module repository */
proc_begin(p, PROC_LAYOUT, ctx, &ctx->arena);
p->layout.end = max(0,r->boxcnt);
p->layout.begin = 0, p->layout.inc = 1;
p->layout.boxes = r->bfs;
p->layout.repo = r;
return p;
}
case STATE_INPUT: {
union process *p = &ctx->proc;
struct input *in = &ctx->input;
if (in->resized) {
/* window resize */
struct box *root = ctx->tree;
int w = root->w, h = root->h;
root->w = in->width;
root->h = in->height;
in->resized = 0;
if (w != in->width || h != in->height)
jmpto(ctx, STATE_BLUEPRINT);
}
proc_begin(p, PROC_INPUT, ctx, &ctx->arena);
list_init(&p->input.evts);
p->input.state = 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) {
/* hot - entered, exited */
union event *evt;
struct box *prev = last;
struct box *cur = ctx->hot;
{const struct box *c = cur, *l = prev;
cur->entered = !inbox(lx, ly, c->x, c->y, c->w, c->h);
prev->exited = !inbox(mx, my, l->x, l->y, l->w, l->h);}
/* exited */
evt = event_begin(p, EVT_EXITED, prev);
evt->entered.cur = ctx->hot;
evt->entered.last = last;
while ((prev = prev->parent)) {
const struct box *l = prev;
int was = inbox(lx, ly, l->x, l->y, l->w, l->h);
int isnt = !inbox(mx, my, l->x, l->y, l->w, l->h);
prev->exited = was && isnt;
event_add_box(evt, prev);
} event_end(evt);
/* entered */
evt = event_begin(p, EVT_ENTERED, cur);
evt->exited.cur = ctx->hot;
evt->exited.last = last;
while ((cur = cur->parent)) {
const struct box *c = cur;
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->flags & BOX_MOVABLE_X) || (a->flags & BOX_MOVABLE_Y)) {
a->moved = 1;
if (a->flags & BOX_MOVABLE_X)
a->x += in->mouse.dx;
if (a->flags & BOX_MOVABLE_Y)
a->y += in->mouse.dy;
ctx->unbalanced = 1;
evt = event_begin(p, 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(p, 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 = (btn->down && btn->transitions) ? ctx->hot: ctx->tree;
/* pressed */
h->pressed = btn->down && btn->transitions;
if (h->pressed) {
evt = event_begin(p, 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(p, 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(p, 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(p, 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(p, 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(p, EVT_SCROLLED, 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(p, 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) {
/* shortcuts */
struct key *key = in->shortcuts + i;
union event *evt = 0;
struct box *a = ctx->active;
if (!key->transitions)
continue;
evt = event_begin(p, EVT_SHORTCUT, a);
evt->key.pressed = key->down && key->transitions;
evt->key.released = !key->down && key->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->shortcuts[i].transitions = 0;
}
if (in->text_len) {
/* text */
struct box *a = ctx->active;
union event *evt = event_begin(p, 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);
}
/* hovered */
{struct box *h = ctx->hot;
int mx = in->mouse.x, my = in->mouse.y;
do if (inbox(mx, my, h->x, h->y, h->w, h->h))
h->hovered = 1;
while ((h = h->parent));}
/* state transition table */
if (ctx->unbalanced && (flags & PROCESS_BLUEPRINT))
ctx->state = STATE_BLUEPRINT;
else if (flags & flag(PROC_PAINT)) {
if (list_empty(&p->input.evts)) {
proc_end(p);
jmpto(ctx, STATE_PAINT);
} ctx->state = STATE_PAINT;
} else if (flags & flag(PROC_CLEAR))
ctx->state = STATE_CLEAR;
else ctx->state = STATE_DONE;
return p;
}
case STATE_PAINT: {
int depth = 0;
struct box **stk = 0;
struct list_hook *pi = 0;
union process *p = &ctx->proc;
struct temp_memory tmp;
proc_begin(p, PROC_PAINT, ctx, &ctx->arena);
p->paint.boxes = 0;
p->paint.cnt = 0;
/* calculate tree depth and number of boxes */
p->paint.cnt = depth = 0;
list_foreach(pi, &ctx->mod) {
struct module *m = list_entry(pi, struct module, hook);
struct repository *r = m->repo[m->repoid];
depth = max(depth, r->tree_depth + r->depth);
p->paint.cnt += r->boxcnt;
}
/* generate list of boxes in DFS-order */
p->paint.boxes = arena_push_array(&ctx->arena, p->paint.cnt+1, struct box*);
tmp = temp_memory_begin(&ctx->arena);
stk = arena_push_array(&ctx->arena, depth + 1, struct box*);
p->paint.cnt = dfs(p->paint.boxes, stk, ctx->tree);
temp_memory_end(tmp);
/* state transition table */
if (flags & flag(PROC_CLEAR))
ctx->state = STATE_CLEAR;
else ctx->state = STATE_DONE;
return p;
}
case STATE_GC: {
struct module *m = 0;
union process *p = &ctx->proc;
/* free each modules old data */
do {ctx->iter = it(&ctx->mod, ctx->iter);
if (!ctx->iter) {
/* free not updated dependend modules */
struct list_hook *h = 0;
d:list_foreach(h, &ctx->mod) {
m = list_entry(h, struct module, hook);
if (m->rel != RELATIONSHIP_DEPENDENT) continue;
{struct module *pm = m->owner;
if (pm->seq == m->seq) continue;
module_destroy(ctx, m->id);
goto d;}
} jmpto(ctx, STATE_CLEAR);
} m = list_entry(ctx->iter, struct module, hook);
} while (!m->repo[!m->repoid]);
/* free old repository */
assert(m->repo[!m->repoid]);
if (m->repo[!m->repoid]->size) {
proc_begin(p, PROC_FREE_FRAME, ctx, &ctx->arena);
p->free.ptr = m->repo[!m->repoid];
m->repo[!m->repoid] = 0;
return p;
} else {
m->repo[!m->repoid] = 0;
jmpto(ctx, STATE_GC);
}
};
case STATE_CLEAR: {
/* free deleted module */
struct module *m = 0;
union process *p = &ctx->proc;
if (list_empty(&ctx->garbage)) {
/* start new frame */
list_init(&ctx->garbage);
arena_clear(&ctx->arena);
if (flags & flag(PROC_CLEAR_FULL))
free_blocks(&ctx->blkmem);
ctx->seq++;
jmpto(ctx, STATE_DONE);
}
/* free temporary frame repository memory */
ctx->iter = ctx->garbage.next;
m = list_entry(ctx->iter, struct module, hook);
if (m->repo[m->repoid] && m->repo[m->repoid]->size) {
proc_begin(p, PROC_FREE_FRAME, ctx, &ctx->arena);
p->free.ptr = m->repo[m->repoid];
m->repo[m->repoid] = 0;
return p;
} m->repo[m->repoid] = 0;
if (m->repo[!m->repoid] && m->repo[!m->repoid]->size) {
proc_begin(p, PROC_FREE_FRAME, ctx, &ctx->arena);
p->free.ptr = m->repo[!m->repoid];
m->repo[!m->repoid] = 0;
return p;
} m->repo[!m->repoid] = 0;
list_del(&m->hook);
/* free persistent module memory */
if (m->size) {
proc_begin(p, PROC_FREE_PERSISTENT, ctx, &ctx->arena);
p->free.ptr = m;
return p;
} else jmpto(ctx, STATE_CLEAR);
}
case STATE_CLEANUP: {
/* free all memory */
list_del(&ctx->root.hook);
list_splice_tail(&ctx->garbage, &ctx->mod);
jmpto(ctx, STATE_GC);
}
case STATE_DONE: {
ctx->state = STATE_DISPATCH;
return 0;
}} return 0;
#undef jmpto
}
intern void
op_begin(struct operation *op, enum operation_type type)
{
zero(op, szof(*op));
op->type = type;
}
intern void
op_end(struct operation *op)
{
unused(op);
}
api struct operation*
commit_begin(struct context *ctx, struct process_commit *p, int idx)
{
enum commit_state {
STATE_DISPATCH,
STATE_CALC_REQ_MEMORY,
STATE_ALLOC_PERSISTENT,
STATE_ALLOC_TEMPORARY,
STATE_COMPILE,
STATE_DONE
};
struct object *obj = &p->objs[idx];
while (1) {
switch (obj->state) {
case STATE_DISPATCH: {
struct state *s = obj->in;
struct operation *op = &obj->op;
if (p->cnt == 0) return 0;
if (s->mod == 0) {
/* allocate new module for state */
op_begin(op, OP_ALLOC_PERSISTENT);
op->alloc.size = szof(struct module);
obj->state = STATE_ALLOC_PERSISTENT;
return op;
}
/* already have module so calculate required repo memory */
s->mod->seq = ctx->seq;
obj->state = STATE_CALC_REQ_MEMORY;
} break;
case STATE_ALLOC_PERSISTENT: {
struct state *s = obj->in;
struct operation *op = &obj->op;
if (!op->alloc.ptr) return op;
assert(op->alloc.ptr);
/* setup new module */
{struct module *m = cast(struct module*, op->alloc.ptr);
assert(type_aligned(m, struct module));
zero(m, szof(*m));
list_init(&m->hook);
m->id = s->id;
m->seq = ctx->seq;
m->size = szof(struct module);
s->mod = m;
list_add_tail(&ctx->mod, &m->hook);
obj->state = STATE_CALC_REQ_MEMORY;}
} break;
case STATE_CALC_REQ_MEMORY: {
struct state *s = obj->in;
struct operation *op = &obj->op;
op_begin(op, OP_ALLOC_FRAME);
s->boxcnt++;
s->tblcnt = cast(int, cast(float, s->boxcnt) * 1.35f);
s->tblcnt = npow2(s->tblcnt);
/* calculate required repository memory */
op->alloc.size = s->total_buf_size;
op->alloc.size += box_align + param_align;
op->alloc.size += repo_size + repo_align;
op->alloc.size += int_align + uint_align + uiid_align;
op->alloc.size += ptr_size * (s->boxcnt + 1);
op->alloc.size += box_size * s->boxcnt;
op->alloc.size += uint_size * s->boxcnt;
op->alloc.size += uiid_size * s->tblcnt;
op->alloc.size += int_size * s->tblcnt;
op->alloc.size += param_size * s->argcnt;
obj->state = STATE_ALLOC_TEMPORARY;
} break;
case STATE_ALLOC_TEMPORARY: {
struct operation *op = &obj->op;
if (op->alloc.ptr) {
obj->out = op->alloc.ptr;
obj->out->size = op->alloc.size;
obj->state = STATE_COMPILE;
continue;
} return op;
}
case STATE_COMPILE: {
struct operation *op = &obj->op;
op_begin(op, OP_COMPILE);
op->obj = obj;
obj->state = STATE_DONE;
return op;
} case STATE_DONE: return 0;}
} return 0;
}
api int
compile(struct object *obj)
{
struct context *ctx = obj->ctx;
struct state *s = obj->in;
struct module *m = s->mod;
struct repository *old = m->repo[m->repoid];
struct repository *repo = obj->out;
zero(repo, szof(repo));
m->repoid = !m->repoid;
m->repo[m->repoid] = repo;
/* I.) Setup repository memory layout */
repo->boxcnt = s->boxcnt;
repo->boxes = (struct box*)align(repo + 1, box_align);
repo->bfs = (struct box**)align(repo->boxes + repo->boxcnt, ptr_align);
repo->tbl.cnt = s->tblcnt;
repo->tbl.keys = (uiid*)align(repo->bfs + repo->boxcnt + 1, uiid_align);
repo->tbl.vals = (int*)align(repo->tbl.keys + repo->tbl.cnt, int_align);
repo->params = (union param*)align(repo->tbl.vals + repo->tbl.cnt, param_align);
repo->buf = (char*)(repo->params + s->argcnt);
repo->depth = s->tree_depth + 1;
repo->tree_depth = s->tree_depth + 1;
repo->bufsiz = s->total_buf_size;
repo->boxcnt = 1;
/* setup sub-tree root box */
m->root = repo->boxes;
m->root->buf = repo->buf;
m->root->params = repo->params;
m->root->flags = 0;
m->root->type = WIDGET_TREE_ROOT;
list_init(&m->root->node);
list_init(&m->root->lnks);
/* II.) Setup repository data */
{struct gizmo {int type, params, argi, argc;} gstk[MAX_TREE_DEPTH];
struct box *boxstk[MAX_TREE_DEPTH];
int gtop = 0, depth = 1;
int buf_off = 0, buf_size = 0;
struct str_buf *buf = s->buf_list;
struct param_buf *ob = s->param_list;
union param *op = &ob->ops[s->op_begin];
boxstk[0] = repo->boxes;
while (1) {
switch (op[0].op) {
/* ---------------------------- Buffer -------------------------- */
case OP_BUF_BEGIN: {
assert(op[1].mid == s->id);
} break;
case OP_BUF_END:
goto eol0;
case OP_NEXT_BUF:
ob = (struct param_buf*)op[1].p;
op = ob->ops;
continue;
/* ---------------------------- Link -------------------------- */
case OP_ULNK: {
union cmd cmd;
cmd.type = CMD_LNK;
cmd.lnk.parent_mid = op[1].mid;
cmd.lnk.parent_id = op[2].id;
cmd.lnk.child_mid = m->id;
cmd.lnk.child_id = 0;
cmd_add(obj->cmds, obj->mem, &cmd);
} break;
case OP_DLNK: {
union cmd cmd;
cmd.type = CMD_LNK;
cmd.lnk.parent_mid = m->id;
cmd.lnk.parent_id = boxstk[depth-1]->id;
cmd.lnk.child_mid = op[1].mid;
cmd.lnk.child_id = op[2].id;
cmd_add(obj->cmds, obj->mem, &cmd);
} break;
case OP_CONCT: {
union cmd cmd;
cmd.type = CMD_CONCT;
cmd.con.parent = op[1].mid;
cmd.con.child = m->id;
cmd.con.rel = op[2].i;
cmd_add(obj->cmds, obj->mem, &cmd);
} break;
/* --------------------------- Widgets -------------------------- */
case OP_WIDGET_BEGIN: {
/* push new widet on stack */
struct gizmo *g = &gstk[gtop++];
assert(gtop < MAX_TREE_DEPTH);
g->type = op[1].type;
g->params = repo->argcnt;
g->argi = 0, g->argc = op[2].i;
repo->argcnt += g->argc;
} break;
case OP_WIDGET_END:
assert(gtop > 0); gtop--; break;
/* -------------------------- Parameter ------------------------- */
case OP_PUSH_STR:
case OP_PUSH_FLOAT:
case OP_PUSH_INT:
case OP_PUSH_UINT:
case OP_PUSH_ID:
case OP_PUSH_MID: {
struct gizmo *g = &gstk[max(0,gtop-1)];
assert(gtop > 0);
assert(g->argi < g->argc);
if (g->argi >= g->argc) break;
{int idx = g->params + g->argi++;
switch (op[0].op) {
case OP_PUSH_STR: {
int len = op[1].i;
repo->params[idx].i = buf_off;
assert(buf_off < repo->bufsiz);
if (buf_off + len > MAX_STR_BUF) {
assert(buf->next);
buf = buf->next;
buf_off = 0;
} copy(repo->buf + buf_size, buf->buf + buf_off, len);
buf_size += len;
buf_off += len;
} break;
case OP_PUSH_FLOAT:
repo->params[idx].f = op[1].f; break;
case OP_PUSH_INT:
repo->params[idx].i = op[1].i; break;
case OP_PUSH_UINT:
repo->params[idx].u = op[1].u; break;
case OP_PUSH_ID:
repo->params[idx].id = op[1].id; break;
case OP_PUSH_MID:
repo->params[idx].mid = op[1].mid; break;
}}
} break;
/* ---------------------------- Boxes ----------------------------*/
case OP_BOX_POP:
assert(depth > 1); depth--; break;
case OP_BOX_PUSH: {
struct gizmo *g = 0;
uiid id = op[1].id;
int idx = repo->boxcnt++;
struct box *pb = boxstk[depth-1];
insert(&repo->tbl, id, idx);
assert(gtop > 0);
g = &gstk[gtop-1];
/* setup box */
{struct box *b = repo->boxes + idx;
b->id = id;
b->flags = 0;
b->parent = pb;
b->type = g->type;
b->wid = op[2].id;
b->buf = repo->buf;
b->params = repo->params + g->params;
b->depth = cast(unsigned short, depth);
/* link box into parent */
list_init(&b->node);
list_init(&b->lnks);
/* update tracked hot boxes */
list_add_tail(&pb->lnks, &b->node);
if (b->id == ctx->active->id)
ctx->active = b;
if (b->id == ctx->origin->id)
ctx->origin = b;
if (b->id == ctx->hot->id)
ctx->hot = b;
/* push box into stack */
assert(depth < MAX_TREE_DEPTH);
boxstk[depth++] = b;}
} break;
/* -------------------------- Properties -------------------------*/
case OP_PROPERTY_SET: {
assert(depth > 0);
{struct box *b = boxstk[depth-1];
b->flags |= op[1].u;}
} break;
case OP_PROPERTY_CLR: {
assert(depth > 0);
{struct box *b = boxstk[depth-1];
b->flags &= ~op[1].u;}
} break;}
op += opdefs[op[0].op].argc + 1;
} eol0:;}
repo->bfs = bfs(repo->bfs, repo->boxes);
ctx->unbalanced = 1;
if (old) list_del(&old->boxes[0].node);
return 0;
}
api void
commit_end(struct operation *op)
{
op_end(op);
}
api void
commit(union process *p)
{
int i = 0;
struct process_commit *c = &p->commit;
assert(p->type == PROC_COMMIT);
for (i = 0; i < c->cnt; ++i) {
struct operation *op;
while ((op = commit_begin(p->hdr.ctx, c, i))) {
switch (op->type) {
case OP_ALLOC_PERSISTENT:
case OP_ALLOC_FRAME:
op->alloc.ptr = calloc(1, (size_t)op->alloc.size); break;
case OP_COMPILE: compile(op->obj); break;}
commit_end(op);
}
}
}
api void
blueprint(union process *op, struct box *b)
{
assert(b);
assert(op);
assert(op->hdr.ctx);
if (!b || !op) return;
{struct context *ctx = op->hdr.ctx;
if (b->type & WIDGET_INTERNAL_BEGIN) {
switch (b->type) {
case WIDGET_ROOT: {
b->dw = ctx->input.width;
b->dh = ctx->input.height;
} break;
case WIDGET_TREE_ROOT:
case WIDGET_SLOT: {
box_blueprint(b,0,0);
} break;}
} else box_blueprint(b,0,0);}
}
intern void
layout_default(struct box *b)
{
struct list_hook *i = 0;
list_foreach(i, &b->lnks) {
struct box *n = 0;
n = list_entry(i, struct box, node);
n->x = b->x, n->y = b->y;
n->w = b->w, n->h = b->h;
}
}
api void
layout(union process *op, struct box *b)
{
assert(b);
assert(op);
assert(op->hdr.ctx);
if (!b || !op) return;
{struct context *ctx = op->hdr.ctx;
if (!(b->type & WIDGET_INTERNAL_BEGIN))
{box_layout(b, 0); return;}
switch (b->type) {
case WIDGET_TREE_ROOT:
case WIDGET_SLOT:
case WIDGET_OVERLAY:
case WIDGET_UNBLOCKING:
case WIDGET_BLOCKING:
case WIDGET_UI:
layout_default(b); break;
case WIDGET_ROOT:
b->w = ctx->input.width;
b->h = ctx->input.height;
layout_default(b); break;
case WIDGET_POPUP:
case WIDGET_CONTEXTUAL: {
struct list_hook *i = 0;
list_foreach(i, &b->lnks) {
struct box *n = list_entry(i, struct box, node);
if (n != ctx->contextual && n != ctx->unblocking) {
n->w = min(n->dw, (b->x + b->w)-n->x);
n->h = min(n->dh, (b->y + b->h)-n->y);
} else n->w = b->w, n->h = b->h;
}
} break;}}
}
api void
input(union process *op, union event *evt, struct box *b)
{
assert(b);
assert(op);
assert(evt);
assert(op->hdr.ctx);
if (!b || !evt || !op) return;
if (!(b->type & WIDGET_INTERNAL_BEGIN)) return;
switch (b->type) {
case WIDGET_UNBLOCKING: {
struct context *ctx = 0;
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->hdr.ctx;
/* hide all contextual menus */
contextual = ctx->contextual;
assert(contextual);
list_foreach(i, &contextual->lnks) {
struct box *c = list_entry(i,struct box,node);
if (c == b) continue;
c->flags |= BOX_HIDDEN;
}
} break;}
}
api void
process_end(union process *p)
{
assert(p);
assert(p->hdr.ctx);
if (!p) return;
switch (p->type) {
case PROC_COMMIT: {
int i = 0;
struct list_hook *it = 0;
struct context *ctx = p->hdr.ctx;
/* linking */
{const struct cmd_buf *buf = &p->commit.lnks;
const struct cmd_blk *blk = buf->list;
do {int n = blk->next ? MAX_CMD_BUF: buf->idx;
for (i = 0; i < n; ++i) {
const union cmd *cmd = blk->cmds + i;
switch (cmd->type) {
default: assert(0); break;
case CMD_LNK: {
/* link two modules together by specified boxes */
const struct cmd_lnk *lnk = &cmd->lnk;
struct module *pm = module_find(ctx, lnk->parent_mid);
struct module *m = module_find(ctx, lnk->child_mid);
assert(p && m);
assert(pm != m);
if (!p || !m || pm == m)
continue;
/* extract compiled repository of each module */
{struct repository *prepo = 0;
struct repository *repo = 0;
prepo = pm->repo[pm->repoid];
repo = m->repo[m->repoid];
assert(prepo && repo);
/* extract boxes to link as parent-child relationship */
{int pidx = lookup(&prepo->tbl, lnk->parent_id);
int idx = lookup(&repo->tbl, lnk->child_id);
struct box *pb = prepo->boxes + pidx;
struct box *b = repo->boxes + idx;
repo->tree_depth = prepo->tree_depth + pb->depth + 1;
/* link child into parent box link list */
list_del(&b->node);
list_add_tail(&pb->lnks, &b->node);
b->parent = pb;
/* relink modules so parent comes before child */
list_del(&m->hook);
list_add_tail(&ctx->mod, &m->hook);}}
} break;
case CMD_CONCT: {
/* connect two modules life-time together */
const struct cmd_con *con = &cmd->con;
struct module *pm = module_find(ctx, con->parent);
struct module *m = module_find(ctx, con->child);
assert(m && pm);
if (!pm || !m) continue;
m->owner = pm;
m->rel = (enum relationship)con->rel;
} break;}
}
} while ((blk = blk->next) != 0);}
/* reset input state and setup all tree nodes */
list_foreach(it, &ctx->mod) {
struct module *m = list_entry(it, struct module, hook);
struct repository *r = m->repo[m->repoid];
for (i = 0; i < r->boxcnt; ++i) {
/* reset box input state */
struct box *b = r->boxes + i;
b->drag_end = b->moved = 0;
b->pressed = b->released = 0;
b->clicked = b->scrolled = 0;
b->drag_begin = b->dragged = 0;
b->hovered = b->entered = b->exited = 0;
/* calculate box tree depth */
b->tree_depth = cast(unsigned short, b->depth + r->tree_depth);
}
}
/* free states */
{struct list_hook *si = 0;
list_foreach(si, &ctx->states) {
struct state *s = list_entry(si, struct state, hook);
arena_clear(&s->arena);
} list_init(&ctx->states);}
} break;
case PROC_INPUT: {
/* handle unblocking after popup have been closed */
struct context *ctx = p->hdr.ctx;
struct input *in = &ctx->input;
const int blk = popup_is_active(ctx, POPUP_BLOCKING);
const int nblk = popup_is_active(ctx, POPUP_NON_BLOCKING);
if (!blk && !nblk)
ctx->blocking->flags &= ~(unsigned)BOX_IMMUTABLE;
else ctx->blocking->flags |= BOX_IMMUTABLE;
in->text_len = 0;
} break;}
proc_end(p);
}
/* ---------------------------------------------------------------------------
* Context
* --------------------------------------------------------------------------- */
api struct box*
query(struct context *ctx, unsigned mid, uiid id)
{
struct module *m = 0;
struct repository *repo = 0;
m = module_find(ctx, mid);
if (!m) return 0;
repo = m->repo[m->repoid];
if (!repo) return 0;
return find(repo, id);
}
api void
load(struct context *ctx, const struct container *cons, int cnt)
{
int i = 0;
assert(ctx);
assert(cons);
assert(cnt >= 0);
if (!cons || !ctx || cnt < 0)
return;
ctx->unbalanced = 1;
for (i = 0; i < cnt; ++i) {
const struct container *con = cons + i;
const struct component *c = con->comp;
struct repository *repo = 0;
struct module *m = 0;
/* validate */
assert(c->bfs);
assert(c->boxes);
assert(c->tbl_keys);
assert(c->tbl_vals);
assert(VERSION == c->version);
if (!c || VERSION != c->version || !ctx) continue;
if (!c->bfs || !c->boxes || !c->tbl_keys || !c->tbl_vals)
continue;
/* setup module */
m = c->module;
zero(m, szof(*m));
m->id = con->id;
m->root = c->boxes;
m->parent = module_find(ctx, con->parent);
m->owner = module_find(ctx, con->owner);
m->rel = (enum relationship)con->rel;
m->repo[m->repoid] = c->repo;
list_init(&m->hook);
list_add_tail(&ctx->mod, &m->hook);
/* setup repository */
repo = c->repo;
zero(repo, szof(*repo));
repo->depth = c->depth;
repo->tree_depth = c->tree_depth;
repo->boxes = c->boxes;
repo->boxcnt = c->boxcnt;
repo->bfs = c->bfs;
repo->tbl.cnt = c->tblcnt;
repo->tbl.keys = cast(uiid*, c->tbl_keys);
repo->tbl.vals = cast(int*, c->tbl_vals);
repo->params = cast(union param*, c->params);
repo->buf = cast(char*, c->buf);
repo->argcnt = c->paramcnt;
repo->bufsiz = c->bufsiz;
repo->size = 0;
/* setup root node */
list_init(&m->root->node);
if (m->parent) {
struct module *pm = m->parent;
struct repository *pr = pm->repo[pm->repoid];
struct box *rb = find(pr, con->parent_box);
m->root->parent = rb;
repo->tree_depth = rb->tree_depth;
list_add_tail(&rb->lnks, &m->root->node);
}
/* setup tree */
for (i = 0; i < c->elmcnt; ++i) {
const struct element *e = c->elms + i;
struct box *b = repo->boxes + lookup(&repo->tbl, e->id);
struct box *p = repo->boxes + lookup(&repo->tbl, e->parent);
/* setup tree node */
list_init(&b->lnks);
if (b != p) {
b->parent = p;
list_init(&b->node);
list_add_tail(&p->lnks, &b->node);
}
/* setup box */
b->id = e->id;
b->wid = e->wid;
b->type = e->type;
b->flags = e->flags;
b->depth = e->depth;
b->tree_depth = e->tree_depth;
b->params = repo->params + e->params;
b->buf = repo->buf + e->state;
} repo->bfs = bfs(repo->bfs, repo->boxes);
}
}
api void
reset(struct context *ctx)
{
assert(ctx);
if (!ctx) return;
ctx->active = ctx->boxes;
ctx->origin = ctx->boxes;
ctx->hot = ctx->boxes;
}
api int
init(struct context *ctx, const struct allocator *a, const struct config *cfg)
{
assert(ctx);
assert(cfg);
if (!ctx || !cfg) return 0;
memset(ctx, 0, sizeof(*ctx));
a = (a) ? a: &default_allocator;
zero(ctx, szof(*ctx));
ctx->cfg = *cfg;
/* setup allocators */
ctx->mem = *a;
ctx->blkmem.mem = &ctx->mem;
ctx->arena.mem = &ctx->blkmem;
block_alloc_init(&ctx->blkmem);
/* setup lists */
list_init(&ctx->mod);
list_init(&ctx->states);
list_init(&ctx->garbage);
/* setup root module */
{struct component root = {0};
struct container cont = {0};
compiler_assert(cntof(g_root_elements) <= cntof(ctx->boxes));
memcpy(&root, &g_root, sizeof(root));
root.boxcnt = cntof(g_root_elements);
root.boxes = ctx->boxes;
root.module = &ctx->root;
root.repo = &ctx->repo;
root.bfs = ctx->bfs;
cont.comp = &root;
load(ctx, &cont, 1);}
/* setup direct root boxes pointers */
ctx->tree = ctx->boxes + (WIDGET_ROOT - WIDGET_LAYER_BEGIN);
ctx->overlay = ctx->boxes + (WIDGET_OVERLAY - WIDGET_LAYER_BEGIN);
ctx->popup = ctx->boxes + (WIDGET_POPUP - WIDGET_LAYER_BEGIN);
ctx->contextual = ctx->boxes + (WIDGET_CONTEXTUAL - WIDGET_LAYER_BEGIN);
ctx->unblocking = ctx->boxes + (WIDGET_UNBLOCKING - WIDGET_LAYER_BEGIN);
ctx->blocking = ctx->boxes + (WIDGET_BLOCKING - WIDGET_LAYER_BEGIN);
ctx->ui = ctx->boxes + (WIDGET_UI - WIDGET_LAYER_BEGIN);
reset(ctx);
return 1;
}
api struct context*
create(const struct allocator *a, const struct config *cfg)
{
struct context *ctx = 0;
a = (a) ? a: &default_allocator;
ctx = qalloc(a, szof(*ctx));
assert(ctx);
if (!ctx) return 0;
init(ctx, a, cfg);
return ctx;
}
api void
destroy(struct context *ctx)
{
assert(ctx);
if (!ctx) return;
qdealloc(&ctx->mem, ctx);
}
api void
clear(struct context *ctx)
{
union process *p = 0;
assert(ctx);
if (!ctx) return;
while ((p = process_begin(ctx, PROCESS_CLEAR))) {
switch (p->type) {
case PROC_FREE_FRAME:
case PROC_FREE_PERSISTENT:
free(p->free.ptr); break;}
process_end(p);
}
}
api void
cleanup(struct context *ctx)
{
union process *p = 0;
assert(ctx);
if (!ctx) return;
while ((p = process_begin(ctx, PROCESS_CLEANUP))) {
switch (p->type) {
case PROC_FREE_FRAME:
case PROC_FREE_PERSISTENT:
free(p->free.ptr); break;}
process_end(p);
} destroy(ctx);
}
api void
store_table(FILE *fp, struct context *ctx, const char *name, int indent)
{
/* generate box flags */
int i = 0;
static const struct property_def {
const char *name; int len;
} property_info[] = {
#define PROP(p) {"BOX_" #p, (cntof("BOX_" #p)-1)},
PROPERTY_MAP(PROP)
#undef PROP
};
/* I.) Dump each repository into C compile time tables */
struct list_hook *it = 0;
list_foreach(it, &ctx->mod) {
struct module *m = list_entry(it, struct module, hook);
const struct repository *r = m->repo[m->repoid];
if (!m->id) continue; /* skip root */
assert(fp);
fprintf(fp, "static const struct element g_%u_elements[] = {\n", m->id);
for (i = 0; i < r->boxcnt; ++i) {
const struct box *b = r->boxes + i;
const struct box *pb = b->parent;
uiid pid = (pb && i) ? pb->id: 0;
char buf[256]; int j, n = 0; buf[n++] = '0';
for (j = 0; j < PROPERTY_INDEX_MAX; ++j) {
if (b->flags & flag(j)) {
const struct property_def *pi = 0;
pi = property_info + j;
buf[n++] = '|';
copy(buf+n, pi->name, pi->len);
n += pi->len;
} buf[n] = 0;
} fprintf(fp, " {%d, " IDFMT "lu, " IDFMT "lu, " IDFMT "lu, %d, %d, %s, %u, %u},\n",
b->type, b->id, pid, b->wid, b->depth, b->tree_depth, buf,
(unsigned)(b->params - r->params), (unsigned)(b->buf - r->buf));
} fprintf(fp, "};\n");
fprintf(fp, "static const uiid g_%u_tbl_keys[%d] = {\n%*s", m->id, r->tbl.cnt, indent, "");
for (i = 0; i < r->tbl.cnt; ++i) {
if (i && !(i & 0x07)) fprintf(fp, "\n%*s", indent, "");
fprintf(fp, IDFMT"lu", r->tbl.keys[i]);
if (i < r->tbl.cnt-1) fputc(',', fp);
} fprintf(fp, "\n};\n");
fprintf(fp, "static const int g_%u_tbl_vals[%d] = {\n%*s",m->id, r->tbl.cnt, indent, "");
for (i = 0; i < r->tbl.cnt; ++i) {
if (i && !(i & 0x0F)) fprintf(fp, "\n%*s", indent, "");
fprintf(fp, "%d", r->tbl.vals[i]);
if (i + 1 < r->tbl.cnt) fputc(',', fp);
} fprintf(fp, "\n};\n");
fprintf(fp, "static union param g_%u_params[%d] = {\n%*s", m->id, r->argcnt, indent, "");
for (i = 0; i < r->argcnt; ++i) {
if (i && !(i & 0x7)) fprintf(fp, "\n%*s", indent, "");
fprintf(fp, "{"IDFMT"lu}", r->params[i].id);
if (i + 1 < r->argcnt) fputc(',', fp);
} fprintf(fp, "\n};\n");
fprintf(fp, "static const unsigned char g_%u_data[%d] = {\n%*s", m->id, r->bufsiz, indent, "");
for (i = 0; i < r->bufsiz; ++i) {
unsigned char c = cast(unsigned char,r->buf[i]);
if (i && !(i & 0x0F)) fprintf(fp, "\n%*s", indent, "");
fprintf(fp, "0x%02x", c);
if (i + 1 < r->bufsiz) fputc(',', fp);
} fprintf(fp, "\n};\n");
fprintf(fp, "static struct box *g_%u_bfs[%d];\n", m->id, r->boxcnt+1);
fprintf(fp, "static struct box g_%u_boxes[%d];\n", m->id, r->boxcnt);
fprintf(fp, "static struct module g_%u_module;\n", m->id);
fprintf(fp, "static struct repository g_%u_repo;\n", m->id);
fprintf(fp, "static const struct component g_%u_component = {\n", m->id);
fprintf(fp, "%*s%d," MIDFMT ", %d, %d,", indent, "", VERSION, m->id, r->depth, r->tree_depth);
fprintf(fp, "g_%u_elements, cntof(g_%u_elements),\n", m->id, m->id);
fprintf(fp, "%*sg_%u_tbl_vals, g_%u_tbl_keys,", indent, "", m->id, m->id);
fprintf(fp, "cntof(g_%u_tbl_keys),\n", m->id);
fprintf(fp, "%*sg_%u_data, cntof(g_%u_data),\n", indent, "", m->id, m->id);
fprintf(fp, "%*sg_%u_params, cntof(g_%u_params),\n", indent, "", m->id, m->id);
fprintf(fp, "%*s&g_%u_module, &g_%u_repo, ", indent, "", m->id, m->id);
fprintf(fp, "g_%u_boxes,\n%*sg_%u_bfs, ", m->id, indent, "", m->id);
fprintf(fp, "cntof(g_%u_boxes)\n", m->id);
fprintf(fp, "};\n");
}
/* II.) Dump each module into C compile time tables */
fprintf(fp, "static const struct container g_%s_containers[] = {\n", name);
list_foreach(it, &ctx->mod) {
struct module *m = list_entry(it, struct module, hook);
if (!m->id || !m->parent || !m->owner) continue; /* skip root */
assert(m->parent);
assert(m->owner);
fprintf(fp, "%*s{%u, %u, "IDFMT", %u, %d, &g_%u_component},\n",
indent, "", m->id, m->parent->id, m->root->parent->id,
m->owner->id, m->rel, m->id);
} fprintf(fp, "};\n");
}
api void
store_binary(FILE *fp, struct context *ctx)
{
int i = 0;
struct list_hook *si = 0;
assert(fp);
assert(ctx);
list_foreach(si, &ctx->states) {
struct state *s = list_entry(si, struct state, hook);
union param *op = &s->param_list->ops[s->op_begin];
while (1) {
const struct opdef *def = opdefs + op->type;
switch (op->type) {
case OP_NEXT_BUF:
op = (union param*)op[1].p; break;
default: {
for (i = 0; i < def->argc; ++i)
fwrite(&op[i], sizeof(op[i]), 1, fp);
if (op[0].op == OP_BUF_END && op[1].mid == s->id)
goto eob;
}} op += def->argc;
} eob: break;
}
}
api void
trace(FILE *fp, struct context *ctx)
{
struct list_hook *si = 0;
if (!fp) return;
list_foreach(si, &ctx->states) {
/* iterate all states */
struct state *s = list_entry(si, struct state, hook);
union param *op = &s->param_list->ops[s->op_begin];
fprintf(fp, "State: " MIDFMT "\n", s->id);
while (1) {
const struct opdef *def = opdefs + op->type;
switch (op->type) {
case OP_NEXT_BUF:
op = (union param*)op[1].p; break;
default: {
/* print out each argument from string format */
union param *param = op;
const char *str = def->str;
while (*str) {
if (*str != '%') {
fputc(*str, fp);
str++; continue;
} str++;
param++;
assert(param - op <= def->argc);
switch (*str++) {default: break;
case 'f': fprintf(fp, "%g", param[0].f); break;
case 'd': fprintf(fp, "%d", param[0].i); break;
case 'u': fprintf(fp, "%u", param[0].u); break;
case 'p': fprintf(fp, "%p", param[0].p); break;}
} fputc('\n', fp);
if (op[0].op == OP_BUF_END && op[1].mid == s->id)
goto eot;
}} op += def->argc + 1;
} eot:break;
}
}
/* ---------------------------------------------------------------------------
* Input
* --------------------------------------------------------------------------- */
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 == 0) ? 0: 1;
}
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 == 0) ? 0: 1;
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, const char *end)
{
int len = 0;
struct input *in = 0;
assert(ctx);
if (!ctx || !buf) return;
in = &ctx->input;
len = (end) ? cast(int, end-buf): (int)strn(buf);
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, &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, buf + len);
}
#ifndef QK_H
#define QK_H
#include <assert.h> /* assert */
#include <stdlib.h> /* calloc, free */
#include <string.h> /* memcpy, memset */
#include <inttypes.h> /* PRIu64 */
#include <limits.h> /* INT_MAX */
#include <stdio.h> /* fprintf, fputc */
/* macros */
#define api extern
#define intern static
#define unused(a) ((void)a)
#define cast(t,p) ((t)(p))
#define ucast(p) ((uintptr_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 isaligned(x,mask) (!((uintptr_t)(x) & (mask-1)))
#define type_aligned(x,t) isaligned(x, alignof(t))
#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 stringify(x) #x
#define stringifyi(x) stringifyi(x)
#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];}
#define uniqstr __FILE__ ":" stringifyi(__LINE__)
/* callbacks */
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);
struct allocator {
void *usr;
alloc_f alloc;
dealloc_f dealloc;
};
/* list */
struct list_hook {
struct list_hook *next;
struct list_hook *prev;
};
/* memory */
#define DEFAULT_ALLOCATOR 0
#define DEFAULT_MEMORY_BLOCK_SIZE (16*1024)
struct memory_block {
struct memory_block *prev;
struct list_hook hook;
int size, used;
unsigned char *base;
};
struct block_allocator {
const struct allocator *mem;
struct list_hook freelist;
struct list_hook blks;
volatile unsigned lock;
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_MIDDLE,
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];
};
/* id */
typedef uint64_t uiid;
typedef unsigned mid;
#define IDFMT "%"PRIu64
#define MIDFMT "%u"
/* parameter */
union param {
uiid id;
mid mid;
int op;
int type;
int i;
unsigned u;
unsigned flags;
float f;
void *p;
};
/* compile-time */
struct element {
int type;
uiid id;
uiid parent;
uiid wid;
unsigned short depth;
unsigned short tree_depth;
unsigned flags;
unsigned params;
unsigned state;
};
struct component {
unsigned version;
mid id;
int tree_depth, depth;
const struct element *elms;
const int elmcnt;
const int *tbl_vals;
const uiid *tbl_keys;
const int tblcnt;
const unsigned char *buf;
const int bufsiz;
const union param *params;
const int paramcnt;
struct module *module;
struct repository *repo;
struct box *boxes;
struct box **bfs;
int boxcnt;
};
struct container {
mid id;
mid parent;
uiid parent_box;
mid owner;
int rel;
const struct component *comp;
};
/* box */
#define PROPERTY_MAP(PROP)\
PROP(IMMUTABLE)\
PROP(UNSELECTABLE)\
PROP(MOVABLE_X)\
PROP(MOVABLE_Y)\
PROP(HIDDEN)
enum property_index {
#define PROP(p) BOX_ ## p ## _INDEX,
PROPERTY_MAP(PROP)
#undef PROP
PROPERTY_INDEX_MAX
};
enum properties {
#define PROP(p) BOX_ ## p = flag(BOX_ ## p ## _INDEX),
PROPERTY_MAP(PROP)
#undef PROP
BOX_MOVABLE = BOX_MOVABLE_X|BOX_MOVABLE_Y,
PROPERTY_ALL
};
struct rect {int x,y,w,h;};
struct box {
uiid id, wid;
int type;
unsigned flags;
union param *params;
char *buf;
/* bounds */
int x,y,w,h;
int dw,dh;
/* state */
unsigned hovered: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;
/* node */
unsigned short depth;
unsigned short tree_depth;
struct box *parent;
struct list_hook node;
struct list_hook lnks;
};
/* state */
#define MAX_IDSTACK 256
#define MAX_TREE_DEPTH 128
#define MAX_OPS ((int)((DEFAULT_MEMORY_BLOCK_SIZE - sizeof(struct memory_block)) / sizeof(union param)))
#define MAX_STR_BUF ((int)(DEFAULT_MEMORY_BLOCK_SIZE - (sizeof(struct memory_block) + sizeof(void*))))
#define MAX_CMD_BUF ((int)((DEFAULT_MEMORY_BLOCK_SIZE - (sizeof(struct memory_block) + sizeof(void*))) / sizeof(union cmd)))
struct repository;
struct idrange {
mid base;
mid cnt;
};
struct widget {
uiid id;
int type;
int *argc;
};
struct param_buf {
union param ops[MAX_OPS];
};
struct str_buf {
struct str_buf *next;
char buf[MAX_STR_BUF];
};
enum relationship {
RELATIONSHIP_INDEPENDENT,
RELATIONSHIP_DEPENDENT
};
enum id_gen_state {
ID_GEN_DEFAULT,
ID_GEN_ONE_TIME
};
struct state {
mid id;
struct list_hook hook;
struct memory_arena arena;
/* references */
struct module *mod;
struct context *ctx;
const struct config *cfg;
struct repository *repo;
/* stats */
int argcnt;
int tblcnt;
int tree_depth;
int boxcnt;
int bufsiz;
/* opcodes */
int op_begin, op_idx;
struct param_buf *param_list;
struct param_buf *opbuf;
/* strings */
int buf_off, total_buf_size;
struct str_buf *buf_list;
struct str_buf *buf;
/* ID generator */
uiid lastid, otid;
enum id_gen_state idstate;
struct idrange idstk[MAX_IDSTACK];
/* tree */
int depth;
int stkcnt;
struct widget wstk[MAX_TREE_DEPTH];
int wtop;
};
/* module */
struct table {
int cnt;
uiid *keys;
int *vals;
};
struct repository {
struct table tbl;
struct box *boxes;
struct box **bfs;
int boxcnt;
union param *params;
int argcnt;
char *buf;
int bufsiz;
int depth, tree_depth;
int size;
};
struct module {
struct list_hook hook;
struct module *parent;
struct module *owner;
enum relationship rel;
mid id;
unsigned seq;
int repoid;
struct box *root;
struct repository *repo[2];
int size;
};
/* 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 input *input;
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;
};
/* commands */
enum cmd_type {
CMD_LNK,
CMD_CONCT,
CMD_CNT
};
struct cmd_lnk {
enum cmd_type type;
mid parent_mid;
uiid parent_id;
mid child_mid;
uiid child_id;
};
struct cmd_con {
enum cmd_type type;
mid parent, child;
int rel;
};
union cmd {
enum cmd_type type;
struct cmd_lnk lnk;
struct cmd_con con;
};
struct cmd_blk {
struct cmd_blk *next;
union cmd cmds[MAX_CMD_BUF];
};
struct cmd_buf {
int idx;
struct cmd_blk *list;
struct cmd_blk *buf;
volatile unsigned lock;
};
/* operation */
struct object;
enum operation_type {
OP_ALLOC_PERSISTENT,
OP_ALLOC_FRAME,
OP_COMPILE
};
struct operation_alloc {
void *ptr;
int size;
};
struct operation {
enum operation_type type;
struct operation_alloc alloc;
struct object *obj;
};
struct object {
int state;
struct operation op;
struct context *ctx;
struct state *in;
struct repository *out;
struct cmd_buf *cmds;
struct memory_arena *mem;
};
/* process */
enum process_type {
PROC_COMMIT,
PROC_BLUEPRINT,
PROC_LAYOUT,
PROC_INPUT,
PROC_PAINT,
PROC_CLEAR,
PROC_CLEAR_FULL,
PROC_CLEANUP,
PROC_FREE_PERSISTENT,
PROC_FREE_FRAME,
PROC_CNT
};
enum processes {
PROCESS_COMMIT = flag(PROC_COMMIT),
PROCESS_BLUEPRINT = PROCESS_COMMIT|flag(PROC_BLUEPRINT),
PROCESS_LAYOUT = PROCESS_BLUEPRINT|flag(PROC_LAYOUT),
PROCESS_INPUT = PROCESS_LAYOUT|flag(PROC_INPUT),
PROCESS_PAINT = PROCESS_LAYOUT|flag(PROC_PAINT),
PROCESS_CLEAR = flag(PROC_CLEAR),
PROCESS_CLEAR_FULL = PROCESS_CLEAR|flag(PROC_CLEAR_FULL),
PROCESS_CLEANUP = PROCESS_CLEAR_FULL|flag(PROC_CLEANUP)
};
struct process_header {
enum process_type type;
struct context *ctx;
struct memory_arena *arena;
struct temp_memory tmp;
};
struct process_commit {
struct process_header hdr;
struct object *objs;
int cnt;
struct cmd_buf lnks;
};
struct process_layouting {
struct process_header hdr;
struct repository *repo;
int begin, end, inc;
struct box **boxes;
};
struct process_input {
struct process_header hdr;
struct input *state;
struct list_hook evts;
};
struct process_paint {
struct process_header hdr;
struct box **boxes;
int cnt;
};
struct process_free {
struct process_header hdr;
void *ptr;
};
union process {
/* base */
enum process_type type;
struct process_header hdr;
/* derived */
struct process_commit commit;
struct process_layouting layout;
struct process_layouting blueprint;
struct process_input input;
struct process_paint paint;
struct process_free free;
};
/* context */
enum visibility {HIDDEN, VISIBLE};
enum popup_type {
POPUP_BLOCKING,
POPUP_NON_BLOCKING,
POPUP_CNT
};
enum widget_internal {
WIDGET_INTERNAL_BEGIN = 0x100000,
/* layers */
WIDGET_LAYER_BEGIN,
WIDGET_ROOT = WIDGET_LAYER_BEGIN,
WIDGET_OVERLAY,
WIDGET_POPUP,
WIDGET_CONTEXTUAL,
WIDGET_UNBLOCKING,
WIDGET_BLOCKING,
WIDGET_UI,
WIDGET_LAYER_END = WIDGET_UI,
/* widgets */
WIDGET_TREE_ROOT,
WIDGET_LINK,
WIDGET_SLOT
};
struct config {
int font_default_id;
int font_default_height;
};
struct context {
int state;
unsigned seq;
struct config cfg;
struct input input;
union process proc;
unsigned unbalanced:1;
struct list_hook *iter;
/* memory */
struct allocator mem;
struct block_allocator blkmem;
struct memory_arena arena;
volatile unsigned mem_lock;
/* modules */
volatile unsigned module_lock;
struct list_hook states;
struct list_hook mod;
struct list_hook garbage;
/* tree */
struct module root;
struct box boxes[8];
struct box *bfs[8];
struct repository repo;
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;
};
/* context */
api struct context *create(const struct allocator *a, const struct config *cfg);
api int init(struct context *ctx, const struct allocator *a, const struct config *cfg);
api struct box *query(struct context *ctx, unsigned mid, uiid id);
api void reset(struct context *ctx);
api void destroy(struct context *ctx);
/* serialize */
api void load(struct context *ctx, const struct container *c, int cnt);
api void store_table(FILE *fp, struct context *ctx, const char *name, int indent);
api void store_binary(FILE *fp, struct context *ctx);
api void trace(FILE *fp, 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, const char *end);
api void input_button(struct context *ctx, enum mouse_button btn, int down);
/* process */
api union process* process_begin(struct context *ctx, unsigned flags);
api struct operation *commit_begin(struct context *ctx, struct process_commit*, int index);
api int compile(struct object *obj);
api void commit_end(struct operation *op);
api void commit(union process *p);
api void blueprint(union process *p, struct box *b);
api void layout(union process *p, struct box *b);
api void input(union process *p, union event *evt, struct box *b);
api void process_end(union process *p);
/* module */
api struct state* begin(struct context *ctx, mid id);
api void end(struct state *s);
api struct state* module_begin(struct context *ctx, mid id, enum relationship, mid parent, mid owner, uiid bid);
api void module_end(struct state *s);
api struct state *section_begin(struct state *s, mid id);
api void section_end(struct state *s);
api void link(struct state *s, mid id, enum relationship);
api void slot(struct state *s, uiid id);
api struct box *polls(struct state *s, uiid id);
/* popup */
api struct state *popup_begin(struct state *s, mid id, enum popup_type type);
api void popup_show(struct context *ctx, mid id, enum visibility vis);
api void popup_toggle(struct context *ctx, mid id);
api void popup_end(struct state *s);
api struct box *popup_find(struct context *ctx, mid id);
/* widget */
api void widget_begin(struct state *s, int type);
api uiid widget_box(struct state *s);
api uiid widget_box_push(struct state *s);
api void widget_box_property_set(struct state *s, enum properties);
api void widget_box_property_clear(struct state *s, enum properties);
api void widget_box_pop(struct state *s);
api void widget_end(struct state *s);
/* parameter */
api float *widget_param_float(struct state *s, float f);
api int *widget_param_int(struct state *s, int i);
api unsigned *widget_param_uint(struct state *s, unsigned u);
api uiid *widget_param_id(struct state *s, uiid id);
api mid *widget_param_mid(struct state *s, mid id);
api const char* widget_param_str(struct state *s, const char *str, int len);
api float* widget_modifier_float(struct state *s, float *f);
api int* widget_modifier_int(struct state *s, int *i);
api unsigned* widget_modifier_uint(struct state *s, unsigned *u);
api float* widget_state_float(struct state *s, float f);
api int* widget_state_int(struct state *s, int i);
api unsigned* widget_state_uint(struct state *s, unsigned u);
api uiid* widget_state_id(struct state *s, uiid u);
api float *widget_get_float(struct box *b, int idx);
api int *widget_get_int(struct box *b, int idx);
api unsigned *widget_get_uint(struct box *b, int indx);
api uiid *widget_get_id(struct box *b, int idx);
api mid *widget_get_mid(struct box *b, int idx);
api const char *widget_get_str(struct box *b, int idx);
/* box */
api void box_shrink(struct box *d, const struct box *s, int pad);
api void box_blueprint(struct box *b, int padx, int pady);
api void box_layout(struct box *b, int pad);
api void box_pad(struct box *d, const struct box *s, int padx, int pady);
api int box_intersect(const struct box *a, const struct box *b);
/* id */
api void pushid(struct state *s, unsigned id);
api void setid(struct state *s, uiid id);
api uiid genwid(struct state *s);
api uiid genbid(struct state *s);
api void popid(struct state *s);
/* list */
#define list_empty(l) ((l)->next == (l))
#define list_entry(ptr,type,member) containerof(ptr,type,member)
#define list_next(l) (l)->next
#define list_prev(l) (l)->prev
#define list_begin(l) list_next(l)
#define list_last(l) list_prev(l)
#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)
api void list_init(struct list_hook *list);
api void list_add_head(struct list_hook *list, struct list_hook *n);
api void list_add_tail(struct list_hook *list, struct list_hook *n);
api void list_del(struct list_hook *entry);
api void list_move_head(struct list_hook *list, struct list_hook *entry);
api void list_move_tail(struct list_hook *list, struct list_hook *entry);
/* utf-8 */
api int utf_encode(char *s, int cap, unsigned long u);
api int utf_decode(unsigned long *rune, const char *str, int len);
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);
/* memory */
#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_push(struct memory_arena *a, int cnt, int size, int align);
/* math */
api int npow2(int n);
api int floori(float x);
api int ceili(float x);
api float roundf(float x);
api int roundi(float x);
api int strn(const char *s);
#endif
#include "qk.h"
#include "widget.h"
/* ---------------------------------------------------------------------------
* COMBO BOX
* --------------------------------------------------------------------------- */
api void
combo_box_popup_input(struct context *ctx, struct box *b, union event *evt)
{
int i = 0;
uiid *selid = 0;
if (evt->type != EVT_CLICKED) return;
if (evt->hdr.origin == b) return;
/* find combo box */
{mid cbmid = *widget_get_mid(b, 0);
uiid cbid = *widget_get_id(b, 1);
struct box *cbx = query(ctx, cbmid, cbid);
if (!cbx) return;
assert(cbx->type == WIDGET_COMBO_BOX);
/* check if clicked on item */
selid = widget_get_id(b, 2);
for (i = 0; i < evt->hdr.cnt; i++) {
struct box *l = evt->hdr.boxes[i];
if (l->type != WIDGET_LABEL) continue;
*selid = l->id;
/* change selected label */
{uiid lblid = *widget_get_id(b, 3);
struct box *lbx = query(ctx, cbmid, lblid);
if (!lbx) return;
lbx->params = l->params;}
/* close combo */
{struct list_hook *cbh = list_begin(&cbx->lnks);
struct box *cb = list_entry(cbh, struct box, node);
struct combo c = combo_ref(cb);
combo_popup_close(ctx, &c);}
}}
}
api struct combo_box
combo_box_begin(struct state *s, mid id)
{
uiid cid;
struct combo_box cbx;
widget_begin(s, WIDGET_COMBO_BOX);
cid = widget_box_push(s);
/* combo */
cbx.combo = combo_begin(s, id);
cbx.ps = combo_popup_begin(s, &cbx.combo);
widget_begin(cbx.ps, WIDGET_COMBO_BOX_POPUP);
widget_box_push(cbx.ps);
widget_param_mid(cbx.ps, s->id);
widget_param_id(cbx.ps, cid);
cbx.selid = widget_state_id(cbx.ps, 0);
cbx.lblid = widget_param_id(cbx.ps, 0);
button_begin(s);
/* setup popup layout */
cbx.fbx = flex_box_begin(cbx.ps);
*cbx.fbx.orientation = FLEX_BOX_VERTICAL;
return cbx;
}
api int
combo_box_item(struct state *s, struct combo_box *cbx, const char *item, const char *end)
{
/* item */
int selected = 0;
struct label lbl;
flex_box_slot_fitting(cbx->ps, &cbx->fbx); {
struct sbox sbx = sbox_begin(cbx->ps);
*sbx.align.horizontal = SALIGN_LEFT;
*sbx.align.vertical = SALIGN_MIDDLE; {
lbl = label(cbx->ps, item, end);
if (!*cbx->selid) *cbx->selid = lbl.id;
} sbox_end(cbx->ps);
}
if (lbl.id == *cbx->selid) {
/* combo header */
struct flex_box fb = flex_box_begin(s);
flex_box_slot_dyn(s, &fb); {
/* selected item label */
struct sborder sb = sborder_begin(s);
*sb.x = *sb.y = 2;
lbl = label(s, item, end);
*cbx->lblid = lbl.id;
sborder_end(s);
}
flex_box_slot_fitting(s, &fb); {
/* down arrow icon */
struct sbox x = sbox_begin(s);
*x.align.horizontal = SALIGN_CENTER;
*x.align.vertical = SALIGN_MIDDLE;
*x.border.x = *x.border.y = 2;
icon(s, ICON_COMBO_CARRET_DOWN);
sbox_end(s);
} flex_box_end(s, &fb);
selected = 1;
} return selected;
}
api void
combo_box_end(struct state *s, struct combo_box *cbx)
{
/* finish combo */
flex_box_end(cbx->ps, &cbx->fbx);
widget_box_pop(cbx->ps);
widget_end(cbx->ps);
combo_popup_end(cbx->ps, &cbx->combo);
button_end(s);
combo_end(s);
/* finish combo box */
widget_box_pop(s);
widget_end(s);
}
api int
combo_box(struct state *s, unsigned id, const char **items, int cnt)
{
int i = 0, sel = 0;
struct combo_box cbx;
cbx = combo_box_begin(s, id);
for (i = 0; i < cnt; ++i) {
if (combo_box_item(s, &cbx, items[i], 0)) sel = i;
} combo_box_end(s, &cbx);
return sel;
}
/* ---------------------------------------------------------------------------
* PANEL
* --------------------------------------------------------------------------- */
api struct panel
panel_begin(struct state *s)
{
struct panel pan = {0};
widget_begin(s, WIDGET_PANEL);
pan.id = widget_box_push(s);
pan.fbx = flex_box_begin(s);
*pan.fbx.orientation = FLEX_BOX_VERTICAL;
*pan.fbx.padding = 0;
return pan;
}
api uiid
panel_header_begin(struct state *s, struct panel *pan)
{
flex_box_slot_fitting(s, &pan->fbx);
widget_begin(s, WIDGET_PANEL_HEADER);
return widget_box_push(s);
}
api void
panel_header_end(struct state *s, struct panel *pan)
{
widget_box_pop(s);
widget_end(s);
}
api void
panel_header(struct state *s, struct panel *pan, const char *title)
{
assert(s);
assert(pan);
if (!s || !pan || !title) return;
panel_header_begin(s, pan); {
struct sbox sbx = sbox_begin(s);
*sbx.border.y = 5; *sbx.border.x = 4;
*sbx.align.horizontal = SALIGN_CENTER;
*sbx.align.vertical = SALIGN_MIDDLE;
label(s, title, 0);
sbox_end(s);
} panel_header_end(s, pan);
}
api uiid
panel_toolbar_begin(struct state *s, struct panel *pan)
{
flex_box_slot_fitting(s, &pan->fbx);
widget_begin(s, WIDGET_PANEL_TOOLBAR);
return widget_box_push(s);
}
api void
panel_toolbar_end(struct state *s, struct panel *pan)
{
widget_box_pop(s);
widget_end(s);
}
api void
panel_content_begin(struct state *s, struct panel *pan)
{
flex_box_slot_dyn(s, &pan->fbx);
}
api void
panel_content_end(struct state *s, struct panel *pan)
{
}
api uiid
panel_status_begin(struct state *s, struct panel *pan)
{
flex_box_slot_fitting(s, &pan->fbx);
widget_begin(s, WIDGET_PANEL_STATUSBAR);
return widget_box_push(s);
}
api void
panel_status_end(struct state *s, struct panel *pan)
{
widget_box_pop(s);
widget_end(s);
}
api void
panel_status(struct state *s, struct panel *pan, const char *status)
{
assert(s);
assert(pan);
if (!s || !pan || !status) return;
panel_status_begin(s, pan); {
struct sbox sbx = sbox_begin(s);
*sbx.border.y = 2; *sbx.border.x = 2;
*sbx.align.horizontal = SALIGN_LEFT;
*sbx.align.vertical = SALIGN_MIDDLE;
label(s, status, 0);
sbox_end(s);
} panel_status_end(s, pan);
}
api void
panel_end(struct state *s, struct panel *pan)
{
flex_box_end(s, &pan->fbx);
widget_box_pop(s);
widget_end(s);
}
/* ---------------------------------------------------------------------------
* PANEL BOX
* --------------------------------------------------------------------------- */
api struct panel
panel_box_begin(struct state *s, const char *title)
{
struct panel pan;
pan = panel_begin(s);
panel_header(s, &pan, title);
panel_content_begin(s, &pan);
scroll_box_begin(s);
return pan;
}
api void
panel_box_end(struct state *s, struct panel *pan, const char *status)
{
scroll_box_end(s);
panel_content_end(s, pan);
panel_status(s, pan, status);
panel_end(s, pan);
}
/* ---------------------------------------------------------------------------
* WINDOW
* --------------------------------------------------------------------------- */
api struct window
window_ref(struct box *b)
{
struct window win = {0};
win.x = widget_get_int(b, 0);
win.y = widget_get_int(b, 1);
win.w = widget_get_int(b, 2);
win.h = widget_get_int(b, 3);
win.border = widget_get_int(b, 4);
return win;
}
api struct window
window_begin(struct state *s, int x, int y, int w, int h)
{
struct window win = {0};
widget_begin(s, WIDGET_WINDOW);
win.id = widget_box_push(s);
win.x = widget_state_int(s, x);
win.y = widget_state_int(s, y);
win.w = widget_state_int(s, w);
win.h = widget_state_int(s, h);
win.border = widget_state_int(s, 3);
widget_begin(s, WIDGET_WINDOW_CONTENT);
widget_box_push(s);
return win;
}
api void
window_end(struct state *s)
{
widget_box_pop(s);
widget_end(s);
widget_box_pop(s);
widget_end(s);
}
api void
window_blueprint(struct box *b)
{
struct window win = window_ref(b);
b->dw = *win.w;
b->dh = *win.h;
}
api void
window_layout(struct box *b)
{
struct window win = window_ref(b);
b->x = *win.x;
b->y = *win.y;
b->w = *win.w;
b->h = *win.h;
box_layout(b, *win.border/2);
}
api void
window_input(struct context *ctx, struct box *b, const union event *evt)
{
int cx, cy, dx, dy;
struct window win;
struct input *in = evt->hdr.input;
if (evt->type != EVT_DRAGGED) return;
if (evt->hdr.origin != b) return;
win = window_ref(b);
cx = *win.x + (*win.w >> 1);
cy = *win.y + (*win.h >> 1);
dx = in->mouse.x - cx;
dy = in->mouse.y - cy;
if (abs(dx) > abs(dy)) {
if (dx < 0) {
*win.x += evt->dragged.x;
*win.w -= evt->dragged.x;
} else *win.w += evt->dragged.x;
} else {
if (dy < 0) {
*win.y += evt->dragged.y;
*win.h -= evt->dragged.y;
} else *win.h += evt->dragged.y;
} ctx->unbalanced = 1;
}
api void
window_content_input(struct context *ctx, struct box *c, const union event *evt)
{
int i = 0;
struct window win;
struct box *b = c->parent;
if (evt->type != EVT_DRAGGED) return;
if (evt->hdr.origin != b) {
for (i = 0; i < evt->hdr.cnt; i++) {
struct box *n = evt->hdr.boxes[i];
switch (n->type) {
default: break;
case WIDGET_PANEL_HEADER: goto move;}
} return;
}
move:
win = window_ref(b);
*win.x += evt->dragged.x;
*win.y += evt->dragged.y;
ctx->unbalanced = 1;
b->moved = 1;
}
/* ---------------------------------------------------------------------------
* SIDEBAR
* --------------------------------------------------------------------------- */
api struct sidebar
sidebar_ref(struct box *b)
{
struct sidebar sb;
sb.state = widget_get_int(b,0);
sb.ratio = widget_get_float(b,1);
sb.icon = widget_get_int(b,2);
sb.total = widget_get_int(b,3);
return sb;
}
intern void
sidebar_validate_icon(struct sidebar *sb)
{
switch (*sb->state) {
case SIDEBAR_OPEN: *sb->icon = ICON_SIDEBAR_LOCK; break;
case SIDEBAR_CLOSED: *sb->icon = ICON_SIDEBAR_OPEN; break;
case SIDEBAR_PINNED: *sb->icon = ICON_SIDEBAR_UNLOCK; break;
case SIDEBAR_DRAGGED: *sb->icon = ICON_SIDEBAR_UNLOCK; break;}
}
api struct sidebar
sidebar_begin(struct state *s)
{
/* sidebar */
struct sidebar sb = {0};
sb.cbx = con_box_begin(s, 0, 0);
{static const struct con sb_cons[] = {
{CON_SET, {0,ATTR_W}, {SPARENT,ATTR_W}, {0.5f,0}},
{CON_SET, {0,ATTR_H}, {SPARENT,ATTR_H}, {0.6f,0}},
{CON_SET, {0,ATTR_L}, {SPARENT,ATTR_L}, {1,0}, ATTR_W},
{CON_SET, {0,ATTR_CY}, {SPARENT,ATTR_CY}, {1,0}, ATTR_H}
}; con_box_slot(s, &sb.cbx, sb_cons, cntof(sb_cons));}
widget_begin(s, WIDGET_SIDEBAR);
sb.id = widget_box_push(s);
sb.state = widget_state_int(s, SIDEBAR_CLOSED);
sb.ratio = widget_state_float(s, 0.5f);
sb.icon = widget_param_int(s, ICON_SIDEBAR_LOCK);
sb.total = widget_param_int(s, 0);
sidebar_validate_icon(&sb);
/* bar */
widget_begin(s, WIDGET_SIDEBAR_BAR);
sb.id = widget_box_push(s); {
struct salign aln = salign_begin(s);
*aln.vertical = SALIGN_MIDDLE;
*aln.horizontal = SALIGN_RIGHT;
icon(s, *sb.icon);
salign_end(s);
} widget_box_pop(s);
widget_end(s);
/* scaler */
widget_begin(s, WIDGET_SIDEBAR_SCALER);
widget_box_push(s);
if (*sb.state == SIDEBAR_CLOSED)
widget_box_property_set(s, BOX_HIDDEN);
widget_box_property_set(s, BOX_MOVABLE_X);
widget_box_pop(s);
widget_end(s);
/* content */
widget_begin(s, WIDGET_SIDEBAR_CONTENT);
widget_box_push(s);
if (*sb.state == SIDEBAR_CLOSED)
widget_box_property_set(s, BOX_HIDDEN);
sb.pan = panel_begin(s);
panel_content_begin(s, &sb.pan);
return sb;
}
api void
sidebar_end(struct state *s, struct sidebar *sb)
{
panel_content_end(s, &sb->pan);
panel_end(s, &sb->pan);
widget_box_pop(s);
widget_end(s);
widget_box_pop(s);
widget_end(s);
con_box_end(s, &sb->cbx);
}
api void
sidebar_layout(struct box *b)
{
struct sidebar sb = sidebar_ref(b);
struct list_hook *hb = b->lnks.next;
struct list_hook *hs = hb->next;
struct list_hook *hc = hs->next;
struct box *bb = list_entry(hb, struct box, node);
struct box *bs = list_entry(hs, struct box, node);
struct box *bc = list_entry(hc, struct box, node);
/* bar */
bb->w = 18, bb->h = b->h;
bb->x = b->x, bb->y = (b->y + b->h/2) - (bb->h/2);
/* scaler */
bs->w = 8, bs->h = bb->h - 20;
bs->x = floori(cast(float, b->w) * *sb.ratio) - bs->w;
bs->x = max(bb->x + bb->w, bs->x);
bs->y = bb->y + 10;
/* content */
bc->x = bb->x + bb->w, bc->y = bs->y;
bc->w = bs->x - (bb->x + bb->w), bc->h = bs->h;
/* fit sidebar */
*sb.total = b->w;
b->y = bb->y, b->h = bb->h;
b->w = (bs->x + bs->w) - b->x;
}
intern void
sidebar_validate(struct sidebar *sb, struct box *b)
{
/* retrieve scaler, content */
struct list_hook *hc = b->node.prev;
{struct list_hook *hs = hc->prev;
struct box *bs = list_entry(hs, struct box, node);
struct box *bc = list_entry(hc, struct box, node);
/* toggle visiblity */
switch (*sb->state) {
case SIDEBAR_CLOSED:
bs->flags |= BOX_HIDDEN;
bc->flags |= BOX_HIDDEN;
break;
case SIDEBAR_OPEN:
case SIDEBAR_PINNED:
bs->flags &= ~(unsigned)BOX_HIDDEN;
bc->flags &= ~(unsigned)BOX_HIDDEN;
break;
}}
}
api void
sidebar_input(struct box *b, union event *evt)
{
struct sidebar sb = sidebar_ref(b);
if (!b->exited) return;
switch (evt->type) {
default: return;
case EVT_EXITED: {
switch (*sb.state) {
case SIDEBAR_OPEN:
*sb.state = SIDEBAR_CLOSED;
sidebar_validate(&sb, b);
sidebar_validate_icon(&sb); break;}
} break;}
}
api void
sidebar_bar_input(struct box *b, union event *evt)
{
struct box *p = b->parent;
struct sidebar sb = sidebar_ref(p);
/* state change */
switch (evt->type) {
default: return;
case EVT_CLICKED: {
switch (*sb.state) {
default: return;
case SIDEBAR_CLOSED: *sb.state = SIDEBAR_OPEN; break;
case SIDEBAR_OPEN: *sb.state = SIDEBAR_PINNED; break;
case SIDEBAR_PINNED: *sb.state = SIDEBAR_OPEN; break;}
} break;}
sidebar_validate(&sb, p);
sidebar_validate_icon(&sb);
}
api void
sidebar_scaler_input(struct box *b, union event *evt)
{
struct box *p = b->parent;
struct sidebar sb = sidebar_ref(p);
if (evt->hdr.origin != b) return;
switch (evt->type){
case EVT_DRAG_BEGIN: *sb.state = SIDEBAR_DRAGGED; break;
case EVT_DRAG_END: *sb.state = SIDEBAR_PINNED; break;
case EVT_MOVED: {
if (!b->moved) return;
*sb.ratio = cast(float,(b->x + b->w) - p->x) / cast(float, *sb.total);
*sb.ratio = clamp(0.2f, *sb.ratio, 1.0f);
} break;}
sidebar_validate_icon(&sb);
}
#include "qk.h"
#include "widget.h"
/* ---------------------------------------------------------------------------
* SBORDER
* --------------------------------------------------------------------------- */
api struct sborder
sborder_ref(struct box *b)
{
struct sborder sbx;
sbx.x = widget_get_int(b, 0);
sbx.y = widget_get_int(b, 1);
sbx.content = *widget_get_id(b, 2);
sbx.id = b->id;
return sbx;
}
api struct sborder
sborder_begin(struct state *s)
{
struct sborder sbx;
widget_begin(s, WIDGET_SBORDER);
sbx.x = widget_param_int(s, 6);
sbx.y = widget_param_int(s, 6);
sbx.id = widget_box_push(s);
sbx.content = widget_box_push(s);
widget_param_id(s, sbx.content);
return sbx;
}
api void
sborder_end(struct state *s)
{
widget_box_pop(s);
widget_box_pop(s);
widget_end(s);
}
api void
sborder_blueprint(struct box *b)
{
struct sborder sbx = sborder_ref(b);
if (b->id != sbx.content)
box_blueprint(b, *sbx.x, *sbx.y);
else box_blueprint(b, 0, 0);
}
api void
sborder_layout(struct box *b)
{
struct box *p = b->parent;
struct sborder sbx = sborder_ref(b);
if (b->id != sbx.content) return;
box_pad(b, p, *sbx.x, *sbx.y);
box_layout(b, 0);
}
/* ---------------------------------------------------------------------------
* SALIGN
* --------------------------------------------------------------------------- */
api struct salign
salign_ref(struct box *b)
{
struct salign aln;
aln.vertical = widget_get_int(b, 0);
aln.horizontal = widget_get_int(b, 1);
aln.content = *widget_get_id(b, 2);
aln.id = b->id;
return aln;
}
api struct salign
salign_begin(struct state *s)
{
struct salign aln;
widget_begin(s, WIDGET_SALIGN);
aln.vertical = widget_param_int(s, SALIGN_TOP);
aln.horizontal = widget_param_int(s, SALIGN_LEFT);
aln.id = widget_box_push(s);
aln.content = widget_box_push(s);
widget_param_id(s, aln.content);
return aln;
}
api void
salign_end(struct state *s)
{
widget_box_pop(s);
widget_box_pop(s);
widget_end(s);
}
api void
salign_layout(struct box *b)
{
struct box *p = b->parent;
struct salign aln = salign_ref(b);
struct list_hook *h = 0;
if (b->id != aln.content) return;
box_pad(b, p, 0, 0);
/* horizontal alignment */
switch (*aln.horizontal) {
case SALIGN_LEFT:
list_foreach(h, &b->lnks) {
struct box *n = list_entry(h, struct box, node);
n->x = b->x;
n->w = min(b->w, n->dw);
} break;
case SALIGN_CENTER:
list_foreach(h, &b->lnks) {
struct box *n = list_entry(h, struct box, node);
n->w = max((b->x + b->w) - n->x, 0);
n->w = min(n->w, n->dw);
n->x = max(b->x, (b->x + b->w/2) - n->w/2);
} break;
case SALIGN_RIGHT:
list_foreach(h, &b->lnks) {
struct box *n = list_entry(h, struct box, node);
n->w = min(n->dw, b->w);
n->x = max(b->x, b->x + b->w - n->w);
} break;}
/* vertical alignment */
switch (*aln.vertical) {
case SALIGN_TOP:
list_foreach(h, &b->lnks) {
struct box *n = list_entry(h, struct box, node);
n->h = min(n->dh, b->h);
n->y = b->y;
} break;
case SALIGN_MIDDLE:
list_foreach(h, &b->lnks) {
struct box *n = list_entry(h, struct box, node);
n->y = max(b->y,(b->y + b->h/2) - n->dh/2);
n->h = max((b->y + b->h) - n->y, 0);
} break;
case SALIGN_BOTTOM:
list_foreach(h, &b->lnks) {
struct box *n = list_entry(h, struct box, node);
n->y = max(b->y,b->y + b->h - n->dh);
n->h = min(n->dh, n->h);
} break;}
/* reshape sbox after content */
{int x = b->x + b->w;
int y = b->y + b->h;
list_foreach(h, &b->lnks) {
struct box *n = list_entry(h, struct box, node);
b->x = max(b->x, n->x);
b->y = max(b->y, n->y);
x = min(x, n->x + n->w);
y = min(y, n->y + n->h);
}
b->w = x - b->x;
b->h = y - b->y;
box_pad(p, b, 0, 0);}
box_layout(b, 0);
}
/* ---------------------------------------------------------------------------
* SBOX
* --------------------------------------------------------------------------- */
api struct sbox
sbox_begin(struct state *s)
{
struct sbox sbx = {0};
widget_begin(s, WIDGET_SBOX);
sbx.id = widget_box_push(s);
sbx.border = sborder_begin(s);
sbx.align = salign_begin(s);
return sbx;
}
api void
sbox_end(struct state *s)
{
salign_end(s);
sborder_end(s);
widget_box_pop(s);
widget_end(s);
}
/* ---------------------------------------------------------------------------
* FLEX BOX
* --------------------------------------------------------------------------- */
api struct flex_box
flex_box_ref(struct box *b)
{
struct flex_box fbx;
fbx.id = *widget_get_id(b, 0);
fbx.orientation = widget_get_int(b, 1);
fbx.flow = widget_get_int(b, 2);
fbx.spacing = widget_get_int(b, 3);
fbx.padding = widget_get_int(b, 4);
fbx.cnt = widget_get_int(b, 5);
return fbx;
}
api struct flex_box
flex_box_begin(struct state *s)
{
struct flex_box fbx;
widget_begin(s, WIDGET_FLEX_BOX);
fbx.id = widget_box_push(s);
widget_param_id(s, fbx.id);
fbx.orientation = widget_param_int(s, FLEX_BOX_HORIZONTAL);
fbx.flow = widget_param_int(s, FLEX_BOX_STRETCH);
fbx.spacing = widget_param_int(s, 4);
fbx.padding = widget_param_int(s, 4);
fbx.cnt = widget_param_int(s, 0);
return fbx;
}
intern void
flex_box_slot(struct state *s, struct flex_box *fbx,
enum flex_box_slot_type type, int value)
{
uiid id = 0;
int idx = *fbx->cnt;
if (idx) {
widget_box_pop(s);
widget_end(s);
}
widget_begin(s, WIDGET_FLEX_BOX_SLOT);
id = widget_box_push(s);
widget_param_int(s, (int)type);
widget_param_int(s, value);
*fbx->cnt = *fbx->cnt + 1;
}
api void
flex_box_slot_dyn(struct state *s, struct flex_box *fbx)
{
assert(*fbx->flow != FLEX_BOX_WRAP);
flex_box_slot(s, fbx, FLEX_BOX_SLOT_DYNAMIC, 0);
}
api void
flex_box_slot_static(struct state *s,
struct flex_box *fbx, int pixel_width)
{
flex_box_slot(s, fbx, FLEX_BOX_SLOT_STATIC, pixel_width);
}
api void
flex_box_slot_variable(struct state *s,
struct flex_box *fbx, int min_pixel_width)
{
assert(*fbx->flow != FLEX_BOX_WRAP);
flex_box_slot(s, fbx, FLEX_BOX_SLOT_VARIABLE, min_pixel_width);
}
api void
flex_box_slot_fitting(struct state *s, struct flex_box *fbx)
{
flex_box_slot(s, fbx, FLEX_BOX_SLOT_FITTING, 0);
}
api void
flex_box_end(struct state *s, struct flex_box *fbx)
{
int idx = *fbx->cnt;
if (idx) {
widget_box_pop(s);
widget_end(s);
} widget_box_pop(s);
widget_end(s);
}
api void
flex_box_blueprint(struct box *b)
{
struct list_hook *it = 0;
struct flex_box fbx = flex_box_ref(b);
b->dw = b->dh = 0;
list_foreach(it, &b->lnks) {
struct box *sb = list_entry(it, struct box, node);
int styp = *widget_get_int(sb, 0);
int spix = *widget_get_int(sb, 1);
/* horizontal layout */
switch (*fbx.orientation) {
case FLEX_BOX_HORIZONTAL: {
switch (styp) {
case FLEX_BOX_SLOT_DYNAMIC:
b->dw += sb->dw; break;
case FLEX_BOX_SLOT_STATIC:
b->dw += spix; break;
case FLEX_BOX_SLOT_FITTING:
case FLEX_BOX_SLOT_VARIABLE:
b->dw += max(sb->dw, spix); break;
} b->dh = max(sb->dh + *fbx.padding*2, b->dh);
} break;
/* vertical layout */
case FLEX_BOX_VERTICAL: {
switch (styp) {
case FLEX_BOX_SLOT_DYNAMIC:
b->dh += sb->dh; break;
case FLEX_BOX_SLOT_STATIC:
b->dh += spix; break;
case FLEX_BOX_SLOT_FITTING:
case FLEX_BOX_SLOT_VARIABLE:
b->dh += max(sb->dh, spix); break;
} b->dw = max(sb->dw + *fbx.padding*2, b->dw);
} break;}
}
/* padding + spacing */
{int pad = *fbx.spacing * (*fbx.cnt-1) + *fbx.padding * 2;
switch (*fbx.orientation) {
case FLEX_BOX_HORIZONTAL: b->dw += pad; break;
case FLEX_BOX_VERTICAL: b->dh += pad; break;}}
}
api void
flex_box_layout(struct box *b)
{
struct list_hook *it;
struct flex_box fbx = flex_box_ref(b);
int space = 0, slot_size = 0;
int varcnt = 0, staticsz = 0, fixsz = 0, maxvar = 0;
int dynsz = 0, varsz = 0, var = 0, dyncnt = 0;
/* calculate space for widgets without padding and spacing */
if (*fbx.orientation == FLEX_BOX_HORIZONTAL)
space = max(b->w - (*fbx.cnt-1)**fbx.spacing, 0), slot_size = b->h;
else space = max(b->h - (*fbx.cnt-1)**fbx.spacing, 0), slot_size = b->w;
space = max(0, space - *fbx.padding*2);
if (*fbx.flow == FLEX_BOX_FIT || *fbx.flow == FLEX_BOX_WRAP) {
/* fit flex box to content */
if (*fbx.orientation == FLEX_BOX_HORIZONTAL) {
list_foreach(it, &b->lnks) {
struct box *n = list_entry(it, struct box, node);
slot_size = max(slot_size, n->dh + *fbx.padding*2);
} slot_size = min(b->dh, slot_size);
if (*fbx.flow == FLEX_BOX_FIT)
b->h = slot_size;
} else {
list_foreach(it, &b->lnks) {
struct box *n = list_entry(it, struct box, node);
slot_size = max(slot_size, n->dw + *fbx.padding*2);
} slot_size = min(b->dw, slot_size);
if (*fbx.flow == FLEX_BOX_FIT)
b->w = slot_size;
}
}
/* calculate space requirements and slot metrics */
list_foreach(it, &b->lnks) {
struct box *sb = list_entry(it, struct box, node);
int slot_typ = *widget_get_int(sb, 0);
int *slot_pix = widget_get_int(sb, 1);
/* calculate min size and dynamic size */
switch (slot_typ) {
case FLEX_BOX_SLOT_DYNAMIC: {
varcnt++, dyncnt++;
} break;
case FLEX_BOX_SLOT_FITTING:
if (*fbx.orientation == FLEX_BOX_HORIZONTAL)
*slot_pix = max(*slot_pix, sb->dw);
else *slot_pix = max(*slot_pix, sb->dh);
case FLEX_BOX_SLOT_STATIC: {
staticsz += *slot_pix;
} break;
case FLEX_BOX_SLOT_VARIABLE: {
fixsz += *slot_pix;
maxvar = max(*slot_pix, maxvar);
varcnt++;
} break;}
/* setup opposite orientation position and size */
if (*fbx.orientation == FLEX_BOX_HORIZONTAL)
sb->h = max(0, slot_size - *fbx.padding*2);
else sb->w = max(0, slot_size - *fbx.padding*2);
}
/* calculate dynamic slot size */
dynsz = max(space - staticsz, 0);
varsz = max(dynsz - fixsz, 0);
if (varsz) {
if (varcnt) {
assert(*fbx.flow != FLEX_BOX_WRAP);
var = dynsz / max(varcnt,1);
if (maxvar > var) {
/* not enough space so shrink dynamic space */
list_foreach(it, &b->lnks) {
struct box *sb = list_entry(it, struct box, node);
int slot_typ = *widget_get_int(sb, 0);
int slot_pix = *widget_get_int(sb, 1);
if (slot_pix <= var) continue;
switch (slot_typ) {
case FLEX_BOX_SLOT_FITTING:
case FLEX_BOX_SLOT_VARIABLE: {
staticsz += slot_pix;
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 size */
{int total_w = b->w, total_h = b->h;
int posx = b->x + *fbx.padding, posy = b->y + *fbx.padding, cnt = 0;
list_foreach(it, &b->lnks) {
struct box *sb = list_entry(it, struct box, node);
int slot_typ = *widget_get_int(sb, 0);
int slot_pix = *widget_get_int(sb, 1);
/* setup slot size (width/height) */
switch (*fbx.orientation) {
case FLEX_BOX_HORIZONTAL: {
if (*fbx.flow == FLEX_BOX_WRAP && cnt) {
if (posx + slot_pix > b->x + (b->w - *fbx.padding*2)) {
posx = b->x, posy += slot_size + *fbx.spacing;
total_h = max(total_h, posy - b->y + *fbx.padding), cnt = 0;
}
} sb->x = posx, sb->y = posy;
switch (slot_typ) {
case FLEX_BOX_SLOT_DYNAMIC: sb->w = var; break;
case FLEX_BOX_SLOT_STATIC: sb->w = slot_pix; break;
case FLEX_BOX_SLOT_FITTING: sb->w = slot_pix; break;
case FLEX_BOX_SLOT_VARIABLE:
sb->w = (slot_pix > var) ? slot_pix: var; break;}
cnt++;
total_w = max(total_w, (posx + sb->w + *fbx.padding) - b->x);
posx += *fbx.spacing + sb->w;
} break;
case FLEX_BOX_VERTICAL: {
if (*fbx.flow == FLEX_BOX_WRAP && cnt) {
if (posy + slot_pix > b->y + (b->h - *fbx.padding*2)) {
posy = b->y, posx += slot_size + *fbx.spacing;
total_w = max(total_w, posx - b->x + *fbx.padding), cnt = 0;
}
} sb->x = posx, sb->y = posy;
switch (slot_typ) {
case FLEX_BOX_SLOT_DYNAMIC: sb->h = var; break;
case FLEX_BOX_SLOT_STATIC: sb->h = slot_pix; break;
case FLEX_BOX_SLOT_FITTING: sb->h = slot_pix; break;
case FLEX_BOX_SLOT_VARIABLE:
sb->h = (slot_pix > var) ? slot_pix: var; break;}
cnt++;
total_h = max(total_h, (posy + sb->h + *fbx.padding) - b->y);
posy += sb->h + *fbx.spacing;
} break;}
}
b->w = total_w;
b->h = total_h;}
}
/* ---------------------------------------------------------------------------
* OVERLAP BOX
* --------------------------------------------------------------------------- */
api struct overlap_box
overlap_box_ref(struct box *b)
{
struct overlap_box obx = {0};
obx.cnt = widget_get_int(b, 0);
obx.slots = 1;
return obx;
}
intern struct overlap_box_slot
overlap_box_slot_ref(struct box *b)
{
struct overlap_box_slot slot;
slot.id = widget_get_id(b, 0);
slot.zorder = widget_get_int(b, 1);
return slot;
}
api struct overlap_box
overlap_box_begin(struct state *s)
{
struct overlap_box obx = {0};
widget_begin(s, WIDGET_OVERLAP_BOX);
obx.id = widget_box_push(s);
widget_box_property_set(s, BOX_UNSELECTABLE);
obx.cnt = widget_param_int(s, 0);
return obx;
}
api void
overlap_box_slot(struct state *s, struct overlap_box *obx, mid id)
{
uiid slot_id = 0;
int idx = *obx->cnt;
if (idx) {
widget_box_pop(s);
widget_end(s);
popid(s);
} pushid(s, id);
widget_begin(s, WIDGET_OVERLAP_BOX_SLOT);
slot_id = widget_box_push(s);
widget_box_property_set(s, BOX_UNSELECTABLE);
widget_param_id(s, slot_id);
widget_state_int(s, 0);
*obx->cnt += 1;
}
api void
overlap_box_end(struct state *s, struct overlap_box *obx)
{
if (*obx->cnt) {
widget_box_pop(s);
widget_end(s);
popid(s);
}
widget_box_pop(s);
widget_end(s);
}
api void
overlap_box_layout(struct box *b, struct memory_arena *arena)
{
int i = 0;
int slot_cnt = 0;
struct box **boxes = 0;
struct list_hook *it = 0, *sit = 0;
box_layout(b, 0);
/* find maximum zorder */
list_foreach(it, &b->lnks) {
struct box *n = list_entry(it, struct box, node);
struct overlap_box_slot s = overlap_box_slot_ref(n);
slot_cnt = max(*s.zorder, slot_cnt);
} slot_cnt += 1;
/* allocate and setup temp sorting array */
boxes = arena_push_array(arena, slot_cnt, struct box*);
list_foreach_s(it, sit, &b->lnks) {
struct box *n = list_entry(it, struct box, node);
struct overlap_box_slot s = overlap_box_slot_ref(n);
if (!*s.zorder) continue;
list_del(&n->node);
boxes[*s.zorder] = n;
}
/* add boxes back in sorted order */
for (i = slot_cnt-1; i >= 0; --i) {
if (!boxes[i]) continue;
list_add_head(&b->lnks, &boxes[i]->node);
} i = 1;
/* set correct zorder for each child box */
list_foreach(it, &b->lnks) {
struct box *n = list_entry(it, struct box, node);
struct overlap_box_slot s = overlap_box_slot_ref(n);
*s.zorder = i++;
}
}
api void
overlap_box_input(struct box *b, union event *evt, struct memory_arena *arena)
{
int i = 0;
struct box *slot = 0;
struct list_hook *it = 0;
if (evt->type != EVT_PRESSED)
return;
/* find clicked on slot */
for (i = 0; i < evt->hdr.cnt; ++i) {
struct box *s = evt->hdr.boxes[i];
list_foreach(it, &b->lnks) {
struct box *n = list_entry(it, struct box, node);
if (n != s) continue;
slot = n; break;
}
if (slot) break;
} if (!slot) return;
/* reorder and rebalance overlap stack */
{int zorder = 0;
struct overlap_box obx = overlap_box_ref(b);
list_foreach(it, &b->lnks) {
struct box *n = list_entry(it, struct box, node);
struct overlap_box_slot s = overlap_box_slot_ref(n);
if (*s.id != slot->id) continue;
zorder = *s.zorder;
*s.zorder = *obx.cnt+1;
break;
}
/* set each slots zorder */
list_foreach(it, &b->lnks) {
struct box *n = list_entry(it, struct box, node);
struct overlap_box_slot s = overlap_box_slot_ref(n);
if (*s.zorder > zorder)
*s.zorder -= 1;
} overlap_box_layout(b, arena);}
}
/* ---------------------------------------------------------------------------
* CONSTRAINT BOX
* --------------------------------------------------------------------------- */
intern struct con_box
con_box_ref(struct box *b)
{
struct con_box cbx = {0};
cbx.cond_cnt = widget_get_int(b, 0);
cbx.slot_cnt = widget_get_int(b, 1);
cbx.conds = 2;
return cbx;
}
intern struct con_box_slot
con_box_slot_ref(struct box *b)
{
struct con_box_slot cbs = {0};
cbs.con_cnt = *widget_get_int(b, 0);
cbs.cons = 1;
return cbs;
}
intern struct cond
cond_ref(struct box *b, int off, int idx)
{
struct cond c = {0};
off += idx * 7;
c.op = (unsigned char)*widget_get_int(b, off + 0);
c.a.slot = *widget_get_int(b, off + 1);
c.a.attr = *widget_get_int(b, off + 2);
c.b.slot = *widget_get_int(b, off + 3);
c.b.attr = *widget_get_int(b, off + 4);
c.cons.mul = *widget_get_float(b, off + 5);
c.cons.off = *widget_get_int(b, off + 6);
return c;
}
intern struct con
con_ref(struct box *b, int off, int idx)
{
struct con c = {0};
off += idx * 9;
c.op = *widget_get_int(b, off + 0);
c.dst.slot = *widget_get_int(b, off + 1);
c.dst.attr = *widget_get_int(b, off + 2);
c.src.slot = *widget_get_int(b, off + 3);
c.src.attr = *widget_get_int(b, off + 4);
c.cons.mul = *widget_get_float(b, off + 5);
c.cons.off = *widget_get_int(b, off + 6);
c.anchor = *widget_get_int(b, off + 7);
c.cond = *widget_get_int(b, off + 8);
return c;
}
api struct con_box
con_box_begin(struct state *s,
struct cond *conds, int cond_cnt)
{
int i = 0;
struct con_box cbx;
/* setup con box */
widget_begin(s, WIDGET_CON_BOX);
cbx.id = widget_box_push(s);
widget_box_property_set(s, BOX_UNSELECTABLE);
cbx.cond_cnt = widget_param_int(s, cond_cnt);
cbx.slot_cnt = widget_param_int(s, 0);
/* push conditions */
for (i = 0; i < cond_cnt; ++i) {
const struct cond *c = conds + i;
widget_param_int(s, c->op);
widget_param_int(s, c->a.slot);
widget_param_int(s, c->a.attr);
widget_param_int(s, c->b.slot);
widget_param_int(s, c->b.attr);
widget_param_float(s, c->cons.mul);
widget_param_int(s, c->cons.off);
} return cbx;
}
api void
con_box_slot(struct state *s, struct con_box *cbx,
const struct con *cons, int con_cnt)
{
int i = 0;
if (*cbx->slot_cnt) {
widget_box_pop(s);
widget_end(s);
}
widget_begin(s, WIDGET_CON_BOX_SLOT);
widget_box_push(s);
widget_box_property_set(s, BOX_UNSELECTABLE);
widget_param_int(s, con_cnt);
for (i = 0; i < con_cnt; ++i) {
const struct con *c = cons + i;
widget_param_int(s, c->op);
widget_param_int(s, c->dst.slot);
widget_param_int(s, c->dst.attr);
widget_param_int(s, c->src.slot);
widget_param_int(s, c->src.attr);
widget_param_float(s, c->cons.mul);
widget_param_int(s, c->cons.off);
widget_param_int(s, c->anchor);
widget_param_int(s, c->cond);
} *cbx->slot_cnt += 1;
}
api void
con_box_end(struct state *s, struct con_box *cbx)
{
if (*cbx->slot_cnt) {
widget_box_pop(s);
widget_end(s);
}
widget_box_pop(s);
widget_end(s);
}
intern int
box_attr_get(const struct box *b, int attr)
{
switch (attr) {
default: return 0;
case ATTR_L: return b->x;
case ATTR_T: return b->y;
case ATTR_R: return b->x + b->w;
case ATTR_B: return b->y + b->h;
case ATTR_CX: return b->x + b->w/2;
case ATTR_CY: return b->y + b->h/2;
case ATTR_W: return b->w;
case ATTR_H: return b->h;
case ATTR_DW: return b->dw;
case ATTR_DH: return b->dh;}
}
intern void
box_attr_set(struct box *b, int attr, int anchor, int val)
{
switch (attr) {
default: return;
case ATTR_L: {
if (anchor == ATTR_R)
b->w = (b->x + b->w) - val;
else if (anchor == ATTR_CX)
b->w = ((b->x + b->w/2) - val) * 2;
b->x = val;
} break;
case ATTR_T: {
if (anchor == ATTR_B)
b->h = (b->y + b->h) - val;
else if (anchor == ATTR_CY)
b->h = ((b->y + b->h/2) - val) * 2;
b->y = val;
} break;
case ATTR_R: {
if (anchor == ATTR_L)
b->w = val - b->x;
else if (anchor == ATTR_CX)
b->w = (val - (b->x + b->w/2)) * 2;
b->x = val - b->w;
} break;
case ATTR_B: {
if (anchor == ATTR_T)
b->h = val - b->y;
else if (anchor == ATTR_CY)
b->h = (val - (b->y + b->h/2)) * 2;
b->y = val - b->h;
} break;
case ATTR_CX: {
if (anchor == ATTR_L)
b->w = (val - b->x) * 2;
else if (anchor == ATTR_R)
b->w = ((b->x + b->w) - val) * 2;
b->x = val - b->w/2;
} break;
case ATTR_CY: {
if (anchor == ATTR_T)
b->h = (val - b->y) * 2;
else if (anchor == ATTR_B)
b->h = ((b->y + b->h) - val) * 2;
b->y = val - b->h/2;
} break;
case ATTR_W: {
if (anchor == ATTR_CX)
b->y = (b->y + b->w/2) - val;
else if (anchor == ATTR_R)
b->y = (b->y + b->w) - val;
b->w = val;
} break;
case ATTR_H: {
if (anchor == ATTR_CY)
b->y = (b->y + b->h/2) - val;
else if (anchor == ATTR_B)
b->y = (b->y + b->h) - val;
b->h = val;
} break;}
}
api void
con_box_layout(struct box *b, struct memory_arena *arena)
{
int i = 0;
struct list_hook *it = 0;
struct con_box cbx = con_box_ref(b);
int *res = arena_push_array(arena, *cbx.cond_cnt, int);
/* setup boxes into slots */
struct box **boxes = arena_push_array(arena, *cbx.slot_cnt, struct box*);
boxes[i++] = b;
list_foreach(it, &b->lnks)
boxes[i++] = list_entry(it, struct box, node);
box_layout(b, 0);
/* evaluate conditions */
for (i = 0; i < *cbx.cond_cnt; ++i) {
int av = 0, bv = 0;
const struct box *dst, *src;
struct cond c = cond_ref(b, cbx.conds, i);
/* extract left operand */
assert(c.a.slot < *cbx.cond_cnt);
dst = boxes[c.a.slot + 1];
av = box_attr_get(dst, c.a.attr);
/* extract right operand */
assert(c.b.slot < *cbx.cond_cnt);
src = boxes[c.b.slot + 1];
bv = box_attr_get(src, c.b.attr);
bv = roundi(cast(float, bv) * c.cons.mul) + c.cons.off;
/* eval operation */
switch (c.op) {
case COND_EQ: res[i+1] = (av == bv); break;
case COND_NE: res[i+1] = (av != bv); break;
case COND_GR: res[i+1] = (av > bv); break;
case COND_GE: res[i+1] = (av >= bv); break;
case COND_LS: res[i+1] = (av < bv); break;
case COND_LE: res[i+1] = (av <= bv); break;}
}
/* evaluate constraints */
list_foreach(it, &b->lnks) {
struct box *sb = list_entry(it, struct box, node);
struct con_box_slot slot = con_box_slot_ref(sb);
for (i = 0; i < slot.con_cnt; ++i) {
struct box *dst, *src;
struct con c = con_ref(sb, slot.cons, i);
assert((c.cond < *cbx.cond_cnt) || (!(*cbx.cond_cnt)));
if (*cbx.cond_cnt && (c.cond >= *cbx.cond_cnt))
continue;
/* destination value */
{int av = 0, bv = 0, v = 0;
assert(c.dst.slot < *cbx.slot_cnt);
dst = boxes[c.dst.slot + 1];
av = box_attr_get(dst, c.dst.attr);
/* source value */
assert(c.src.slot < *cbx.slot_cnt);
src = boxes[c.src.slot + 1];
bv = box_attr_get(src, c.src.attr);
v = roundi(cast(float, bv) * c.cons.mul) + c.cons.off;
/* eval attribute */
switch (c.op) {
case CON_NOP: break;
case CON_SET: box_attr_set(dst, c.dst.attr, c.anchor, v); break;
case CON_MIN: box_attr_set(dst, c.dst.attr, c.anchor, min(av, v)); break;
case CON_MAX: box_attr_set(dst, c.dst.attr, c.anchor, max(av, v)); break;}}
}
}
/* reshape after contents */
{int x0 = INT_MAX, y0 = INT_MAX;
int x1 = INT_MIN, y1 = INT_MIN;
list_foreach(it, &b->lnks) {
struct box *n = list_entry(it, struct box, node);
x0 = min(x0, n->x);
y0 = min(y0, n->y);
x1 = max(x1, n->x + n->w);
y1 = max(y1, n->y + n->h);
}
b->x = x0, b->y = y0;
b->w = x1 - x0, b->h = y1 - y0;}
}
#include "qk.h"
#include "widget.h"
/* ---------------------------------------------------------------------------
* LABEL
* --------------------------------------------------------------------------- */
api struct label
label_ref(struct box *b)
{
struct label lbl;
lbl.height = widget_get_int(b, 0);
lbl.font = widget_get_int(b, 1);
lbl.txt = widget_get_str(b, 2);
return lbl;
}
api struct label
label(struct state *s, const char *txt, const char *end)
{
int len = 0;
struct label lbl = {0};
const struct config *cfg = s->cfg;
widget_begin(s, WIDGET_LABEL);
lbl.id = widget_box_push(s);
widget_box_pop(s);
len = (end) ? cast(int, (end-txt)): (int)strn(txt);
lbl.height = widget_param_int(s, cfg->font_default_height);
lbl.font = widget_param_int(s, cfg->font_default_id);
lbl.txt = widget_param_str(s, txt, len);
widget_end(s);
return lbl;
}
api void
label_blueprint(struct box *b, void *usr, text_measure_f measure)
{
struct label lbl = label_ref(b);
int len = cast(int, strn(lbl.txt));
b->dw = measure(usr, *lbl.font, *lbl.height, lbl.txt, len);
b->dh = *lbl.height;
}
/* ---------------------------------------------------------------------------
* ICON
* --------------------------------------------------------------------------- */
api struct icon
icon_ref(struct box *b)
{
struct icon ico = {0};
ico.sym = widget_get_int(b, 0);
ico.size = widget_get_int(b, 1);
return ico;
}
api struct icon
icon(struct state *s, int sym)
{
struct icon ico = {0};
const struct config *cfg = s->cfg;
widget_begin(s, WIDGET_ICON);
ico.id = widget_box_push(s);
ico.sym = widget_param_int(s, sym);
ico.size = widget_param_int(s, cfg->font_default_height);
widget_box_pop(s);
widget_end(s);
return ico;
}
api void
icon_blueprint(struct box *b)
{
struct icon ico = icon_ref(b);
b->dw = *ico.size;
b->dh = *ico.size;
}
api void
icon_layout(struct box *b)
{
struct icon ico = icon_ref(b);
*ico.size = min(b->w, b->h);
}
/* ---------------------------------------------------------------------------
* BUTTON
* --------------------------------------------------------------------------- */
intern void
button_pull(struct button_state *btn, const struct box *b)
{
btn->clicked = b->clicked;
btn->pressed = b->pressed;
btn->released = b->released;
btn->entered = b->entered;
btn->exited = b->exited;
}
api int
button_poll(struct state *s, struct button_state *st, struct button *btn)
{
struct box *b = polls(s, btn->id);
if (!b) {
zero(st, szof(*st));
return 0;
} button_pull(st, b);
return 1;
}
api int
button_query(struct context *ctx, struct button_state *st,
mid mid, struct button *btn)
{
struct box *b = 0;
b = query(ctx, mid, btn->id);
if (!b) return 0;
btn->style = widget_get_int(b, 0);
button_pull(st, b);
return 1;
}
api struct button
button_begin(struct state *s)
{
struct button btn = {0};
widget_begin(s, WIDGET_BUTTON);
btn.style =widget_param_int(s, 0);
btn.id = widget_box_push(s);
return btn;
}
api void
button_end(struct state *s)
{
widget_box_pop(s);
widget_end(s);
}
/* ---------------------------------------------------------------------------
* SLIDER
* --------------------------------------------------------------------------- */
api struct slider
slider_ref(struct box *b)
{
struct slider sld;
sld.min = widget_get_float(b, 0);
sld.value = widget_get_float(b, 1);
sld.max = widget_get_float(b, 2);
sld.cursor_size = widget_get_int(b, 3);
sld.cid = *widget_get_id(b, 4);
return sld;
}
intern void
slider_pull(struct slider_state *sld, const struct box *b)
{
sld->clicked = b->clicked;
sld->pressed = b->pressed;
sld->released = b->released;
sld->entered = b->entered;
sld->exited = b->exited;
sld->value_changed = b->moved;
}
api int
slider_poll(struct state *s, struct slider_state *st, struct slider *sld)
{
struct box *b = polls(s, sld->id);
if (!b) return 0;
*sld = slider_ref(b);
slider_pull(st, b);
return 1;
}
api int
slider_query(struct context *ctx, mid mid,
struct slider_state *st, struct slider *sld)
{
uiid id;
struct box *b = 0;
b = query(ctx, mid, sld->id);
if (!b) return 0;
id = sld->id;
*sld = slider_ref(b);
sld->id = id;
slider_pull(st, b);
return 1;
}
api struct slider
sliderf(struct state *s, float min, float *value, float max)
{
struct slider sld = {0};
const struct config *cfg = s->cfg;
widget_begin(s, WIDGET_SLIDER);
sld.min = widget_param_float(s, min);
sld.value = widget_modifier_float(s, value);
sld.max = widget_param_float(s, max);
sld.cursor_size = widget_param_int(s, cfg->font_default_height/2);
sld.id = widget_box_push(s);
/* cursor */
sld.cid = widget_box_push(s);
widget_box_property_set(s, BOX_MOVABLE_X);
widget_box_pop(s);
widget_param_id(s, sld.cid);
widget_box_pop(s);
widget_end(s);
return sld;
}
api void
slider_blueprint(struct box *b)
{
struct slider sld = slider_ref(b);
const int cursor_size = *sld.cursor_size;
if (b->id == sld.cid) {
b->dw = max(b->dw, cursor_size);
b->dh = max(b->dh, cursor_size);
box_blueprint(b, 0, 0);
} else box_blueprint(b, 0, 0);
}
api void
slider_layout(struct box *b)
{
struct slider sld = slider_ref(b);
if (b->id != sld.cid)
{box_layout(b, 0); return;}
/* validate slider values */
{const struct box *p = b->parent;
float sld_min = min(*sld.min, *sld.max);
float sld_max = max(*sld.min, *sld.max);
float sld_val = clamp(sld_min, *sld.value, sld_max);
float cur_off = (sld_val - sld_min) / sld_max;
/* remove half of cursor size of each box left and right */
const int cursor_size = *sld.cursor_size;
int x = p->x + cursor_size/2;
int w = p->w - cursor_size;
/* calculate cursor bounds */
b->w = cursor_size;
b->h = cursor_size * 2;
b->x = x + roundi(w * cur_off) - cursor_size/2;
b->y = p->y + p->h/2 - b->h/2;}
}
api void
slider_input(struct box *b, const union event *evt)
{
struct box *p = b->parent;
struct slider sld = slider_ref(b);
if (b->id != sld.cid) return;
if (!b->moved || evt->type != EVT_MOVED)
return;
/* validate cursor pos */
{float sld_min = min(*sld.min, *sld.max);
float sld_max = max(*sld.min, *sld.max);
b->x = min(p->x + p->w - b->w, b->x);
b->x = max(p->x, b->x);
/* calculate slider value from cursor position */
{const int cursor_size = *sld.cursor_size;
int x = p->x, w = p->w - cursor_size;
*sld.value = cast(float,(b->x - x)) / cast(float,w);
*sld.value = (*sld.value * sld_max) + sld_min;}}
p->moved = 1;
}
/* ---------------------------------------------------------------------------
* SCROLL
* --------------------------------------------------------------------------- */
api struct scroll
scroll_ref(struct box *b)
{
struct scroll scrl = {0};
scrl.total_x = widget_get_float(b, 0);
scrl.total_y = widget_get_float(b, 1);
scrl.size_x = widget_get_float(b, 2);
scrl.size_y = widget_get_float(b, 3);
scrl.off_x = widget_get_float(b, 4);
scrl.off_y = widget_get_float(b, 5);
scrl.cid = *widget_get_id(b, 6);
return scrl;
}
api struct scroll
scroll_begin(struct state *s, float *off_x, float *off_y)
{
struct scroll scrl = {0};
widget_begin(s, WIDGET_SCROLL);
scrl.total_x = widget_param_float(s, 1);
scrl.total_y = widget_param_float(s, 1);
scrl.size_x = widget_param_float(s, 1);
scrl.size_y = widget_param_float(s, 1);
scrl.off_x = widget_modifier_float(s, off_x);
scrl.off_y = widget_modifier_float(s, off_y);
scrl.id = widget_box_push(s);
scrl.cid = widget_box_push(s);
widget_box_property_set(s, BOX_MOVABLE);
widget_param_id(s, scrl.cid);
return scrl;
}
api void
scroll_end(struct state *s)
{
widget_box_pop(s);
widget_box_pop(s);
widget_end(s);
}
api struct scroll
scroll(struct state *s, float *off_x, float *off_y)
{
struct scroll scrl;
scrl = scroll_begin(s, off_x, off_y);
scroll_end(s);
return scrl;
}
api void
scroll_layout(struct box *b)
{
struct box *p = b->parent;
struct scroll scrl = scroll_ref(b);
if (b->id != scrl.cid) return;
*scrl.total_x = max(*scrl.total_x, *scrl.size_x);
*scrl.total_y = max(*scrl.total_y, *scrl.size_y);
*scrl.off_x = clamp(0, *scrl.off_x, *scrl.total_x - *scrl.size_x);
*scrl.off_y = clamp(0, *scrl.off_y, *scrl.total_y - *scrl.size_y);
b->x = floori((float)p->x + (*scrl.off_x / *scrl.total_x) * (float)p->w);
b->y = floori((float)p->y + (*scrl.off_y / *scrl.total_y) * (float)p->h);
b->w = ceili((*scrl.size_x / *scrl.total_x) * (float)p->w);
b->h = ceili((*scrl.size_y / *scrl.total_y) * (float)p->h);
}
api void
scroll_input(struct box *b, const union event *evt)
{
struct box *p = b->parent;
struct scroll scrl = scroll_ref(b);
switch (evt->type) {
case EVT_CLICKED: {
if (b->id == scrl.cid) break;
{struct list_hook *h = list_begin(&b->lnks);
struct box *cursor = list_entry(h, struct box, node);
int off_x = (evt->clicked.x - b->x) - floori(*scrl.size_x)/2;
int off_y = (evt->clicked.y - b->y) - floori(*scrl.size_y)/2;
cursor->x = b->x + clamp(0, off_x, ceili(*scrl.total_x - *scrl.size_x));
cursor->y = b->y + clamp(0, off_y, ceili(*scrl.total_y - *scrl.size_y));
p = b, b = cursor;}
} /* ---- fallthrough ---- */
case EVT_MOVED: {
if (b->id != scrl.cid) break;
/* calculate scroll ratio from cursor position */
{int scrl_x = b->x - p->x, scrl_y = b->y - p->y;
float scrl_rx = cast(float, scrl_x) / cast(float, p->w);
float scrl_ry = cast(float, scrl_y) / cast(float, p->h);
/* validate and set cursor position */
*scrl.off_x = clamp(0, scrl_rx * *scrl.total_x, *scrl.total_x - *scrl.size_x);
*scrl.off_y = clamp(0, scrl_ry * *scrl.total_y, *scrl.total_y - *scrl.size_y);}
p->moved = 1;
} break;}
}
/* ---------------------------------------------------------------------------
* COMBO
* --------------------------------------------------------------------------- */
api struct combo
combo_begin(struct state *s, mid id)
{
struct combo c = {0};
widget_begin(s, WIDGET_COMBO);
c.id = widget_box_push(s);
c.state = widget_state_int(s, COMBO_COLLAPSED);
c.popup = widget_param_mid(s, id);
return c;
}
api struct state*
combo_popup_begin(struct state *s, struct combo *c)
{
struct state *ps = 0;
ps = popup_begin(s, *c->popup, POPUP_NON_BLOCKING);
widget_begin(ps, WIDGET_COMBO_POPUP);
widget_box_push(ps);
if (ps->mod && (ps->mod->root->flags & BOX_HIDDEN))
*c->state = COMBO_COLLAPSED;
return ps;
}
api void
combo_popup_end(struct state *s, struct combo *c)
{
widget_box_pop(s);
widget_end(s);
if (*c->state == COMBO_COLLAPSED)
widget_box_property_set(s, BOX_HIDDEN);
popup_end(s);
}
api void
combo_end(struct state *s)
{
widget_box_pop(s);
widget_end(s);
}
api void
combo_popup_show(struct context *ctx, struct combo *c)
{
*c->state = COMBO_VISIBLE;
popup_show(ctx, *c->popup, VISIBLE);
}
api void
combo_popup_close(struct context *ctx, struct combo *c)
{
*c->state = COMBO_COLLAPSED;
popup_show(ctx, *c->popup, HIDDEN);
}
api void
combo_layout(struct context *ctx, struct box *b)
{
struct combo c = combo_ref(b);
struct box *p = popup_find(ctx, *c.popup);
assert(p);
p->x = b->x;
p->h = p->dh;
p->y = b->y + b->h;
p->w = max(b->w, p->dw);
box_layout(b, 0);
}
api void
combo_input(struct context *ctx, struct box *b, const union event *evt)
{
struct combo c = combo_ref(b);
if (!b->clicked || evt->type != EVT_CLICKED) return;
if (*c.state == COMBO_COLLAPSED)
combo_popup_show(ctx, &c);
else combo_popup_close(ctx, &c);
}
/* ---------------------------------------------------------------------------
* CLIP BOX
* --------------------------------------------------------------------------- */
api uiid
clip_box_begin(struct state *s)
{
assert(s);
if (!s) return 0;
widget_begin(s, WIDGET_CLIP_BOX);
return widget_box_push(s);
}
api void
clip_box_end(struct state *s)
{
uiid id;
assert(s);
if (!s) return;
id = widget_box_push(s);
widget_param_id(s, id);
widget_box_property_set(s, BOX_IMMUTABLE);
widget_box_pop(s);
widget_box_pop(s);
widget_end(s);
}
/* ---------------------------------------------------------------------------
* SCROLL REGION
* --------------------------------------------------------------------------- */
api struct scroll_region
scroll_region_ref(struct box *b)
{
struct scroll_region sr = {0};
sr.dir = widget_get_int(b, 0);
sr.off_x = widget_get_float(b, 1);
sr.off_y = widget_get_float(b, 2);
sr.id = *widget_get_id(b, 3);
return sr;
}
api struct scroll_region
scroll_region_begin(struct state *s)
{
struct scroll_region sr = {0};
assert(s);
if (!s) return sr;
widget_begin(s, WIDGET_SCROLL_REGION);
sr.dir = widget_state_int(s, SCROLL_DEFAULT);
sr.off_x = widget_state_float(s, 0);
sr.off_y = widget_state_float(s, 0);
sr.id = widget_box_push(s);
widget_param_id(s, sr.id);
return sr;
}
api void
scroll_region_end(struct state *s)
{
assert(s);
if (!s) return;
clip_box_end(s);
}
api void
scroll_region_layout(struct box *b)
{
struct list_hook *i = 0;
struct scroll_region sr;
assert(b);
if (!b) return;
sr = scroll_region_ref(b);
if (b->id != sr.id) return;
list_foreach(i, &b->lnks) {
struct box *n = list_entry(i, struct box, node);
n->x = b->x - floori(*sr.off_x);
n->y = b->y - floori(*sr.off_y);
n->w = b->w, n->h = b->h;
}
}
api void
scroll_region_input(struct context *ctx, struct box *b, const union event *evt)
{
struct input *in = evt->hdr.input;
struct scroll_region sr = scroll_region_ref(b);
if (evt->type == EVT_DRAGGED && in->shortcuts[SHORTCUT_SCROLL_REGION_SCROLL].down) {
b->moved = 1;
switch (*sr.dir) {
case SCROLL_DEFAULT:
*sr.off_x -= evt->moved.x;
*sr.off_y -= evt->moved.y;
ctx->unbalanced = 1;
break;
case SCROLL_REVERSE:
*sr.off_x += evt->moved.x;
*sr.off_y += evt->moved.y;
ctx->unbalanced = 1;
break;
}
} else if (in->shortcuts[SHORTCUT_SCROLL_REGION_RESET].down) {
*sr.off_x = *sr.off_y = 0;
ctx->unbalanced = 1;
}
}
/* ---------------------------------------------------------------------------
* SCROLL BOX
* --------------------------------------------------------------------------- */
api uiid
scroll_box_begin(struct state *s)
{
uiid sid = 0;
float tmpx = 0, tmpy = 0;
struct scroll x, y;
struct scroll_region sr;
assert(s);
if (!s) return 0;
widget_begin(s, WIDGET_SCROLL_BOX);
sid = widget_box_push(s);
x = scroll(s, &tmpx, &tmpy);
y = scroll(s, &tmpx, &tmpy);
sr = scroll_region_begin(s);
*x.off_x = *sr.off_x;
*y.off_y = *sr.off_y;
return sid;
}
api void
scroll_box_end(struct state *s)
{
assert(s);
if (!s) return;
scroll_region_end(s);
widget_box_pop(s);
widget_end(s);
}
api void
scroll_box_blueprint(struct box *b)
{
static const int scroll_size = 10;
struct list_hook *h = b->lnks.prev;
struct box *bsr = list_entry(h, struct box, node);
b->dw = max(b->dw, bsr->dw + scroll_size);
b->dh = max(b->dh, bsr->dh + scroll_size);
}
api void
scroll_box_layout(struct box *b)
{
/* retrieve pointer for each widget */
struct list_hook *i = 0;
static const int scroll_size = 10;
struct list_hook *hx = b->lnks.next;
struct list_hook *hy = hx->next;
struct list_hook *hsr = hy->next;
struct box *bx = list_entry(hx, struct box, node);
struct box *by = list_entry(hy, struct box, node);
struct box *bsr = list_entry(hsr, struct box, node);
struct scroll x = scroll_ref(bx);
struct scroll y = scroll_ref(by);
struct scroll_region sr = scroll_region_ref(bsr);
/* x-scrollbar */
bx->x = b->x;
bx->y = b->y + b->h - scroll_size;
bx->w = b->w - scroll_size;
bx->h = scroll_size;
/* y-scrollbar */
by->x = b->x + b->w - scroll_size;
by->y = b->y;
by->w = scroll_size;
by->h = b->h - scroll_size;
/* scroll region */
bsr->x = b->x;
bsr->y = b->y;
bsr->w = b->w - scroll_size;
bsr->h = b->h - scroll_size;
scroll_region_layout(bsr);
/* offset */
if (bx->moved)
*sr.off_x = *x.off_x;
else *x.off_x = *sr.off_x;
if (by->moved)
*sr.off_y = *y.off_y;
else *y.off_y = *sr.off_y;
/* HACK(micha): handle wrapping flex box */
list_foreach(i, &bsr->lnks) {
struct box *n = list_entry(i, struct box, node);
struct flex_box fbx;
if (n->type != WIDGET_FLEX_BOX) continue;
fbx = flex_box_ref(n);
if (*fbx.flow != FLEX_BOX_WRAP) continue;
{struct list_hook *j = 0;
int max_x = b->x, max_y = b->y;
flex_box_layout(n);
list_foreach(j, &n->lnks) {
const struct box *s = list_entry(j, struct box, node);
max_x = max(max_x, s->x + (int)*sr.off_x + s->w);
max_y = max(max_y, s->y + (int)*sr.off_y + s->h);
}
bsr->dw = max_x - b->x;
bsr->dh = max_y - b->y;}
}
/* scrollbars */
*x.size_x = bsr->w;
*y.size_y = bsr->h;
*x.total_x = bsr->dw;
*y.total_y = bsr->dh;
if (bsr->dw <= bsr->w)
*sr.off_x = *x.off_x = 0;
if (bsr->dh <= bsr->h)
*sr.off_y = *y.off_y = 0;
}
api void
scroll_box_input(struct context *ctx, struct box *b, union event *evt)
{
struct list_hook *hsr = list_last(&b->lnks);
struct list_hook *hy = list_prev(hsr);
struct list_hook *hx = list_prev(hy);
struct box *bx = list_entry(hx, struct box, node);
struct box *by = list_entry(hy, struct box, node);
struct box *bsr = list_entry(hsr, struct box, node);
struct scroll x = scroll_ref(bx);
struct scroll y = scroll_ref(by);
struct scroll_region sr = scroll_region_ref(bsr);
switch (evt->type) {
case EVT_CLICKED:
case EVT_MOVED: {
/* scrollbars */
if (bx->moved) *sr.off_x = *x.off_x;
if (by->moved) *sr.off_y = *y.off_y;
} break;
case EVT_DRAG_END: {
/* overscrolling */
*sr.off_x = clamp(0, *sr.off_x, *x.total_x - *x.size_x);
*sr.off_y = clamp(0, *sr.off_y, *y.total_y - *y.size_y);
ctx->unbalanced = 1;
} break;
case EVT_SCROLLED: {
/* mouse scrolling */
*sr.off_y += -evt->scroll.y * floori((cast(float, *y.size_y) * 0.1f));
*sr.off_y = clamp(0, *sr.off_y, *y.total_y - *y.size_y);
ctx->unbalanced = 1;
} break;
case EVT_SHORTCUT: {
/* shortcuts */
if (!evt->key.pressed) break;
if (evt->key.code == SHORTCUT_SCROLL_BOX_BEGIN)
*sr.off_y = 0, ctx->unbalanced = 1;
else if (evt->key.code == SHORTCUT_SCROLL_BOX_END)
*sr.off_y = *y.total_y - *y.size_y, ctx->unbalanced = 1;
else if (evt->key.code == SHORTCUT_SCROLL_BOX_PGDN) {
*sr.off_y = min(*sr.off_y + *y.size_y, *y.total_y - *y.size_y);
ctx->unbalanced = 1;
} else if (evt->key.code == SHORTCUT_SCROLL_BOX_PGUP) {
*sr.off_y = max(*sr.off_y - *y.size_y, 0);
ctx->unbalanced = 1;
}
} break;}
}
/* ---------------------------------------------------------------------------
* ZOOM BOX
* --------------------------------------------------------------------------- */
api struct zoom_box
zoom_box_ref(struct box *b)
{
struct zoom_box sb;
sb.scale_x = widget_get_float(b, 0);
sb.scale_y = widget_get_float(b, 1);
sb.id = *widget_get_id(b, 2);
return sb;
}
api struct zoom_box
zoom_box_begin(struct state *s)
{
struct zoom_box sb = {0};
assert(s);
if (!s) return sb;
widget_begin(s, WIDGET_ZOOM_BOX);
sb.scale_x = widget_state_float(s, 1);
sb.scale_y = widget_state_float(s, 1);
sb.id = widget_box_push(s);
widget_param_id(s, sb.id);
return sb;
}
api void
zoom_box_end(struct state *s)
{
uiid id = 0;
assert(s);
if (!s) return;
id = widget_box_push(s);
widget_param_id(s, id);
widget_box_property_set(s, BOX_IMMUTABLE);
widget_box_pop(s);
widget_box_pop(s);
widget_end(s);
}
api void
zoom_box_input(struct box *b, union event *evt)
{
struct input *in = evt->hdr.input;
struct zoom_box sb = zoom_box_ref(b);
if (evt->type != EVT_SCROLLED) return;
if (in->shortcuts[SHORTCUT_ZOOM_BOX_SCALE_X].down ||
in->shortcuts[SHORTCUT_ZOOM_BOX_SCALE].down)
*sb.scale_x = -evt->scroll.x * 0.1f;
if (in->shortcuts[SHORTCUT_ZOOM_BOX_SCALE_Y].down ||
in->shortcuts[SHORTCUT_ZOOM_BOX_SCALE].down)
*sb.scale_y = -evt->scroll.y * 0.1f;
}
/* ---------------------------------------------------------------------------
* BUTTON_LABEL
* --------------------------------------------------------------------------- */
intern void
button_label_pull(struct button_state *btn, const struct box *b)
{
button_pull(btn, b);
}
api int
button_label_poll(struct state *s, struct button_state *st,
struct button_label *btn)
{
return button_poll(s, st, &btn->btn);
}
api int
button_label_query(struct context *ctx, struct button_state *st,
mid mid, struct button_label *btn)
{
return button_query(ctx, st, mid, &btn->btn);
}
api struct button_label
button_label(struct state *s, const char *txt, const char *end)
{
struct button_label btn;
assert(s);
assert(txt);
btn.btn = button_begin(s);
btn.sbx = sbox_begin(s);
*btn.sbx.align.horizontal = SALIGN_CENTER;
*btn.sbx.align.vertical = SALIGN_MIDDLE;
btn.lbl = label(s, txt, end);
sbox_end(s);
button_end(s);
return btn;
}
api int
button_label_clicked(struct state *s, const char *label, const char *end)
{
struct button_state st;
struct button_label bl;
bl = button_label(s, label, end);
button_poll(s, &st, &bl.btn);
return st.clicked != 0;
}
/* ---------------------------------------------------------------------------
* BUTTON_ICON
* --------------------------------------------------------------------------- */
api struct button_icon
button_icon(struct state *s, int sym)
{
struct button_icon bti;
bti.btn = button_begin(s);
bti.sbx = sbox_begin(s);
*bti.sbx.align.horizontal = SALIGN_CENTER;
*bti.sbx.align.vertical = SALIGN_MIDDLE;
bti.ico = icon(s, sym);
sbox_end(s);
button_end(s);
return bti;
}
api int
button_icon_clicked(struct state *s, int sym)
{
struct button_state st;
struct button_icon bi;
bi = button_icon(s, sym);
button_poll(s, &st, &bi.btn);
return st.clicked != 0;
}
/* ---------------------------------------------------------------------------
* TOGGLE
* --------------------------------------------------------------------------- */
api struct toggle
toggle_ref(struct box *b)
{
struct toggle tog = {0};
tog.val = widget_get_int(b, 0);
tog.icon_def = widget_get_int(b, 1);
tog.icon_act = widget_get_int(b, 2);
tog.mid = widget_get_mid(b, 3);
tog.iconid = widget_get_id(b, 4);
return tog;
}
intern void
toggle_setup(struct state *s, struct toggle *tog,
const char *txt, const char *end)
{
struct sbox sbx = sbox_begin(s);
*sbx.align.horizontal = SALIGN_LEFT;
*sbx.align.vertical = SALIGN_MIDDLE;
{
/* icon */
tog->fbx = flex_box_begin(s);
*tog->fbx.spacing = 4;
*tog->fbx.padding = 0;
flex_box_slot_fitting(s, &tog->fbx);
tog->icon = icon(s, (*tog->val) ? *tog->icon_act: *tog->icon_def);
/* label */
flex_box_slot_dyn(s, &tog->fbx);
tog->lbl = label(s, txt, end);
flex_box_end(s, &tog->fbx);
} sbox_end(s);
widget_param_mid(s, s->id);
tog->iconid = widget_param_id(s, tog->icon.id);
}
api struct toggle
checkbox(struct state *s, int *checked, const char *txt, const char *end)
{
struct toggle chk;
widget_begin(s, WIDGET_CHECKBOX);
chk.id = widget_box_push(s);
chk.val = widget_modifier_int(s, checked);
chk.icon_def = widget_param_int(s, ICON_UNCHECKED);
chk.icon_act = widget_param_int(s, ICON_CHECKED);
toggle_setup(s, &chk, txt, end);
widget_box_pop(s);
widget_end(s);
return chk;
}
api struct toggle
toggle(struct state *s, int *active, const char *txt, const char *end)
{
struct toggle tog;
widget_begin(s, WIDGET_TOGGLE);
tog.id = widget_box_push(s);
tog.val = widget_modifier_int(s, active);
tog.icon_def = widget_param_int(s, ICON_UNTOGGLED);
tog.icon_act = widget_param_int(s, ICON_TOGGLED);
toggle_setup(s, &tog, txt, end);
widget_box_pop(s);
widget_end(s);
return tog;
}
api struct toggle
radio(struct state *s, int *sel, const char *txt, const char *end)
{
struct toggle tog;
widget_begin(s, WIDGET_RADIO);
tog.id = widget_box_push(s);
tog.val = widget_modifier_int(s, sel);
tog.icon_def = widget_param_int(s, ICON_UNSELECTED);
tog.icon_act = widget_param_int(s, ICON_SELECTED);
toggle_setup(s, &tog, txt, end);
widget_box_pop(s);
widget_end(s);
return tog;
}
api void
toggle_input(struct context *ctx, struct box *b, union event *evt)
{
struct toggle tog = toggle_ref(b);
if (evt->type != EVT_CLICKED) return;
*tog.val = !(*tog.val);
{struct box *ib = query(ctx, *tog.mid, *tog.iconid);
if (!ib) return;
{struct icon ico = icon_ref(ib);
*ico.sym = (*tog.val) ? *tog.icon_act: *tog.icon_def;}}
ctx->active = b;
}
#ifndef WIDGET_H
#define WIDGET_H
enum widget_type {
/* Widgets */
WIDGET_GENERIC_FIRST = 0x10000,
WIDGET_ICON = WIDGET_GENERIC_FIRST,
WIDGET_LABEL,
WIDGET_BUTTON,
WIDGET_CHECKBOX,
WIDGET_TOGGLE,
WIDGET_RADIO,
WIDGET_SLIDER,
WIDGET_SCROLL,
WIDGET_GENERIC_LAST = WIDGET_SCROLL,
/* Layouting */
WIDGET_LAYOUT_FIRST = 0x20000,
WIDGET_SBORDER = WIDGET_LAYOUT_FIRST,
WIDGET_SALIGN,
WIDGET_SBOX,
WIDGET_FLEX_BOX,
WIDGET_FLEX_BOX_SLOT,
WIDGET_OVERLAP_BOX,
WIDGET_OVERLAP_BOX_SLOT,
WIDGET_CON_BOX,
WIDGET_CON_BOX_SLOT,
WIDGET_LAYOUT_LAST = WIDGET_CON_BOX_SLOT,
/* Container */
WIDGET_CONTAINER_FIRST = 0x30000,
WIDGET_COMBO = WIDGET_CONTAINER_FIRST,
WIDGET_COMBO_POPUP,
WIDGET_COMBO_BOX,
WIDGET_COMBO_BOX_POPUP,
WIDGET_PANEL,
WIDGET_PANEL_HEADER,
WIDGET_PANEL_TOOLBAR,
WIDGET_PANEL_STATUSBAR,
WIDGET_SIDEBAR,
WIDGET_SIDEBAR_BAR,
WIDGET_SIDEBAR_SCALER,
WIDGET_SIDEBAR_CONTENT,
WIDGET_WINDOW,
WIDGET_WINDOW_CONTENT,
WIDGET_CONTAINER_LAST = WIDGET_WINDOW_CONTENT,
/* Transformation */
WIDGET_TRANSFORM_FIRST = 0x40000,
WIDGET_CLIP_BOX,
WIDGET_SCROLL_REGION,
WIDGET_SCROLL_BOX,
WIDGET_ZOOM_BOX,
WIDGET_MOVABLE_BOX,
WIDGET_SCALABLE_BOX,
WIDGET_TRANSFORM_LAST = WIDGET_SCALABLE_BOX
};
enum widget_shortcuts {
SHORTCUT_INTERNAL_BEGIN = 0x80,
SHORTCUT_SCROLL_REGION_SCROLL = SHORTCUT_INTERNAL_BEGIN,
SHORTCUT_SCROLL_REGION_RESET,
SHORTCUT_SCROLL_BOX_BEGIN,
SHORTCUT_SCROLL_BOX_PGDN,
SHORTCUT_SCROLL_BOX_PGUP,
SHORTCUT_SCROLL_BOX_END,
SHORTCUT_ZOOM_BOX_SCALE_X,
SHORTCUT_ZOOM_BOX_SCALE_Y,
SHORTCUT_ZOOM_BOX_SCALE,
SHORTCUT_INTERNAL_END
};
enum icon_symbol {
ICONS_INTERNAL_BEGIN = 0x100000,
ICON_COMBO_CARRET_DOWN = ICONS_INTERNAL_BEGIN,
ICON_UNCHECKED,
ICON_CHECKED,
ICON_UNTOGGLED,
ICON_TOGGLED,
ICON_UNSELECTED,
ICON_SELECTED,
ICON_SIDEBAR_LOCK,
ICON_SIDEBAR_UNLOCK,
ICON_SIDEBAR_OPEN,
ICONS_INTERNAL_END
};
/* ---------------------------------------------------------------------------
* BASE
* --------------------------------------------------------------------------- */
/* label */
typedef int(*text_measure_f)(void *usr, int font, int fh, const char *txt, int len);
struct label {
uiid id;
const char *txt;
int *font;
int *height;
};
api struct label label(struct state *s, const char *txt, const char *end);
api void label_blueprint(struct box *b, void *usr, text_measure_f measure);
api struct label label_ref(struct box *b);
/* icon */
struct icon {
uiid id;
int *sym;
int *size;
};
api struct icon icon(struct state *s, int sym);
api void icon_blueprint(struct box *b);
api void icon_layout(struct box *b);
api struct icon icon_ref(struct box *b);
/* button */
struct button_state {
unsigned clicked:1;
unsigned pressed:1;
unsigned released:1;
unsigned entered:1;
unsigned exited:1;
};
struct button {
uiid id;
int *style;
};
api struct button button_begin(struct state *s);
api struct button button_begin(struct state *s);
api int button_query(struct context *ctx, struct button_state *st, mid mid, struct button *btn);
api int button_poll(struct state *s, struct button_state *st, struct button *btn);
/* slider */
struct slider_state {
unsigned clicked:1;
unsigned pressed:1;
unsigned released:1;
unsigned entered:1;
unsigned exited:1;
unsigned value_changed:1;
};
struct slider {
uiid id;
uiid cid;
float *min;
float *value;
float *max;
int *cursor_size;
};
api struct slider sliderf(struct state *s, float min, float *value, float max);
api void slider_blueprint(struct box *b);
api void slider_layout(struct box *b);
api void slider_input(struct box *b, const union event *evt);
api int slider_poll(struct state *s, struct slider_state *st, struct slider *sld);
api int slider_query(struct context *ctx, mid mid, struct slider_state *st, struct slider *sld);
/* scroll */
struct scroll {
uiid id, cid;
float *total_x;
float *total_y;
float *size_x;
float *size_y;
float *off_x;
float *off_y;
};
api struct scroll scroll(struct state *s, float *off_x, float *off_y);
api struct scroll scroll_begin(struct state *s, float *off_x, float *off_y);
api void scroll_end(struct state *s);
api void scroll_layout(struct box *b);
api void scroll_input(struct box *b, const union event *evt);
api struct scroll scroll_ref(struct box *b);
/* combo */
enum combo_state {
COMBO_COLLAPSED,
COMBO_VISIBLE
};
struct combo {
int *state;
uiid id;
mid *popup;
};
static struct combo
combo_ref(struct box *b)
{
struct combo c = {0};
c.state = widget_get_int(b, 0);
c.popup = widget_get_mid(b, 1);
return c;
}
api struct combo combo_begin(struct state *s, mid id);
api struct state* combo_popup_begin(struct state *s, struct combo *c);
api void combo_popup_end(struct state *s, struct combo *c);
api void combo_end(struct state *s);
api void combo_popup_show(struct context *ctx, struct combo *c);
api void combo_popup_close(struct context *ctx, struct combo *c);
api void combo_layout(struct context *ctx, struct box *b);
api void combo_input(struct context *ctx, struct box *b, const union event *evt);
/* ---------------------------------------------------------------------------
* LAYOUT
* --------------------------------------------------------------------------- */
/* sborder */
struct sborder {
uiid id;
uiid content;
int *x;
int *y;
};
api struct sborder sborder_begin(struct state *s);
api void sborder_end(struct state *s);
api void sborder_blueprint(struct box *b);
api void sborder_layout(struct box *b);
api struct sborder sborder_ref(struct box *b);
/* salign */
enum salign_vertical {
SALIGN_TOP,
SALIGN_MIDDLE,
SALIGN_BOTTOM
};
enum salign_horizontal {
SALIGN_LEFT,
SALIGN_CENTER,
SALIGN_RIGHT
};
struct salign {
uiid id;
uiid content;
int *vertical;
int *horizontal;
};
api struct salign salign_begin(struct state *s);
api void salign_end(struct state *s);
api void salign_layout(struct box *b);
api struct salign salign_ref(struct box *b);
/* sbox */
struct sbox {
uiid id;
struct salign align;
struct sborder border;
};
api struct sbox sbox_begin(struct state *s);
api void sbox_end(struct state *s);
/* flex box */
enum flex_box_orientation {
FLEX_BOX_HORIZONTAL,
FLEX_BOX_VERTICAL
};
enum flex_box_flow {
FLEX_BOX_STRETCH,
FLEX_BOX_FIT,
FLEX_BOX_WRAP
};
enum flex_box_slot_type {
FLEX_BOX_SLOT_DYNAMIC,
FLEX_BOX_SLOT_STATIC,
FLEX_BOX_SLOT_VARIABLE,
FLEX_BOX_SLOT_FITTING
};
struct flex_box {
uiid id;
int *orientation;
int *flow;
int *spacing;
int *padding;
int *cnt;
};
api struct flex_box flex_box_begin(struct state *s);
api void flex_box_slot_dyn(struct state *s, struct flex_box *fbx);
api void flex_box_slot_static(struct state *s, struct flex_box *fbx, int pixel_width);
api void flex_box_slot_variable(struct state *s, struct flex_box *fbx, int min_pixel_width);
api void flex_box_slot_fitting(struct state *s, struct flex_box *fbx);
api void flex_box_end(struct state *s, struct flex_box *fbx);
api void flex_box_blueprint(struct box *b);
api void flex_box_layout(struct box *b);
api struct flex_box flex_box_ref(struct box *b);
/* overlap box */
struct overlap_box {
uiid id;
int slots;
int *cnt;
};
struct overlap_box_slot {
uiid *id;
int *zorder;
};
api struct overlap_box overlap_box_begin(struct state *s);
api void overlap_box_slot(struct state *s, struct overlap_box *obx, mid id);
api void overlap_box_end(struct state *s, struct overlap_box *obx);
api void overlap_box_layout(struct box *b, struct memory_arena *arena);
api void overlap_box_input(struct box *b, union event *evt, struct memory_arena *arena);
api struct overlap_box overlap_box_ref(struct box *b);
/* constraint box */
#define SPARENT (-1)
enum expr_op {EXPR_AND, EXPR_OR};
enum cond_eq {COND_EQ, COND_NE, COND_GR, COND_GE, COND_LS, COND_LE};
enum con_eq {CON_NOP, CON_SET, CON_MIN, CON_MAX};
enum con_attr {ATTR_NONE, ATTR_L, ATTR_T, ATTR_R, ATTR_B, ATTR_CX,
ATTR_CY, ATTR_W, ATTR_H, ATTR_DW, ATTR_DH};
struct cons {float mul; int off;};
struct convar {int slot; int attr;};
struct cond {unsigned char op; struct convar a, b; struct cons cons;};
struct con {int op; struct convar dst, src; struct cons cons; int anchor, cond;};
struct con_box_slot {int con_cnt, cons;};
struct con_box {
uiid id;
int *cond_cnt;
int *slot_cnt;
int conds;
};
api struct con_box con_box_begin(struct state *s, struct cond *conds, int cond_cnt);
api void con_box_slot(struct state *s, struct con_box *cbx, const struct con *cons, int con_cnt);
api void con_box_end(struct state *s, struct con_box *cbx);
api void con_box_layout(struct box *b, struct memory_arena *arena);
/* ---------------------------------------------------------------------------
* TRANSFORM
* --------------------------------------------------------------------------- */
/* clip box */
api uiid clip_box_begin(struct state *s);
api void clip_box_end(struct state *s);
/* scroll region */
enum scroll_direction {
SCROLL_DEFAULT,
SCROLL_REVERSE
};
struct scroll_region {
uiid id;
int *dir;
float *off_x;
float *off_y;
};
api struct scroll_region scroll_region_begin(struct state *s);
api void scroll_region_end(struct state *s);
api void scroll_region_layout(struct box *b);
api void scroll_region_input(struct context *ctx, struct box *b, const union event *evt);
api struct scroll_region scroll_region_ref(struct box *b);
/* scroll box */
api uiid scroll_box_begin(struct state *s);
api void scroll_box_end(struct state *s);
api void scroll_box_blueprint(struct box *b);
api void scroll_box_layout(struct box *b);
api void scroll_box_input(struct context *ctx, struct box *b, union event *evt);
/* zoom box */
struct zoom_box {
uiid id;
float *scale_x;
float *scale_y;
};
api struct zoom_box zoom_box_begin(struct state *s);
api void zoom_box_end(struct state *s);
api void zoom_box_input(struct box *b, union event *evt);
api struct zoom_box zoom_box_ref(struct box *b);
/* ---------------------------------------------------------------------------
* CONTAINER
* --------------------------------------------------------------------------- */
/* combo box */
struct combo_box {
struct combo combo;
struct flex_box fbx;
struct state *ps;
uiid *popup;
uiid *selid;
uiid *lblid;
};
api int combo_box(struct state *s, unsigned id, const char **items, int cnt);
api struct combo_box combo_box_begin(struct state *s, mid id);
api int combo_box_item(struct state *s, struct combo_box *cbx, const char *item, const char *end);
api void combo_box_end(struct state *s, struct combo_box *cbx);
api void combo_box_popup_input(struct context *ctx, struct box *b, union event *evt);
/* panel */
struct panel {
uiid id;
struct sborder sbx;
struct flex_box fbx;
};
api struct panel panel_begin(struct state *s);
api void panel_header(struct state *s, struct panel *pan, const char *title);
api uiid panel_header_begin(struct state *s, struct panel *pan);
api void panel_header_end(struct state *s, struct panel *pan);
api uiid panel_toolbar_begin(struct state *s, struct panel *pan);
api void panel_toolbar_end(struct state *s, struct panel *pan);
api void panel_content_begin(struct state *s, struct panel *pan);
api void panel_content_end(struct state *s, struct panel *pan);
api void panel_status(struct state *s, struct panel *pan, const char *status);
api uiid panel_status_begin(struct state *s, struct panel *pan);
api void panel_status_end(struct state *s, struct panel *pan);
/* panel box */
api struct panel panel_box_begin(struct state *s, const char *title);
api void panel_box_end(struct state *s, struct panel *pan, const char *status);
/* window */
struct window {
uiid id;
int *x;
int *y;
int *w;
int *h;
int *border;
};
api struct window window_begin(struct state *s, int x, int y, int w, int h);
api void window_end(struct state *s);
api void window_blueprint(struct box *b);
api void window_layout(struct box *b);
api void window_input(struct context *ctx, struct box *b, const union event *evt);
api struct window window_ref(struct box *b);
/* sidebar */
enum sidebar_state {
SIDEBAR_CLOSED,
SIDEBAR_OPEN,
SIDEBAR_PINNED,
SIDEBAR_DRAGGED
};
struct sidebar {
uiid id;
int *state;
float *ratio;
int *icon;
int *total;
struct con_box cbx;
struct panel pan;
};
api struct sidebar sidebar_begin(struct state *s);
api void sidebar_end(struct state *s, struct sidebar *sb);
api void sidebar_layout(struct box *b);
api void sidebar_input(struct box *b, union event *evt);
api void sidebar_bar_input(struct box *b, union event *evt);
api void sidebar_scaler_input(struct box *b, union event *evt);
api struct sidebar sidebar_ref(struct box *b);
/* ---------------------------------------------------------------------------
* COMPOSIT
* --------------------------------------------------------------------------- */
/* label button */
struct button_label {
struct button btn;
struct label lbl;
struct sbox sbx;
};
api int button_label_clicked(struct state *s, const char *label, const char *end);
api struct button_label button_label(struct state *s, const char *txt, const char *end);
api int button_label_poll(struct state *s, struct button_state *st, struct button_label *btn);
api int button_label_query(struct context *ctx, struct button_state *st, mid mid, struct button_label *btn);
/* icon button */
struct button_icon {
struct button btn;
struct icon ico;
struct sbox sbx;
};
api struct button_icon button_icon(struct state *s, int sym);
api int button_icon_clicked(struct state *s, int sym);
/* toogle */
struct toggle {
uiid id;
struct flex_box fbx;
struct label lbl;
struct icon icon;
int *val;
int *icon_def;
int *icon_act;
mid *mid;
uiid *iconid;
};
api struct toggle checkbox(struct state *s, int *checked, const char *txt, const char *end);
api struct toggle toggle(struct state *s, int *active, const char *txt, const char *end);
api struct toggle radio(struct state *s, int *sel, const char *txt, const char *end);
api void toggle_input(struct context *ctx, struct box *b, union event *evt);
api struct toggle toggle_ref(struct box *b);
#endif
#include <stdlib.h>
#include <stdio.h>
#include "qk.h"
#include "qk.c"
#include "widget.h"
#include "widget.c"
#include "layout.c"
#include "container.c"
/* ===========================================================================
*
* APP
*
* =========================================================================== */
#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 h32(s,i,x) h16(s,i,h16(s,i+16,x))
#define hash(s,i) ((unsigned)(h32(s,0,i)^(h32(s,0,i)>>32)))
#define idx(s,i) hash(s,(unsigned)i)
#define id(s) idx(s,0)
#define lit(s) s,len(s)
#define txt(s) s,s+strlen(s)
/* System */
#include <GL/gl.h>
#include <GL/glu.h>
#include "SDL2/SDL.h"
/* ---------------------------------------------------------------------------
* CANVAS
* --------------------------------------------------------------------------- */
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Woverlength-strings"
#pragma clang diagnostic ignored "-Wstrict-prototypes"
#pragma clang diagnostic ignored "-Wsign-conversion"
#pragma clang diagnostic ignored "-Wimplicit-function-declaration"
#pragma clang diagnostic ignored "-Wundef"
#pragma clang diagnostic ignored "-Wc99-extensions"
#pragma clang diagnostic ignored "-Wswitch-enum"
#pragma clang diagnostic ignored "-Wc11-extensions"
#pragma clang diagnostic ignored "-Wbad-function-cast"
#pragma clang diagnostic ignored "-Wdeclaration-after-statement"
#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
#pragma clang diagnostic ignored "-Wmissing-prototypes"
#pragma clang diagnostic ignored "-Wcast-qual"
#pragma clang diagnostic ignored "-Wconversion"
#pragma clang diagnostic ignored "-Wcomment"
#pragma clang diagnostic ignored "-Wsign-conversion"
#pragma clang diagnostic ignored "-Wtypedef-redefinition"
#pragma clang diagnostic ignored "-Wsign-compare"
#pragma clang diagnostic ignored "-Wcast-align"
#elif defined(__GNUC__) || defined(__GNUG__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Woverlength-strings"
#endif
/* NanoVG */
#define NANOVG_GL2_IMPLEMENTATION
#include "nanovg/src/fontstash.h"
#include "nanovg/src/stb_image.h"
#include "nanovg/src/stb_truetype.h"
#include "nanovg/src/nanovg.h"
#include "nanovg/src/nanovg.c"
#include "nanovg/src/nanovg_gl.h"
#include "IconsFontAwesome.h"
#ifdef __clang__
#pragma clang diagnostic pop
#elif defined(__GNUC__) || defined(__GNUG__)
#pragma GCC diagnostic pop
#endif
/* Hacks:
* To lazy to write complete gui render context on top of NanoVG with icon and
* image declaration and font,style tables addressable by index, etc ....
*/
static int default_icon_font;
enum icons {
ICON_CONFIG,
ICON_CHART_BAR,
ICON_DESKTOP,
ICON_DOWNLOAD,
ICON_FOLDER,
ICON_CNT
};
static int
nvgTextMeasure(void *usr, int font,
int fh, const char *txt, int len)
{
float bounds[4];
struct NVGcontext *vg = usr;
nvgFontFaceId(vg, font);
nvgFontSize(vg, fh);
nvgTextBounds(vg, 0,0, txt, txt + len, bounds);
return cast(int, bounds[2]);
}
static void
nvgLabel(struct NVGcontext *vg, struct box *b)
{
struct label lbl = label_ref(b);
nvgFontFaceId(vg, *lbl.font);
nvgFontSize(vg, *lbl.height);
nvgTextAlign(vg, NVG_ALIGN_LEFT|NVG_ALIGN_TOP);
nvgFillColor(vg, nvgRGBA(200,200,200,255));
nvgText(vg, b->x, b->y, lbl.txt, NULL);
}
static void
nvgIcon(struct NVGcontext *vg, struct box *b)
{
struct icon ico = icon_ref(b);
const char *icon_rune = 0;
nvgFontFaceId(vg, default_icon_font);
nvgFontSize(vg, *ico.size);
nvgTextAlign(vg, NVG_ALIGN_LEFT|NVG_ALIGN_TOP);
nvgFillColor(vg, nvgRGBA(220,220,220,255));
switch (*ico.sym) {
default: break;
case ICON_COMBO_CARRET_DOWN: icon_rune = ICON_FA_CARET_DOWN; break;
case ICON_UNCHECKED: icon_rune = ICON_FA_TIMES; break;
case ICON_CHECKED: icon_rune = ICON_FA_CHECK; break;
case ICON_UNTOGGLED: icon_rune = ICON_FA_TOGGLE_OFF; break;
case ICON_TOGGLED: icon_rune = ICON_FA_TOGGLE_ON; break;
case ICON_UNSELECTED: icon_rune = ICON_FA_CIRCLE; break;
case ICON_SELECTED: icon_rune = ICON_FA_CIRCLE_THIN; break;
case ICON_SIDEBAR_LOCK: icon_rune = ICON_FA_LOCK; break;
case ICON_SIDEBAR_UNLOCK: icon_rune = ICON_FA_UNLOCK_ALT; break;
case ICON_SIDEBAR_OPEN: icon_rune = ICON_FA_CARET_RIGHT; break;
case ICON_CONFIG: icon_rune = ICON_FA_COGS; break;
case ICON_CHART_BAR: icon_rune = ICON_FA_BAR_CHART; break;
case ICON_DESKTOP: icon_rune = ICON_FA_DESKTOP; break;
case ICON_DOWNLOAD: icon_rune = ICON_FA_DOWNLOAD; break;
case ICON_FOLDER: icon_rune = ICON_FA_FOLDER_OPEN; break;}
nvgText(vg, b->x, b->y, icon_rune, NULL);
}
enum nvgButtonStyle {
NVG_BUTTON_DEFAULT,
NVG_BUTTON_TRANSPARENT
};
static void
nvgButton(struct NVGcontext *vg, struct box *b)
{
NVGcolor col;
int style = *widget_get_int(b, 0);
switch (style) {
default:
case NVG_BUTTON_DEFAULT: {
if (b->hovered)
col = nvgRGBA(40,40,40,255);
else if (b->pressed)
col = nvgRGBA(35,35,35,255);
else col = nvgRGBA(55,55,55,255);
nvgBeginPath(vg);
nvgRect(vg, b->x,b->y, b->w,b->h);
nvgFillColor(vg, col);
nvgFill(vg);
nvgBeginPath(vg);
nvgRect(vg, b->x,b->y, b->w, b->h);
nvgStrokeColor(vg, nvgRGBA(60,60,60,255));
nvgStroke(vg);
} break;
case NVG_BUTTON_TRANSPARENT:
col = nvgRGBA(0,0,0,0);
if (b->hovered || b->pressed) {
nvgBeginPath(vg);
nvgRect(vg, b->x,b->y, b->w, b->h);
nvgStrokeColor(vg, nvgRGBA(60,60,60,255));
nvgStroke(vg);
} break;
}
}
static void
nvgSlider(struct NVGcontext *vg, struct box *b)
{
NVGpaint bg;
struct slider sld = slider_ref(b);
struct box *p = b->parent;
float cy = p->y+(int)(p->h*0.5f);
if (sld.cid != b->id) return;
/* Slot */
bg = nvgBoxGradient(vg, p->x,cy-2+1, p->w,4, 2,2, nvgRGBA(10,10,10,32), nvgRGBA(10,10,10,128));
nvgBeginPath(vg);
nvgRoundedRect(vg, p->x, cy-2, p->w, 4, 2);
nvgFillPaint(vg, bg);
nvgFill(vg);
/* knob */
nvgBeginPath(vg);
nvgRect(vg, b->x, b->y, b->w, b->h);
if (b->hovered)
nvgFillColor(vg, nvgRGBA(50,50,50,255));
else nvgFillColor(vg, nvgRGBA(60,60,60,255));
nvgFill(vg);
/* knob border */
nvgBeginPath(vg);
nvgRect(vg, b->x,b->y, b->w, b->h);
nvgStrokeColor(vg, nvgRGBA(70,70,70,255));
nvgStroke(vg);
}
static void
nvgScroll(struct NVGcontext *vg, struct box *b)
{
NVGcolor col;
struct box *p = b->parent;
struct scroll scrl = scroll_ref(b);
if (scrl.cid != b->id) return;
if (b->h == p->h && b->w == p->w)
return; /* skip if fully visible */
/* draw scroll background */
nvgBeginPath(vg);
nvgRect(vg, p->x,p->y, p->w,p->h);
nvgFillColor(vg, nvgRGBA(40,40,40,255));
nvgFill(vg);
/* draw scroll cursor */
if (b->hovered)
col = nvgRGBA(55,55,55,255);
else if (b->pressed)
col = nvgRGBA(50,50,50,255);
else col = nvgRGBA(60,60,60,255);
nvgBeginPath(vg);
nvgRect(vg, b->x,b->y, b->w,b->h);
nvgFillColor(vg, col);
nvgFill(vg);
}
static void
nvgComboPopup(struct NVGcontext *vg, struct box *b)
{
nvgBeginPath(vg);
nvgRect(vg, b->x,b->y, b->w,b->h);
nvgFillColor(vg, nvgRGBA(45,45,45,255));
nvgFill(vg);
nvgBeginPath(vg);
nvgRect(vg, b->x,b->y, b->w, b->h);
nvgStrokeColor(vg, nvgRGBA(60,60,60,255));
nvgStroke(vg);
}
static void
nvgClipRegion(struct NVGcontext *vg, struct box *b, int pidx, struct rect *sis)
{
uiid reset_id = *widget_get_id(b, pidx);
if (b->id != reset_id) {
/* safe old scissor rect and generate new one */
struct list_hook *t = b->lnks.prev;
struct box *n = list_entry(t, struct box, node);
n->x = sis->x, n->y = sis->y;
n->w = sis->w, n->h = sis->h;
{int minx = max(sis->x, b->x);
int miny = max(sis->y, b->y);
int maxx = min(sis->x + sis->w, b->x + b->w);
int maxy = min(sis->y + sis->h, b->y + b->h);
sis->x = minx, sis->y = miny;
sis->w = max(maxx - minx, 0);
sis->h = max(maxy - miny, 0);}
nvgScissor(vg, sis->x, sis->y, sis->w, sis->h);
} else {
/* reset to previous scissor rect */
nvgScissor(vg, b->x, b->y, b->w, b->h);
sis->x = b->x, sis->y = b->y;
sis->w = b->w, sis->h = b->h;
}
}
static void
nvgScaleRegion(struct NVGcontext *vg, struct box *b, float *x, float *y)
{
struct zoom_box sbx = zoom_box_ref(b);
if (b->id != sbx.id) {
float xform[6];
*x *= *sbx.scale_x;
*y *= *sbx.scale_y;
/* scale all content */
nvgTransformIdentity(xform);
nvgTransformScale(xform, *x, *y);
nvgCurrentTransform(vg, xform);
} else {
float xform[6];
*x /= *sbx.scale_x;
*y /= *sbx.scale_y;
/* reset scaler */
nvgTransformIdentity(xform);
nvgTransformScale(xform, *x, *y);
nvgCurrentTransform(vg, xform);
}
}
static void
nvgPanel(struct NVGcontext *vg, struct box *b)
{
nvgBeginPath(vg);
nvgRect(vg, b->x,b->y, b->w, b->h);
nvgFillColor(vg, nvgRGBA(45,45,45,255));
nvgFill(vg);
nvgBeginPath(vg);
nvgRect(vg, b->x,b->y, b->w, b->h);
nvgStrokeColor(vg, nvgRGBA(60,60,60,255));
nvgStroke(vg);
}
static void
nvgPanelHeader(struct NVGcontext *vg, struct box *b)
{
nvgBeginPath(vg);
nvgRect(vg, b->x,b->y, b->w,b->h);
nvgFillColor(vg, nvgRGBA(50,50,50,255));
nvgFill(vg);
}
static void
nvgSidebarScaler(struct NVGcontext *vg, struct box *b)
{
nvgBeginPath(vg);
nvgMoveTo(vg, b->x, b->y);
nvgLineTo(vg, b->x + b->w, b->y + 5);
nvgLineTo(vg, b->x + b->w, b->y + b->h - 5);
nvgLineTo(vg, b->x, b->y + b->h);
nvgClosePath(vg);
nvgFillColor(vg, nvgRGBA(35,35,35,255));
nvgFill(vg);
nvgStrokeColor(vg, nvgRGBA(60,60,60,255));
nvgStroke(vg);
}
static void
nvgSidebarBar(struct NVGcontext *vg, struct box *b)
{
nvgBeginPath(vg);
nvgMoveTo(vg, b->x, b->y);
nvgLineTo(vg, b->x + b->w, b->y + 10);
nvgLineTo(vg, b->x + b->w, b->y + b->h - 10);
nvgLineTo(vg, b->x, b->y + b->h);
nvgClosePath(vg);
nvgFillColor(vg, nvgRGBA(35,35,35,255));
nvgFill(vg);
nvgStrokeColor(vg, nvgRGBA(60,60,60,255));
nvgStroke(vg);
}
/* ---------------------------------------------------------------------------
* PLATFORM
* --------------------------------------------------------------------------- */
static void
ui_sdl_event(struct context *ctx, const SDL_Event *evt)
{
assert(ctx);
assert(evt);
switch (evt->type) {
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);
else if (evt->button.button == SDL_BUTTON_MIDDLE)
input_button(ctx, MOUSE_BUTTON_MIDDLE, down);
} break;
case SDL_KEYUP:
case SDL_KEYDOWN: {
int down = (evt->type == SDL_KEYDOWN);
SDL_Keycode sym = evt->key.keysym.sym;
if (sym == SDLK_BACKSPACE)
input_shortcut(ctx, SHORTCUT_SCROLL_REGION_RESET, down);
else if (sym == SDLK_LALT)
input_shortcut(ctx, SHORTCUT_SCROLL_REGION_SCROLL, down);
else if (sym == SDLK_HOME)
input_shortcut(ctx, SHORTCUT_SCROLL_BOX_BEGIN, down);
else if (sym == SDLK_END)
input_shortcut(ctx, SHORTCUT_SCROLL_BOX_END, down);
else if (sym == SDLK_PAGEUP)
input_shortcut(ctx, SHORTCUT_SCROLL_BOX_PGUP, down);
else if (sym == SDLK_PAGEDOWN)
input_shortcut(ctx, SHORTCUT_SCROLL_BOX_PGDN, down);
input_key(ctx, sym, down);
} break;}
}
static void
ui_commit(struct context *ctx)
{
union process *p = 0;
while ((p = process_begin(ctx, PROCESS_COMMIT))) {
assert(p->type == PROC_COMMIT);
commit(p);
process_end(p);
}
}
static void
ui_blueprint(struct NVGcontext *vg, union process *p, struct box *b)
{
switch (b->type) {
case WIDGET_LABEL: label_blueprint(b, vg, nvgTextMeasure); break;
case WIDGET_ICON: icon_blueprint(b); break;
case WIDGET_SLIDER: slider_blueprint(b); break;
case WIDGET_SBORDER: sborder_blueprint(b); break;
case WIDGET_FLEX_BOX: flex_box_blueprint(b); break;
case WIDGET_SCROLL_BOX: scroll_box_blueprint(b); break;
case WIDGET_WINDOW: window_blueprint(b); break;
default: blueprint(p, b); break;}
}
static void
ui_layout(struct NVGcontext *vg, union process *p, struct box *b)
{
switch (b->type) {
case WIDGET_ICON: icon_layout(b); break;
case WIDGET_SLIDER: slider_layout(b); break;
case WIDGET_SCROLL: scroll_layout(b); break;
case WIDGET_COMBO: combo_layout(p->hdr.ctx, b); break;
case WIDGET_SALIGN: salign_layout(b); break;
case WIDGET_SBORDER: sborder_layout(b); break;
case WIDGET_FLEX_BOX: flex_box_layout(b); break;
case WIDGET_OVERLAP_BOX: overlap_box_layout(b, p->hdr.arena); break;
case WIDGET_CON_BOX: con_box_layout(b, p->hdr.arena); break;
case WIDGET_SCROLL_REGION: scroll_region_layout(b); break;
case WIDGET_SCROLL_BOX: scroll_box_layout(b); break;
case WIDGET_SIDEBAR: sidebar_layout(b); break;
case WIDGET_WINDOW: window_layout(b); break;
default: layout(p, b); break;}
}
static void
ui_input(union process *p, union event *evt)
{
int i = 0;
struct context *ctx = p->hdr.ctx;
for (i = 0; i < evt->hdr.cnt; i++) {
struct box *b = evt->hdr.boxes[i];
switch (b->type) {
case WIDGET_CHECKBOX:
case WIDGET_TOGGLE:
case WIDGET_RADIO: toggle_input(ctx, b, evt); break;
case WIDGET_SLIDER: slider_input(b, evt); break;
case WIDGET_COMBO: combo_input(ctx, b, evt); break;
case WIDGET_COMBO_BOX_POPUP: combo_box_popup_input(ctx, b, evt); break;
case WIDGET_SCROLL: scroll_input(b, evt); break;
case WIDGET_SCROLL_REGION: scroll_region_input(ctx, b, evt); break;
case WIDGET_SCROLL_BOX: scroll_box_input(ctx, b, evt); break;
case WIDGET_OVERLAP_BOX: overlap_box_input(b, evt, p->hdr.arena); break;
case WIDGET_ZOOM_BOX: zoom_box_input(b, evt); break;
case WIDGET_SIDEBAR: sidebar_input(b, evt); break;
case WIDGET_SIDEBAR_BAR: sidebar_bar_input(b, evt); break;
case WIDGET_SIDEBAR_SCALER: sidebar_scaler_input(b, evt); break;
case WIDGET_WINDOW: window_input(ctx, b, evt); break;
case WIDGET_WINDOW_CONTENT: window_content_input(ctx, b, evt); break;
default: input(p, evt, b); break;}
}
}
static void
ui_draw(struct NVGcontext *vg, struct box *b,
struct rect *scissor, float *scale_x, float *scale_y)
{
switch (b->type) {
case WIDGET_LABEL: nvgLabel(vg, b); break;
case WIDGET_ICON: nvgIcon(vg, b); break;
case WIDGET_BUTTON: nvgButton(vg, b); break;
case WIDGET_SLIDER: nvgSlider(vg, b); break;
case WIDGET_COMBO_POPUP: nvgComboPopup(vg, b); break;
case WIDGET_SCROLL: nvgScroll(vg, b); break;
case WIDGET_SCROLL_REGION: nvgClipRegion(vg, b, 4, scissor); break;
case WIDGET_ZOOM_BOX: nvgScaleRegion(vg, b, scale_x, scale_y); break;
case WIDGET_PANEL: nvgPanel(vg, b); break;
case WIDGET_PANEL_HEADER: nvgPanelHeader(vg, b); break;
case WIDGET_PANEL_STATUSBAR: nvgPanelHeader(vg, b); break;
case WIDGET_SIDEBAR_BAR: nvgSidebarBar(vg, b); break;
case WIDGET_SIDEBAR_SCALER: nvgSidebarScaler(vg, b); break;
default: break;}
}
static void
ui_paint(struct NVGcontext *vg, struct context *ctx, int w, int h)
{
int i = 0;
union process *p = 0;
assert(vg);
assert(ctx);
if (!ctx || !vg) return;
/* Process: Paint */
while ((p = process_begin(ctx, PROCESS_PAINT))) {
switch (p->type) {default:break;
case PROC_COMMIT: commit(p); break;
case PROC_BLUEPRINT: {
/* not called unless PROC_INPUT generates new UI */
struct process_layouting *op = &p->layout;
for (i = op->begin; i != op->end; i += op->inc)
ui_blueprint(vg, p, op->boxes[i]);
} break;
case PROC_LAYOUT: {
/* not called unless PROC_INPUT generates new UI */
struct process_layouting *op = &p->layout;
for (i = op->begin; i != op->end; i += op->inc)
ui_layout(vg, p, op->boxes[i]);
} break;
case PROC_PAINT: {
struct process_paint *op = &p->paint;
struct rect scissor = {0,0,0,0};
float scale_x = 1.0f, scale_y = 1.0f;
scissor.w = w, scissor.h = h;
for (i = 0; i < op->cnt; ++i)
ui_draw(vg, op->boxes[i], &scissor, &scale_x, &scale_y);
nvgResetScissor(vg);
} break;}
process_end(p);
}
}
static void
ui_run(struct context *ctx, struct NVGcontext *vg)
{
int i; union process *p = 0;
while ((p = process_begin(ctx, PROCESS_INPUT))) {
switch (p->type) {default:break;
case PROC_COMMIT: commit(p); break;
case PROC_BLUEPRINT: {
struct process_layouting *op = &p->layout;
for (i = op->begin; i != op->end; i += op->inc)
ui_blueprint(vg, p, op->boxes[i]);
} break;
case PROC_LAYOUT: {
struct process_layouting *op = &p->layout;
for (i = op->begin; i != op->end; i += op->inc)
ui_layout(vg, p, op->boxes[i]);
} break;
case PROC_INPUT: {
struct list_hook *it = 0;
struct process_input *op = &p->input;
list_foreach(it, &op->evts) {
union event *evt = list_entry(it, union event, hdr.hook);
ui_input(p, evt); /* handle widget events */
}
} break;}
process_end(p);
}
}
/* ===========================================================================
*
* Main
*
* =========================================================================== */
struct ui_retained {
mid id;
struct button_label btn;
struct slider sld;
};
static void
ui_build_retained(struct context *ctx, struct ui_retained *ui, const char *label)
{
struct state *s = 0;
ui->id = id("retained");
s = begin(ctx, ui->id); {
struct panel pan = panel_box_begin(s, "Retained"); {
{struct flex_box fbx = flex_box_begin(s);
*fbx.orientation = FLEX_BOX_VERTICAL;
*fbx.spacing = 6;
{
/* label button */
flex_box_slot_fitting(s, &fbx);
ui->btn = button_label(s, label, 0);
/* slider */
{static float sld_val = 5.0f;
flex_box_slot_static(s, &fbx, 30);
ui->sld = sliderf(s, 0.0f, &sld_val, 10.0f);}
} flex_box_end(s, &fbx);}
} panel_box_end(s, &pan, 0);
} end(s);
}
static void
ui_build_serialized_tables(FILE *fp)
{
struct context *ctx;
struct config cfg;
cfg.font_default_id = 0;
cfg.font_default_height = 16;
ctx = create(DEFAULT_ALLOCATOR, &cfg);
{struct state *s = 0;
if ((s = begin(ctx, id("serialized_tables")))) {
struct panel pan = panel_box_begin(s, "Serialized: Compile Time"); {
struct flex_box fbx = flex_box_begin(s);
*fbx.orientation = FLEX_BOX_VERTICAL;
*fbx.spacing = 6, *fbx.padding = 6;
{
/* label button */
flex_box_slot_fitting(s, &fbx);
setid(s, id("SERIALIZED_BUTTON"));
button_label(s, txt("Label"));
/* icon buttons */
flex_box_slot_fitting(s, &fbx); {
struct flex_box gbx = flex_box_begin(s);
*gbx.padding = 0;
flex_box_slot_dyn(s, &gbx);
setid(s, id("SERIALIZED_BUTTON_CONFIG"));
button_icon(s, ICON_CONFIG);
flex_box_slot_dyn(s, &gbx);
setid(s, id("SERIALIZED_BUTTON_CHART"));
button_icon(s, ICON_CHART_BAR);
flex_box_slot_dyn(s, &gbx);
setid(s, id("SERIALIZED_BUTTON_DESKTOP"));
button_icon(s, ICON_DESKTOP);
flex_box_slot_dyn(s, &gbx);
setid(s, id("SERIALIZED_BUTTON_DOWNLOAD"));
button_icon(s, ICON_DOWNLOAD);
flex_box_end(s, &gbx);
}
} flex_box_end(s, &fbx);
} panel_box_end(s, &pan, 0);
end(s);
}}
/*trace(stdout, ctx);*/
ui_commit(ctx);
store_table(fp, ctx, "ui", 4);
cleanup(ctx);
}
static void
ui_load_serialized_tables(struct context *ctx)
{
static const struct element g_1555159930_elements[] = {
{1048584, 0lu, 0lu, 0lu, 0, 7, 0, 0, 0},
{196612, 6679361039399649282lu, 0lu, 6679361039399649281lu, 1, 8, 0, 0, 0},
{131075, 6679361039399649284lu, 6679361039399649282lu, 6679361039399649283lu, 2, 9, 0, 0, 0},
{131076, 6679361039399649286lu, 6679361039399649284lu, 6679361039399649285lu, 3, 10, 0, 6, 0},
{196613, 6679361039399649288lu, 6679361039399649286lu, 6679361039399649287lu, 4, 11, 0, 8, 0},
{131074, 6679361039399649290lu, 6679361039399649288lu, 6679361039399649289lu, 5, 12, 0, 8, 0},
{131072, 6679361039399649292lu, 6679361039399649290lu, 6679361039399649291lu, 6, 13, 0, 8, 0},
{131072, 6679361039399649293lu, 6679361039399649292lu, 6679361039399649291lu, 7, 14, 0, 8, 0},
{131073, 6679361039399649295lu, 6679361039399649293lu, 6679361039399649294lu, 8, 15, 0, 11, 0},
{131073, 6679361039399649296lu, 6679361039399649295lu, 6679361039399649294lu, 9, 16, 0, 11, 0},
{65537, 6679361039399649298lu, 6679361039399649296lu, 6679361039399649297lu, 10, 17, 0, 14, 0},
{131076, 6679361039399649300lu, 6679361039399649284lu, 6679361039399649299lu, 3, 10, 0, 17, 0},
{262147, 6679361039399649302lu, 6679361039399649300lu, 6679361039399649301lu, 4, 11, 0, 19, 0},
{65543, 6679361039399649304lu, 6679361039399649302lu, 6679361039399649303lu, 5, 12, 0, 19, 0},
{65543, 6679361039399649305lu, 6679361039399649304lu, 6679361039399649303lu, 6, 13, 0|BOX_MOVABLE_X|BOX_MOVABLE_Y, 19, 0},
{65543, 6679361039399649307lu, 6679361039399649302lu, 6679361039399649306lu, 5, 12, 0, 26, 0},
{65543, 6679361039399649308lu, 6679361039399649307lu, 6679361039399649306lu, 6, 13, 0|BOX_MOVABLE_X|BOX_MOVABLE_Y, 26, 0},
{262146, 6679361039399649310lu, 6679361039399649302lu, 6679361039399649309lu, 5, 12, 0, 33, 0},
{131075, 6679361039399649312lu, 6679361039399649310lu, 6679361039399649311lu, 6, 13, 0, 38, 0},
{131076, 6679361039399649314lu, 6679361039399649312lu, 6679361039399649313lu, 7, 14, 0, 44, 0},
{65538, 998704728lu, 6679361039399649314lu, 6679361039399649315lu, 8, 15, 0, 46, 0},
{131074, 6679361039399649317lu, 998704728lu, 6679361039399649316lu, 9, 16, 0, 47, 0},
{131072, 6679361039399649319lu, 6679361039399649317lu, 6679361039399649318lu, 10, 17, 0, 47, 0},
{131072, 6679361039399649320lu, 6679361039399649319lu, 6679361039399649318lu, 11, 18, 0, 47, 0},
{131073, 6679361039399649322lu, 6679361039399649320lu, 6679361039399649321lu, 12, 19, 0, 50, 0},
{131073, 6679361039399649323lu, 6679361039399649322lu, 6679361039399649321lu, 13, 20, 0, 50, 0},
{65537, 6679361039399649325lu, 6679361039399649323lu, 6679361039399649324lu, 14, 21, 0, 53, 0},
{131076, 6679361039399649327lu, 6679361039399649312lu, 6679361039399649326lu, 7, 14, 0, 56, 0},
{131075, 6679361039399649329lu, 6679361039399649327lu, 6679361039399649328lu, 8, 15, 0, 58, 0},
{131076, 6679361039399649331lu, 6679361039399649329lu, 6679361039399649330lu, 9, 16, 0, 64, 0},
{65538, 3567268847lu, 6679361039399649331lu, 6679361039399649332lu, 10, 17, 0, 66, 0},
{131074, 6679361039399649334lu, 3567268847lu, 6679361039399649333lu, 11, 18, 0, 67, 0},
{131072, 6679361039399649336lu, 6679361039399649334lu, 6679361039399649335lu, 12, 19, 0, 67, 0},
{131072, 6679361039399649337lu, 6679361039399649336lu, 6679361039399649335lu, 13, 20, 0, 67, 0},
{131073, 6679361039399649339lu, 6679361039399649337lu, 6679361039399649338lu, 14, 21, 0, 70, 0},
{131073, 6679361039399649340lu, 6679361039399649339lu, 6679361039399649338lu, 15, 22, 0, 70, 0},
{65536, 6679361039399649342lu, 6679361039399649340lu, 6679361039399649341lu, 16, 23, 0, 73, 0},
{131076, 6679361039399649344lu, 6679361039399649329lu, 6679361039399649343lu, 9, 16, 0, 75, 0},
{65538, 3282852853lu, 6679361039399649344lu, 6679361039399649345lu, 10, 17, 0, 77, 0},
{131074, 6679361039399649347lu, 3282852853lu, 6679361039399649346lu, 11, 18, 0, 78, 0},
{131072, 6679361039399649349lu, 6679361039399649347lu, 6679361039399649348lu, 12, 19, 0, 78, 0},
{131072, 6679361039399649350lu, 6679361039399649349lu, 6679361039399649348lu, 13, 20, 0, 78, 0},
{131073, 6679361039399649352lu, 6679361039399649350lu, 6679361039399649351lu, 14, 21, 0, 81, 0},
{131073, 6679361039399649353lu, 6679361039399649352lu, 6679361039399649351lu, 15, 22, 0, 81, 0},
{65536, 6679361039399649355lu, 6679361039399649353lu, 6679361039399649354lu, 16, 23, 0, 84, 0},
{131076, 6679361039399649357lu, 6679361039399649329lu, 6679361039399649356lu, 9, 16, 0, 86, 0},
{65538, 3042075085lu, 6679361039399649357lu, 6679361039399649358lu, 10, 17, 0, 88, 0},
{131074, 6679361039399649360lu, 3042075085lu, 6679361039399649359lu, 11, 18, 0, 89, 0},
{131072, 6679361039399649362lu, 6679361039399649360lu, 6679361039399649361lu, 12, 19, 0, 89, 0},
{131072, 6679361039399649363lu, 6679361039399649362lu, 6679361039399649361lu, 13, 20, 0, 89, 0},
{131073, 6679361039399649365lu, 6679361039399649363lu, 6679361039399649364lu, 14, 21, 0, 92, 0},
{131073, 6679361039399649366lu, 6679361039399649365lu, 6679361039399649364lu, 15, 22, 0, 92, 0},
{65536, 6679361039399649368lu, 6679361039399649366lu, 6679361039399649367lu, 16, 23, 0, 95, 0},
{131076, 6679361039399649370lu, 6679361039399649329lu, 6679361039399649369lu, 9, 16, 0, 97, 0},
{65538, 79436257lu, 6679361039399649370lu, 6679361039399649371lu, 10, 17, 0, 99, 0},
{131074, 6679361039399649373lu, 79436257lu, 6679361039399649372lu, 11, 18, 0, 100, 0},
{131072, 6679361039399649375lu, 6679361039399649373lu, 6679361039399649374lu, 12, 19, 0, 100, 0},
{131072, 6679361039399649376lu, 6679361039399649375lu, 6679361039399649374lu, 13, 20, 0, 100, 0},
{131073, 6679361039399649378lu, 6679361039399649376lu, 6679361039399649377lu, 14, 21, 0, 103, 0},
{131073, 6679361039399649379lu, 6679361039399649378lu, 6679361039399649377lu, 15, 22, 0, 103, 0},
{65536, 6679361039399649381lu, 6679361039399649379lu, 6679361039399649380lu, 16, 23, 0, 106, 0},
{262146, 6679361039399649382lu, 6679361039399649310lu, 6679361039399649309lu, 6, 13, 0|BOX_IMMUTABLE, 33, 0},
};
static const uiid g_1555159930_tbl_keys[128] = {
0lu,0lu,6679361039399649282lu,0lu,6679361039399649284lu,0lu,6679361039399649286lu,0lu,
6679361039399649288lu,0lu,6679361039399649290lu,0lu,6679361039399649292lu,6679361039399649293lu,0lu,6679361039399649295lu,
6679361039399649296lu,0lu,6679361039399649298lu,0lu,6679361039399649300lu,0lu,6679361039399649302lu,0lu,
6679361039399649304lu,6679361039399649305lu,0lu,6679361039399649307lu,6679361039399649308lu,0lu,6679361039399649310lu,0lu,
6679361039399649312lu,0lu,6679361039399649314lu,0lu,0lu,6679361039399649317lu,0lu,6679361039399649319lu,
6679361039399649320lu,0lu,6679361039399649322lu,6679361039399649323lu,0lu,6679361039399649325lu,0lu,6679361039399649327lu,
0lu,6679361039399649329lu,0lu,6679361039399649331lu,0lu,0lu,6679361039399649334lu,0lu,
6679361039399649336lu,6679361039399649337lu,0lu,6679361039399649339lu,6679361039399649340lu,0lu,6679361039399649342lu,0lu,
6679361039399649344lu,0lu,0lu,6679361039399649347lu,0lu,6679361039399649349lu,6679361039399649350lu,0lu,
6679361039399649352lu,6679361039399649353lu,0lu,6679361039399649355lu,0lu,6679361039399649357lu,3042075085lu,0lu,
6679361039399649360lu,0lu,6679361039399649362lu,6679361039399649363lu,0lu,6679361039399649365lu,6679361039399649366lu,0lu,
998704728lu,6679361039399649368lu,6679361039399649370lu,0lu,0lu,6679361039399649373lu,0lu,6679361039399649375lu,
6679361039399649376lu,79436257lu,6679361039399649378lu,6679361039399649379lu,0lu,6679361039399649381lu,6679361039399649382lu,0lu,
0lu,0lu,0lu,0lu,0lu,0lu,0lu,3567268847lu,
0lu,0lu,0lu,0lu,0lu,3282852853lu,0lu,0lu,
0lu,0lu,0lu,0lu,0lu,0lu,0lu,0lu
};
static const int g_1555159930_tbl_vals[128] = {
0,0,1,0,2,0,3,0,4,0,5,0,6,7,0,8,
9,0,10,0,11,0,12,0,13,14,0,15,16,0,17,0,
18,0,19,0,0,21,0,22,23,0,24,25,0,26,0,27,
0,28,0,29,0,0,31,0,32,33,0,34,35,0,36,0,
37,0,0,39,0,40,41,0,42,43,0,44,0,45,46,0,
47,0,48,49,0,50,51,0,20,52,53,0,0,55,0,56,
57,54,58,59,0,60,61,0,0,0,0,0,0,0,0,30,
0,0,0,0,0,38,0,0,0,0,0,0,0,0,0,0
};
static union param g_1555159930_params[108] = {
{6679361039399649284lu},{1lu},{0lu},{4lu},{0lu},{2lu},{3lu},{0lu},
{4lu},{5lu},{6679361039399649293lu},{1lu},{1lu},{6679361039399649296lu},{16lu},{0lu},
{0lu},{0lu},{0lu},{1065353216lu},{1065353216lu},{1065353216lu},{1065353216lu},{0lu},
{0lu},{6679361039399649305lu},{1065353216lu},{1065353216lu},{1065353216lu},{1065353216lu},{0lu},{0lu},
{6679361039399649308lu},{0lu},{0lu},{0lu},{6679361039399649310lu},{6679361039399649382lu},{6679361039399649312lu},{1lu},
{0lu},{6lu},{6lu},{2lu},{3lu},{0lu},{0lu},{6lu},
{6lu},{6679361039399649320lu},{1lu},{1lu},{6679361039399649323lu},{16lu},{0lu},{25lu},
{3lu},{0lu},{6679361039399649329lu},{0lu},{0lu},{4lu},{0lu},{4lu},
{0lu},{0lu},{0lu},{6lu},{6lu},{6679361039399649337lu},{1lu},{1lu},
{6679361039399649340lu},{0lu},{16lu},{0lu},{0lu},{0lu},{6lu},{6lu},
{6679361039399649350lu},{1lu},{1lu},{6679361039399649353lu},{1lu},{16lu},{0lu},{0lu},
{0lu},{6lu},{6lu},{6679361039399649363lu},{1lu},{1lu},{6679361039399649366lu},{2lu},
{16lu},{0lu},{0lu},{0lu},{6lu},{6lu},{6679361039399649376lu},{1lu},
{1lu},{6679361039399649379lu},{3lu},{16lu}
};
static const unsigned char g_1555159930_data[31] = {
0x53,0x65,0x72,0x69,0x61,0x6c,0x69,0x7a,0x65,0x64,0x3a,0x20,0x43,0x6f,0x6d,0x70,
0x69,0x6c,0x65,0x20,0x54,0x69,0x6d,0x65,0x00,0x4c,0x61,0x62,0x65,0x6c,0x00
};
static struct box *g_1555159930_bfs[63];
static struct box g_1555159930_boxes[62];
static struct module g_1555159930_module;
static struct repository g_1555159930_repo;
static const struct component g_1555159930_component = {
1,1555159930, 17, 7,g_1555159930_elements, cntof(g_1555159930_elements),
g_1555159930_tbl_vals, g_1555159930_tbl_keys,cntof(g_1555159930_tbl_keys),
g_1555159930_data, cntof(g_1555159930_data),
g_1555159930_params, cntof(g_1555159930_params),
&g_1555159930_module, &g_1555159930_repo, g_1555159930_boxes,
g_1555159930_bfs, cntof(g_1555159930_boxes)
};
static const struct container g_ui_containers[] = {
{1555159930, 0, 6, 0, 0, &g_1555159930_component},
};
load(ctx, g_ui_containers, cntof(g_ui_containers));
}
intern int
icon_label(struct state *s, int icon_id, const char *lbl)
{
struct button_state st;
struct button btn = button_begin(s);
button_poll(s, &st, &btn);
*btn.style = NVG_BUTTON_TRANSPARENT;
{
struct sborder sbr = sborder_begin(s);
*sbr.x = 6, *sbr.y = 6;
{
/* folder icon */
struct flex_box gbx = flex_box_begin(s);
*gbx.spacing = *gbx.padding = 0;
*gbx.orientation = FLEX_BOX_VERTICAL;
flex_box_slot_dyn(s, &gbx); {
struct salign aln = salign_begin(s);
*aln.horizontal = SALIGN_CENTER;
*aln.vertical = SALIGN_MIDDLE; {
struct icon ico = icon(s, icon_id);
*ico.size = 32;
} salign_end(s);
}
/* folder title */
flex_box_slot_fitting(s, &gbx); {
struct salign aln = salign_begin(s);
*aln.horizontal = SALIGN_CENTER;
*aln.vertical = SALIGN_MIDDLE;
label(s, txt(lbl));
salign_end(s);
} flex_box_end(s, &gbx);
} sborder_end(s);
} button_end(s);
return st.clicked;
}
static void
ui_sidebar(struct state *s)
{
static const char *folders[] = {
"Documents", "Download",
"Desktop", "Images",
"boot", "dev", "lib",
"include", "tmp",
"usr", "root", "bin", "local",
"src", "shaders", "srv", "sys",
"var", "proc", "mnt"
}; int i = 0;
struct sidebar sb = sidebar_begin(s); {
sborder_begin(s);
scroll_box_begin(s); {
struct flex_box fbx = flex_box_begin(s);
*fbx.flow = FLEX_BOX_WRAP, *fbx.padding = 0;
for (i = 0; i < cntof(folders); ++i) {
flex_box_slot_static(s, &fbx, 60);
if (icon_label(s, ICON_FOLDER, folders[i]))
printf("Button: %s clicked\n", folders[i]);
} flex_box_end(s, &fbx);
} scroll_box_end(s);
sborder_end(s);
} sidebar_end(s, &sb);
}
static void
ui_immediate_mode(struct state *s)
{
/* Immediate mode panel */
struct panel pan = panel_box_begin(s, "Immediate"); {
struct flex_box fbx = flex_box_begin(s);
*fbx.orientation = FLEX_BOX_VERTICAL;
*fbx.spacing = 6;
{
/* label button */
flex_box_slot_fitting(s, &fbx);
if (button_label_clicked(s, txt("Label")))
fprintf(stdout, "Button pressed\n");
/* icon buttons */
flex_box_slot_fitting(s, &fbx); {
struct flex_box gbx = flex_box_begin(s);
*gbx.padding = 0;
flex_box_slot_dyn(s, &gbx);
if (button_icon_clicked(s, ICON_CONFIG))
fprintf(stdout, "Button: Config pressed\n");
flex_box_slot_dyn(s, &gbx);
if (button_icon_clicked(s, ICON_CHART_BAR))
fprintf(stdout, "Button: Chart pressed\n");
flex_box_slot_dyn(s, &gbx);
if (button_icon_clicked(s, ICON_DESKTOP))
fprintf(stdout, "Button: Desktop pressed\n");
flex_box_slot_dyn(s, &gbx);
if (button_icon_clicked(s, ICON_DOWNLOAD))
fprintf(stdout, "Button: Download pressed\n");
flex_box_end(s, &gbx);
}
/* combo */
flex_box_slot_fitting(s, &fbx); {
static const char *items[] = {"Pistol","Shotgun","Plasma","BFG"};
combo_box(s, id("weapons"), items, cntof(items));
}
/* slider */
{static float sld_val = 5.0f;
flex_box_slot_static(s, &fbx, 30);
sliderf(s, 0.0f, &sld_val, 10.0f);}
/* checkbox */
flex_box_slot_fitting(s, &fbx); {
static int unchecked = 0, checked = 1;
struct flex_box gbx = flex_box_begin(s);
*gbx.padding = 0;
flex_box_slot_dyn(s, &gbx);
checkbox(s, &unchecked, "unchecked", 0);
flex_box_slot_dyn(s, &gbx);
checkbox(s, &checked, "checked", 0);
flex_box_end(s, &gbx);
}
/* toggle */
flex_box_slot_fitting(s, &fbx); {
static int inactive = 0, active = 1;
struct flex_box gbx = flex_box_begin(s);
*gbx.padding = 0;
flex_box_slot_dyn(s, &gbx);
toggle(s, &inactive, "inactive", 0);
flex_box_slot_dyn(s, &gbx);
toggle(s, &active, "active", 0);
flex_box_end(s, &gbx);
}
/* radio */
flex_box_slot_fitting(s, &fbx); {
static int unselected = 0, selected = 1;
struct flex_box gbx = flex_box_begin(s);
*gbx.padding = 0;
flex_box_slot_dyn(s, &gbx);
radio(s, &unselected, "unselected", 0);
flex_box_slot_dyn(s, &gbx);
radio(s, &selected, "selected", 0);
flex_box_end(s, &gbx);
}
} flex_box_end(s, &fbx);
} panel_box_end(s, &pan, 0);
}
int main(int argc, char *argv[])
{
enum fonts {FONT_HL, FONT_ICONS, FONT_CNT};
int quit = 0;
int fnts[FONT_CNT];
struct context *ctx;
struct ui_retained ui = {0};
struct NVGcontext *vg;
SDL_GLContext glContext;
/* SDL */
SDL_Window *win = 0;
SDL_Init(SDL_INIT_VIDEO);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
win = SDL_CreateWindow("GUI", SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, 1000, 600, SDL_WINDOW_SHOWN|SDL_WINDOW_OPENGL);
glContext = SDL_GL_CreateContext(win);
/* NanoVG */
vg = nvgCreateGL2(NVG_ANTIALIAS);
if (!vg) return -1;
fnts[FONT_HL] = nvgCreateFont(vg, "half_life", "half_life.ttf");
fnts[FONT_ICONS] = nvgCreateFont(vg, "icons", "icons.ttf");
default_icon_font = fnts[FONT_ICONS];
/* GUI */
{struct config cfg;
cfg.font_default_height = 16;
cfg.font_default_id = fnts[FONT_HL];
ctx = create(DEFAULT_ALLOCATOR, &cfg);}
input_resize(ctx, 1000, 600);
if (argc > 1) {
ui_build_serialized_tables(stdout);
return 0;
} else ui_load_serialized_tables(ctx);
ui_build_retained(ctx, &ui, "Initial");
while (!quit) {
/* Input */
int w, h;
{SDL_Event evt;
SDL_GetWindowSize(win, &w, &h);
input_resize(ctx, w, h);
while (SDL_PollEvent(&evt)) {
switch (evt.type) {
case SDL_QUIT: quit = 1; break;}
ui_sdl_event(ctx, &evt);
}}
/* UI */
{struct state *s = 0;
if ((s = begin(ctx, id("Main")))) {
ui_sidebar(s);
/* Panels */
{struct overlap_box obx = overlap_box_begin(s);
overlap_box_slot(s, &obx, id("Immdiate Mode")); {
window_begin(s, 600, 50, 180, 250);
ui_immediate_mode(s);
window_end(s);
}
overlap_box_slot(s, &obx, id("Retained Mode")); {
window_begin(s, 320, 50, 200, 130);
link(s, id("retained"), RELATIONSHIP_INDEPENDENT);
window_end(s);
}
overlap_box_slot(s, &obx, id("Compile Time")); {
window_begin(s, 320, 250, 200, 130);
link(s, id("serialized_tables"), RELATIONSHIP_INDEPENDENT);
window_end(s);
} overlap_box_end(s, &obx);}
} end(s);}
ui_run(ctx, vg);
/* Paint */
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
glClearColor(0.10f, 0.18f, 0.24f, 1);
nvgBeginFrame(vg, w, h, 1.0f);
ui_paint(vg, ctx, w, h);
nvgEndFrame(vg);
clear(ctx);
/* Finish frame */
SDL_GL_SwapWindow(win);
SDL_Delay(16);
}
cleanup(ctx);
/* Cleanup */
nvgDeleteGL2(vg);
SDL_DestroyWindow(win);
SDL_Quit();
return 0;
}
@dumblob
Copy link

dumblob commented Mar 3, 2018

On line 2569 and 2570 (revision 62a4077), there is a "double assignment":

root->w = root->w = in->width;
root->h = root->h = in->height;

On line 2571 the condition will never be true.

if (root->w != in->width || root->h != in->height)

Also I think the use of goto in the function at() is a little bit too much. Feel free to use the following version with standard loops (I think it's really more readable and not confusing).

intern struct box*
at(struct box *b, int mx, int my)
{
    struct list_hook *i = b->lnks.prev;
    for(;;){
      while (&b->lnks != i) {
          struct box *sub = list_entry(i, struct box, node);
          if (! (sub->flags & BOX_IMMUTABLE) && ! (sub->flags & BOX_HIDDEN)){
            if (inbox(mx, my, sub->x, sub->y, sub->w, sub->h)){
              b = sub;
              i = b->lnks.prev;
              continue;
            }
          }
          i = i->prev;
      }
      if (b->flags & BOX_UNSELECTABLE){
        i = b->node.prev;
        b = b->parent;
      }
      else
        break;
    }
    return b;
}

And on the line 4109 (revision 62a4077), there is a "double assignment":

n->w = max((b->x + b->w) - n->x, 0);
n->w = min(b->w, n->dw);

@vurtun
Copy link
Author

vurtun commented Mar 5, 2018

Thanks bugs should be fixed now.

Yeah the at function grow rather chaotically over time. I changed it to your implementation.
Still somewhat ugly function but way better than my version.

@dumblob
Copy link

dumblob commented Apr 1, 2018

In process_begin() the goto statement as follows is really not necessary (doesn't improve readability) and needlessly prevents potential compiler optimization. Plain if() condition would do a better job.

case STATE_LINK: {
    struct list_hook *it = 0;
    if (list_empty(&ctx->states))
        goto lnktbl;

Same goes for commit_begin() - goto (currently used through jmpto) is not necessary and a plain loop with the continue statement would make it more readable and probably also more performant (the predictor and caches etc. would guess better).

@vurtun
Copy link
Author

vurtun commented Apr 3, 2018

In process_begin() the goto statement as follows is really not necessary (doesn't improve readability) and needlessly prevents potential compiler optimization. Plain if() condition would do a better job.

I suspect you mean the old version since your referenced code does not exist anymore.

Same goes for commit_begin() - goto (currently used through jmpto) is not necessary and a plain loop with the continue statement would make it more readable and probably also more performant (the predictor and caches etc. would guess better).

Good catch. I just blindly copy & pasted the process_begin state machine without any deeper thought and did not notice that the goto was not required in this instance. Changed it and should be a little bit simpler now.

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