Last active
September 3, 2024 03:47
-
-
Save vurtun/c5b0374c27d2f5e9905bfbe7431d9dc0 to your computer and use it in GitHub Desktop.
Quarks: Graphical user interface
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |