-
-
Save qtxie/cfb673168d2f42346af5cee6b1b03e32 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; | |
struct event_int2 scroll; | |
struct event_int2 clicked; | |
struct event_int2 drag_begin; | |
struct event_int2 dragged; | |
struct event_int2 drag_end; | |
struct event_int2 pressed; | |
struct event_int2 released; | |
struct event_text text; | |
struct event_key key; | |
struct event_key shortcut; | |
}; | |
/* commands */ | |
enum cmd_type { | |
CMD_LNK, | |
CMD_CONCT, | |
CMD_CNT | |
}; | |
struct cmd_lnk { | |
enum cmd_type type; | |
mid parent_mid; | |
uiid parent_id; | |
mid child_mid; | |
uiid child_id; | |
}; | |
struct cmd_con { | |
enum cmd_type type; | |
mid parent, child; | |
int rel; | |
}; | |
union cmd { | |
enum cmd_type type; | |
struct cmd_lnk lnk; | |
struct cmd_con con; | |
}; | |
struct cmd_blk { | |
struct cmd_blk *next; | |
union cmd cmds[MAX_CMD_BUF]; | |
}; | |
struct cmd_buf { | |
int idx; | |
struct cmd_blk *list; | |
struct cmd_blk *buf; | |
volatile unsigned lock; | |
}; | |
/* operation */ | |
struct object; | |
enum operation_type { | |
OP_ALLOC_PERSISTENT, | |
OP_ALLOC_FRAME, | |
OP_COMPILE | |
}; | |
struct operation_alloc { | |
void *ptr; | |
int size; | |
}; | |
struct operation { | |
enum operation_type type; | |
struct operation_alloc alloc; | |
struct object *obj; | |
}; | |
struct object { | |
int state; | |
struct operation op; | |
struct context *ctx; | |
struct state *in; | |
struct repository *out; | |
struct cmd_buf *cmds; | |
struct memory_arena *mem; | |
}; | |
/* process */ | |
enum process_type { | |
PROC_COMMIT, | |
PROC_BLUEPRINT, | |
PROC_LAYOUT, | |
PROC_INPUT, | |
PROC_PAINT, | |
PROC_CLEAR, | |
PROC_CLEAR_FULL, | |
PROC_CLEANUP, | |
PROC_FREE_PERSISTENT, | |
PROC_FREE_FRAME, | |
PROC_CNT | |
}; | |
enum processes { | |
PROCESS_COMMIT = flag(PROC_COMMIT), | |
PROCESS_BLUEPRINT = PROCESS_COMMIT|flag(PROC_BLUEPRINT), | |
PROCESS_LAYOUT = PROCESS_BLUEPRINT|flag(PROC_LAYOUT), | |
PROCESS_INPUT = PROCESS_LAYOUT|flag(PROC_INPUT), | |
PROCESS_PAINT = PROCESS_LAYOUT|flag(PROC_PAINT), | |
PROCESS_CLEAR = flag(PROC_CLEAR), | |
PROCESS_CLEAR_FULL = PROCESS_CLEAR|flag(PROC_CLEAR_FULL), | |
PROCESS_CLEANUP = PROCESS_CLEAR_FULL|flag(PROC_CLEANUP) | |
}; | |
struct process_header { | |
enum process_type type; | |
struct context *ctx; | |
struct memory_arena *arena; | |
struct temp_memory tmp; | |
}; | |
struct process_commit { | |
struct process_header hdr; | |
struct object *objs; | |
int cnt; | |
struct cmd_buf lnks; | |
}; | |
struct process_layouting { | |
struct process_header hdr; | |
struct repository *repo; | |
int begin, end, inc; | |
struct box **boxes; | |
}; | |
struct process_input { | |
struct process_header hdr; | |
struct input *state; | |
struct list_hook evts; | |
}; | |
struct process_paint { | |
struct process_header hdr; | |
struct box **boxes; | |
int cnt; | |
}; | |
struct process_free { | |
struct process_header hdr; | |
void *ptr; | |
}; | |
union process { | |
/* base */ | |
enum process_type type; | |
struct process_header hdr; | |
/* derived */ | |
struct process_commit commit; | |
struct process_layouting layout; | |
struct process_layouting blueprint; | |
struct process_input input; | |
struct process_paint paint; | |
struct process_free free; | |
}; | |
/* context */ | |
enum visibility {HIDDEN, VISIBLE}; | |
enum popup_type { | |
POPUP_BLOCKING, | |
POPUP_NON_BLOCKING, | |
POPUP_CNT | |
}; | |
enum widget_internal { | |
WIDGET_INTERNAL_BEGIN = 0x100000, | |
/* layers */ | |
WIDGET_LAYER_BEGIN, | |
WIDGET_ROOT = WIDGET_LAYER_BEGIN, | |
WIDGET_OVERLAY, | |
WIDGET_POPUP, | |
WIDGET_CONTEXTUAL, | |
WIDGET_UNBLOCKING, | |
WIDGET_BLOCKING, | |
WIDGET_UI, | |
WIDGET_LAYER_END = WIDGET_UI, | |
/* widgets */ | |
WIDGET_TREE_ROOT, | |
WIDGET_LINK, | |
WIDGET_SLOT | |
}; | |
struct config { | |
int font_default_id; | |
int font_default_height; | |
}; | |
struct context { | |
int state; | |
unsigned seq; | |
struct config cfg; | |
struct input input; | |
union process proc; | |
unsigned unbalanced:1; | |
struct list_hook *iter; | |
/* memory */ | |
struct allocator mem; | |
struct block_allocator blkmem; | |
struct memory_arena arena; | |
volatile unsigned mem_lock; | |
/* modules */ | |
volatile unsigned module_lock; | |
struct list_hook states; | |
struct list_hook mod; | |
struct list_hook garbage; | |
/* tree */ | |
struct module root; | |
struct box boxes[8]; | |
struct box *bfs[8]; | |
struct repository repo; | |
struct box *tree; | |
struct box *overlay; | |
struct box *popup; | |
struct box *contextual; | |
struct box *unblocking; | |
struct box *blocking; | |
struct box *ui; | |
struct box *active; | |
struct box *origin; | |
struct box *hot; | |
}; | |
/* context */ | |
api struct context *create(const struct allocator *a, const struct config *cfg); | |
api int init(struct context *ctx, const struct allocator *a, const struct config *cfg); | |
api struct box *query(struct context *ctx, unsigned mid, uiid id); | |
api void reset(struct context *ctx); | |
api void destroy(struct context *ctx); | |
/* serialize */ | |
api void load(struct context *ctx, const struct container *c, int cnt); | |
api void store_table(FILE *fp, struct context *ctx, const char *name, int indent); | |
api void store_binary(FILE *fp, struct context *ctx); | |
api void trace(FILE *fp, struct context *ctx); | |
/* input */ | |
api void input_char(struct context *ctx, char c); | |
api void input_resize(struct context *ctx, int w, int h); | |
api void input_scroll(struct context *ctx, int x, int y); | |
api void input_motion(struct context *ctx, int x, int y); | |
api void input_rune(struct context *ctx, unsigned long r); | |
api void input_key(struct context *ctx, int key, int down); | |
api void input_shortcut(struct context *ctx, int id, int down); | |
api void input_text(struct context *ctx, const char *t, const char *end); | |
api void input_button(struct context *ctx, enum mouse_button btn, int down); | |
/* process */ | |
api union process* process_begin(struct context *ctx, unsigned flags); | |
api struct operation *commit_begin(struct context *ctx, struct process_commit*, int index); | |
api int compile(struct object *obj); | |
api void commit_end(struct operation *op); | |
api void commit(union process *p); | |
api void blueprint(union process *p, struct box *b); | |
api void layout(union process *p, struct box *b); | |
api void input(union process *p, union event *evt, struct box *b); | |
api void process_end(union process *p); | |
/* module */ | |
api struct state* begin(struct context *ctx, mid id); | |
api void end(struct state *s); | |
api struct state* module_begin(struct context *ctx, mid id, enum relationship, mid parent, mid owner, uiid bid); | |
api void module_end(struct state *s); | |
api struct state *section_begin(struct state *s, mid id); | |
api void section_end(struct state *s); | |
api void link(struct state *s, mid id, enum relationship); | |
api void slot(struct state *s, uiid id); | |
api struct box *polls(struct state *s, uiid id); | |
/* popup */ | |
api struct state *popup_begin(struct state *s, mid id, enum popup_type type); | |
api void popup_show(struct context *ctx, mid id, enum visibility vis); | |
api void popup_toggle(struct context *ctx, mid id); | |
api void popup_end(struct state *s); | |
api struct box *popup_find(struct context *ctx, mid id); | |
/* widget */ | |
api void widget_begin(struct state *s, int type); | |
api uiid widget_box(struct state *s); | |
api uiid widget_box_push(struct state *s); | |
api void widget_box_property_set(struct state *s, enum properties); | |
api void widget_box_property_clear(struct state *s, enum properties); | |
api void widget_box_pop(struct state *s); | |
api void widget_end(struct state *s); | |
/* parameter */ | |
api float *widget_param_float(struct state *s, float f); | |
api int *widget_param_int(struct state *s, int i); | |
api unsigned *widget_param_uint(struct state *s, unsigned u); | |
api uiid *widget_param_id(struct state *s, uiid id); | |
api mid *widget_param_mid(struct state *s, mid id); | |
api const char* widget_param_str(struct state *s, const char *str, int len); | |
api float* widget_modifier_float(struct state *s, float *f); | |
api int* widget_modifier_int(struct state *s, int *i); | |
api unsigned* widget_modifier_uint(struct state *s, unsigned *u); | |
api float* widget_state_float(struct state *s, float f); | |
api int* widget_state_int(struct state *s, int i); | |
api unsigned* widget_state_uint(struct state *s, unsigned u); | |
api uiid* widget_state_id(struct state *s, uiid u); | |
api float *widget_get_float(struct box *b, int idx); | |
api int *widget_get_int(struct box *b, int idx); | |
api unsigned *widget_get_uint(struct box *b, int indx); | |
api uiid *widget_get_id(struct box *b, int idx); | |
api mid *widget_get_mid(struct box *b, int idx); | |
api const char *widget_get_str(struct box *b, int idx); | |
/* box */ | |
api void box_shrink(struct box *d, const struct box *s, int pad); | |
api void box_blueprint(struct box *b, int padx, int pady); | |
api void box_layout(struct box *b, int pad); | |
api void box_pad(struct box *d, const struct box *s, int padx, int pady); | |
api int box_intersect(const struct box *a, const struct box *b); | |
/* id */ | |
api void pushid(struct state *s, unsigned id); | |
api void setid(struct state *s, uiid id); | |
api uiid genwid(struct state *s); | |
api uiid genbid(struct state *s); | |
api void popid(struct state *s); | |
/* list */ | |
#define list_empty(l) ((l)->next == (l)) | |
#define list_entry(ptr,type,member) containerof(ptr,type,member) | |
#define list_next(l) (l)->next | |
#define list_prev(l) (l)->prev | |
#define list_begin(l) list_next(l) | |
#define list_last(l) list_prev(l) | |
#define list_foreach(i,l) for ((i)=(l)->next; (i)!=(l); (i)=(i)->next) | |
#define list_foreach_rev(i,l) for ((i)=(l)->prev; (i)!=(l); (i)=(i)->prev) | |
#define list_foreach_s(i,n,l) for ((i)=(l)->next,(n)=(i)->next;(i)!=(l);(i)=(n),(n)=(i)->next) | |
#define list_foreach_rev_s(i,n,l) for ((i)=(l)->prev,(n)=(i)->prev;(i)!=(l);(i)=(n),(n)=(i)->prev) | |
api void list_init(struct list_hook *list); | |
api void list_add_head(struct list_hook *list, struct list_hook *n); | |
api void list_add_tail(struct list_hook *list, struct list_hook *n); | |
api void list_del(struct list_hook *entry); | |
api void list_move_head(struct list_hook *list, struct list_hook *entry); | |
api void list_move_tail(struct list_hook *list, struct list_hook *entry); | |
/* utf-8 */ | |
api int utf_encode(char *s, int cap, unsigned long u); | |
api int utf_decode(unsigned long *rune, const char *str, int len); | |
api int utf_len(const char *s, int len); | |
api const char* utf_at(unsigned long *rune, int *rune_len, const char *s, int len, int idx); | |
/* memory */ | |
#define arena_push_type(a,type) (type*)arena_push(a, 1, szof(type), alignof(type)) | |
#define arena_push_array(a,n,type) (type*)arena_push(a, (n), szof(type), alignof(type)) | |
api void *arena_push(struct memory_arena *a, int cnt, int size, int align); | |
/* math */ | |
api int npow2(int n); | |
api int floori(float x); | |
api int ceili(float x); | |
api float roundf(float x); | |
api int roundi(float x); | |
api int strn(const char *s); | |
#endif |
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" | |
#include "widget.h" | |
/* --------------------------------------------------------------------------- | |
* COMBO BOX | |
* --------------------------------------------------------------------------- */ | |
api void | |
combo_box_popup_input(struct context *ctx, struct box *b, union event *evt) | |
{ | |
int i = 0; | |
uiid *selid = 0; | |
if (evt->type != EVT_CLICKED) return; | |
if (evt->hdr.origin == b) return; | |
/* find combo box */ | |
{mid cbmid = *widget_get_mid(b, 0); | |
uiid cbid = *widget_get_id(b, 1); | |
struct box *cbx = query(ctx, cbmid, cbid); | |
if (!cbx) return; | |
assert(cbx->type == WIDGET_COMBO_BOX); | |
/* check if clicked on item */ | |
selid = widget_get_id(b, 2); | |
for (i = 0; i < evt->hdr.cnt; i++) { | |
struct box *l = evt->hdr.boxes[i]; | |
if (l->type != WIDGET_LABEL) continue; | |
*selid = l->id; | |
/* change selected label */ | |
{uiid lblid = *widget_get_id(b, 3); | |
struct box *lbx = query(ctx, cbmid, lblid); | |
if (!lbx) return; | |
lbx->params = l->params;} | |
/* close combo */ | |
{struct list_hook *cbh = list_begin(&cbx->lnks); | |
struct box *cb = list_entry(cbh, struct box, node); | |
struct combo c = combo_ref(cb); | |
combo_popup_close(ctx, &c);} | |
}} | |
} | |
api struct combo_box | |
combo_box_begin(struct state *s, mid id) | |
{ | |
uiid cid; | |
struct combo_box cbx; | |
widget_begin(s, WIDGET_COMBO_BOX); | |
cid = widget_box_push(s); | |
/* combo */ | |
cbx.combo = combo_begin(s, id); | |
cbx.ps = combo_popup_begin(s, &cbx.combo); | |
widget_begin(cbx.ps, WIDGET_COMBO_BOX_POPUP); | |
widget_box_push(cbx.ps); | |
widget_param_mid(cbx.ps, s->id); | |
widget_param_id(cbx.ps, cid); | |
cbx.selid = widget_state_id(cbx.ps, 0); | |
cbx.lblid = widget_param_id(cbx.ps, 0); | |
button_begin(s); | |
/* setup popup layout */ | |
cbx.fbx = flex_box_begin(cbx.ps); | |
*cbx.fbx.orientation = FLEX_BOX_VERTICAL; | |
return cbx; | |
} | |
api int | |
combo_box_item(struct state *s, struct combo_box *cbx, const char *item, const char *end) | |
{ | |
/* item */ | |
int selected = 0; | |
struct label lbl; | |
flex_box_slot_fitting(cbx->ps, &cbx->fbx); { | |
struct sbox sbx = sbox_begin(cbx->ps); | |
*sbx.align.horizontal = SALIGN_LEFT; | |
*sbx.align.vertical = SALIGN_MIDDLE; { | |
lbl = label(cbx->ps, item, end); | |
if (!*cbx->selid) *cbx->selid = lbl.id; | |
} sbox_end(cbx->ps); | |
} | |
if (lbl.id == *cbx->selid) { | |
/* combo header */ | |
struct flex_box fb = flex_box_begin(s); | |
flex_box_slot_dyn(s, &fb); { | |
/* selected item label */ | |
struct sborder sb = sborder_begin(s); | |
*sb.x = *sb.y = 2; | |
lbl = label(s, item, end); | |
*cbx->lblid = lbl.id; | |
sborder_end(s); | |
} | |
flex_box_slot_fitting(s, &fb); { | |
/* down arrow icon */ | |
struct sbox x = sbox_begin(s); | |
*x.align.horizontal = SALIGN_CENTER; | |
*x.align.vertical = SALIGN_MIDDLE; | |
*x.border.x = *x.border.y = 2; | |
icon(s, ICON_COMBO_CARRET_DOWN); | |
sbox_end(s); | |
} flex_box_end(s, &fb); | |
selected = 1; | |
} return selected; | |
} | |
api void | |
combo_box_end(struct state *s, struct combo_box *cbx) | |
{ | |
/* finish combo */ | |
flex_box_end(cbx->ps, &cbx->fbx); | |
widget_box_pop(cbx->ps); | |
widget_end(cbx->ps); | |
combo_popup_end(cbx->ps, &cbx->combo); | |
button_end(s); | |
combo_end(s); | |
/* finish combo box */ | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
api int | |
combo_box(struct state *s, unsigned id, const char **items, int cnt) | |
{ | |
int i = 0, sel = 0; | |
struct combo_box cbx; | |
cbx = combo_box_begin(s, id); | |
for (i = 0; i < cnt; ++i) { | |
if (combo_box_item(s, &cbx, items[i], 0)) sel = i; | |
} combo_box_end(s, &cbx); | |
return sel; | |
} | |
/* --------------------------------------------------------------------------- | |
* PANEL | |
* --------------------------------------------------------------------------- */ | |
api struct panel | |
panel_begin(struct state *s) | |
{ | |
struct panel pan = {0}; | |
widget_begin(s, WIDGET_PANEL); | |
pan.id = widget_box_push(s); | |
pan.fbx = flex_box_begin(s); | |
*pan.fbx.orientation = FLEX_BOX_VERTICAL; | |
*pan.fbx.padding = 0; | |
return pan; | |
} | |
api uiid | |
panel_header_begin(struct state *s, struct panel *pan) | |
{ | |
flex_box_slot_fitting(s, &pan->fbx); | |
widget_begin(s, WIDGET_PANEL_HEADER); | |
return widget_box_push(s); | |
} | |
api void | |
panel_header_end(struct state *s, struct panel *pan) | |
{ | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
api void | |
panel_header(struct state *s, struct panel *pan, const char *title) | |
{ | |
assert(s); | |
assert(pan); | |
if (!s || !pan || !title) return; | |
panel_header_begin(s, pan); { | |
struct sbox sbx = sbox_begin(s); | |
*sbx.border.y = 5; *sbx.border.x = 4; | |
*sbx.align.horizontal = SALIGN_CENTER; | |
*sbx.align.vertical = SALIGN_MIDDLE; | |
label(s, title, 0); | |
sbox_end(s); | |
} panel_header_end(s, pan); | |
} | |
api uiid | |
panel_toolbar_begin(struct state *s, struct panel *pan) | |
{ | |
flex_box_slot_fitting(s, &pan->fbx); | |
widget_begin(s, WIDGET_PANEL_TOOLBAR); | |
return widget_box_push(s); | |
} | |
api void | |
panel_toolbar_end(struct state *s, struct panel *pan) | |
{ | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
api void | |
panel_content_begin(struct state *s, struct panel *pan) | |
{ | |
flex_box_slot_dyn(s, &pan->fbx); | |
} | |
api void | |
panel_content_end(struct state *s, struct panel *pan) | |
{ | |
} | |
api uiid | |
panel_status_begin(struct state *s, struct panel *pan) | |
{ | |
flex_box_slot_fitting(s, &pan->fbx); | |
widget_begin(s, WIDGET_PANEL_STATUSBAR); | |
return widget_box_push(s); | |
} | |
api void | |
panel_status_end(struct state *s, struct panel *pan) | |
{ | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
api void | |
panel_status(struct state *s, struct panel *pan, const char *status) | |
{ | |
assert(s); | |
assert(pan); | |
if (!s || !pan || !status) return; | |
panel_status_begin(s, pan); { | |
struct sbox sbx = sbox_begin(s); | |
*sbx.border.y = 2; *sbx.border.x = 2; | |
*sbx.align.horizontal = SALIGN_LEFT; | |
*sbx.align.vertical = SALIGN_MIDDLE; | |
label(s, status, 0); | |
sbox_end(s); | |
} panel_status_end(s, pan); | |
} | |
api void | |
panel_end(struct state *s, struct panel *pan) | |
{ | |
flex_box_end(s, &pan->fbx); | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
/* --------------------------------------------------------------------------- | |
* PANEL BOX | |
* --------------------------------------------------------------------------- */ | |
api struct panel | |
panel_box_begin(struct state *s, const char *title) | |
{ | |
struct panel pan; | |
pan = panel_begin(s); | |
panel_header(s, &pan, title); | |
panel_content_begin(s, &pan); | |
scroll_box_begin(s); | |
return pan; | |
} | |
api void | |
panel_box_end(struct state *s, struct panel *pan, const char *status) | |
{ | |
scroll_box_end(s); | |
panel_content_end(s, pan); | |
panel_status(s, pan, status); | |
panel_end(s, pan); | |
} | |
/* --------------------------------------------------------------------------- | |
* WINDOW | |
* --------------------------------------------------------------------------- */ | |
api struct window | |
window_ref(struct box *b) | |
{ | |
struct window win = {0}; | |
win.x = widget_get_int(b, 0); | |
win.y = widget_get_int(b, 1); | |
win.w = widget_get_int(b, 2); | |
win.h = widget_get_int(b, 3); | |
win.border = widget_get_int(b, 4); | |
return win; | |
} | |
api struct window | |
window_begin(struct state *s, int x, int y, int w, int h) | |
{ | |
struct window win = {0}; | |
widget_begin(s, WIDGET_WINDOW); | |
win.id = widget_box_push(s); | |
win.x = widget_state_int(s, x); | |
win.y = widget_state_int(s, y); | |
win.w = widget_state_int(s, w); | |
win.h = widget_state_int(s, h); | |
win.border = widget_state_int(s, 3); | |
widget_begin(s, WIDGET_WINDOW_CONTENT); | |
widget_box_push(s); | |
return win; | |
} | |
api void | |
window_end(struct state *s) | |
{ | |
widget_box_pop(s); | |
widget_end(s); | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
api void | |
window_blueprint(struct box *b) | |
{ | |
struct window win = window_ref(b); | |
b->dw = *win.w; | |
b->dh = *win.h; | |
} | |
api void | |
window_layout(struct box *b) | |
{ | |
struct window win = window_ref(b); | |
b->x = *win.x; | |
b->y = *win.y; | |
b->w = *win.w; | |
b->h = *win.h; | |
box_layout(b, *win.border/2); | |
} | |
api void | |
window_input(struct context *ctx, struct box *b, const union event *evt) | |
{ | |
int cx, cy, dx, dy; | |
struct window win; | |
struct input *in = evt->hdr.input; | |
if (evt->type != EVT_DRAGGED) return; | |
if (evt->hdr.origin != b) return; | |
win = window_ref(b); | |
cx = *win.x + (*win.w >> 1); | |
cy = *win.y + (*win.h >> 1); | |
dx = in->mouse.x - cx; | |
dy = in->mouse.y - cy; | |
if (abs(dx) > abs(dy)) { | |
if (dx < 0) { | |
*win.x += evt->dragged.x; | |
*win.w -= evt->dragged.x; | |
} else *win.w += evt->dragged.x; | |
} else { | |
if (dy < 0) { | |
*win.y += evt->dragged.y; | |
*win.h -= evt->dragged.y; | |
} else *win.h += evt->dragged.y; | |
} ctx->unbalanced = 1; | |
} | |
api void | |
window_content_input(struct context *ctx, struct box *c, const union event *evt) | |
{ | |
int i = 0; | |
struct window win; | |
struct box *b = c->parent; | |
if (evt->type != EVT_DRAGGED) return; | |
if (evt->hdr.origin != b) { | |
for (i = 0; i < evt->hdr.cnt; i++) { | |
struct box *n = evt->hdr.boxes[i]; | |
switch (n->type) { | |
default: break; | |
case WIDGET_PANEL_HEADER: goto move;} | |
} return; | |
} | |
move: | |
win = window_ref(b); | |
*win.x += evt->dragged.x; | |
*win.y += evt->dragged.y; | |
ctx->unbalanced = 1; | |
b->moved = 1; | |
} | |
/* --------------------------------------------------------------------------- | |
* SIDEBAR | |
* --------------------------------------------------------------------------- */ | |
api struct sidebar | |
sidebar_ref(struct box *b) | |
{ | |
struct sidebar sb; | |
sb.state = widget_get_int(b,0); | |
sb.ratio = widget_get_float(b,1); | |
sb.icon = widget_get_int(b,2); | |
sb.total = widget_get_int(b,3); | |
return sb; | |
} | |
intern void | |
sidebar_validate_icon(struct sidebar *sb) | |
{ | |
switch (*sb->state) { | |
case SIDEBAR_OPEN: *sb->icon = ICON_SIDEBAR_LOCK; break; | |
case SIDEBAR_CLOSED: *sb->icon = ICON_SIDEBAR_OPEN; break; | |
case SIDEBAR_PINNED: *sb->icon = ICON_SIDEBAR_UNLOCK; break; | |
case SIDEBAR_DRAGGED: *sb->icon = ICON_SIDEBAR_UNLOCK; break;} | |
} | |
api struct sidebar | |
sidebar_begin(struct state *s) | |
{ | |
/* sidebar */ | |
struct sidebar sb = {0}; | |
sb.cbx = con_box_begin(s, 0, 0); | |
{static const struct con sb_cons[] = { | |
{CON_SET, {0,ATTR_W}, {SPARENT,ATTR_W}, {0.5f,0}}, | |
{CON_SET, {0,ATTR_H}, {SPARENT,ATTR_H}, {0.6f,0}}, | |
{CON_SET, {0,ATTR_L}, {SPARENT,ATTR_L}, {1,0}, ATTR_W}, | |
{CON_SET, {0,ATTR_CY}, {SPARENT,ATTR_CY}, {1,0}, ATTR_H} | |
}; con_box_slot(s, &sb.cbx, sb_cons, cntof(sb_cons));} | |
widget_begin(s, WIDGET_SIDEBAR); | |
sb.id = widget_box_push(s); | |
sb.state = widget_state_int(s, SIDEBAR_CLOSED); | |
sb.ratio = widget_state_float(s, 0.5f); | |
sb.icon = widget_param_int(s, ICON_SIDEBAR_LOCK); | |
sb.total = widget_param_int(s, 0); | |
sidebar_validate_icon(&sb); | |
/* bar */ | |
widget_begin(s, WIDGET_SIDEBAR_BAR); | |
sb.id = widget_box_push(s); { | |
struct salign aln = salign_begin(s); | |
*aln.vertical = SALIGN_MIDDLE; | |
*aln.horizontal = SALIGN_RIGHT; | |
icon(s, *sb.icon); | |
salign_end(s); | |
} widget_box_pop(s); | |
widget_end(s); | |
/* scaler */ | |
widget_begin(s, WIDGET_SIDEBAR_SCALER); | |
widget_box_push(s); | |
if (*sb.state == SIDEBAR_CLOSED) | |
widget_box_property_set(s, BOX_HIDDEN); | |
widget_box_property_set(s, BOX_MOVABLE_X); | |
widget_box_pop(s); | |
widget_end(s); | |
/* content */ | |
widget_begin(s, WIDGET_SIDEBAR_CONTENT); | |
widget_box_push(s); | |
if (*sb.state == SIDEBAR_CLOSED) | |
widget_box_property_set(s, BOX_HIDDEN); | |
sb.pan = panel_begin(s); | |
panel_content_begin(s, &sb.pan); | |
return sb; | |
} | |
api void | |
sidebar_end(struct state *s, struct sidebar *sb) | |
{ | |
panel_content_end(s, &sb->pan); | |
panel_end(s, &sb->pan); | |
widget_box_pop(s); | |
widget_end(s); | |
widget_box_pop(s); | |
widget_end(s); | |
con_box_end(s, &sb->cbx); | |
} | |
api void | |
sidebar_layout(struct box *b) | |
{ | |
struct sidebar sb = sidebar_ref(b); | |
struct list_hook *hb = b->lnks.next; | |
struct list_hook *hs = hb->next; | |
struct list_hook *hc = hs->next; | |
struct box *bb = list_entry(hb, struct box, node); | |
struct box *bs = list_entry(hs, struct box, node); | |
struct box *bc = list_entry(hc, struct box, node); | |
/* bar */ | |
bb->w = 18, bb->h = b->h; | |
bb->x = b->x, bb->y = (b->y + b->h/2) - (bb->h/2); | |
/* scaler */ | |
bs->w = 8, bs->h = bb->h - 20; | |
bs->x = floori(cast(float, b->w) * *sb.ratio) - bs->w; | |
bs->x = max(bb->x + bb->w, bs->x); | |
bs->y = bb->y + 10; | |
/* content */ | |
bc->x = bb->x + bb->w, bc->y = bs->y; | |
bc->w = bs->x - (bb->x + bb->w), bc->h = bs->h; | |
/* fit sidebar */ | |
*sb.total = b->w; | |
b->y = bb->y, b->h = bb->h; | |
b->w = (bs->x + bs->w) - b->x; | |
} | |
intern void | |
sidebar_validate(struct sidebar *sb, struct box *b) | |
{ | |
/* retrieve scaler, content */ | |
struct list_hook *hc = b->node.prev; | |
{struct list_hook *hs = hc->prev; | |
struct box *bs = list_entry(hs, struct box, node); | |
struct box *bc = list_entry(hc, struct box, node); | |
/* toggle visiblity */ | |
switch (*sb->state) { | |
case SIDEBAR_CLOSED: | |
bs->flags |= BOX_HIDDEN; | |
bc->flags |= BOX_HIDDEN; | |
break; | |
case SIDEBAR_OPEN: | |
case SIDEBAR_PINNED: | |
bs->flags &= ~(unsigned)BOX_HIDDEN; | |
bc->flags &= ~(unsigned)BOX_HIDDEN; | |
break; | |
}} | |
} | |
api void | |
sidebar_input(struct box *b, union event *evt) | |
{ | |
struct sidebar sb = sidebar_ref(b); | |
if (!b->exited) return; | |
switch (evt->type) { | |
default: return; | |
case EVT_EXITED: { | |
switch (*sb.state) { | |
case SIDEBAR_OPEN: | |
*sb.state = SIDEBAR_CLOSED; | |
sidebar_validate(&sb, b); | |
sidebar_validate_icon(&sb); break;} | |
} break;} | |
} | |
api void | |
sidebar_bar_input(struct box *b, union event *evt) | |
{ | |
struct box *p = b->parent; | |
struct sidebar sb = sidebar_ref(p); | |
/* state change */ | |
switch (evt->type) { | |
default: return; | |
case EVT_CLICKED: { | |
switch (*sb.state) { | |
default: return; | |
case SIDEBAR_CLOSED: *sb.state = SIDEBAR_OPEN; break; | |
case SIDEBAR_OPEN: *sb.state = SIDEBAR_PINNED; break; | |
case SIDEBAR_PINNED: *sb.state = SIDEBAR_OPEN; break;} | |
} break;} | |
sidebar_validate(&sb, p); | |
sidebar_validate_icon(&sb); | |
} | |
api void | |
sidebar_scaler_input(struct box *b, union event *evt) | |
{ | |
struct box *p = b->parent; | |
struct sidebar sb = sidebar_ref(p); | |
if (evt->hdr.origin != b) return; | |
switch (evt->type){ | |
case EVT_DRAG_BEGIN: *sb.state = SIDEBAR_DRAGGED; break; | |
case EVT_DRAG_END: *sb.state = SIDEBAR_PINNED; break; | |
case EVT_MOVED: { | |
if (!b->moved) return; | |
*sb.ratio = cast(float,(b->x + b->w) - p->x) / cast(float, *sb.total); | |
*sb.ratio = clamp(0.2f, *sb.ratio, 1.0f); | |
} break;} | |
sidebar_validate_icon(&sb); | |
} |
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" | |
#include "widget.h" | |
/* --------------------------------------------------------------------------- | |
* SBORDER | |
* --------------------------------------------------------------------------- */ | |
api struct sborder | |
sborder_ref(struct box *b) | |
{ | |
struct sborder sbx; | |
sbx.x = widget_get_int(b, 0); | |
sbx.y = widget_get_int(b, 1); | |
sbx.content = *widget_get_id(b, 2); | |
sbx.id = b->id; | |
return sbx; | |
} | |
api struct sborder | |
sborder_begin(struct state *s) | |
{ | |
struct sborder sbx; | |
widget_begin(s, WIDGET_SBORDER); | |
sbx.x = widget_param_int(s, 6); | |
sbx.y = widget_param_int(s, 6); | |
sbx.id = widget_box_push(s); | |
sbx.content = widget_box_push(s); | |
widget_param_id(s, sbx.content); | |
return sbx; | |
} | |
api void | |
sborder_end(struct state *s) | |
{ | |
widget_box_pop(s); | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
api void | |
sborder_blueprint(struct box *b) | |
{ | |
struct sborder sbx = sborder_ref(b); | |
if (b->id != sbx.content) | |
box_blueprint(b, *sbx.x, *sbx.y); | |
else box_blueprint(b, 0, 0); | |
} | |
api void | |
sborder_layout(struct box *b) | |
{ | |
struct box *p = b->parent; | |
struct sborder sbx = sborder_ref(b); | |
if (b->id != sbx.content) return; | |
box_pad(b, p, *sbx.x, *sbx.y); | |
box_layout(b, 0); | |
} | |
/* --------------------------------------------------------------------------- | |
* SALIGN | |
* --------------------------------------------------------------------------- */ | |
api struct salign | |
salign_ref(struct box *b) | |
{ | |
struct salign aln; | |
aln.vertical = widget_get_int(b, 0); | |
aln.horizontal = widget_get_int(b, 1); | |
aln.content = *widget_get_id(b, 2); | |
aln.id = b->id; | |
return aln; | |
} | |
api struct salign | |
salign_begin(struct state *s) | |
{ | |
struct salign aln; | |
widget_begin(s, WIDGET_SALIGN); | |
aln.vertical = widget_param_int(s, SALIGN_TOP); | |
aln.horizontal = widget_param_int(s, SALIGN_LEFT); | |
aln.id = widget_box_push(s); | |
aln.content = widget_box_push(s); | |
widget_param_id(s, aln.content); | |
return aln; | |
} | |
api void | |
salign_end(struct state *s) | |
{ | |
widget_box_pop(s); | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
api void | |
salign_layout(struct box *b) | |
{ | |
struct box *p = b->parent; | |
struct salign aln = salign_ref(b); | |
struct list_hook *h = 0; | |
if (b->id != aln.content) return; | |
box_pad(b, p, 0, 0); | |
/* horizontal alignment */ | |
switch (*aln.horizontal) { | |
case SALIGN_LEFT: | |
list_foreach(h, &b->lnks) { | |
struct box *n = list_entry(h, struct box, node); | |
n->x = b->x; | |
n->w = min(b->w, n->dw); | |
} break; | |
case SALIGN_CENTER: | |
list_foreach(h, &b->lnks) { | |
struct box *n = list_entry(h, struct box, node); | |
n->w = max((b->x + b->w) - n->x, 0); | |
n->w = min(n->w, n->dw); | |
n->x = max(b->x, (b->x + b->w/2) - n->w/2); | |
} break; | |
case SALIGN_RIGHT: | |
list_foreach(h, &b->lnks) { | |
struct box *n = list_entry(h, struct box, node); | |
n->w = min(n->dw, b->w); | |
n->x = max(b->x, b->x + b->w - n->w); | |
} break;} | |
/* vertical alignment */ | |
switch (*aln.vertical) { | |
case SALIGN_TOP: | |
list_foreach(h, &b->lnks) { | |
struct box *n = list_entry(h, struct box, node); | |
n->h = min(n->dh, b->h); | |
n->y = b->y; | |
} break; | |
case SALIGN_MIDDLE: | |
list_foreach(h, &b->lnks) { | |
struct box *n = list_entry(h, struct box, node); | |
n->y = max(b->y,(b->y + b->h/2) - n->dh/2); | |
n->h = max((b->y + b->h) - n->y, 0); | |
} break; | |
case SALIGN_BOTTOM: | |
list_foreach(h, &b->lnks) { | |
struct box *n = list_entry(h, struct box, node); | |
n->y = max(b->y,b->y + b->h - n->dh); | |
n->h = min(n->dh, n->h); | |
} break;} | |
/* reshape sbox after content */ | |
{int x = b->x + b->w; | |
int y = b->y + b->h; | |
list_foreach(h, &b->lnks) { | |
struct box *n = list_entry(h, struct box, node); | |
b->x = max(b->x, n->x); | |
b->y = max(b->y, n->y); | |
x = min(x, n->x + n->w); | |
y = min(y, n->y + n->h); | |
} | |
b->w = x - b->x; | |
b->h = y - b->y; | |
box_pad(p, b, 0, 0);} | |
box_layout(b, 0); | |
} | |
/* --------------------------------------------------------------------------- | |
* SBOX | |
* --------------------------------------------------------------------------- */ | |
api struct sbox | |
sbox_begin(struct state *s) | |
{ | |
struct sbox sbx = {0}; | |
widget_begin(s, WIDGET_SBOX); | |
sbx.id = widget_box_push(s); | |
sbx.border = sborder_begin(s); | |
sbx.align = salign_begin(s); | |
return sbx; | |
} | |
api void | |
sbox_end(struct state *s) | |
{ | |
salign_end(s); | |
sborder_end(s); | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
/* --------------------------------------------------------------------------- | |
* FLEX BOX | |
* --------------------------------------------------------------------------- */ | |
api struct flex_box | |
flex_box_ref(struct box *b) | |
{ | |
struct flex_box fbx; | |
fbx.id = *widget_get_id(b, 0); | |
fbx.orientation = widget_get_int(b, 1); | |
fbx.flow = widget_get_int(b, 2); | |
fbx.spacing = widget_get_int(b, 3); | |
fbx.padding = widget_get_int(b, 4); | |
fbx.cnt = widget_get_int(b, 5); | |
return fbx; | |
} | |
api struct flex_box | |
flex_box_begin(struct state *s) | |
{ | |
struct flex_box fbx; | |
widget_begin(s, WIDGET_FLEX_BOX); | |
fbx.id = widget_box_push(s); | |
widget_param_id(s, fbx.id); | |
fbx.orientation = widget_param_int(s, FLEX_BOX_HORIZONTAL); | |
fbx.flow = widget_param_int(s, FLEX_BOX_STRETCH); | |
fbx.spacing = widget_param_int(s, 4); | |
fbx.padding = widget_param_int(s, 4); | |
fbx.cnt = widget_param_int(s, 0); | |
return fbx; | |
} | |
intern void | |
flex_box_slot(struct state *s, struct flex_box *fbx, | |
enum flex_box_slot_type type, int value) | |
{ | |
uiid id = 0; | |
int idx = *fbx->cnt; | |
if (idx) { | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
widget_begin(s, WIDGET_FLEX_BOX_SLOT); | |
id = widget_box_push(s); | |
widget_param_int(s, (int)type); | |
widget_param_int(s, value); | |
*fbx->cnt = *fbx->cnt + 1; | |
} | |
api void | |
flex_box_slot_dyn(struct state *s, struct flex_box *fbx) | |
{ | |
assert(*fbx->flow != FLEX_BOX_WRAP); | |
flex_box_slot(s, fbx, FLEX_BOX_SLOT_DYNAMIC, 0); | |
} | |
api void | |
flex_box_slot_static(struct state *s, | |
struct flex_box *fbx, int pixel_width) | |
{ | |
flex_box_slot(s, fbx, FLEX_BOX_SLOT_STATIC, pixel_width); | |
} | |
api void | |
flex_box_slot_variable(struct state *s, | |
struct flex_box *fbx, int min_pixel_width) | |
{ | |
assert(*fbx->flow != FLEX_BOX_WRAP); | |
flex_box_slot(s, fbx, FLEX_BOX_SLOT_VARIABLE, min_pixel_width); | |
} | |
api void | |
flex_box_slot_fitting(struct state *s, struct flex_box *fbx) | |
{ | |
flex_box_slot(s, fbx, FLEX_BOX_SLOT_FITTING, 0); | |
} | |
api void | |
flex_box_end(struct state *s, struct flex_box *fbx) | |
{ | |
int idx = *fbx->cnt; | |
if (idx) { | |
widget_box_pop(s); | |
widget_end(s); | |
} widget_box_pop(s); | |
widget_end(s); | |
} | |
api void | |
flex_box_blueprint(struct box *b) | |
{ | |
struct list_hook *it = 0; | |
struct flex_box fbx = flex_box_ref(b); | |
b->dw = b->dh = 0; | |
list_foreach(it, &b->lnks) { | |
struct box *sb = list_entry(it, struct box, node); | |
int styp = *widget_get_int(sb, 0); | |
int spix = *widget_get_int(sb, 1); | |
/* horizontal layout */ | |
switch (*fbx.orientation) { | |
case FLEX_BOX_HORIZONTAL: { | |
switch (styp) { | |
case FLEX_BOX_SLOT_DYNAMIC: | |
b->dw += sb->dw; break; | |
case FLEX_BOX_SLOT_STATIC: | |
b->dw += spix; break; | |
case FLEX_BOX_SLOT_FITTING: | |
case FLEX_BOX_SLOT_VARIABLE: | |
b->dw += max(sb->dw, spix); break; | |
} b->dh = max(sb->dh + *fbx.padding*2, b->dh); | |
} break; | |
/* vertical layout */ | |
case FLEX_BOX_VERTICAL: { | |
switch (styp) { | |
case FLEX_BOX_SLOT_DYNAMIC: | |
b->dh += sb->dh; break; | |
case FLEX_BOX_SLOT_STATIC: | |
b->dh += spix; break; | |
case FLEX_BOX_SLOT_FITTING: | |
case FLEX_BOX_SLOT_VARIABLE: | |
b->dh += max(sb->dh, spix); break; | |
} b->dw = max(sb->dw + *fbx.padding*2, b->dw); | |
} break;} | |
} | |
/* padding + spacing */ | |
{int pad = *fbx.spacing * (*fbx.cnt-1) + *fbx.padding * 2; | |
switch (*fbx.orientation) { | |
case FLEX_BOX_HORIZONTAL: b->dw += pad; break; | |
case FLEX_BOX_VERTICAL: b->dh += pad; break;}} | |
} | |
api void | |
flex_box_layout(struct box *b) | |
{ | |
struct list_hook *it; | |
struct flex_box fbx = flex_box_ref(b); | |
int space = 0, slot_size = 0; | |
int varcnt = 0, staticsz = 0, fixsz = 0, maxvar = 0; | |
int dynsz = 0, varsz = 0, var = 0, dyncnt = 0; | |
/* calculate space for widgets without padding and spacing */ | |
if (*fbx.orientation == FLEX_BOX_HORIZONTAL) | |
space = max(b->w - (*fbx.cnt-1)**fbx.spacing, 0), slot_size = b->h; | |
else space = max(b->h - (*fbx.cnt-1)**fbx.spacing, 0), slot_size = b->w; | |
space = max(0, space - *fbx.padding*2); | |
if (*fbx.flow == FLEX_BOX_FIT || *fbx.flow == FLEX_BOX_WRAP) { | |
/* fit flex box to content */ | |
if (*fbx.orientation == FLEX_BOX_HORIZONTAL) { | |
list_foreach(it, &b->lnks) { | |
struct box *n = list_entry(it, struct box, node); | |
slot_size = max(slot_size, n->dh + *fbx.padding*2); | |
} slot_size = min(b->dh, slot_size); | |
if (*fbx.flow == FLEX_BOX_FIT) | |
b->h = slot_size; | |
} else { | |
list_foreach(it, &b->lnks) { | |
struct box *n = list_entry(it, struct box, node); | |
slot_size = max(slot_size, n->dw + *fbx.padding*2); | |
} slot_size = min(b->dw, slot_size); | |
if (*fbx.flow == FLEX_BOX_FIT) | |
b->w = slot_size; | |
} | |
} | |
/* calculate space requirements and slot metrics */ | |
list_foreach(it, &b->lnks) { | |
struct box *sb = list_entry(it, struct box, node); | |
int slot_typ = *widget_get_int(sb, 0); | |
int *slot_pix = widget_get_int(sb, 1); | |
/* calculate min size and dynamic size */ | |
switch (slot_typ) { | |
case FLEX_BOX_SLOT_DYNAMIC: { | |
varcnt++, dyncnt++; | |
} break; | |
case FLEX_BOX_SLOT_FITTING: | |
if (*fbx.orientation == FLEX_BOX_HORIZONTAL) | |
*slot_pix = max(*slot_pix, sb->dw); | |
else *slot_pix = max(*slot_pix, sb->dh); | |
case FLEX_BOX_SLOT_STATIC: { | |
staticsz += *slot_pix; | |
} break; | |
case FLEX_BOX_SLOT_VARIABLE: { | |
fixsz += *slot_pix; | |
maxvar = max(*slot_pix, maxvar); | |
varcnt++; | |
} break;} | |
/* setup opposite orientation position and size */ | |
if (*fbx.orientation == FLEX_BOX_HORIZONTAL) | |
sb->h = max(0, slot_size - *fbx.padding*2); | |
else sb->w = max(0, slot_size - *fbx.padding*2); | |
} | |
/* calculate dynamic slot size */ | |
dynsz = max(space - staticsz, 0); | |
varsz = max(dynsz - fixsz, 0); | |
if (varsz) { | |
if (varcnt) { | |
assert(*fbx.flow != FLEX_BOX_WRAP); | |
var = dynsz / max(varcnt,1); | |
if (maxvar > var) { | |
/* not enough space so shrink dynamic space */ | |
list_foreach(it, &b->lnks) { | |
struct box *sb = list_entry(it, struct box, node); | |
int slot_typ = *widget_get_int(sb, 0); | |
int slot_pix = *widget_get_int(sb, 1); | |
if (slot_pix <= var) continue; | |
switch (slot_typ) { | |
case FLEX_BOX_SLOT_FITTING: | |
case FLEX_BOX_SLOT_VARIABLE: { | |
staticsz += slot_pix; | |
varcnt--; | |
} break;} | |
} | |
dynsz = max(space - staticsz, 0); | |
var = dynsz / max(varcnt,1); | |
} | |
} else var = dynsz / max(varcnt + dyncnt,1); | |
} else var = 0; | |
/* set position and size */ | |
{int total_w = b->w, total_h = b->h; | |
int posx = b->x + *fbx.padding, posy = b->y + *fbx.padding, cnt = 0; | |
list_foreach(it, &b->lnks) { | |
struct box *sb = list_entry(it, struct box, node); | |
int slot_typ = *widget_get_int(sb, 0); | |
int slot_pix = *widget_get_int(sb, 1); | |
/* setup slot size (width/height) */ | |
switch (*fbx.orientation) { | |
case FLEX_BOX_HORIZONTAL: { | |
if (*fbx.flow == FLEX_BOX_WRAP && cnt) { | |
if (posx + slot_pix > b->x + (b->w - *fbx.padding*2)) { | |
posx = b->x, posy += slot_size + *fbx.spacing; | |
total_h = max(total_h, posy - b->y + *fbx.padding), cnt = 0; | |
} | |
} sb->x = posx, sb->y = posy; | |
switch (slot_typ) { | |
case FLEX_BOX_SLOT_DYNAMIC: sb->w = var; break; | |
case FLEX_BOX_SLOT_STATIC: sb->w = slot_pix; break; | |
case FLEX_BOX_SLOT_FITTING: sb->w = slot_pix; break; | |
case FLEX_BOX_SLOT_VARIABLE: | |
sb->w = (slot_pix > var) ? slot_pix: var; break;} | |
cnt++; | |
total_w = max(total_w, (posx + sb->w + *fbx.padding) - b->x); | |
posx += *fbx.spacing + sb->w; | |
} break; | |
case FLEX_BOX_VERTICAL: { | |
if (*fbx.flow == FLEX_BOX_WRAP && cnt) { | |
if (posy + slot_pix > b->y + (b->h - *fbx.padding*2)) { | |
posy = b->y, posx += slot_size + *fbx.spacing; | |
total_w = max(total_w, posx - b->x + *fbx.padding), cnt = 0; | |
} | |
} sb->x = posx, sb->y = posy; | |
switch (slot_typ) { | |
case FLEX_BOX_SLOT_DYNAMIC: sb->h = var; break; | |
case FLEX_BOX_SLOT_STATIC: sb->h = slot_pix; break; | |
case FLEX_BOX_SLOT_FITTING: sb->h = slot_pix; break; | |
case FLEX_BOX_SLOT_VARIABLE: | |
sb->h = (slot_pix > var) ? slot_pix: var; break;} | |
cnt++; | |
total_h = max(total_h, (posy + sb->h + *fbx.padding) - b->y); | |
posy += sb->h + *fbx.spacing; | |
} break;} | |
} | |
b->w = total_w; | |
b->h = total_h;} | |
} | |
/* --------------------------------------------------------------------------- | |
* OVERLAP BOX | |
* --------------------------------------------------------------------------- */ | |
api struct overlap_box | |
overlap_box_ref(struct box *b) | |
{ | |
struct overlap_box obx = {0}; | |
obx.cnt = widget_get_int(b, 0); | |
obx.slots = 1; | |
return obx; | |
} | |
intern struct overlap_box_slot | |
overlap_box_slot_ref(struct box *b) | |
{ | |
struct overlap_box_slot slot; | |
slot.id = widget_get_id(b, 0); | |
slot.zorder = widget_get_int(b, 1); | |
return slot; | |
} | |
api struct overlap_box | |
overlap_box_begin(struct state *s) | |
{ | |
struct overlap_box obx = {0}; | |
widget_begin(s, WIDGET_OVERLAP_BOX); | |
obx.id = widget_box_push(s); | |
widget_box_property_set(s, BOX_UNSELECTABLE); | |
obx.cnt = widget_param_int(s, 0); | |
return obx; | |
} | |
api void | |
overlap_box_slot(struct state *s, struct overlap_box *obx, mid id) | |
{ | |
uiid slot_id = 0; | |
int idx = *obx->cnt; | |
if (idx) { | |
widget_box_pop(s); | |
widget_end(s); | |
popid(s); | |
} pushid(s, id); | |
widget_begin(s, WIDGET_OVERLAP_BOX_SLOT); | |
slot_id = widget_box_push(s); | |
widget_box_property_set(s, BOX_UNSELECTABLE); | |
widget_param_id(s, slot_id); | |
widget_state_int(s, 0); | |
*obx->cnt += 1; | |
} | |
api void | |
overlap_box_end(struct state *s, struct overlap_box *obx) | |
{ | |
if (*obx->cnt) { | |
widget_box_pop(s); | |
widget_end(s); | |
popid(s); | |
} | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
api void | |
overlap_box_layout(struct box *b, struct memory_arena *arena) | |
{ | |
int i = 0; | |
int slot_cnt = 0; | |
struct box **boxes = 0; | |
struct list_hook *it = 0, *sit = 0; | |
box_layout(b, 0); | |
/* find maximum zorder */ | |
list_foreach(it, &b->lnks) { | |
struct box *n = list_entry(it, struct box, node); | |
struct overlap_box_slot s = overlap_box_slot_ref(n); | |
slot_cnt = max(*s.zorder, slot_cnt); | |
} slot_cnt += 1; | |
/* allocate and setup temp sorting array */ | |
boxes = arena_push_array(arena, slot_cnt, struct box*); | |
list_foreach_s(it, sit, &b->lnks) { | |
struct box *n = list_entry(it, struct box, node); | |
struct overlap_box_slot s = overlap_box_slot_ref(n); | |
if (!*s.zorder) continue; | |
list_del(&n->node); | |
boxes[*s.zorder] = n; | |
} | |
/* add boxes back in sorted order */ | |
for (i = slot_cnt-1; i >= 0; --i) { | |
if (!boxes[i]) continue; | |
list_add_head(&b->lnks, &boxes[i]->node); | |
} i = 1; | |
/* set correct zorder for each child box */ | |
list_foreach(it, &b->lnks) { | |
struct box *n = list_entry(it, struct box, node); | |
struct overlap_box_slot s = overlap_box_slot_ref(n); | |
*s.zorder = i++; | |
} | |
} | |
api void | |
overlap_box_input(struct box *b, union event *evt, struct memory_arena *arena) | |
{ | |
int i = 0; | |
struct box *slot = 0; | |
struct list_hook *it = 0; | |
if (evt->type != EVT_PRESSED) | |
return; | |
/* find clicked on slot */ | |
for (i = 0; i < evt->hdr.cnt; ++i) { | |
struct box *s = evt->hdr.boxes[i]; | |
list_foreach(it, &b->lnks) { | |
struct box *n = list_entry(it, struct box, node); | |
if (n != s) continue; | |
slot = n; break; | |
} | |
if (slot) break; | |
} if (!slot) return; | |
/* reorder and rebalance overlap stack */ | |
{int zorder = 0; | |
struct overlap_box obx = overlap_box_ref(b); | |
list_foreach(it, &b->lnks) { | |
struct box *n = list_entry(it, struct box, node); | |
struct overlap_box_slot s = overlap_box_slot_ref(n); | |
if (*s.id != slot->id) continue; | |
zorder = *s.zorder; | |
*s.zorder = *obx.cnt+1; | |
break; | |
} | |
/* set each slots zorder */ | |
list_foreach(it, &b->lnks) { | |
struct box *n = list_entry(it, struct box, node); | |
struct overlap_box_slot s = overlap_box_slot_ref(n); | |
if (*s.zorder > zorder) | |
*s.zorder -= 1; | |
} overlap_box_layout(b, arena);} | |
} | |
/* --------------------------------------------------------------------------- | |
* CONSTRAINT BOX | |
* --------------------------------------------------------------------------- */ | |
intern struct con_box | |
con_box_ref(struct box *b) | |
{ | |
struct con_box cbx = {0}; | |
cbx.cond_cnt = widget_get_int(b, 0); | |
cbx.slot_cnt = widget_get_int(b, 1); | |
cbx.conds = 2; | |
return cbx; | |
} | |
intern struct con_box_slot | |
con_box_slot_ref(struct box *b) | |
{ | |
struct con_box_slot cbs = {0}; | |
cbs.con_cnt = *widget_get_int(b, 0); | |
cbs.cons = 1; | |
return cbs; | |
} | |
intern struct cond | |
cond_ref(struct box *b, int off, int idx) | |
{ | |
struct cond c = {0}; | |
off += idx * 7; | |
c.op = (unsigned char)*widget_get_int(b, off + 0); | |
c.a.slot = *widget_get_int(b, off + 1); | |
c.a.attr = *widget_get_int(b, off + 2); | |
c.b.slot = *widget_get_int(b, off + 3); | |
c.b.attr = *widget_get_int(b, off + 4); | |
c.cons.mul = *widget_get_float(b, off + 5); | |
c.cons.off = *widget_get_int(b, off + 6); | |
return c; | |
} | |
intern struct con | |
con_ref(struct box *b, int off, int idx) | |
{ | |
struct con c = {0}; | |
off += idx * 9; | |
c.op = *widget_get_int(b, off + 0); | |
c.dst.slot = *widget_get_int(b, off + 1); | |
c.dst.attr = *widget_get_int(b, off + 2); | |
c.src.slot = *widget_get_int(b, off + 3); | |
c.src.attr = *widget_get_int(b, off + 4); | |
c.cons.mul = *widget_get_float(b, off + 5); | |
c.cons.off = *widget_get_int(b, off + 6); | |
c.anchor = *widget_get_int(b, off + 7); | |
c.cond = *widget_get_int(b, off + 8); | |
return c; | |
} | |
api struct con_box | |
con_box_begin(struct state *s, | |
struct cond *conds, int cond_cnt) | |
{ | |
int i = 0; | |
struct con_box cbx; | |
/* setup con box */ | |
widget_begin(s, WIDGET_CON_BOX); | |
cbx.id = widget_box_push(s); | |
widget_box_property_set(s, BOX_UNSELECTABLE); | |
cbx.cond_cnt = widget_param_int(s, cond_cnt); | |
cbx.slot_cnt = widget_param_int(s, 0); | |
/* push conditions */ | |
for (i = 0; i < cond_cnt; ++i) { | |
const struct cond *c = conds + i; | |
widget_param_int(s, c->op); | |
widget_param_int(s, c->a.slot); | |
widget_param_int(s, c->a.attr); | |
widget_param_int(s, c->b.slot); | |
widget_param_int(s, c->b.attr); | |
widget_param_float(s, c->cons.mul); | |
widget_param_int(s, c->cons.off); | |
} return cbx; | |
} | |
api void | |
con_box_slot(struct state *s, struct con_box *cbx, | |
const struct con *cons, int con_cnt) | |
{ | |
int i = 0; | |
if (*cbx->slot_cnt) { | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
widget_begin(s, WIDGET_CON_BOX_SLOT); | |
widget_box_push(s); | |
widget_box_property_set(s, BOX_UNSELECTABLE); | |
widget_param_int(s, con_cnt); | |
for (i = 0; i < con_cnt; ++i) { | |
const struct con *c = cons + i; | |
widget_param_int(s, c->op); | |
widget_param_int(s, c->dst.slot); | |
widget_param_int(s, c->dst.attr); | |
widget_param_int(s, c->src.slot); | |
widget_param_int(s, c->src.attr); | |
widget_param_float(s, c->cons.mul); | |
widget_param_int(s, c->cons.off); | |
widget_param_int(s, c->anchor); | |
widget_param_int(s, c->cond); | |
} *cbx->slot_cnt += 1; | |
} | |
api void | |
con_box_end(struct state *s, struct con_box *cbx) | |
{ | |
if (*cbx->slot_cnt) { | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
intern int | |
box_attr_get(const struct box *b, int attr) | |
{ | |
switch (attr) { | |
default: return 0; | |
case ATTR_L: return b->x; | |
case ATTR_T: return b->y; | |
case ATTR_R: return b->x + b->w; | |
case ATTR_B: return b->y + b->h; | |
case ATTR_CX: return b->x + b->w/2; | |
case ATTR_CY: return b->y + b->h/2; | |
case ATTR_W: return b->w; | |
case ATTR_H: return b->h; | |
case ATTR_DW: return b->dw; | |
case ATTR_DH: return b->dh;} | |
} | |
intern void | |
box_attr_set(struct box *b, int attr, int anchor, int val) | |
{ | |
switch (attr) { | |
default: return; | |
case ATTR_L: { | |
if (anchor == ATTR_R) | |
b->w = (b->x + b->w) - val; | |
else if (anchor == ATTR_CX) | |
b->w = ((b->x + b->w/2) - val) * 2; | |
b->x = val; | |
} break; | |
case ATTR_T: { | |
if (anchor == ATTR_B) | |
b->h = (b->y + b->h) - val; | |
else if (anchor == ATTR_CY) | |
b->h = ((b->y + b->h/2) - val) * 2; | |
b->y = val; | |
} break; | |
case ATTR_R: { | |
if (anchor == ATTR_L) | |
b->w = val - b->x; | |
else if (anchor == ATTR_CX) | |
b->w = (val - (b->x + b->w/2)) * 2; | |
b->x = val - b->w; | |
} break; | |
case ATTR_B: { | |
if (anchor == ATTR_T) | |
b->h = val - b->y; | |
else if (anchor == ATTR_CY) | |
b->h = (val - (b->y + b->h/2)) * 2; | |
b->y = val - b->h; | |
} break; | |
case ATTR_CX: { | |
if (anchor == ATTR_L) | |
b->w = (val - b->x) * 2; | |
else if (anchor == ATTR_R) | |
b->w = ((b->x + b->w) - val) * 2; | |
b->x = val - b->w/2; | |
} break; | |
case ATTR_CY: { | |
if (anchor == ATTR_T) | |
b->h = (val - b->y) * 2; | |
else if (anchor == ATTR_B) | |
b->h = ((b->y + b->h) - val) * 2; | |
b->y = val - b->h/2; | |
} break; | |
case ATTR_W: { | |
if (anchor == ATTR_CX) | |
b->y = (b->y + b->w/2) - val; | |
else if (anchor == ATTR_R) | |
b->y = (b->y + b->w) - val; | |
b->w = val; | |
} break; | |
case ATTR_H: { | |
if (anchor == ATTR_CY) | |
b->y = (b->y + b->h/2) - val; | |
else if (anchor == ATTR_B) | |
b->y = (b->y + b->h) - val; | |
b->h = val; | |
} break;} | |
} | |
api void | |
con_box_layout(struct box *b, struct memory_arena *arena) | |
{ | |
int i = 0; | |
struct list_hook *it = 0; | |
struct con_box cbx = con_box_ref(b); | |
int *res = arena_push_array(arena, *cbx.cond_cnt, int); | |
/* setup boxes into slots */ | |
struct box **boxes = arena_push_array(arena, *cbx.slot_cnt, struct box*); | |
boxes[i++] = b; | |
list_foreach(it, &b->lnks) | |
boxes[i++] = list_entry(it, struct box, node); | |
box_layout(b, 0); | |
/* evaluate conditions */ | |
for (i = 0; i < *cbx.cond_cnt; ++i) { | |
int av = 0, bv = 0; | |
const struct box *dst, *src; | |
struct cond c = cond_ref(b, cbx.conds, i); | |
/* extract left operand */ | |
assert(c.a.slot < *cbx.cond_cnt); | |
dst = boxes[c.a.slot + 1]; | |
av = box_attr_get(dst, c.a.attr); | |
/* extract right operand */ | |
assert(c.b.slot < *cbx.cond_cnt); | |
src = boxes[c.b.slot + 1]; | |
bv = box_attr_get(src, c.b.attr); | |
bv = roundi(cast(float, bv) * c.cons.mul) + c.cons.off; | |
/* eval operation */ | |
switch (c.op) { | |
case COND_EQ: res[i+1] = (av == bv); break; | |
case COND_NE: res[i+1] = (av != bv); break; | |
case COND_GR: res[i+1] = (av > bv); break; | |
case COND_GE: res[i+1] = (av >= bv); break; | |
case COND_LS: res[i+1] = (av < bv); break; | |
case COND_LE: res[i+1] = (av <= bv); break;} | |
} | |
/* evaluate constraints */ | |
list_foreach(it, &b->lnks) { | |
struct box *sb = list_entry(it, struct box, node); | |
struct con_box_slot slot = con_box_slot_ref(sb); | |
for (i = 0; i < slot.con_cnt; ++i) { | |
struct box *dst, *src; | |
struct con c = con_ref(sb, slot.cons, i); | |
assert((c.cond < *cbx.cond_cnt) || (!(*cbx.cond_cnt))); | |
if (*cbx.cond_cnt && (c.cond >= *cbx.cond_cnt)) | |
continue; | |
/* destination value */ | |
{int av = 0, bv = 0, v = 0; | |
assert(c.dst.slot < *cbx.slot_cnt); | |
dst = boxes[c.dst.slot + 1]; | |
av = box_attr_get(dst, c.dst.attr); | |
/* source value */ | |
assert(c.src.slot < *cbx.slot_cnt); | |
src = boxes[c.src.slot + 1]; | |
bv = box_attr_get(src, c.src.attr); | |
v = roundi(cast(float, bv) * c.cons.mul) + c.cons.off; | |
/* eval attribute */ | |
switch (c.op) { | |
case CON_NOP: break; | |
case CON_SET: box_attr_set(dst, c.dst.attr, c.anchor, v); break; | |
case CON_MIN: box_attr_set(dst, c.dst.attr, c.anchor, min(av, v)); break; | |
case CON_MAX: box_attr_set(dst, c.dst.attr, c.anchor, max(av, v)); break;}} | |
} | |
} | |
/* reshape after contents */ | |
{int x0 = INT_MAX, y0 = INT_MAX; | |
int x1 = INT_MIN, y1 = INT_MIN; | |
list_foreach(it, &b->lnks) { | |
struct box *n = list_entry(it, struct box, node); | |
x0 = min(x0, n->x); | |
y0 = min(y0, n->y); | |
x1 = max(x1, n->x + n->w); | |
y1 = max(y1, n->y + n->h); | |
} | |
b->x = x0, b->y = y0; | |
b->w = x1 - x0, b->h = y1 - y0;} | |
} |
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" | |
#include "widget.h" | |
/* --------------------------------------------------------------------------- | |
* LABEL | |
* --------------------------------------------------------------------------- */ | |
api struct label | |
label_ref(struct box *b) | |
{ | |
struct label lbl; | |
lbl.height = widget_get_int(b, 0); | |
lbl.font = widget_get_int(b, 1); | |
lbl.txt = widget_get_str(b, 2); | |
return lbl; | |
} | |
api struct label | |
label(struct state *s, const char *txt, const char *end) | |
{ | |
int len = 0; | |
struct label lbl = {0}; | |
const struct config *cfg = s->cfg; | |
widget_begin(s, WIDGET_LABEL); | |
lbl.id = widget_box_push(s); | |
widget_box_pop(s); | |
len = (end) ? cast(int, (end-txt)): (int)strn(txt); | |
lbl.height = widget_param_int(s, cfg->font_default_height); | |
lbl.font = widget_param_int(s, cfg->font_default_id); | |
lbl.txt = widget_param_str(s, txt, len); | |
widget_end(s); | |
return lbl; | |
} | |
api void | |
label_blueprint(struct box *b, void *usr, text_measure_f measure) | |
{ | |
struct label lbl = label_ref(b); | |
int len = cast(int, strn(lbl.txt)); | |
b->dw = measure(usr, *lbl.font, *lbl.height, lbl.txt, len); | |
b->dh = *lbl.height; | |
} | |
/* --------------------------------------------------------------------------- | |
* ICON | |
* --------------------------------------------------------------------------- */ | |
api struct icon | |
icon_ref(struct box *b) | |
{ | |
struct icon ico = {0}; | |
ico.sym = widget_get_int(b, 0); | |
ico.size = widget_get_int(b, 1); | |
return ico; | |
} | |
api struct icon | |
icon(struct state *s, int sym) | |
{ | |
struct icon ico = {0}; | |
const struct config *cfg = s->cfg; | |
widget_begin(s, WIDGET_ICON); | |
ico.id = widget_box_push(s); | |
ico.sym = widget_param_int(s, sym); | |
ico.size = widget_param_int(s, cfg->font_default_height); | |
widget_box_pop(s); | |
widget_end(s); | |
return ico; | |
} | |
api void | |
icon_blueprint(struct box *b) | |
{ | |
struct icon ico = icon_ref(b); | |
b->dw = *ico.size; | |
b->dh = *ico.size; | |
} | |
api void | |
icon_layout(struct box *b) | |
{ | |
struct icon ico = icon_ref(b); | |
*ico.size = min(b->w, b->h); | |
} | |
/* --------------------------------------------------------------------------- | |
* BUTTON | |
* --------------------------------------------------------------------------- */ | |
intern void | |
button_pull(struct button_state *btn, const struct box *b) | |
{ | |
btn->clicked = b->clicked; | |
btn->pressed = b->pressed; | |
btn->released = b->released; | |
btn->entered = b->entered; | |
btn->exited = b->exited; | |
} | |
api int | |
button_poll(struct state *s, struct button_state *st, struct button *btn) | |
{ | |
struct box *b = polls(s, btn->id); | |
if (!b) { | |
zero(st, szof(*st)); | |
return 0; | |
} button_pull(st, b); | |
return 1; | |
} | |
api int | |
button_query(struct context *ctx, struct button_state *st, | |
mid mid, struct button *btn) | |
{ | |
struct box *b = 0; | |
b = query(ctx, mid, btn->id); | |
if (!b) return 0; | |
btn->style = widget_get_int(b, 0); | |
button_pull(st, b); | |
return 1; | |
} | |
api struct button | |
button_begin(struct state *s) | |
{ | |
struct button btn = {0}; | |
widget_begin(s, WIDGET_BUTTON); | |
btn.style =widget_param_int(s, 0); | |
btn.id = widget_box_push(s); | |
return btn; | |
} | |
api void | |
button_end(struct state *s) | |
{ | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
/* --------------------------------------------------------------------------- | |
* SLIDER | |
* --------------------------------------------------------------------------- */ | |
api struct slider | |
slider_ref(struct box *b) | |
{ | |
struct slider sld; | |
sld.min = widget_get_float(b, 0); | |
sld.value = widget_get_float(b, 1); | |
sld.max = widget_get_float(b, 2); | |
sld.cursor_size = widget_get_int(b, 3); | |
sld.cid = *widget_get_id(b, 4); | |
return sld; | |
} | |
intern void | |
slider_pull(struct slider_state *sld, const struct box *b) | |
{ | |
sld->clicked = b->clicked; | |
sld->pressed = b->pressed; | |
sld->released = b->released; | |
sld->entered = b->entered; | |
sld->exited = b->exited; | |
sld->value_changed = b->moved; | |
} | |
api int | |
slider_poll(struct state *s, struct slider_state *st, struct slider *sld) | |
{ | |
struct box *b = polls(s, sld->id); | |
if (!b) return 0; | |
*sld = slider_ref(b); | |
slider_pull(st, b); | |
return 1; | |
} | |
api int | |
slider_query(struct context *ctx, mid mid, | |
struct slider_state *st, struct slider *sld) | |
{ | |
uiid id; | |
struct box *b = 0; | |
b = query(ctx, mid, sld->id); | |
if (!b) return 0; | |
id = sld->id; | |
*sld = slider_ref(b); | |
sld->id = id; | |
slider_pull(st, b); | |
return 1; | |
} | |
api struct slider | |
sliderf(struct state *s, float min, float *value, float max) | |
{ | |
struct slider sld = {0}; | |
const struct config *cfg = s->cfg; | |
widget_begin(s, WIDGET_SLIDER); | |
sld.min = widget_param_float(s, min); | |
sld.value = widget_modifier_float(s, value); | |
sld.max = widget_param_float(s, max); | |
sld.cursor_size = widget_param_int(s, cfg->font_default_height/2); | |
sld.id = widget_box_push(s); | |
/* cursor */ | |
sld.cid = widget_box_push(s); | |
widget_box_property_set(s, BOX_MOVABLE_X); | |
widget_box_pop(s); | |
widget_param_id(s, sld.cid); | |
widget_box_pop(s); | |
widget_end(s); | |
return sld; | |
} | |
api void | |
slider_blueprint(struct box *b) | |
{ | |
struct slider sld = slider_ref(b); | |
const int cursor_size = *sld.cursor_size; | |
if (b->id == sld.cid) { | |
b->dw = max(b->dw, cursor_size); | |
b->dh = max(b->dh, cursor_size); | |
box_blueprint(b, 0, 0); | |
} else box_blueprint(b, 0, 0); | |
} | |
api void | |
slider_layout(struct box *b) | |
{ | |
struct slider sld = slider_ref(b); | |
if (b->id != sld.cid) | |
{box_layout(b, 0); return;} | |
/* validate slider values */ | |
{const struct box *p = b->parent; | |
float sld_min = min(*sld.min, *sld.max); | |
float sld_max = max(*sld.min, *sld.max); | |
float sld_val = clamp(sld_min, *sld.value, sld_max); | |
float cur_off = (sld_val - sld_min) / sld_max; | |
/* remove half of cursor size of each box left and right */ | |
const int cursor_size = *sld.cursor_size; | |
int x = p->x + cursor_size/2; | |
int w = p->w - cursor_size; | |
/* calculate cursor bounds */ | |
b->w = cursor_size; | |
b->h = cursor_size * 2; | |
b->x = x + roundi(w * cur_off) - cursor_size/2; | |
b->y = p->y + p->h/2 - b->h/2;} | |
} | |
api void | |
slider_input(struct box *b, const union event *evt) | |
{ | |
struct box *p = b->parent; | |
struct slider sld = slider_ref(b); | |
if (b->id != sld.cid) return; | |
if (!b->moved || evt->type != EVT_MOVED) | |
return; | |
/* validate cursor pos */ | |
{float sld_min = min(*sld.min, *sld.max); | |
float sld_max = max(*sld.min, *sld.max); | |
b->x = min(p->x + p->w - b->w, b->x); | |
b->x = max(p->x, b->x); | |
/* calculate slider value from cursor position */ | |
{const int cursor_size = *sld.cursor_size; | |
int x = p->x, w = p->w - cursor_size; | |
*sld.value = cast(float,(b->x - x)) / cast(float,w); | |
*sld.value = (*sld.value * sld_max) + sld_min;}} | |
p->moved = 1; | |
} | |
/* --------------------------------------------------------------------------- | |
* SCROLL | |
* --------------------------------------------------------------------------- */ | |
api struct scroll | |
scroll_ref(struct box *b) | |
{ | |
struct scroll scrl = {0}; | |
scrl.total_x = widget_get_float(b, 0); | |
scrl.total_y = widget_get_float(b, 1); | |
scrl.size_x = widget_get_float(b, 2); | |
scrl.size_y = widget_get_float(b, 3); | |
scrl.off_x = widget_get_float(b, 4); | |
scrl.off_y = widget_get_float(b, 5); | |
scrl.cid = *widget_get_id(b, 6); | |
return scrl; | |
} | |
api struct scroll | |
scroll_begin(struct state *s, float *off_x, float *off_y) | |
{ | |
struct scroll scrl = {0}; | |
widget_begin(s, WIDGET_SCROLL); | |
scrl.total_x = widget_param_float(s, 1); | |
scrl.total_y = widget_param_float(s, 1); | |
scrl.size_x = widget_param_float(s, 1); | |
scrl.size_y = widget_param_float(s, 1); | |
scrl.off_x = widget_modifier_float(s, off_x); | |
scrl.off_y = widget_modifier_float(s, off_y); | |
scrl.id = widget_box_push(s); | |
scrl.cid = widget_box_push(s); | |
widget_box_property_set(s, BOX_MOVABLE); | |
widget_param_id(s, scrl.cid); | |
return scrl; | |
} | |
api void | |
scroll_end(struct state *s) | |
{ | |
widget_box_pop(s); | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
api struct scroll | |
scroll(struct state *s, float *off_x, float *off_y) | |
{ | |
struct scroll scrl; | |
scrl = scroll_begin(s, off_x, off_y); | |
scroll_end(s); | |
return scrl; | |
} | |
api void | |
scroll_layout(struct box *b) | |
{ | |
struct box *p = b->parent; | |
struct scroll scrl = scroll_ref(b); | |
if (b->id != scrl.cid) return; | |
*scrl.total_x = max(*scrl.total_x, *scrl.size_x); | |
*scrl.total_y = max(*scrl.total_y, *scrl.size_y); | |
*scrl.off_x = clamp(0, *scrl.off_x, *scrl.total_x - *scrl.size_x); | |
*scrl.off_y = clamp(0, *scrl.off_y, *scrl.total_y - *scrl.size_y); | |
b->x = floori((float)p->x + (*scrl.off_x / *scrl.total_x) * (float)p->w); | |
b->y = floori((float)p->y + (*scrl.off_y / *scrl.total_y) * (float)p->h); | |
b->w = ceili((*scrl.size_x / *scrl.total_x) * (float)p->w); | |
b->h = ceili((*scrl.size_y / *scrl.total_y) * (float)p->h); | |
} | |
api void | |
scroll_input(struct box *b, const union event *evt) | |
{ | |
struct box *p = b->parent; | |
struct scroll scrl = scroll_ref(b); | |
switch (evt->type) { | |
case EVT_CLICKED: { | |
if (b->id == scrl.cid) break; | |
{struct list_hook *h = list_begin(&b->lnks); | |
struct box *cursor = list_entry(h, struct box, node); | |
int off_x = (evt->clicked.x - b->x) - floori(*scrl.size_x)/2; | |
int off_y = (evt->clicked.y - b->y) - floori(*scrl.size_y)/2; | |
cursor->x = b->x + clamp(0, off_x, ceili(*scrl.total_x - *scrl.size_x)); | |
cursor->y = b->y + clamp(0, off_y, ceili(*scrl.total_y - *scrl.size_y)); | |
p = b, b = cursor;} | |
} /* ---- fallthrough ---- */ | |
case EVT_MOVED: { | |
if (b->id != scrl.cid) break; | |
/* calculate scroll ratio from cursor position */ | |
{int scrl_x = b->x - p->x, scrl_y = b->y - p->y; | |
float scrl_rx = cast(float, scrl_x) / cast(float, p->w); | |
float scrl_ry = cast(float, scrl_y) / cast(float, p->h); | |
/* validate and set cursor position */ | |
*scrl.off_x = clamp(0, scrl_rx * *scrl.total_x, *scrl.total_x - *scrl.size_x); | |
*scrl.off_y = clamp(0, scrl_ry * *scrl.total_y, *scrl.total_y - *scrl.size_y);} | |
p->moved = 1; | |
} break;} | |
} | |
/* --------------------------------------------------------------------------- | |
* COMBO | |
* --------------------------------------------------------------------------- */ | |
api struct combo | |
combo_begin(struct state *s, mid id) | |
{ | |
struct combo c = {0}; | |
widget_begin(s, WIDGET_COMBO); | |
c.id = widget_box_push(s); | |
c.state = widget_state_int(s, COMBO_COLLAPSED); | |
c.popup = widget_param_mid(s, id); | |
return c; | |
} | |
api struct state* | |
combo_popup_begin(struct state *s, struct combo *c) | |
{ | |
struct state *ps = 0; | |
ps = popup_begin(s, *c->popup, POPUP_NON_BLOCKING); | |
widget_begin(ps, WIDGET_COMBO_POPUP); | |
widget_box_push(ps); | |
if (ps->mod && (ps->mod->root->flags & BOX_HIDDEN)) | |
*c->state = COMBO_COLLAPSED; | |
return ps; | |
} | |
api void | |
combo_popup_end(struct state *s, struct combo *c) | |
{ | |
widget_box_pop(s); | |
widget_end(s); | |
if (*c->state == COMBO_COLLAPSED) | |
widget_box_property_set(s, BOX_HIDDEN); | |
popup_end(s); | |
} | |
api void | |
combo_end(struct state *s) | |
{ | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
api void | |
combo_popup_show(struct context *ctx, struct combo *c) | |
{ | |
*c->state = COMBO_VISIBLE; | |
popup_show(ctx, *c->popup, VISIBLE); | |
} | |
api void | |
combo_popup_close(struct context *ctx, struct combo *c) | |
{ | |
*c->state = COMBO_COLLAPSED; | |
popup_show(ctx, *c->popup, HIDDEN); | |
} | |
api void | |
combo_layout(struct context *ctx, struct box *b) | |
{ | |
struct combo c = combo_ref(b); | |
struct box *p = popup_find(ctx, *c.popup); | |
assert(p); | |
p->x = b->x; | |
p->h = p->dh; | |
p->y = b->y + b->h; | |
p->w = max(b->w, p->dw); | |
box_layout(b, 0); | |
} | |
api void | |
combo_input(struct context *ctx, struct box *b, const union event *evt) | |
{ | |
struct combo c = combo_ref(b); | |
if (!b->clicked || evt->type != EVT_CLICKED) return; | |
if (*c.state == COMBO_COLLAPSED) | |
combo_popup_show(ctx, &c); | |
else combo_popup_close(ctx, &c); | |
} | |
/* --------------------------------------------------------------------------- | |
* CLIP BOX | |
* --------------------------------------------------------------------------- */ | |
api uiid | |
clip_box_begin(struct state *s) | |
{ | |
assert(s); | |
if (!s) return 0; | |
widget_begin(s, WIDGET_CLIP_BOX); | |
return widget_box_push(s); | |
} | |
api void | |
clip_box_end(struct state *s) | |
{ | |
uiid id; | |
assert(s); | |
if (!s) return; | |
id = widget_box_push(s); | |
widget_param_id(s, id); | |
widget_box_property_set(s, BOX_IMMUTABLE); | |
widget_box_pop(s); | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
/* --------------------------------------------------------------------------- | |
* SCROLL REGION | |
* --------------------------------------------------------------------------- */ | |
api struct scroll_region | |
scroll_region_ref(struct box *b) | |
{ | |
struct scroll_region sr = {0}; | |
sr.dir = widget_get_int(b, 0); | |
sr.off_x = widget_get_float(b, 1); | |
sr.off_y = widget_get_float(b, 2); | |
sr.id = *widget_get_id(b, 3); | |
return sr; | |
} | |
api struct scroll_region | |
scroll_region_begin(struct state *s) | |
{ | |
struct scroll_region sr = {0}; | |
assert(s); | |
if (!s) return sr; | |
widget_begin(s, WIDGET_SCROLL_REGION); | |
sr.dir = widget_state_int(s, SCROLL_DEFAULT); | |
sr.off_x = widget_state_float(s, 0); | |
sr.off_y = widget_state_float(s, 0); | |
sr.id = widget_box_push(s); | |
widget_param_id(s, sr.id); | |
return sr; | |
} | |
api void | |
scroll_region_end(struct state *s) | |
{ | |
assert(s); | |
if (!s) return; | |
clip_box_end(s); | |
} | |
api void | |
scroll_region_layout(struct box *b) | |
{ | |
struct list_hook *i = 0; | |
struct scroll_region sr; | |
assert(b); | |
if (!b) return; | |
sr = scroll_region_ref(b); | |
if (b->id != sr.id) return; | |
list_foreach(i, &b->lnks) { | |
struct box *n = list_entry(i, struct box, node); | |
n->x = b->x - floori(*sr.off_x); | |
n->y = b->y - floori(*sr.off_y); | |
n->w = b->w, n->h = b->h; | |
} | |
} | |
api void | |
scroll_region_input(struct context *ctx, struct box *b, const union event *evt) | |
{ | |
struct input *in = evt->hdr.input; | |
struct scroll_region sr = scroll_region_ref(b); | |
if (evt->type == EVT_DRAGGED && in->shortcuts[SHORTCUT_SCROLL_REGION_SCROLL].down) { | |
b->moved = 1; | |
switch (*sr.dir) { | |
case SCROLL_DEFAULT: | |
*sr.off_x -= evt->moved.x; | |
*sr.off_y -= evt->moved.y; | |
ctx->unbalanced = 1; | |
break; | |
case SCROLL_REVERSE: | |
*sr.off_x += evt->moved.x; | |
*sr.off_y += evt->moved.y; | |
ctx->unbalanced = 1; | |
break; | |
} | |
} else if (in->shortcuts[SHORTCUT_SCROLL_REGION_RESET].down) { | |
*sr.off_x = *sr.off_y = 0; | |
ctx->unbalanced = 1; | |
} | |
} | |
/* --------------------------------------------------------------------------- | |
* SCROLL BOX | |
* --------------------------------------------------------------------------- */ | |
api uiid | |
scroll_box_begin(struct state *s) | |
{ | |
uiid sid = 0; | |
float tmpx = 0, tmpy = 0; | |
struct scroll x, y; | |
struct scroll_region sr; | |
assert(s); | |
if (!s) return 0; | |
widget_begin(s, WIDGET_SCROLL_BOX); | |
sid = widget_box_push(s); | |
x = scroll(s, &tmpx, &tmpy); | |
y = scroll(s, &tmpx, &tmpy); | |
sr = scroll_region_begin(s); | |
*x.off_x = *sr.off_x; | |
*y.off_y = *sr.off_y; | |
return sid; | |
} | |
api void | |
scroll_box_end(struct state *s) | |
{ | |
assert(s); | |
if (!s) return; | |
scroll_region_end(s); | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
api void | |
scroll_box_blueprint(struct box *b) | |
{ | |
static const int scroll_size = 10; | |
struct list_hook *h = b->lnks.prev; | |
struct box *bsr = list_entry(h, struct box, node); | |
b->dw = max(b->dw, bsr->dw + scroll_size); | |
b->dh = max(b->dh, bsr->dh + scroll_size); | |
} | |
api void | |
scroll_box_layout(struct box *b) | |
{ | |
/* retrieve pointer for each widget */ | |
struct list_hook *i = 0; | |
static const int scroll_size = 10; | |
struct list_hook *hx = b->lnks.next; | |
struct list_hook *hy = hx->next; | |
struct list_hook *hsr = hy->next; | |
struct box *bx = list_entry(hx, struct box, node); | |
struct box *by = list_entry(hy, struct box, node); | |
struct box *bsr = list_entry(hsr, struct box, node); | |
struct scroll x = scroll_ref(bx); | |
struct scroll y = scroll_ref(by); | |
struct scroll_region sr = scroll_region_ref(bsr); | |
/* x-scrollbar */ | |
bx->x = b->x; | |
bx->y = b->y + b->h - scroll_size; | |
bx->w = b->w - scroll_size; | |
bx->h = scroll_size; | |
/* y-scrollbar */ | |
by->x = b->x + b->w - scroll_size; | |
by->y = b->y; | |
by->w = scroll_size; | |
by->h = b->h - scroll_size; | |
/* scroll region */ | |
bsr->x = b->x; | |
bsr->y = b->y; | |
bsr->w = b->w - scroll_size; | |
bsr->h = b->h - scroll_size; | |
scroll_region_layout(bsr); | |
/* offset */ | |
if (bx->moved) | |
*sr.off_x = *x.off_x; | |
else *x.off_x = *sr.off_x; | |
if (by->moved) | |
*sr.off_y = *y.off_y; | |
else *y.off_y = *sr.off_y; | |
/* HACK(micha): handle wrapping flex box */ | |
list_foreach(i, &bsr->lnks) { | |
struct box *n = list_entry(i, struct box, node); | |
struct flex_box fbx; | |
if (n->type != WIDGET_FLEX_BOX) continue; | |
fbx = flex_box_ref(n); | |
if (*fbx.flow != FLEX_BOX_WRAP) continue; | |
{struct list_hook *j = 0; | |
int max_x = b->x, max_y = b->y; | |
flex_box_layout(n); | |
list_foreach(j, &n->lnks) { | |
const struct box *s = list_entry(j, struct box, node); | |
max_x = max(max_x, s->x + (int)*sr.off_x + s->w); | |
max_y = max(max_y, s->y + (int)*sr.off_y + s->h); | |
} | |
bsr->dw = max_x - b->x; | |
bsr->dh = max_y - b->y;} | |
} | |
/* scrollbars */ | |
*x.size_x = bsr->w; | |
*y.size_y = bsr->h; | |
*x.total_x = bsr->dw; | |
*y.total_y = bsr->dh; | |
if (bsr->dw <= bsr->w) | |
*sr.off_x = *x.off_x = 0; | |
if (bsr->dh <= bsr->h) | |
*sr.off_y = *y.off_y = 0; | |
} | |
api void | |
scroll_box_input(struct context *ctx, struct box *b, union event *evt) | |
{ | |
struct list_hook *hsr = list_last(&b->lnks); | |
struct list_hook *hy = list_prev(hsr); | |
struct list_hook *hx = list_prev(hy); | |
struct box *bx = list_entry(hx, struct box, node); | |
struct box *by = list_entry(hy, struct box, node); | |
struct box *bsr = list_entry(hsr, struct box, node); | |
struct scroll x = scroll_ref(bx); | |
struct scroll y = scroll_ref(by); | |
struct scroll_region sr = scroll_region_ref(bsr); | |
switch (evt->type) { | |
case EVT_CLICKED: | |
case EVT_MOVED: { | |
/* scrollbars */ | |
if (bx->moved) *sr.off_x = *x.off_x; | |
if (by->moved) *sr.off_y = *y.off_y; | |
} break; | |
case EVT_DRAG_END: { | |
/* overscrolling */ | |
*sr.off_x = clamp(0, *sr.off_x, *x.total_x - *x.size_x); | |
*sr.off_y = clamp(0, *sr.off_y, *y.total_y - *y.size_y); | |
ctx->unbalanced = 1; | |
} break; | |
case EVT_SCROLLED: { | |
/* mouse scrolling */ | |
*sr.off_y += -evt->scroll.y * floori((cast(float, *y.size_y) * 0.1f)); | |
*sr.off_y = clamp(0, *sr.off_y, *y.total_y - *y.size_y); | |
ctx->unbalanced = 1; | |
} break; | |
case EVT_SHORTCUT: { | |
/* shortcuts */ | |
if (!evt->key.pressed) break; | |
if (evt->key.code == SHORTCUT_SCROLL_BOX_BEGIN) | |
*sr.off_y = 0, ctx->unbalanced = 1; | |
else if (evt->key.code == SHORTCUT_SCROLL_BOX_END) | |
*sr.off_y = *y.total_y - *y.size_y, ctx->unbalanced = 1; | |
else if (evt->key.code == SHORTCUT_SCROLL_BOX_PGDN) { | |
*sr.off_y = min(*sr.off_y + *y.size_y, *y.total_y - *y.size_y); | |
ctx->unbalanced = 1; | |
} else if (evt->key.code == SHORTCUT_SCROLL_BOX_PGUP) { | |
*sr.off_y = max(*sr.off_y - *y.size_y, 0); | |
ctx->unbalanced = 1; | |
} | |
} break;} | |
} | |
/* --------------------------------------------------------------------------- | |
* ZOOM BOX | |
* --------------------------------------------------------------------------- */ | |
api struct zoom_box | |
zoom_box_ref(struct box *b) | |
{ | |
struct zoom_box sb; | |
sb.scale_x = widget_get_float(b, 0); | |
sb.scale_y = widget_get_float(b, 1); | |
sb.id = *widget_get_id(b, 2); | |
return sb; | |
} | |
api struct zoom_box | |
zoom_box_begin(struct state *s) | |
{ | |
struct zoom_box sb = {0}; | |
assert(s); | |
if (!s) return sb; | |
widget_begin(s, WIDGET_ZOOM_BOX); | |
sb.scale_x = widget_state_float(s, 1); | |
sb.scale_y = widget_state_float(s, 1); | |
sb.id = widget_box_push(s); | |
widget_param_id(s, sb.id); | |
return sb; | |
} | |
api void | |
zoom_box_end(struct state *s) | |
{ | |
uiid id = 0; | |
assert(s); | |
if (!s) return; | |
id = widget_box_push(s); | |
widget_param_id(s, id); | |
widget_box_property_set(s, BOX_IMMUTABLE); | |
widget_box_pop(s); | |
widget_box_pop(s); | |
widget_end(s); | |
} | |
api void | |
zoom_box_input(struct box *b, union event *evt) | |
{ | |
struct input *in = evt->hdr.input; | |
struct zoom_box sb = zoom_box_ref(b); | |
if (evt->type != EVT_SCROLLED) return; | |
if (in->shortcuts[SHORTCUT_ZOOM_BOX_SCALE_X].down || | |
in->shortcuts[SHORTCUT_ZOOM_BOX_SCALE].down) | |
*sb.scale_x = -evt->scroll.x * 0.1f; | |
if (in->shortcuts[SHORTCUT_ZOOM_BOX_SCALE_Y].down || | |
in->shortcuts[SHORTCUT_ZOOM_BOX_SCALE].down) | |
*sb.scale_y = -evt->scroll.y * 0.1f; | |
} | |
/* --------------------------------------------------------------------------- | |
* BUTTON_LABEL | |
* --------------------------------------------------------------------------- */ | |
intern void | |
button_label_pull(struct button_state *btn, const struct box *b) | |
{ | |
button_pull(btn, b); | |
} | |
api int | |
button_label_poll(struct state *s, struct button_state *st, | |
struct button_label *btn) | |
{ | |
return button_poll(s, st, &btn->btn); | |
} | |
api int | |
button_label_query(struct context *ctx, struct button_state *st, | |
mid mid, struct button_label *btn) | |
{ | |
return button_query(ctx, st, mid, &btn->btn); | |
} | |
api struct button_label | |
button_label(struct state *s, const char *txt, const char *end) | |
{ | |
struct button_label btn; | |
assert(s); | |
assert(txt); | |
btn.btn = button_begin(s); | |
btn.sbx = sbox_begin(s); | |
*btn.sbx.align.horizontal = SALIGN_CENTER; | |
*btn.sbx.align.vertical = SALIGN_MIDDLE; | |
btn.lbl = label(s, txt, end); | |
sbox_end(s); | |
button_end(s); | |
return btn; | |
} | |
api int | |
button_label_clicked(struct state *s, const char *label, const char *end) | |
{ | |
struct button_state st; | |
struct button_label bl; | |
bl = button_label(s, label, end); | |
button_poll(s, &st, &bl.btn); | |
return st.clicked != 0; | |
} | |
/* --------------------------------------------------------------------------- | |
* BUTTON_ICON | |
* --------------------------------------------------------------------------- */ | |
api struct button_icon | |
button_icon(struct state *s, int sym) | |
{ | |
struct button_icon bti; | |
bti.btn = button_begin(s); | |
bti.sbx = sbox_begin(s); | |
*bti.sbx.align.horizontal = SALIGN_CENTER; | |
*bti.sbx.align.vertical = SALIGN_MIDDLE; | |
bti.ico = icon(s, sym); | |
sbox_end(s); | |
button_end(s); | |
return bti; | |
} | |
api int | |
button_icon_clicked(struct state *s, int sym) | |
{ | |
struct button_state st; | |
struct button_icon bi; | |
bi = button_icon(s, sym); | |
button_poll(s, &st, &bi.btn); | |
return st.clicked != 0; | |
} | |
/* --------------------------------------------------------------------------- | |
* TOGGLE | |
* --------------------------------------------------------------------------- */ | |
api struct toggle | |
toggle_ref(struct box *b) | |
{ | |
struct toggle tog = {0}; | |
tog.val = widget_get_int(b, 0); | |
tog.icon_def = widget_get_int(b, 1); | |
tog.icon_act = widget_get_int(b, 2); | |
tog.mid = widget_get_mid(b, 3); | |
tog.iconid = widget_get_id(b, 4); | |
return tog; | |
} | |
intern void | |
toggle_setup(struct state *s, struct toggle *tog, | |
const char *txt, const char *end) | |
{ | |
struct sbox sbx = sbox_begin(s); | |
*sbx.align.horizontal = SALIGN_LEFT; | |
*sbx.align.vertical = SALIGN_MIDDLE; | |
{ | |
/* icon */ | |
tog->fbx = flex_box_begin(s); | |
*tog->fbx.spacing = 4; | |
*tog->fbx.padding = 0; | |
flex_box_slot_fitting(s, &tog->fbx); | |
tog->icon = icon(s, (*tog->val) ? *tog->icon_act: *tog->icon_def); | |
/* label */ | |
flex_box_slot_dyn(s, &tog->fbx); | |
tog->lbl = label(s, txt, end); | |
flex_box_end(s, &tog->fbx); | |
} sbox_end(s); | |
widget_param_mid(s, s->id); | |
tog->iconid = widget_param_id(s, tog->icon.id); | |
} | |
api struct toggle | |
checkbox(struct state *s, int *checked, const char *txt, const char *end) | |
{ | |
struct toggle chk; | |
widget_begin(s, WIDGET_CHECKBOX); | |
chk.id = widget_box_push(s); | |
chk.val = widget_modifier_int(s, checked); | |
chk.icon_def = widget_param_int(s, ICON_UNCHECKED); | |
chk.icon_act = widget_param_int(s, ICON_CHECKED); | |
toggle_setup(s, &chk, txt, end); | |
widget_box_pop(s); | |
widget_end(s); | |
return chk; | |
} | |
api struct toggle | |
toggle(struct state *s, int *active, const char *txt, const char *end) | |
{ | |
struct toggle tog; | |
widget_begin(s, WIDGET_TOGGLE); | |
tog.id = widget_box_push(s); | |
tog.val = widget_modifier_int(s, active); | |
tog.icon_def = widget_param_int(s, ICON_UNTOGGLED); | |
tog.icon_act = widget_param_int(s, ICON_TOGGLED); | |
toggle_setup(s, &tog, txt, end); | |
widget_box_pop(s); | |
widget_end(s); | |
return tog; | |
} | |
api struct toggle | |
radio(struct state *s, int *sel, const char *txt, const char *end) | |
{ | |
struct toggle tog; | |
widget_begin(s, WIDGET_RADIO); | |
tog.id = widget_box_push(s); | |
tog.val = widget_modifier_int(s, sel); | |
tog.icon_def = widget_param_int(s, ICON_UNSELECTED); | |
tog.icon_act = widget_param_int(s, ICON_SELECTED); | |
toggle_setup(s, &tog, txt, end); | |
widget_box_pop(s); | |
widget_end(s); | |
return tog; | |
} | |
api void | |
toggle_input(struct context *ctx, struct box *b, union event *evt) | |
{ | |
struct toggle tog = toggle_ref(b); | |
if (evt->type != EVT_CLICKED) return; | |
*tog.val = !(*tog.val); | |
{struct box *ib = query(ctx, *tog.mid, *tog.iconid); | |
if (!ib) return; | |
{struct icon ico = icon_ref(ib); | |
*ico.sym = (*tog.val) ? *tog.icon_act: *tog.icon_def;}} | |
ctx->active = b; | |
} |
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 WIDGET_H | |
#define WIDGET_H | |
enum widget_type { | |
/* Widgets */ | |
WIDGET_GENERIC_FIRST = 0x10000, | |
WIDGET_ICON = WIDGET_GENERIC_FIRST, | |
WIDGET_LABEL, | |
WIDGET_BUTTON, | |
WIDGET_CHECKBOX, | |
WIDGET_TOGGLE, | |
WIDGET_RADIO, | |
WIDGET_SLIDER, | |
WIDGET_SCROLL, | |
WIDGET_GENERIC_LAST = WIDGET_SCROLL, | |
/* Layouting */ | |
WIDGET_LAYOUT_FIRST = 0x20000, | |
WIDGET_SBORDER = WIDGET_LAYOUT_FIRST, | |
WIDGET_SALIGN, | |
WIDGET_SBOX, | |
WIDGET_FLEX_BOX, | |
WIDGET_FLEX_BOX_SLOT, | |
WIDGET_OVERLAP_BOX, | |
WIDGET_OVERLAP_BOX_SLOT, | |
WIDGET_CON_BOX, | |
WIDGET_CON_BOX_SLOT, | |
WIDGET_LAYOUT_LAST = WIDGET_CON_BOX_SLOT, | |
/* Container */ | |
WIDGET_CONTAINER_FIRST = 0x30000, | |
WIDGET_COMBO = WIDGET_CONTAINER_FIRST, | |
WIDGET_COMBO_POPUP, | |
WIDGET_COMBO_BOX, | |
WIDGET_COMBO_BOX_POPUP, | |
WIDGET_PANEL, | |
WIDGET_PANEL_HEADER, | |
WIDGET_PANEL_TOOLBAR, | |
WIDGET_PANEL_STATUSBAR, | |
WIDGET_SIDEBAR, | |
WIDGET_SIDEBAR_BAR, | |
WIDGET_SIDEBAR_SCALER, | |
WIDGET_SIDEBAR_CONTENT, | |
WIDGET_WINDOW, | |
WIDGET_WINDOW_CONTENT, | |
WIDGET_CONTAINER_LAST = WIDGET_WINDOW_CONTENT, | |
/* Transformation */ | |
WIDGET_TRANSFORM_FIRST = 0x40000, | |
WIDGET_CLIP_BOX, | |
WIDGET_SCROLL_REGION, | |
WIDGET_SCROLL_BOX, | |
WIDGET_ZOOM_BOX, | |
WIDGET_MOVABLE_BOX, | |
WIDGET_SCALABLE_BOX, | |
WIDGET_TRANSFORM_LAST = WIDGET_SCALABLE_BOX | |
}; | |
enum widget_shortcuts { | |
SHORTCUT_INTERNAL_BEGIN = 0x80, | |
SHORTCUT_SCROLL_REGION_SCROLL = SHORTCUT_INTERNAL_BEGIN, | |
SHORTCUT_SCROLL_REGION_RESET, | |
SHORTCUT_SCROLL_BOX_BEGIN, | |
SHORTCUT_SCROLL_BOX_PGDN, | |
SHORTCUT_SCROLL_BOX_PGUP, | |
SHORTCUT_SCROLL_BOX_END, | |
SHORTCUT_ZOOM_BOX_SCALE_X, | |
SHORTCUT_ZOOM_BOX_SCALE_Y, | |
SHORTCUT_ZOOM_BOX_SCALE, | |
SHORTCUT_INTERNAL_END | |
}; | |
enum icon_symbol { | |
ICONS_INTERNAL_BEGIN = 0x100000, | |
ICON_COMBO_CARRET_DOWN = ICONS_INTERNAL_BEGIN, | |
ICON_UNCHECKED, | |
ICON_CHECKED, | |
ICON_UNTOGGLED, | |
ICON_TOGGLED, | |
ICON_UNSELECTED, | |
ICON_SELECTED, | |
ICON_SIDEBAR_LOCK, | |
ICON_SIDEBAR_UNLOCK, | |
ICON_SIDEBAR_OPEN, | |
ICONS_INTERNAL_END | |
}; | |
/* --------------------------------------------------------------------------- | |
* BASE | |
* --------------------------------------------------------------------------- */ | |
/* label */ | |
typedef int(*text_measure_f)(void *usr, int font, int fh, const char *txt, int len); | |
struct label { | |
uiid id; | |
const char *txt; | |
int *font; | |
int *height; | |
}; | |
api struct label label(struct state *s, const char *txt, const char *end); | |
api void label_blueprint(struct box *b, void *usr, text_measure_f measure); | |
api struct label label_ref(struct box *b); | |
/* icon */ | |
struct icon { | |
uiid id; | |
int *sym; | |
int *size; | |
}; | |
api struct icon icon(struct state *s, int sym); | |
api void icon_blueprint(struct box *b); | |
api void icon_layout(struct box *b); | |
api struct icon icon_ref(struct box *b); | |
/* button */ | |
struct button_state { | |
unsigned clicked:1; | |
unsigned pressed:1; | |
unsigned released:1; | |
unsigned entered:1; | |
unsigned exited:1; | |
}; | |
struct button { | |
uiid id; | |
int *style; | |
}; | |
api struct button button_begin(struct state *s); | |
api struct button button_begin(struct state *s); | |
api int button_query(struct context *ctx, struct button_state *st, mid mid, struct button *btn); | |
api int button_poll(struct state *s, struct button_state *st, struct button *btn); | |
/* slider */ | |
struct slider_state { | |
unsigned clicked:1; | |
unsigned pressed:1; | |
unsigned released:1; | |
unsigned entered:1; | |
unsigned exited:1; | |
unsigned value_changed:1; | |
}; | |
struct slider { | |
uiid id; | |
uiid cid; | |
float *min; | |
float *value; | |
float *max; | |
int *cursor_size; | |
}; | |
api struct slider sliderf(struct state *s, float min, float *value, float max); | |
api void slider_blueprint(struct box *b); | |
api void slider_layout(struct box *b); | |
api void slider_input(struct box *b, const union event *evt); | |
api int slider_poll(struct state *s, struct slider_state *st, struct slider *sld); | |
api int slider_query(struct context *ctx, mid mid, struct slider_state *st, struct slider *sld); | |
/* scroll */ | |
struct scroll { | |
uiid id, cid; | |
float *total_x; | |
float *total_y; | |
float *size_x; | |
float *size_y; | |
float *off_x; | |
float *off_y; | |
}; | |
api struct scroll scroll(struct state *s, float *off_x, float *off_y); | |
api struct scroll scroll_begin(struct state *s, float *off_x, float *off_y); | |
api void scroll_end(struct state *s); | |
api void scroll_layout(struct box *b); | |
api void scroll_input(struct box *b, const union event *evt); | |
api struct scroll scroll_ref(struct box *b); | |
/* combo */ | |
enum combo_state { | |
COMBO_COLLAPSED, | |
COMBO_VISIBLE | |
}; | |
struct combo { | |
int *state; | |
uiid id; | |
mid *popup; | |
}; | |
static struct combo | |
combo_ref(struct box *b) | |
{ | |
struct combo c = {0}; | |
c.state = widget_get_int(b, 0); | |
c.popup = widget_get_mid(b, 1); | |
return c; | |
} | |
api struct combo combo_begin(struct state *s, mid id); | |
api struct state* combo_popup_begin(struct state *s, struct combo *c); | |
api void combo_popup_end(struct state *s, struct combo *c); | |
api void combo_end(struct state *s); | |
api void combo_popup_show(struct context *ctx, struct combo *c); | |
api void combo_popup_close(struct context *ctx, struct combo *c); | |
api void combo_layout(struct context *ctx, struct box *b); | |
api void combo_input(struct context *ctx, struct box *b, const union event *evt); | |
/* --------------------------------------------------------------------------- | |
* LAYOUT | |
* --------------------------------------------------------------------------- */ | |
/* sborder */ | |
struct sborder { | |
uiid id; | |
uiid content; | |
int *x; | |
int *y; | |
}; | |
api struct sborder sborder_begin(struct state *s); | |
api void sborder_end(struct state *s); | |
api void sborder_blueprint(struct box *b); | |
api void sborder_layout(struct box *b); | |
api struct sborder sborder_ref(struct box *b); | |
/* salign */ | |
enum salign_vertical { | |
SALIGN_TOP, | |
SALIGN_MIDDLE, | |
SALIGN_BOTTOM | |
}; | |
enum salign_horizontal { | |
SALIGN_LEFT, | |
SALIGN_CENTER, | |
SALIGN_RIGHT | |
}; | |
struct salign { | |
uiid id; | |
uiid content; | |
int *vertical; | |
int *horizontal; | |
}; | |
api struct salign salign_begin(struct state *s); | |
api void salign_end(struct state *s); | |
api void salign_layout(struct box *b); | |
api struct salign salign_ref(struct box *b); | |
/* sbox */ | |
struct sbox { | |
uiid id; | |
struct salign align; | |
struct sborder border; | |
}; | |
api struct sbox sbox_begin(struct state *s); | |
api void sbox_end(struct state *s); | |
/* flex box */ | |
enum flex_box_orientation { | |
FLEX_BOX_HORIZONTAL, | |
FLEX_BOX_VERTICAL | |
}; | |
enum flex_box_flow { | |
FLEX_BOX_STRETCH, | |
FLEX_BOX_FIT, | |
FLEX_BOX_WRAP | |
}; | |
enum flex_box_slot_type { | |
FLEX_BOX_SLOT_DYNAMIC, | |
FLEX_BOX_SLOT_STATIC, | |
FLEX_BOX_SLOT_VARIABLE, | |
FLEX_BOX_SLOT_FITTING | |
}; | |
struct flex_box { | |
uiid id; | |
int *orientation; | |
int *flow; | |
int *spacing; | |
int *padding; | |
int *cnt; | |
}; | |
api struct flex_box flex_box_begin(struct state *s); | |
api void flex_box_slot_dyn(struct state *s, struct flex_box *fbx); | |
api void flex_box_slot_static(struct state *s, struct flex_box *fbx, int pixel_width); | |
api void flex_box_slot_variable(struct state *s, struct flex_box *fbx, int min_pixel_width); | |
api void flex_box_slot_fitting(struct state *s, struct flex_box *fbx); | |
api void flex_box_end(struct state *s, struct flex_box *fbx); | |
api void flex_box_blueprint(struct box *b); | |
api void flex_box_layout(struct box *b); | |
api struct flex_box flex_box_ref(struct box *b); | |
/* overlap box */ | |
struct overlap_box { | |
uiid id; | |
int slots; | |
int *cnt; | |
}; | |
struct overlap_box_slot { | |
uiid *id; | |
int *zorder; | |
}; | |
api struct overlap_box overlap_box_begin(struct state *s); | |
api void overlap_box_slot(struct state *s, struct overlap_box *obx, mid id); | |
api void overlap_box_end(struct state *s, struct overlap_box *obx); | |
api void overlap_box_layout(struct box *b, struct memory_arena *arena); | |
api void overlap_box_input(struct box *b, union event *evt, struct memory_arena *arena); | |
api struct overlap_box overlap_box_ref(struct box *b); | |
/* constraint box */ | |
#define SPARENT (-1) | |
enum expr_op {EXPR_AND, EXPR_OR}; | |
enum cond_eq {COND_EQ, COND_NE, COND_GR, COND_GE, COND_LS, COND_LE}; | |
enum con_eq {CON_NOP, CON_SET, CON_MIN, CON_MAX}; | |
enum con_attr {ATTR_NONE, ATTR_L, ATTR_T, ATTR_R, ATTR_B, ATTR_CX, | |
ATTR_CY, ATTR_W, ATTR_H, ATTR_DW, ATTR_DH}; | |
struct cons {float mul; int off;}; | |
struct convar {int slot; int attr;}; | |
struct cond {unsigned char op; struct convar a, b; struct cons cons;}; | |
struct con {int op; struct convar dst, src; struct cons cons; int anchor, cond;}; | |
struct con_box_slot {int con_cnt, cons;}; | |
struct con_box { | |
uiid id; | |
int *cond_cnt; | |
int *slot_cnt; | |
int conds; | |
}; | |
api struct con_box con_box_begin(struct state *s, struct cond *conds, int cond_cnt); | |
api void con_box_slot(struct state *s, struct con_box *cbx, const struct con *cons, int con_cnt); | |
api void con_box_end(struct state *s, struct con_box *cbx); | |
api void con_box_layout(struct box *b, struct memory_arena *arena); | |
/* --------------------------------------------------------------------------- | |
* TRANSFORM | |
* --------------------------------------------------------------------------- */ | |
/* clip box */ | |
api uiid clip_box_begin(struct state *s); | |
api void clip_box_end(struct state *s); | |
/* scroll region */ | |
enum scroll_direction { | |
SCROLL_DEFAULT, | |
SCROLL_REVERSE | |
}; | |
struct scroll_region { | |
uiid id; | |
int *dir; | |
float *off_x; | |
float *off_y; | |
}; | |
api struct scroll_region scroll_region_begin(struct state *s); | |
api void scroll_region_end(struct state *s); | |
api void scroll_region_layout(struct box *b); | |
api void scroll_region_input(struct context *ctx, struct box *b, const union event *evt); | |
api struct scroll_region scroll_region_ref(struct box *b); | |
/* scroll box */ | |
api uiid scroll_box_begin(struct state *s); | |
api void scroll_box_end(struct state *s); | |
api void scroll_box_blueprint(struct box *b); | |
api void scroll_box_layout(struct box *b); | |
api void scroll_box_input(struct context *ctx, struct box *b, union event *evt); | |
/* zoom box */ | |
struct zoom_box { | |
uiid id; | |
float *scale_x; | |
float *scale_y; | |
}; | |
api struct zoom_box zoom_box_begin(struct state *s); | |
api void zoom_box_end(struct state *s); | |
api void zoom_box_input(struct box *b, union event *evt); | |
api struct zoom_box zoom_box_ref(struct box *b); | |
/* --------------------------------------------------------------------------- | |
* CONTAINER | |
* --------------------------------------------------------------------------- */ | |
/* combo box */ | |
struct combo_box { | |
struct combo combo; | |
struct flex_box fbx; | |
struct state *ps; | |
uiid *popup; | |
uiid *selid; | |
uiid *lblid; | |
}; | |
api int combo_box(struct state *s, unsigned id, const char **items, int cnt); | |
api struct combo_box combo_box_begin(struct state *s, mid id); | |
api int combo_box_item(struct state *s, struct combo_box *cbx, const char *item, const char *end); | |
api void combo_box_end(struct state *s, struct combo_box *cbx); | |
api void combo_box_popup_input(struct context *ctx, struct box *b, union event *evt); | |
/* panel */ | |
struct panel { | |
uiid id; | |
struct sborder sbx; | |
struct flex_box fbx; | |
}; | |
api struct panel panel_begin(struct state *s); | |
api void panel_header(struct state *s, struct panel *pan, const char *title); | |
api uiid panel_header_begin(struct state *s, struct panel *pan); | |
api void panel_header_end(struct state *s, struct panel *pan); | |
api uiid panel_toolbar_begin(struct state *s, struct panel *pan); | |
api void panel_toolbar_end(struct state *s, struct panel *pan); | |
api void panel_content_begin(struct state *s, struct panel *pan); | |
api void panel_content_end(struct state *s, struct panel *pan); | |
api void panel_status(struct state *s, struct panel *pan, const char *status); | |
api uiid panel_status_begin(struct state *s, struct panel *pan); | |
api void panel_status_end(struct state *s, struct panel *pan); | |
/* panel box */ | |
api struct panel panel_box_begin(struct state *s, const char *title); | |
api void panel_box_end(struct state *s, struct panel *pan, const char *status); | |
/* window */ | |
struct window { | |
uiid id; | |
int *x; | |
int *y; | |
int *w; | |
int *h; | |
int *border; | |
}; | |
api struct window window_begin(struct state *s, int x, int y, int w, int h); | |
api void window_end(struct state *s); | |
api void window_blueprint(struct box *b); | |
api void window_layout(struct box *b); | |
api void window_input(struct context *ctx, struct box *b, const union event *evt); | |
api struct window window_ref(struct box *b); | |
/* sidebar */ | |
enum sidebar_state { | |
SIDEBAR_CLOSED, | |
SIDEBAR_OPEN, | |
SIDEBAR_PINNED, | |
SIDEBAR_DRAGGED | |
}; | |
struct sidebar { | |
uiid id; | |
int *state; | |
float *ratio; | |
int *icon; | |
int *total; | |
struct con_box cbx; | |
struct panel pan; | |
}; | |
api struct sidebar sidebar_begin(struct state *s); | |
api void sidebar_end(struct state *s, struct sidebar *sb); | |
api void sidebar_layout(struct box *b); | |
api void sidebar_input(struct box *b, union event *evt); | |
api void sidebar_bar_input(struct box *b, union event *evt); | |
api void sidebar_scaler_input(struct box *b, union event *evt); | |
api struct sidebar sidebar_ref(struct box *b); | |
/* --------------------------------------------------------------------------- | |
* COMPOSIT | |
* --------------------------------------------------------------------------- */ | |
/* label button */ | |
struct button_label { | |
struct button btn; | |
struct label lbl; | |
struct sbox sbx; | |
}; | |
api int button_label_clicked(struct state *s, const char *label, const char *end); | |
api struct button_label button_label(struct state *s, const char *txt, const char *end); | |
api int button_label_poll(struct state *s, struct button_state *st, struct button_label *btn); | |
api int button_label_query(struct context *ctx, struct button_state *st, mid mid, struct button_label *btn); | |
/* icon button */ | |
struct button_icon { | |
struct button btn; | |
struct icon ico; | |
struct sbox sbx; | |
}; | |
api struct button_icon button_icon(struct state *s, int sym); | |
api int button_icon_clicked(struct state *s, int sym); | |
/* toogle */ | |
struct toggle { | |
uiid id; | |
struct flex_box fbx; | |
struct label lbl; | |
struct icon icon; | |
int *val; | |
int *icon_def; | |
int *icon_act; | |
mid *mid; | |
uiid *iconid; | |
}; | |
api struct toggle checkbox(struct state *s, int *checked, const char *txt, const char *end); | |
api struct toggle toggle(struct state *s, int *active, const char *txt, const char *end); | |
api struct toggle radio(struct state *s, int *sel, const char *txt, const char *end); | |
api void toggle_input(struct context *ctx, struct box *b, union event *evt); | |
api struct toggle toggle_ref(struct box *b); | |
#endif |
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 <stdlib.h> | |
#include <stdio.h> | |
#include "qk.h" | |
#include "qk.c" | |
#include "widget.h" | |
#include "widget.c" | |
#include "layout.c" | |
#include "container.c" | |
/* =========================================================================== | |
* | |
* APP | |
* | |
* =========================================================================== */ | |
#define len(s) (cntof(s)-1) | |
#define h1(s,i,x) (x*65599lu+(unsigned char)(s)[(i)<len(s)?len(s)-1-(i):len(s)]) | |
#define h4(s,i,x) h1(s,i,h1(s,i+1,h1(s,i+2,h1(s,i+3,x)))) | |
#define h16(s,i,x) h4(s,i,h4(s,i+4,h4(s,i+8,h4(s,i+12,x)))) | |
#define h32(s,i,x) h16(s,i,h16(s,i+16,x)) | |
#define hash(s,i) ((unsigned)(h32(s,0,i)^(h32(s,0,i)>>32))) | |
#define idx(s,i) hash(s,(unsigned)i) | |
#define id(s) idx(s,0) | |
#define lit(s) s,len(s) | |
#define txt(s) s,s+strlen(s) | |
/* System */ | |
#include <GL/gl.h> | |
#include <GL/glu.h> | |
#include "SDL2/SDL.h" | |
/* --------------------------------------------------------------------------- | |
* CANVAS | |
* --------------------------------------------------------------------------- */ | |
#ifdef __clang__ | |
#pragma clang diagnostic push | |
#pragma clang diagnostic ignored "-Woverlength-strings" | |
#pragma clang diagnostic ignored "-Wstrict-prototypes" | |
#pragma clang diagnostic ignored "-Wsign-conversion" | |
#pragma clang diagnostic ignored "-Wimplicit-function-declaration" | |
#pragma clang diagnostic ignored "-Wundef" | |
#pragma clang diagnostic ignored "-Wc99-extensions" | |
#pragma clang diagnostic ignored "-Wswitch-enum" | |
#pragma clang diagnostic ignored "-Wc11-extensions" | |
#pragma clang diagnostic ignored "-Wbad-function-cast" | |
#pragma clang diagnostic ignored "-Wdeclaration-after-statement" | |
#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" | |
#pragma clang diagnostic ignored "-Wmissing-prototypes" | |
#pragma clang diagnostic ignored "-Wcast-qual" | |
#pragma clang diagnostic ignored "-Wconversion" | |
#pragma clang diagnostic ignored "-Wcomment" | |
#pragma clang diagnostic ignored "-Wsign-conversion" | |
#pragma clang diagnostic ignored "-Wtypedef-redefinition" | |
#pragma clang diagnostic ignored "-Wsign-compare" | |
#pragma clang diagnostic ignored "-Wcast-align" | |
#elif defined(__GNUC__) || defined(__GNUG__) | |
#pragma GCC diagnostic push | |
#pragma GCC diagnostic ignored "-Woverlength-strings" | |
#endif | |
/* NanoVG */ | |
#define NANOVG_GL2_IMPLEMENTATION | |
#include "nanovg/src/fontstash.h" | |
#include "nanovg/src/stb_image.h" | |
#include "nanovg/src/stb_truetype.h" | |
#include "nanovg/src/nanovg.h" | |
#include "nanovg/src/nanovg.c" | |
#include "nanovg/src/nanovg_gl.h" | |
#include "IconsFontAwesome.h" | |
#ifdef __clang__ | |
#pragma clang diagnostic pop | |
#elif defined(__GNUC__) || defined(__GNUG__) | |
#pragma GCC diagnostic pop | |
#endif | |
/* Hacks: | |
* To lazy to write complete gui render context on top of NanoVG with icon and | |
* image declaration and font,style tables addressable by index, etc .... | |
*/ | |
static int default_icon_font; | |
enum icons { | |
ICON_CONFIG, | |
ICON_CHART_BAR, | |
ICON_DESKTOP, | |
ICON_DOWNLOAD, | |
ICON_FOLDER, | |
ICON_CNT | |
}; | |
static int | |
nvgTextMeasure(void *usr, int font, | |
int fh, const char *txt, int len) | |
{ | |
float bounds[4]; | |
struct NVGcontext *vg = usr; | |
nvgFontFaceId(vg, font); | |
nvgFontSize(vg, fh); | |
nvgTextBounds(vg, 0,0, txt, txt + len, bounds); | |
return cast(int, bounds[2]); | |
} | |
static void | |
nvgLabel(struct NVGcontext *vg, struct box *b) | |
{ | |
struct label lbl = label_ref(b); | |
nvgFontFaceId(vg, *lbl.font); | |
nvgFontSize(vg, *lbl.height); | |
nvgTextAlign(vg, NVG_ALIGN_LEFT|NVG_ALIGN_TOP); | |
nvgFillColor(vg, nvgRGBA(200,200,200,255)); | |
nvgText(vg, b->x, b->y, lbl.txt, NULL); | |
} | |
static void | |
nvgIcon(struct NVGcontext *vg, struct box *b) | |
{ | |
struct icon ico = icon_ref(b); | |
const char *icon_rune = 0; | |
nvgFontFaceId(vg, default_icon_font); | |
nvgFontSize(vg, *ico.size); | |
nvgTextAlign(vg, NVG_ALIGN_LEFT|NVG_ALIGN_TOP); | |
nvgFillColor(vg, nvgRGBA(220,220,220,255)); | |
switch (*ico.sym) { | |
default: break; | |
case ICON_COMBO_CARRET_DOWN: icon_rune = ICON_FA_CARET_DOWN; break; | |
case ICON_UNCHECKED: icon_rune = ICON_FA_TIMES; break; | |
case ICON_CHECKED: icon_rune = ICON_FA_CHECK; break; | |
case ICON_UNTOGGLED: icon_rune = ICON_FA_TOGGLE_OFF; break; | |
case ICON_TOGGLED: icon_rune = ICON_FA_TOGGLE_ON; break; | |
case ICON_UNSELECTED: icon_rune = ICON_FA_CIRCLE; break; | |
case ICON_SELECTED: icon_rune = ICON_FA_CIRCLE_THIN; break; | |
case ICON_SIDEBAR_LOCK: icon_rune = ICON_FA_LOCK; break; | |
case ICON_SIDEBAR_UNLOCK: icon_rune = ICON_FA_UNLOCK_ALT; break; | |
case ICON_SIDEBAR_OPEN: icon_rune = ICON_FA_CARET_RIGHT; break; | |
case ICON_CONFIG: icon_rune = ICON_FA_COGS; break; | |
case ICON_CHART_BAR: icon_rune = ICON_FA_BAR_CHART; break; | |
case ICON_DESKTOP: icon_rune = ICON_FA_DESKTOP; break; | |
case ICON_DOWNLOAD: icon_rune = ICON_FA_DOWNLOAD; break; | |
case ICON_FOLDER: icon_rune = ICON_FA_FOLDER_OPEN; break;} | |
nvgText(vg, b->x, b->y, icon_rune, NULL); | |
} | |
enum nvgButtonStyle { | |
NVG_BUTTON_DEFAULT, | |
NVG_BUTTON_TRANSPARENT | |
}; | |
static void | |
nvgButton(struct NVGcontext *vg, struct box *b) | |
{ | |
NVGcolor col; | |
int style = *widget_get_int(b, 0); | |
switch (style) { | |
default: | |
case NVG_BUTTON_DEFAULT: { | |
if (b->hovered) | |
col = nvgRGBA(40,40,40,255); | |
else if (b->pressed) | |
col = nvgRGBA(35,35,35,255); | |
else col = nvgRGBA(55,55,55,255); | |
nvgBeginPath(vg); | |
nvgRect(vg, b->x,b->y, b->w,b->h); | |
nvgFillColor(vg, col); | |
nvgFill(vg); | |
nvgBeginPath(vg); | |
nvgRect(vg, b->x,b->y, b->w, b->h); | |
nvgStrokeColor(vg, nvgRGBA(60,60,60,255)); | |
nvgStroke(vg); | |
} break; | |
case NVG_BUTTON_TRANSPARENT: | |
col = nvgRGBA(0,0,0,0); | |
if (b->hovered || b->pressed) { | |
nvgBeginPath(vg); | |
nvgRect(vg, b->x,b->y, b->w, b->h); | |
nvgStrokeColor(vg, nvgRGBA(60,60,60,255)); | |
nvgStroke(vg); | |
} break; | |
} | |
} | |
static void | |
nvgSlider(struct NVGcontext *vg, struct box *b) | |
{ | |
NVGpaint bg; | |
struct slider sld = slider_ref(b); | |
struct box *p = b->parent; | |
float cy = p->y+(int)(p->h*0.5f); | |
if (sld.cid != b->id) return; | |
/* Slot */ | |
bg = nvgBoxGradient(vg, p->x,cy-2+1, p->w,4, 2,2, nvgRGBA(10,10,10,32), nvgRGBA(10,10,10,128)); | |
nvgBeginPath(vg); | |
nvgRoundedRect(vg, p->x, cy-2, p->w, 4, 2); | |
nvgFillPaint(vg, bg); | |
nvgFill(vg); | |
/* knob */ | |
nvgBeginPath(vg); | |
nvgRect(vg, b->x, b->y, b->w, b->h); | |
if (b->hovered) | |
nvgFillColor(vg, nvgRGBA(50,50,50,255)); | |
else nvgFillColor(vg, nvgRGBA(60,60,60,255)); | |
nvgFill(vg); | |
/* knob border */ | |
nvgBeginPath(vg); | |
nvgRect(vg, b->x,b->y, b->w, b->h); | |
nvgStrokeColor(vg, nvgRGBA(70,70,70,255)); | |
nvgStroke(vg); | |
} | |
static void | |
nvgScroll(struct NVGcontext *vg, struct box *b) | |
{ | |
NVGcolor col; | |
struct box *p = b->parent; | |
struct scroll scrl = scroll_ref(b); | |
if (scrl.cid != b->id) return; | |
if (b->h == p->h && b->w == p->w) | |
return; /* skip if fully visible */ | |
/* draw scroll background */ | |
nvgBeginPath(vg); | |
nvgRect(vg, p->x,p->y, p->w,p->h); | |
nvgFillColor(vg, nvgRGBA(40,40,40,255)); | |
nvgFill(vg); | |
/* draw scroll cursor */ | |
if (b->hovered) | |
col = nvgRGBA(55,55,55,255); | |
else if (b->pressed) | |
col = nvgRGBA(50,50,50,255); | |
else col = nvgRGBA(60,60,60,255); | |
nvgBeginPath(vg); | |
nvgRect(vg, b->x,b->y, b->w,b->h); | |
nvgFillColor(vg, col); | |
nvgFill(vg); | |
} | |
static void | |
nvgComboPopup(struct NVGcontext *vg, struct box *b) | |
{ | |
nvgBeginPath(vg); | |
nvgRect(vg, b->x,b->y, b->w,b->h); | |
nvgFillColor(vg, nvgRGBA(45,45,45,255)); | |
nvgFill(vg); | |
nvgBeginPath(vg); | |
nvgRect(vg, b->x,b->y, b->w, b->h); | |
nvgStrokeColor(vg, nvgRGBA(60,60,60,255)); | |
nvgStroke(vg); | |
} | |
static void | |
nvgClipRegion(struct NVGcontext *vg, struct box *b, int pidx, struct rect *sis) | |
{ | |
uiid reset_id = *widget_get_id(b, pidx); | |
if (b->id != reset_id) { | |
/* safe old scissor rect and generate new one */ | |
struct list_hook *t = b->lnks.prev; | |
struct box *n = list_entry(t, struct box, node); | |
n->x = sis->x, n->y = sis->y; | |
n->w = sis->w, n->h = sis->h; | |
{int minx = max(sis->x, b->x); | |
int miny = max(sis->y, b->y); | |
int maxx = min(sis->x + sis->w, b->x + b->w); | |
int maxy = min(sis->y + sis->h, b->y + b->h); | |
sis->x = minx, sis->y = miny; | |
sis->w = max(maxx - minx, 0); | |
sis->h = max(maxy - miny, 0);} | |
nvgScissor(vg, sis->x, sis->y, sis->w, sis->h); | |
} else { | |
/* reset to previous scissor rect */ | |
nvgScissor(vg, b->x, b->y, b->w, b->h); | |
sis->x = b->x, sis->y = b->y; | |
sis->w = b->w, sis->h = b->h; | |
} | |
} | |
static void | |
nvgScaleRegion(struct NVGcontext *vg, struct box *b, float *x, float *y) | |
{ | |
struct zoom_box sbx = zoom_box_ref(b); | |
if (b->id != sbx.id) { | |
float xform[6]; | |
*x *= *sbx.scale_x; | |
*y *= *sbx.scale_y; | |
/* scale all content */ | |
nvgTransformIdentity(xform); | |
nvgTransformScale(xform, *x, *y); | |
nvgCurrentTransform(vg, xform); | |
} else { | |
float xform[6]; | |
*x /= *sbx.scale_x; | |
*y /= *sbx.scale_y; | |
/* reset scaler */ | |
nvgTransformIdentity(xform); | |
nvgTransformScale(xform, *x, *y); | |
nvgCurrentTransform(vg, xform); | |
} | |
} | |
static void | |
nvgPanel(struct NVGcontext *vg, struct box *b) | |
{ | |
nvgBeginPath(vg); | |
nvgRect(vg, b->x,b->y, b->w, b->h); | |
nvgFillColor(vg, nvgRGBA(45,45,45,255)); | |
nvgFill(vg); | |
nvgBeginPath(vg); | |
nvgRect(vg, b->x,b->y, b->w, b->h); | |
nvgStrokeColor(vg, nvgRGBA(60,60,60,255)); | |
nvgStroke(vg); | |
} | |
static void | |
nvgPanelHeader(struct NVGcontext *vg, struct box *b) | |
{ | |
nvgBeginPath(vg); | |
nvgRect(vg, b->x,b->y, b->w,b->h); | |
nvgFillColor(vg, nvgRGBA(50,50,50,255)); | |
nvgFill(vg); | |
} | |
static void | |
nvgSidebarScaler(struct NVGcontext *vg, struct box *b) | |
{ | |
nvgBeginPath(vg); | |
nvgMoveTo(vg, b->x, b->y); | |
nvgLineTo(vg, b->x + b->w, b->y + 5); | |
nvgLineTo(vg, b->x + b->w, b->y + b->h - 5); | |
nvgLineTo(vg, b->x, b->y + b->h); | |
nvgClosePath(vg); | |
nvgFillColor(vg, nvgRGBA(35,35,35,255)); | |
nvgFill(vg); | |
nvgStrokeColor(vg, nvgRGBA(60,60,60,255)); | |
nvgStroke(vg); | |
} | |
static void | |
nvgSidebarBar(struct NVGcontext *vg, struct box *b) | |
{ | |
nvgBeginPath(vg); | |
nvgMoveTo(vg, b->x, b->y); | |
nvgLineTo(vg, b->x + b->w, b->y + 10); | |
nvgLineTo(vg, b->x + b->w, b->y + b->h - 10); | |
nvgLineTo(vg, b->x, b->y + b->h); | |
nvgClosePath(vg); | |
nvgFillColor(vg, nvgRGBA(35,35,35,255)); | |
nvgFill(vg); | |
nvgStrokeColor(vg, nvgRGBA(60,60,60,255)); | |
nvgStroke(vg); | |
} | |
/* --------------------------------------------------------------------------- | |
* PLATFORM | |
* --------------------------------------------------------------------------- */ | |
static void | |
ui_sdl_event(struct context *ctx, const SDL_Event *evt) | |
{ | |
assert(ctx); | |
assert(evt); | |
switch (evt->type) { | |
case SDL_MOUSEMOTION: input_motion(ctx, evt->motion.x, evt->motion.y); break; | |
case SDL_MOUSEWHEEL: input_scroll(ctx, evt->wheel.x, evt->wheel.y); break; | |
case SDL_TEXTINPUT: input_text(ctx, txt(evt->text.text)); break; | |
case SDL_WINDOWEVENT: { | |
if (evt->window.event == SDL_WINDOWEVENT_RESIZED || | |
evt->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) | |
input_resize(ctx, evt->window.data1, evt->window.data2); | |
} break; | |
case SDL_MOUSEBUTTONUP: | |
case SDL_MOUSEBUTTONDOWN: { | |
int down = (evt->type == SDL_MOUSEBUTTONDOWN); | |
if (evt->button.button == SDL_BUTTON_LEFT) | |
input_button(ctx, MOUSE_BUTTON_LEFT, down); | |
else if (evt->button.button == SDL_BUTTON_RIGHT) | |
input_button(ctx, MOUSE_BUTTON_RIGHT, down); | |
else if (evt->button.button == SDL_BUTTON_MIDDLE) | |
input_button(ctx, MOUSE_BUTTON_MIDDLE, down); | |
} break; | |
case SDL_KEYUP: | |
case SDL_KEYDOWN: { | |
int down = (evt->type == SDL_KEYDOWN); | |
SDL_Keycode sym = evt->key.keysym.sym; | |
if (sym == SDLK_BACKSPACE) | |
input_shortcut(ctx, SHORTCUT_SCROLL_REGION_RESET, down); | |
else if (sym == SDLK_LALT) | |
input_shortcut(ctx, SHORTCUT_SCROLL_REGION_SCROLL, down); | |
else if (sym == SDLK_HOME) | |
input_shortcut(ctx, SHORTCUT_SCROLL_BOX_BEGIN, down); | |
else if (sym == SDLK_END) | |
input_shortcut(ctx, SHORTCUT_SCROLL_BOX_END, down); | |
else if (sym == SDLK_PAGEUP) | |
input_shortcut(ctx, SHORTCUT_SCROLL_BOX_PGUP, down); | |
else if (sym == SDLK_PAGEDOWN) | |
input_shortcut(ctx, SHORTCUT_SCROLL_BOX_PGDN, down); | |
input_key(ctx, sym, down); | |
} break;} | |
} | |
static void | |
ui_commit(struct context *ctx) | |
{ | |
union process *p = 0; | |
while ((p = process_begin(ctx, PROCESS_COMMIT))) { | |
assert(p->type == PROC_COMMIT); | |
commit(p); | |
process_end(p); | |
} | |
} | |
static void | |
ui_blueprint(struct NVGcontext *vg, union process *p, struct box *b) | |
{ | |
switch (b->type) { | |
case WIDGET_LABEL: label_blueprint(b, vg, nvgTextMeasure); break; | |
case WIDGET_ICON: icon_blueprint(b); break; | |
case WIDGET_SLIDER: slider_blueprint(b); break; | |
case WIDGET_SBORDER: sborder_blueprint(b); break; | |
case WIDGET_FLEX_BOX: flex_box_blueprint(b); break; | |
case WIDGET_SCROLL_BOX: scroll_box_blueprint(b); break; | |
case WIDGET_WINDOW: window_blueprint(b); break; | |
default: blueprint(p, b); break;} | |
} | |
static void | |
ui_layout(struct NVGcontext *vg, union process *p, struct box *b) | |
{ | |
switch (b->type) { | |
case WIDGET_ICON: icon_layout(b); break; | |
case WIDGET_SLIDER: slider_layout(b); break; | |
case WIDGET_SCROLL: scroll_layout(b); break; | |
case WIDGET_COMBO: combo_layout(p->hdr.ctx, b); break; | |
case WIDGET_SALIGN: salign_layout(b); break; | |
case WIDGET_SBORDER: sborder_layout(b); break; | |
case WIDGET_FLEX_BOX: flex_box_layout(b); break; | |
case WIDGET_OVERLAP_BOX: overlap_box_layout(b, p->hdr.arena); break; | |
case WIDGET_CON_BOX: con_box_layout(b, p->hdr.arena); break; | |
case WIDGET_SCROLL_REGION: scroll_region_layout(b); break; | |
case WIDGET_SCROLL_BOX: scroll_box_layout(b); break; | |
case WIDGET_SIDEBAR: sidebar_layout(b); break; | |
case WIDGET_WINDOW: window_layout(b); break; | |
default: layout(p, b); break;} | |
} | |
static void | |
ui_input(union process *p, union event *evt) | |
{ | |
int i = 0; | |
struct context *ctx = p->hdr.ctx; | |
for (i = 0; i < evt->hdr.cnt; i++) { | |
struct box *b = evt->hdr.boxes[i]; | |
switch (b->type) { | |
case WIDGET_CHECKBOX: | |
case WIDGET_TOGGLE: | |
case WIDGET_RADIO: toggle_input(ctx, b, evt); break; | |
case WIDGET_SLIDER: slider_input(b, evt); break; | |
case WIDGET_COMBO: combo_input(ctx, b, evt); break; | |
case WIDGET_COMBO_BOX_POPUP: combo_box_popup_input(ctx, b, evt); break; | |
case WIDGET_SCROLL: scroll_input(b, evt); break; | |
case WIDGET_SCROLL_REGION: scroll_region_input(ctx, b, evt); break; | |
case WIDGET_SCROLL_BOX: scroll_box_input(ctx, b, evt); break; | |
case WIDGET_OVERLAP_BOX: overlap_box_input(b, evt, p->hdr.arena); break; | |
case WIDGET_ZOOM_BOX: zoom_box_input(b, evt); break; | |
case WIDGET_SIDEBAR: sidebar_input(b, evt); break; | |
case WIDGET_SIDEBAR_BAR: sidebar_bar_input(b, evt); break; | |
case WIDGET_SIDEBAR_SCALER: sidebar_scaler_input(b, evt); break; | |
case WIDGET_WINDOW: window_input(ctx, b, evt); break; | |
case WIDGET_WINDOW_CONTENT: window_content_input(ctx, b, evt); break; | |
default: input(p, evt, b); break;} | |
} | |
} | |
static void | |
ui_draw(struct NVGcontext *vg, struct box *b, | |
struct rect *scissor, float *scale_x, float *scale_y) | |
{ | |
switch (b->type) { | |
case WIDGET_LABEL: nvgLabel(vg, b); break; | |
case WIDGET_ICON: nvgIcon(vg, b); break; | |
case WIDGET_BUTTON: nvgButton(vg, b); break; | |
case WIDGET_SLIDER: nvgSlider(vg, b); break; | |
case WIDGET_COMBO_POPUP: nvgComboPopup(vg, b); break; | |
case WIDGET_SCROLL: nvgScroll(vg, b); break; | |
case WIDGET_SCROLL_REGION: nvgClipRegion(vg, b, 4, scissor); break; | |
case WIDGET_ZOOM_BOX: nvgScaleRegion(vg, b, scale_x, scale_y); break; | |
case WIDGET_PANEL: nvgPanel(vg, b); break; | |
case WIDGET_PANEL_HEADER: nvgPanelHeader(vg, b); break; | |
case WIDGET_PANEL_STATUSBAR: nvgPanelHeader(vg, b); break; | |
case WIDGET_SIDEBAR_BAR: nvgSidebarBar(vg, b); break; | |
case WIDGET_SIDEBAR_SCALER: nvgSidebarScaler(vg, b); break; | |
default: break;} | |
} | |
static void | |
ui_paint(struct NVGcontext *vg, struct context *ctx, int w, int h) | |
{ | |
int i = 0; | |
union process *p = 0; | |
assert(vg); | |
assert(ctx); | |
if (!ctx || !vg) return; | |
/* Process: Paint */ | |
while ((p = process_begin(ctx, PROCESS_PAINT))) { | |
switch (p->type) {default:break; | |
case PROC_COMMIT: commit(p); break; | |
case PROC_BLUEPRINT: { | |
/* not called unless PROC_INPUT generates new UI */ | |
struct process_layouting *op = &p->layout; | |
for (i = op->begin; i != op->end; i += op->inc) | |
ui_blueprint(vg, p, op->boxes[i]); | |
} break; | |
case PROC_LAYOUT: { | |
/* not called unless PROC_INPUT generates new UI */ | |
struct process_layouting *op = &p->layout; | |
for (i = op->begin; i != op->end; i += op->inc) | |
ui_layout(vg, p, op->boxes[i]); | |
} break; | |
case PROC_PAINT: { | |
struct process_paint *op = &p->paint; | |
struct rect scissor = {0,0,0,0}; | |
float scale_x = 1.0f, scale_y = 1.0f; | |
scissor.w = w, scissor.h = h; | |
for (i = 0; i < op->cnt; ++i) | |
ui_draw(vg, op->boxes[i], &scissor, &scale_x, &scale_y); | |
nvgResetScissor(vg); | |
} break;} | |
process_end(p); | |
} | |
} | |
static void | |
ui_run(struct context *ctx, struct NVGcontext *vg) | |
{ | |
int i; union process *p = 0; | |
while ((p = process_begin(ctx, PROCESS_INPUT))) { | |
switch (p->type) {default:break; | |
case PROC_COMMIT: commit(p); break; | |
case PROC_BLUEPRINT: { | |
struct process_layouting *op = &p->layout; | |
for (i = op->begin; i != op->end; i += op->inc) | |
ui_blueprint(vg, p, op->boxes[i]); | |
} break; | |
case PROC_LAYOUT: { | |
struct process_layouting *op = &p->layout; | |
for (i = op->begin; i != op->end; i += op->inc) | |
ui_layout(vg, p, op->boxes[i]); | |
} break; | |
case PROC_INPUT: { | |
struct list_hook *it = 0; | |
struct process_input *op = &p->input; | |
list_foreach(it, &op->evts) { | |
union event *evt = list_entry(it, union event, hdr.hook); | |
ui_input(p, evt); /* handle widget events */ | |
} | |
} break;} | |
process_end(p); | |
} | |
} | |
/* =========================================================================== | |
* | |
* Main | |
* | |
* =========================================================================== */ | |
struct ui_retained { | |
mid id; | |
struct button_label btn; | |
struct slider sld; | |
}; | |
static void | |
ui_build_retained(struct context *ctx, struct ui_retained *ui, const char *label) | |
{ | |
struct state *s = 0; | |
ui->id = id("retained"); | |
s = begin(ctx, ui->id); { | |
struct panel pan = panel_box_begin(s, "Retained"); { | |
{struct flex_box fbx = flex_box_begin(s); | |
*fbx.orientation = FLEX_BOX_VERTICAL; | |
*fbx.spacing = 6; | |
{ | |
/* label button */ | |
flex_box_slot_fitting(s, &fbx); | |
ui->btn = button_label(s, label, 0); | |
/* slider */ | |
{static float sld_val = 5.0f; | |
flex_box_slot_static(s, &fbx, 30); | |
ui->sld = sliderf(s, 0.0f, &sld_val, 10.0f);} | |
} flex_box_end(s, &fbx);} | |
} panel_box_end(s, &pan, 0); | |
} end(s); | |
} | |
static void | |
ui_build_serialized_tables(FILE *fp) | |
{ | |
struct context *ctx; | |
struct config cfg; | |
cfg.font_default_id = 0; | |
cfg.font_default_height = 16; | |
ctx = create(DEFAULT_ALLOCATOR, &cfg); | |
{struct state *s = 0; | |
if ((s = begin(ctx, id("serialized_tables")))) { | |
struct panel pan = panel_box_begin(s, "Serialized: Compile Time"); { | |
struct flex_box fbx = flex_box_begin(s); | |
*fbx.orientation = FLEX_BOX_VERTICAL; | |
*fbx.spacing = 6, *fbx.padding = 6; | |
{ | |
/* label button */ | |
flex_box_slot_fitting(s, &fbx); | |
setid(s, id("SERIALIZED_BUTTON")); | |
button_label(s, txt("Label")); | |
/* icon buttons */ | |
flex_box_slot_fitting(s, &fbx); { | |
struct flex_box gbx = flex_box_begin(s); | |
*gbx.padding = 0; | |
flex_box_slot_dyn(s, &gbx); | |
setid(s, id("SERIALIZED_BUTTON_CONFIG")); | |
button_icon(s, ICON_CONFIG); | |
flex_box_slot_dyn(s, &gbx); | |
setid(s, id("SERIALIZED_BUTTON_CHART")); | |
button_icon(s, ICON_CHART_BAR); | |
flex_box_slot_dyn(s, &gbx); | |
setid(s, id("SERIALIZED_BUTTON_DESKTOP")); | |
button_icon(s, ICON_DESKTOP); | |
flex_box_slot_dyn(s, &gbx); | |
setid(s, id("SERIALIZED_BUTTON_DOWNLOAD")); | |
button_icon(s, ICON_DOWNLOAD); | |
flex_box_end(s, &gbx); | |
} | |
} flex_box_end(s, &fbx); | |
} panel_box_end(s, &pan, 0); | |
end(s); | |
}} | |
/*trace(stdout, ctx);*/ | |
ui_commit(ctx); | |
store_table(fp, ctx, "ui", 4); | |
cleanup(ctx); | |
} | |
static void | |
ui_load_serialized_tables(struct context *ctx) | |
{ | |
static const struct element g_1555159930_elements[] = { | |
{1048584, 0lu, 0lu, 0lu, 0, 7, 0, 0, 0}, | |
{196612, 6679361039399649282lu, 0lu, 6679361039399649281lu, 1, 8, 0, 0, 0}, | |
{131075, 6679361039399649284lu, 6679361039399649282lu, 6679361039399649283lu, 2, 9, 0, 0, 0}, | |
{131076, 6679361039399649286lu, 6679361039399649284lu, 6679361039399649285lu, 3, 10, 0, 6, 0}, | |
{196613, 6679361039399649288lu, 6679361039399649286lu, 6679361039399649287lu, 4, 11, 0, 8, 0}, | |
{131074, 6679361039399649290lu, 6679361039399649288lu, 6679361039399649289lu, 5, 12, 0, 8, 0}, | |
{131072, 6679361039399649292lu, 6679361039399649290lu, 6679361039399649291lu, 6, 13, 0, 8, 0}, | |
{131072, 6679361039399649293lu, 6679361039399649292lu, 6679361039399649291lu, 7, 14, 0, 8, 0}, | |
{131073, 6679361039399649295lu, 6679361039399649293lu, 6679361039399649294lu, 8, 15, 0, 11, 0}, | |
{131073, 6679361039399649296lu, 6679361039399649295lu, 6679361039399649294lu, 9, 16, 0, 11, 0}, | |
{65537, 6679361039399649298lu, 6679361039399649296lu, 6679361039399649297lu, 10, 17, 0, 14, 0}, | |
{131076, 6679361039399649300lu, 6679361039399649284lu, 6679361039399649299lu, 3, 10, 0, 17, 0}, | |
{262147, 6679361039399649302lu, 6679361039399649300lu, 6679361039399649301lu, 4, 11, 0, 19, 0}, | |
{65543, 6679361039399649304lu, 6679361039399649302lu, 6679361039399649303lu, 5, 12, 0, 19, 0}, | |
{65543, 6679361039399649305lu, 6679361039399649304lu, 6679361039399649303lu, 6, 13, 0|BOX_MOVABLE_X|BOX_MOVABLE_Y, 19, 0}, | |
{65543, 6679361039399649307lu, 6679361039399649302lu, 6679361039399649306lu, 5, 12, 0, 26, 0}, | |
{65543, 6679361039399649308lu, 6679361039399649307lu, 6679361039399649306lu, 6, 13, 0|BOX_MOVABLE_X|BOX_MOVABLE_Y, 26, 0}, | |
{262146, 6679361039399649310lu, 6679361039399649302lu, 6679361039399649309lu, 5, 12, 0, 33, 0}, | |
{131075, 6679361039399649312lu, 6679361039399649310lu, 6679361039399649311lu, 6, 13, 0, 38, 0}, | |
{131076, 6679361039399649314lu, 6679361039399649312lu, 6679361039399649313lu, 7, 14, 0, 44, 0}, | |
{65538, 998704728lu, 6679361039399649314lu, 6679361039399649315lu, 8, 15, 0, 46, 0}, | |
{131074, 6679361039399649317lu, 998704728lu, 6679361039399649316lu, 9, 16, 0, 47, 0}, | |
{131072, 6679361039399649319lu, 6679361039399649317lu, 6679361039399649318lu, 10, 17, 0, 47, 0}, | |
{131072, 6679361039399649320lu, 6679361039399649319lu, 6679361039399649318lu, 11, 18, 0, 47, 0}, | |
{131073, 6679361039399649322lu, 6679361039399649320lu, 6679361039399649321lu, 12, 19, 0, 50, 0}, | |
{131073, 6679361039399649323lu, 6679361039399649322lu, 6679361039399649321lu, 13, 20, 0, 50, 0}, | |
{65537, 6679361039399649325lu, 6679361039399649323lu, 6679361039399649324lu, 14, 21, 0, 53, 0}, | |
{131076, 6679361039399649327lu, 6679361039399649312lu, 6679361039399649326lu, 7, 14, 0, 56, 0}, | |
{131075, 6679361039399649329lu, 6679361039399649327lu, 6679361039399649328lu, 8, 15, 0, 58, 0}, | |
{131076, 6679361039399649331lu, 6679361039399649329lu, 6679361039399649330lu, 9, 16, 0, 64, 0}, | |
{65538, 3567268847lu, 6679361039399649331lu, 6679361039399649332lu, 10, 17, 0, 66, 0}, | |
{131074, 6679361039399649334lu, 3567268847lu, 6679361039399649333lu, 11, 18, 0, 67, 0}, | |
{131072, 6679361039399649336lu, 6679361039399649334lu, 6679361039399649335lu, 12, 19, 0, 67, 0}, | |
{131072, 6679361039399649337lu, 6679361039399649336lu, 6679361039399649335lu, 13, 20, 0, 67, 0}, | |
{131073, 6679361039399649339lu, 6679361039399649337lu, 6679361039399649338lu, 14, 21, 0, 70, 0}, | |
{131073, 6679361039399649340lu, 6679361039399649339lu, 6679361039399649338lu, 15, 22, 0, 70, 0}, | |
{65536, 6679361039399649342lu, 6679361039399649340lu, 6679361039399649341lu, 16, 23, 0, 73, 0}, | |
{131076, 6679361039399649344lu, 6679361039399649329lu, 6679361039399649343lu, 9, 16, 0, 75, 0}, | |
{65538, 3282852853lu, 6679361039399649344lu, 6679361039399649345lu, 10, 17, 0, 77, 0}, | |
{131074, 6679361039399649347lu, 3282852853lu, 6679361039399649346lu, 11, 18, 0, 78, 0}, | |
{131072, 6679361039399649349lu, 6679361039399649347lu, 6679361039399649348lu, 12, 19, 0, 78, 0}, | |
{131072, 6679361039399649350lu, 6679361039399649349lu, 6679361039399649348lu, 13, 20, 0, 78, 0}, | |
{131073, 6679361039399649352lu, 6679361039399649350lu, 6679361039399649351lu, 14, 21, 0, 81, 0}, | |
{131073, 6679361039399649353lu, 6679361039399649352lu, 6679361039399649351lu, 15, 22, 0, 81, 0}, | |
{65536, 6679361039399649355lu, 6679361039399649353lu, 6679361039399649354lu, 16, 23, 0, 84, 0}, | |
{131076, 6679361039399649357lu, 6679361039399649329lu, 6679361039399649356lu, 9, 16, 0, 86, 0}, | |
{65538, 3042075085lu, 6679361039399649357lu, 6679361039399649358lu, 10, 17, 0, 88, 0}, | |
{131074, 6679361039399649360lu, 3042075085lu, 6679361039399649359lu, 11, 18, 0, 89, 0}, | |
{131072, 6679361039399649362lu, 6679361039399649360lu, 6679361039399649361lu, 12, 19, 0, 89, 0}, | |
{131072, 6679361039399649363lu, 6679361039399649362lu, 6679361039399649361lu, 13, 20, 0, 89, 0}, | |
{131073, 6679361039399649365lu, 6679361039399649363lu, 6679361039399649364lu, 14, 21, 0, 92, 0}, | |
{131073, 6679361039399649366lu, 6679361039399649365lu, 6679361039399649364lu, 15, 22, 0, 92, 0}, | |
{65536, 6679361039399649368lu, 6679361039399649366lu, 6679361039399649367lu, 16, 23, 0, 95, 0}, | |
{131076, 6679361039399649370lu, 6679361039399649329lu, 6679361039399649369lu, 9, 16, 0, 97, 0}, | |
{65538, 79436257lu, 6679361039399649370lu, 6679361039399649371lu, 10, 17, 0, 99, 0}, | |
{131074, 6679361039399649373lu, 79436257lu, 6679361039399649372lu, 11, 18, 0, 100, 0}, | |
{131072, 6679361039399649375lu, 6679361039399649373lu, 6679361039399649374lu, 12, 19, 0, 100, 0}, | |
{131072, 6679361039399649376lu, 6679361039399649375lu, 6679361039399649374lu, 13, 20, 0, 100, 0}, | |
{131073, 6679361039399649378lu, 6679361039399649376lu, 6679361039399649377lu, 14, 21, 0, 103, 0}, | |
{131073, 6679361039399649379lu, 6679361039399649378lu, 6679361039399649377lu, 15, 22, 0, 103, 0}, | |
{65536, 6679361039399649381lu, 6679361039399649379lu, 6679361039399649380lu, 16, 23, 0, 106, 0}, | |
{262146, 6679361039399649382lu, 6679361039399649310lu, 6679361039399649309lu, 6, 13, 0|BOX_IMMUTABLE, 33, 0}, | |
}; | |
static const uiid g_1555159930_tbl_keys[128] = { | |
0lu,0lu,6679361039399649282lu,0lu,6679361039399649284lu,0lu,6679361039399649286lu,0lu, | |
6679361039399649288lu,0lu,6679361039399649290lu,0lu,6679361039399649292lu,6679361039399649293lu,0lu,6679361039399649295lu, | |
6679361039399649296lu,0lu,6679361039399649298lu,0lu,6679361039399649300lu,0lu,6679361039399649302lu,0lu, | |
6679361039399649304lu,6679361039399649305lu,0lu,6679361039399649307lu,6679361039399649308lu,0lu,6679361039399649310lu,0lu, | |
6679361039399649312lu,0lu,6679361039399649314lu,0lu,0lu,6679361039399649317lu,0lu,6679361039399649319lu, | |
6679361039399649320lu,0lu,6679361039399649322lu,6679361039399649323lu,0lu,6679361039399649325lu,0lu,6679361039399649327lu, | |
0lu,6679361039399649329lu,0lu,6679361039399649331lu,0lu,0lu,6679361039399649334lu,0lu, | |
6679361039399649336lu,6679361039399649337lu,0lu,6679361039399649339lu,6679361039399649340lu,0lu,6679361039399649342lu,0lu, | |
6679361039399649344lu,0lu,0lu,6679361039399649347lu,0lu,6679361039399649349lu,6679361039399649350lu,0lu, | |
6679361039399649352lu,6679361039399649353lu,0lu,6679361039399649355lu,0lu,6679361039399649357lu,3042075085lu,0lu, | |
6679361039399649360lu,0lu,6679361039399649362lu,6679361039399649363lu,0lu,6679361039399649365lu,6679361039399649366lu,0lu, | |
998704728lu,6679361039399649368lu,6679361039399649370lu,0lu,0lu,6679361039399649373lu,0lu,6679361039399649375lu, | |
6679361039399649376lu,79436257lu,6679361039399649378lu,6679361039399649379lu,0lu,6679361039399649381lu,6679361039399649382lu,0lu, | |
0lu,0lu,0lu,0lu,0lu,0lu,0lu,3567268847lu, | |
0lu,0lu,0lu,0lu,0lu,3282852853lu,0lu,0lu, | |
0lu,0lu,0lu,0lu,0lu,0lu,0lu,0lu | |
}; | |
static const int g_1555159930_tbl_vals[128] = { | |
0,0,1,0,2,0,3,0,4,0,5,0,6,7,0,8, | |
9,0,10,0,11,0,12,0,13,14,0,15,16,0,17,0, | |
18,0,19,0,0,21,0,22,23,0,24,25,0,26,0,27, | |
0,28,0,29,0,0,31,0,32,33,0,34,35,0,36,0, | |
37,0,0,39,0,40,41,0,42,43,0,44,0,45,46,0, | |
47,0,48,49,0,50,51,0,20,52,53,0,0,55,0,56, | |
57,54,58,59,0,60,61,0,0,0,0,0,0,0,0,30, | |
0,0,0,0,0,38,0,0,0,0,0,0,0,0,0,0 | |
}; | |
static union param g_1555159930_params[108] = { | |
{6679361039399649284lu},{1lu},{0lu},{4lu},{0lu},{2lu},{3lu},{0lu}, | |
{4lu},{5lu},{6679361039399649293lu},{1lu},{1lu},{6679361039399649296lu},{16lu},{0lu}, | |
{0lu},{0lu},{0lu},{1065353216lu},{1065353216lu},{1065353216lu},{1065353216lu},{0lu}, | |
{0lu},{6679361039399649305lu},{1065353216lu},{1065353216lu},{1065353216lu},{1065353216lu},{0lu},{0lu}, | |
{6679361039399649308lu},{0lu},{0lu},{0lu},{6679361039399649310lu},{6679361039399649382lu},{6679361039399649312lu},{1lu}, | |
{0lu},{6lu},{6lu},{2lu},{3lu},{0lu},{0lu},{6lu}, | |
{6lu},{6679361039399649320lu},{1lu},{1lu},{6679361039399649323lu},{16lu},{0lu},{25lu}, | |
{3lu},{0lu},{6679361039399649329lu},{0lu},{0lu},{4lu},{0lu},{4lu}, | |
{0lu},{0lu},{0lu},{6lu},{6lu},{6679361039399649337lu},{1lu},{1lu}, | |
{6679361039399649340lu},{0lu},{16lu},{0lu},{0lu},{0lu},{6lu},{6lu}, | |
{6679361039399649350lu},{1lu},{1lu},{6679361039399649353lu},{1lu},{16lu},{0lu},{0lu}, | |
{0lu},{6lu},{6lu},{6679361039399649363lu},{1lu},{1lu},{6679361039399649366lu},{2lu}, | |
{16lu},{0lu},{0lu},{0lu},{6lu},{6lu},{6679361039399649376lu},{1lu}, | |
{1lu},{6679361039399649379lu},{3lu},{16lu} | |
}; | |
static const unsigned char g_1555159930_data[31] = { | |
0x53,0x65,0x72,0x69,0x61,0x6c,0x69,0x7a,0x65,0x64,0x3a,0x20,0x43,0x6f,0x6d,0x70, | |
0x69,0x6c,0x65,0x20,0x54,0x69,0x6d,0x65,0x00,0x4c,0x61,0x62,0x65,0x6c,0x00 | |
}; | |
static struct box *g_1555159930_bfs[63]; | |
static struct box g_1555159930_boxes[62]; | |
static struct module g_1555159930_module; | |
static struct repository g_1555159930_repo; | |
static const struct component g_1555159930_component = { | |
1,1555159930, 17, 7,g_1555159930_elements, cntof(g_1555159930_elements), | |
g_1555159930_tbl_vals, g_1555159930_tbl_keys,cntof(g_1555159930_tbl_keys), | |
g_1555159930_data, cntof(g_1555159930_data), | |
g_1555159930_params, cntof(g_1555159930_params), | |
&g_1555159930_module, &g_1555159930_repo, g_1555159930_boxes, | |
g_1555159930_bfs, cntof(g_1555159930_boxes) | |
}; | |
static const struct container g_ui_containers[] = { | |
{1555159930, 0, 6, 0, 0, &g_1555159930_component}, | |
}; | |
load(ctx, g_ui_containers, cntof(g_ui_containers)); | |
} | |
intern int | |
icon_label(struct state *s, int icon_id, const char *lbl) | |
{ | |
struct button_state st; | |
struct button btn = button_begin(s); | |
button_poll(s, &st, &btn); | |
*btn.style = NVG_BUTTON_TRANSPARENT; | |
{ | |
struct sborder sbr = sborder_begin(s); | |
*sbr.x = 6, *sbr.y = 6; | |
{ | |
/* folder icon */ | |
struct flex_box gbx = flex_box_begin(s); | |
*gbx.spacing = *gbx.padding = 0; | |
*gbx.orientation = FLEX_BOX_VERTICAL; | |
flex_box_slot_dyn(s, &gbx); { | |
struct salign aln = salign_begin(s); | |
*aln.horizontal = SALIGN_CENTER; | |
*aln.vertical = SALIGN_MIDDLE; { | |
struct icon ico = icon(s, icon_id); | |
*ico.size = 32; | |
} salign_end(s); | |
} | |
/* folder title */ | |
flex_box_slot_fitting(s, &gbx); { | |
struct salign aln = salign_begin(s); | |
*aln.horizontal = SALIGN_CENTER; | |
*aln.vertical = SALIGN_MIDDLE; | |
label(s, txt(lbl)); | |
salign_end(s); | |
} flex_box_end(s, &gbx); | |
} sborder_end(s); | |
} button_end(s); | |
return st.clicked; | |
} | |
static void | |
ui_sidebar(struct state *s) | |
{ | |
static const char *folders[] = { | |
"Documents", "Download", | |
"Desktop", "Images", | |
"boot", "dev", "lib", | |
"include", "tmp", | |
"usr", "root", "bin", "local", | |
"src", "shaders", "srv", "sys", | |
"var", "proc", "mnt" | |
}; int i = 0; | |
struct sidebar sb = sidebar_begin(s); { | |
sborder_begin(s); | |
scroll_box_begin(s); { | |
struct flex_box fbx = flex_box_begin(s); | |
*fbx.flow = FLEX_BOX_WRAP, *fbx.padding = 0; | |
for (i = 0; i < cntof(folders); ++i) { | |
flex_box_slot_static(s, &fbx, 60); | |
if (icon_label(s, ICON_FOLDER, folders[i])) | |
printf("Button: %s clicked\n", folders[i]); | |
} flex_box_end(s, &fbx); | |
} scroll_box_end(s); | |
sborder_end(s); | |
} sidebar_end(s, &sb); | |
} | |
static void | |
ui_immediate_mode(struct state *s) | |
{ | |
/* Immediate mode panel */ | |
struct panel pan = panel_box_begin(s, "Immediate"); { | |
struct flex_box fbx = flex_box_begin(s); | |
*fbx.orientation = FLEX_BOX_VERTICAL; | |
*fbx.spacing = 6; | |
{ | |
/* label button */ | |
flex_box_slot_fitting(s, &fbx); | |
if (button_label_clicked(s, txt("Label"))) | |
fprintf(stdout, "Button pressed\n"); | |
/* icon buttons */ | |
flex_box_slot_fitting(s, &fbx); { | |
struct flex_box gbx = flex_box_begin(s); | |
*gbx.padding = 0; | |
flex_box_slot_dyn(s, &gbx); | |
if (button_icon_clicked(s, ICON_CONFIG)) | |
fprintf(stdout, "Button: Config pressed\n"); | |
flex_box_slot_dyn(s, &gbx); | |
if (button_icon_clicked(s, ICON_CHART_BAR)) | |
fprintf(stdout, "Button: Chart pressed\n"); | |
flex_box_slot_dyn(s, &gbx); | |
if (button_icon_clicked(s, ICON_DESKTOP)) | |
fprintf(stdout, "Button: Desktop pressed\n"); | |
flex_box_slot_dyn(s, &gbx); | |
if (button_icon_clicked(s, ICON_DOWNLOAD)) | |
fprintf(stdout, "Button: Download pressed\n"); | |
flex_box_end(s, &gbx); | |
} | |
/* combo */ | |
flex_box_slot_fitting(s, &fbx); { | |
static const char *items[] = {"Pistol","Shotgun","Plasma","BFG"}; | |
combo_box(s, id("weapons"), items, cntof(items)); | |
} | |
/* slider */ | |
{static float sld_val = 5.0f; | |
flex_box_slot_static(s, &fbx, 30); | |
sliderf(s, 0.0f, &sld_val, 10.0f);} | |
/* checkbox */ | |
flex_box_slot_fitting(s, &fbx); { | |
static int unchecked = 0, checked = 1; | |
struct flex_box gbx = flex_box_begin(s); | |
*gbx.padding = 0; | |
flex_box_slot_dyn(s, &gbx); | |
checkbox(s, &unchecked, "unchecked", 0); | |
flex_box_slot_dyn(s, &gbx); | |
checkbox(s, &checked, "checked", 0); | |
flex_box_end(s, &gbx); | |
} | |
/* toggle */ | |
flex_box_slot_fitting(s, &fbx); { | |
static int inactive = 0, active = 1; | |
struct flex_box gbx = flex_box_begin(s); | |
*gbx.padding = 0; | |
flex_box_slot_dyn(s, &gbx); | |
toggle(s, &inactive, "inactive", 0); | |
flex_box_slot_dyn(s, &gbx); | |
toggle(s, &active, "active", 0); | |
flex_box_end(s, &gbx); | |
} | |
/* radio */ | |
flex_box_slot_fitting(s, &fbx); { | |
static int unselected = 0, selected = 1; | |
struct flex_box gbx = flex_box_begin(s); | |
*gbx.padding = 0; | |
flex_box_slot_dyn(s, &gbx); | |
radio(s, &unselected, "unselected", 0); | |
flex_box_slot_dyn(s, &gbx); | |
radio(s, &selected, "selected", 0); | |
flex_box_end(s, &gbx); | |
} | |
} flex_box_end(s, &fbx); | |
} panel_box_end(s, &pan, 0); | |
} | |
int main(int argc, char *argv[]) | |
{ | |
enum fonts {FONT_HL, FONT_ICONS, FONT_CNT}; | |
int quit = 0; | |
int fnts[FONT_CNT]; | |
struct context *ctx; | |
struct ui_retained ui = {0}; | |
struct NVGcontext *vg; | |
SDL_GLContext glContext; | |
/* SDL */ | |
SDL_Window *win = 0; | |
SDL_Init(SDL_INIT_VIDEO); | |
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); | |
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 0); | |
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1); | |
win = SDL_CreateWindow("GUI", SDL_WINDOWPOS_CENTERED, | |
SDL_WINDOWPOS_CENTERED, 1000, 600, SDL_WINDOW_SHOWN|SDL_WINDOW_OPENGL); | |
glContext = SDL_GL_CreateContext(win); | |
/* NanoVG */ | |
vg = nvgCreateGL2(NVG_ANTIALIAS); | |
if (!vg) return -1; | |
fnts[FONT_HL] = nvgCreateFont(vg, "half_life", "half_life.ttf"); | |
fnts[FONT_ICONS] = nvgCreateFont(vg, "icons", "icons.ttf"); | |
default_icon_font = fnts[FONT_ICONS]; | |
/* GUI */ | |
{struct config cfg; | |
cfg.font_default_height = 16; | |
cfg.font_default_id = fnts[FONT_HL]; | |
ctx = create(DEFAULT_ALLOCATOR, &cfg);} | |
input_resize(ctx, 1000, 600); | |
if (argc > 1) { | |
ui_build_serialized_tables(stdout); | |
return 0; | |
} else ui_load_serialized_tables(ctx); | |
ui_build_retained(ctx, &ui, "Initial"); | |
while (!quit) { | |
/* Input */ | |
int w, h; | |
{SDL_Event evt; | |
SDL_GetWindowSize(win, &w, &h); | |
input_resize(ctx, w, h); | |
while (SDL_PollEvent(&evt)) { | |
switch (evt.type) { | |
case SDL_QUIT: quit = 1; break;} | |
ui_sdl_event(ctx, &evt); | |
}} | |
/* UI */ | |
{struct state *s = 0; | |
if ((s = begin(ctx, id("Main")))) { | |
ui_sidebar(s); | |
/* Panels */ | |
{struct overlap_box obx = overlap_box_begin(s); | |
overlap_box_slot(s, &obx, id("Immdiate Mode")); { | |
window_begin(s, 600, 50, 180, 250); | |
ui_immediate_mode(s); | |
window_end(s); | |
} | |
overlap_box_slot(s, &obx, id("Retained Mode")); { | |
window_begin(s, 320, 50, 200, 130); | |
link(s, id("retained"), RELATIONSHIP_INDEPENDENT); | |
window_end(s); | |
} | |
overlap_box_slot(s, &obx, id("Compile Time")); { | |
window_begin(s, 320, 250, 200, 130); | |
link(s, id("serialized_tables"), RELATIONSHIP_INDEPENDENT); | |
window_end(s); | |
} overlap_box_end(s, &obx);} | |
} end(s);} | |
ui_run(ctx, vg); | |
/* Paint */ | |
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); | |
glClearColor(0.10f, 0.18f, 0.24f, 1); | |
nvgBeginFrame(vg, w, h, 1.0f); | |
ui_paint(vg, ctx, w, h); | |
nvgEndFrame(vg); | |
clear(ctx); | |
/* Finish frame */ | |
SDL_GL_SwapWindow(win); | |
SDL_Delay(16); | |
} | |
cleanup(ctx); | |
/* Cleanup */ | |
nvgDeleteGL2(vg); | |
SDL_DestroyWindow(win); | |
SDL_Quit(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment