-
-
Save lamarqua/620065e605547f45592bd4c9c3f7ce08 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| /* =========================================================================== | |
| * | |
| * LIBRARY | |
| * | |
| * =========================================================================== */ | |
| /* Proof of Concept GUI: | |
| * - PoC UI implementation in ~2kLOC of C89 (ANSI C) | |
| * => Core solutions has no external dependencies (removing standard library dependency is trival) | |
| * => Does not use or require any special language constructs or macro constructs just pointers and enums | |
| * - Combines both "retained" and "immediate mode" UI by providing control over update frequency | |
| * => Does not require frame based updates while avoiding state mutability complexity found in retain mode GUIs | |
| * - Tree node processor: Revolves around tree datastructure operations | |
| * => Simplistic design composed around tree with rectangle nodes. Even "widgets" or "panels" are only sub-trees | |
| * - Does not use any callbacks for tree operations like layouting, drawing or input handling. | |
| * => Everything can be implemented using simple loops and conditions | |
| * => Callbacks can be implemented by dispatch table | |
| * - Event handling supports both polling as well as events | |
| * => Both polling directly in code while constructing tree (dear imgui) and outside | |
| * - Keeps full main application/game loop control including input handling in hand of user | |
| * => Does not directly enforce any framework styled control flow | |
| * => Supports both frame based updates as well as more energy conserving update on event | |
| * - No widget inheriting commonly found in frameworks. Instead everything is just composited | |
| * => Everything can be fit into each other. Want a checkbox inside button? Just combine them | |
| * - Supports full layouting with desired size calculation | |
| * => Avoids layouting problems commonly found in "pure" immediate mode GUIs | |
| * - Does not specify anything rendering related. Just another tree traversal operation | |
| * => Real production implementation would need a number of default backends (vertexes, rendering library bindings,...) | |
| * - Provides solution for persistent state management | |
| * => Widget state can be mananaged in library or outside by user | |
| * => Special care taken for garbage collected language bindings | |
| * - Provides callbacks for memory allocations and deallocations | |
| * => Implements block and linear allocator for better memory allocation patterns | |
| * - Simplifies both UI tree creation as well as commonly less focused tree traversing | |
| * => No blackbox single "update" function call. Extending the library should be as simple as using it | |
| * => Production level code would need to provide default widget implementations handled in switch default case | |
| * - Core implementation can be extended to keep track of UI state over time (debugging) | |
| * => Library internally keeps track of current and last frame which could be extended | |
| * - Does not specify any font handling or text layouting scheme | |
| * => Production level code would need default implementation (stb_truetype, freetype, slug, harfbuzz,...) | |
| * | |
| * Problems: | |
| * - Basic UI unit "box" is memory intensive (~170 bytes per box) | |
| * - In general more memory intensive than "pure" immediate mode UIs | |
| * - Frame based in-place polling always has *one* frame of latency | |
| * - Core is, compared to "pure" immediate mode UIs like nuklear/dear imgui, harder to implement | |
| * - Tree creation still needs some work for multithreading (spinlock for panel list operations) | |
| * - Processing steps are sequential so cannot be separated for multithreading, only complete process | |
| */ | |
| #include <assert.h> /* assert */ | |
| #include <stdlib.h> /* malloc, free */ | |
| #include <string.h> /* memcpy, memset */ | |
| #include <stdint.h> /* uintptr_t, intptr_t */ | |
| #define unused(a) ((void)a) | |
| #define cast(t,p) ((t)(p)) | |
| #define flag(n) ((1u)<<(n)) | |
| #define szof(a) ((int)sizeof(a)) | |
| #define min(a,b) ((a)<(b)?(a):(b)) | |
| #define max(a,b) ((a)>(b)?(a):(b)) | |
| #define clamp(a,v,b) (max(min(b,v),a)) | |
| #define zero(d,sz) memset(d,0,(size_t)(sz)) | |
| #define copy(d,s,sz) memcpy(d,s,(size_t)(sz)) | |
| #define cntof(a) ((int)(sizeof(a)/sizeof((a)[0]))) | |
| #define offsof(st,m) ((int)((uintptr_t)&(((st*)0)->m))) | |
| #define containerof(ptr,type,member) (type*)((void*)((char*)(1?(ptr):&((type*)0)->member)-offsof(type, member))) | |
| #define alignof(t) ((int)((char*)(&((struct {char c; t _h;}*)0)->_h) - (char*)0)) | |
| #define align(x,mask) ((void*)(((intptr_t)((const char*)(x)+(mask-1))&~(mask-1)))) | |
| #define between(x,a,b) ((a)<=(x) && (x)<=(b)) | |
| #define inbox(px,py,x,y,w,h) (between(px,x,x+w) && between(py,y,y+h)) | |
| #define intersect(x,y,w,h,X,Y,W,H) ((x)<(X)+(W) && (x)+(w)>(X) && (y)<(Y)+(H) && (y)+(h)>(Y)) | |
| struct panel; | |
| struct widget; | |
| struct context; | |
| struct repository; | |
| struct panel_state; | |
| struct memory_arena; | |
| typedef uintptr_t uiid; | |
| typedef void(*dealloc_f)(void *usr, void *data, const char *file, int line); | |
| typedef void*(*alloc_f)(void *usr, long size, const char *file, int line); | |
| /* list */ | |
| struct list_hook { | |
| struct list_hook *next; | |
| struct list_hook *prev; | |
| }; | |
| /* memory */ | |
| #define DEFAULT_ALLOCATOR 0 | |
| #define DEFAULT_MEMORY_BLOCK_SIZE (64*1024) | |
| struct allocator { | |
| void *usr; | |
| alloc_f alloc; | |
| dealloc_f dealloc; | |
| }; | |
| struct memory_block { | |
| struct list_hook hook; | |
| struct memory_block *prev; | |
| long size, used; | |
| unsigned char *base; | |
| }; | |
| struct block_allocator { | |
| const struct allocator *mem; | |
| struct list_hook freelist; | |
| struct list_hook blks; | |
| int blkcnt; | |
| }; | |
| struct temp_memory { | |
| struct memory_arena *arena; | |
| struct memory_block *blk; | |
| long used; | |
| }; | |
| struct memory_arena { | |
| struct block_allocator *mem; | |
| struct memory_block *blk; | |
| int blkcnt, tmpcnt; | |
| }; | |
| /* input */ | |
| enum mouse_button { | |
| MOUSE_BUTTON_LEFT, | |
| MOUSE_BUTTON_RIGHT, | |
| MOUSE_BUTTON_COUNT | |
| }; | |
| struct key { | |
| unsigned char down; | |
| unsigned char transitions; | |
| }; | |
| struct mouse { | |
| int x, y; | |
| int lx, ly; | |
| int dx, dy; | |
| int wheelx, wheely; | |
| struct key btn[MOUSE_BUTTON_COUNT]; | |
| }; | |
| struct input { | |
| struct mouse mouse; | |
| char text[32]; | |
| int text_len; | |
| int width, height; | |
| unsigned resized:1; | |
| unsigned ctrl:1; | |
| unsigned shift:1; | |
| unsigned alt:1; | |
| unsigned super:1; | |
| struct key shortcuts[256]; | |
| struct key keys[512]; | |
| }; | |
| /* box */ | |
| struct rect {int x,y,w,h;}; | |
| struct transform {int x,y; float sx,sy;}; | |
| enum property_type { | |
| PROPERTY_INTERACTIVE, | |
| PROPERTY_MOVABLE_X, | |
| PROPERTY_MOVABLE_Y, | |
| PROPERTY_BACKGROUND, | |
| PROPERTY_UNIFORM, | |
| PROPERTY_SELECTABLE, | |
| PROPERTY_COUNT | |
| }; | |
| struct box { | |
| uiid box_id, widget_id, type_id; | |
| struct transform local_transform; | |
| struct transform screen_transform; | |
| struct rect local, screen; | |
| int dw, dh; | |
| unsigned alive:1; | |
| /* properties */ | |
| unsigned interactive:1; | |
| unsigned movable_x:1; | |
| unsigned movable_y:1; | |
| unsigned background:1; | |
| unsigned uniform:1; | |
| unsigned selectable:1; | |
| /* state */ | |
| unsigned hidden:1; | |
| unsigned activated:1; | |
| unsigned deactivated:1; | |
| unsigned clicked:1; | |
| unsigned pressed:1; | |
| unsigned released:1; | |
| unsigned entered:1; | |
| unsigned exited:1; | |
| unsigned drag_begin:1; | |
| unsigned dragged:1; | |
| unsigned drag_end:1; | |
| unsigned moved:1; | |
| unsigned scrolled:1; | |
| short depth, cnt; | |
| struct list_hook links; | |
| struct list_hook node; | |
| struct list_hook hook; | |
| struct box *parent; | |
| void *widget; | |
| }; | |
| /* panel */ | |
| struct widget_builder { | |
| uiid type_id, widget_id; | |
| int stkcnt, cnt; | |
| struct box *stk[16]; | |
| struct panel_state *state; | |
| struct memory_arena *arena; | |
| void *widget, *widget_last; | |
| }; | |
| struct panel_state { | |
| struct memory_arena *arena; | |
| struct context *ctx; | |
| struct panel *panel; | |
| struct repository *repo; | |
| struct widget_builder *builder; | |
| struct box *stk[16]; | |
| int stkcnt; | |
| }; | |
| struct table { | |
| int cnt; | |
| uiid *keys; | |
| struct box **boxes; | |
| }; | |
| struct repository { | |
| struct memory_arena arena; | |
| int cnt, depth; | |
| struct list_hook widgets; | |
| struct box *tree; | |
| struct table tbl; | |
| struct box **bfs; | |
| }; | |
| struct panel { | |
| uiid id; | |
| struct panel_state state; | |
| struct widget_builder builder; | |
| struct repository repo[2]; | |
| struct box root; | |
| struct panel *parent; | |
| struct list_hook hook; | |
| unsigned act; | |
| }; | |
| /* event */ | |
| enum event_type { | |
| EVT_ACTIVATED, | |
| EVT_DEACTIVATED, | |
| EVT_CLICKED, | |
| EVT_PRESSED, | |
| EVT_RELEASED, | |
| EVT_ENTERED, | |
| EVT_EXITED, | |
| EVT_DRAG_BEGIN, | |
| EVT_DRAGGED, | |
| EVT_DRAG_END, | |
| EVT_MOVED, | |
| EVT_KEY, | |
| EVT_TEXT, | |
| EVT_SCROLLED, | |
| EVT_SHORTCUT, | |
| EVT_COUNT | |
| }; | |
| struct event_entered_exited { | |
| struct box *last; | |
| struct box *cur; | |
| }; | |
| struct event_int2 {int x, y;}; | |
| struct event_key { | |
| int code; | |
| unsigned pressed:1; | |
| unsigned released:1; | |
| unsigned ctrl:1; | |
| unsigned shift:1; | |
| unsigned alt:1; | |
| unsigned super:1; | |
| }; | |
| struct event_text { | |
| char *buf; | |
| int len; | |
| unsigned ctrl:1; | |
| unsigned shift:1; | |
| unsigned alt:1; | |
| unsigned super:1; | |
| unsigned resized:1; | |
| }; | |
| union event_data { | |
| struct event_entered_exited entered; | |
| struct event_entered_exited exited; | |
| struct event_int2 moved; | |
| struct event_int2 scroll; | |
| struct event_int2 clicked; | |
| struct event_int2 drag_begin; | |
| struct event_int2 dragged; | |
| struct event_int2 drag_end; | |
| struct event_int2 pressed; | |
| struct event_int2 released; | |
| struct event_text text; | |
| struct event_key key; | |
| struct event_key shortcut; | |
| }; | |
| struct event { | |
| enum event_type type; | |
| int cap, cnt; | |
| struct box *origin; | |
| struct box **boxes; | |
| struct list_hook hook; | |
| union event_data with; | |
| }; | |
| /* operation */ | |
| enum operation_type { | |
| OP_BLUEPRINT, | |
| OP_LAYOUTING, | |
| OP_TRANSFORM, | |
| OP_INPUT, | |
| OP_PAINT, | |
| OP_CLEANUP, | |
| OP_COUNT | |
| }; | |
| enum process { | |
| PROCESS_BLUEPRINT = flag(OP_BLUEPRINT), | |
| PROCESS_LAYOUTING = flag(OP_LAYOUTING), | |
| PROCESS_TRANSFORM = flag(OP_TRANSFORM), | |
| PROCESS_CLEANUP = flag(OP_CLEANUP), | |
| PROCESS_COMMIT = PROCESS_TRANSFORM|PROCESS_BLUEPRINT|PROCESS_LAYOUTING, | |
| PROCESS_INPUT = PROCESS_COMMIT|flag(OP_INPUT), | |
| PROCESS_PAINT = PROCESS_COMMIT|flag(OP_PAINT), | |
| PROCESS_EVERYTHING = 0xFFF | |
| }; | |
| struct operation { | |
| /* common */ | |
| enum operation_type type; | |
| struct context *ctx; | |
| struct memory_arena *arena; | |
| struct temp_memory tmp; | |
| /* layout + blueprint + cleanup */ | |
| struct panel *panel; | |
| struct repository *repo; | |
| int begin, end, inc; | |
| struct box **boxes; /* layout + blueprint */ | |
| struct list_hook widgets; /* cleanup */ | |
| /* input */ | |
| struct list_hook evts; | |
| struct input *in; | |
| /* paint */ | |
| struct box **surfaces; | |
| int cnt; | |
| }; | |
| /* context */ | |
| enum widget_internal { | |
| WIDGET_INTERNAL_BEGIN = 0x100000, | |
| /* layers */ | |
| WIDGET_ROOT, | |
| WIDGET_OVERLAY, | |
| WIDGET_POPUP, | |
| WIDGET_CONTEXTUAL, | |
| WIDGET_UNBLOCKING, | |
| WIDGET_BLOCKING, | |
| WIDGET_UI, | |
| /* widgets */ | |
| WIDGET_PANEL, | |
| }; | |
| enum state_machine_state { | |
| STATE_PREPROCESS, | |
| STATE_GC, | |
| STATE_CLEANUP, | |
| STATE_BLUEPRINT, | |
| STATE_LAYOUTING, | |
| STATE_TRANSFORM, | |
| STATE_INPUT, | |
| STATE_PAINT, | |
| STATE_POSTPROCESS | |
| }; | |
| struct state_machine { | |
| enum state_machine_state state; | |
| unsigned unbalanced:1; | |
| struct list_hook *iter; | |
| int cnt; | |
| }; | |
| struct context { | |
| struct input input; | |
| struct state_machine state_machine; | |
| /* memory */ | |
| struct allocator mem; | |
| struct block_allocator blkmem; | |
| struct memory_arena arena; | |
| /* panel */ | |
| struct list_hook pan; | |
| struct list_hook garbage; | |
| struct list_hook freelist; | |
| /* tree */ | |
| struct box *tree; | |
| struct box *overlay; | |
| struct box *popup; | |
| struct box *contextual; | |
| struct box *unblocking; | |
| struct box *blocking; | |
| struct box *ui; | |
| struct box *active; | |
| struct box *origin; | |
| struct box *hot; | |
| /* misc */ | |
| struct panel_state *panel_state; | |
| struct panel *root; | |
| struct panel *stk[16]; | |
| int stkcnt; | |
| }; | |
| /* context */ | |
| static struct context *create(const struct allocator *a); | |
| static void destroy(struct context *ctx); | |
| /* input */ | |
| static void input_char(struct context *ctx, char c); | |
| static void input_resize(struct context *ctx, int w, int h); | |
| static void input_scroll(struct context *ctx, int x, int y); | |
| static void input_motion(struct context *ctx, int x, int y); | |
| static void input_rune(struct context *ctx, unsigned long r); | |
| static void input_key(struct context *ctx, int key, int down); | |
| static void input_shortcut(struct context *ctx, int id, int down); | |
| static void input_text(struct context *ctx, const char *t, int len); | |
| static void input_button(struct context *ctx, enum mouse_button btn, int down); | |
| /* process */ | |
| static struct operation *process_begin(struct context *ctx, unsigned int flags); | |
| static void blueprint(struct operation *op, struct box *b); | |
| static void layout(struct operation *op, struct box *b); | |
| static void process_end(struct operation *op); | |
| /* panel */ | |
| static struct panel_state *begin(struct context *ctx, uiid id); | |
| static struct box *find(struct panel_state *p, uiid type_id, uiid widget_id); | |
| static void end(struct panel_state *s); | |
| static void clear(struct context *ctx, uiid pid); | |
| static struct box *query(struct context *ctx, uiid pid, uiid type_id, uiid widget_id); | |
| /* widgets */ | |
| static struct widget_builder *widget_begin(struct panel_state *s, uiid type_id, uiid widget_id, int size, int alignment); | |
| static struct box *widget_add_box_begin(struct widget_builder *w, uiid box_id); | |
| static void widget_add_box_end(struct widget_builder *w); | |
| static struct box *widget_add_box(struct widget_builder *w, uiid box_id); | |
| static void widget_end(struct widget_builder *w); | |
| /* stack */ | |
| static void push(struct panel_state *s, struct box *b); | |
| static struct box *peek(struct panel_state *s); | |
| static void pop(struct panel_state *s); | |
| static void reset(struct panel_state *s); | |
| /* box */ | |
| static void box_add_property(struct box *b, enum property_type type); | |
| static void box_shrink(struct box *d, const struct box *s, int pad); | |
| static int box_intersect(const struct box *a, const struct box *b); | |
| static void measure(struct box *b, int pad); | |
| static void compute(struct box *b, int pad); | |
| /* arena */ | |
| static void *arena_push(struct memory_arena *a, long size, long align); | |
| #define arena_push_type(a,type) (type*)arena_push(a, szof(type), alignof(type)) | |
| #define arena_push_array(a,n,type) (type*)arena_push(a, (n)*szof(type), alignof(type)) | |
| static void arena_clear(struct memory_arena *a); | |
| /* utf-8 */ | |
| static int utf_decode(unsigned long *rune, const char *str, int len); | |
| static int utf_encode(char *s, int cap, unsigned long u); | |
| static int utf_len(const char *s, int len); | |
| static const char* utf_at(unsigned long *rune, int *rune_len, const char *s, int len, int idx); | |
| /* --------------------------------------------------------------------------- | |
| * IMPLEMENTATION | |
| * --------------------------------------------------------------------------- */ | |
| #define UTF_SIZE 4 | |
| #define UTF_INVALID 0xFFFD | |
| static const unsigned char utfbyte[UTF_SIZE+1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; | |
| static const unsigned char utfmask[UTF_SIZE+1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; | |
| static const unsigned long utfmin[UTF_SIZE+1] = {0, 0, 0x80, 0x800, 0x10000}; | |
| static const unsigned long utfmax[UTF_SIZE+1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; | |
| #define qalloc(a,sz) (a)->alloc((a)->usr, sz, __FILE__, __LINE__) | |
| #define qdealloc(a,ptr) (a)->dealloc((a)->usr, ptr, __FILE__, __LINE__) | |
| static void *dalloc(void *usr, long s, const char *file, int line){return malloc((size_t)s);} | |
| static void dfree(void *usr, void *d, const char *file, int line){free(d);} | |
| static const struct allocator default_allocator = {0,dalloc,dfree}; | |
| #define list_empty(l) ((l)->next == (l)) | |
| #define list_entry(ptr,type,member) containerof(ptr,type,member) | |
| #define list_foreach(i,l) for ((i)=(l)->next; (i)!=(l); (i)=(i)->next) | |
| #define list_foreach_rev(i,l) for ((i)=(l)->prev; (i)!=(l); (i)=(i)->prev) | |
| #define list_foreach_s(i,n,l) for ((i)=(l)->next,(n)=(i)->next;(i)!=(l);(i)=(n),(n)=(i)->next) | |
| #define list_foreach_rev_s(i,n,l) for ((i)=(l)->prev,(n)=(i)->prev;(i)!=(l);(i)=(n),(n)=(i)->prev) | |
| static unsigned long | |
| next_pow2(unsigned long n) | |
| { | |
| n--; | |
| n |= n >> 1; | |
| n |= n >> 2; | |
| n |= n >> 4; | |
| n |= n >> 8; | |
| n |= n >> 16; | |
| return ++n; | |
| } | |
| static int | |
| floori(float x) | |
| { | |
| x = cast(float,(cast(int,x) - ((x < 0.0f) ? 1 : 0))); | |
| return cast(int,x); | |
| } | |
| static int | |
| ceili(float x) | |
| { | |
| if (x < 0) { | |
| int t = cast(int,x); | |
| float r = x - cast(float,t); | |
| return (r > 0.0f) ? (t+1): t; | |
| } else { | |
| int i = cast(int,x); | |
| return (x > i) ? (i+1): i; | |
| } | |
| } | |
| static int | |
| utf_validate(unsigned long *u, int i) | |
| { | |
| assert(u); | |
| if (!u) return 0; | |
| if (!between(*u, utfmin[i], utfmax[i]) || | |
| between(*u, 0xD800, 0xDFFF)) | |
| *u = UTF_INVALID; | |
| for (i = 1; *u > utfmax[i]; ++i); | |
| return i; | |
| } | |
| static unsigned long | |
| utf_decode_byte(char c, int *i) | |
| { | |
| assert(i); | |
| if (!i) return 0; | |
| for(*i = 0; *i < cntof(utfmask); ++(*i)) { | |
| if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) | |
| return (unsigned char)(c & ~utfmask[*i]); | |
| } return 0; | |
| } | |
| static int | |
| utf_decode(unsigned long *u, const char *s, int slen) | |
| { | |
| int i,j, len, type = 0; | |
| unsigned long udecoded; | |
| assert(s); assert(u); | |
| if (!s || !u || !slen) | |
| return 0; | |
| *u = UTF_INVALID; | |
| udecoded = utf_decode_byte(s[0], &len); | |
| if (!between(len, 1, UTF_SIZE)) | |
| return 1; | |
| for (i = 1, j = 1; i < slen && j < len; ++i, ++j) { | |
| udecoded = (udecoded << 6) | utf_decode_byte(s[i], &type); | |
| if (type != 0) return j; | |
| } if (j < len) return 0; | |
| *u = udecoded; | |
| utf_validate(u, len); | |
| return len; | |
| } | |
| static char | |
| utf_encode_byte(unsigned long u, int i) | |
| { | |
| return (char)((utfbyte[i]) | ((unsigned char)u & ~utfmask[i])); | |
| } | |
| static int | |
| utf_encode(char *s, int cap, unsigned long u) | |
| { | |
| int len, i; | |
| len = utf_validate(&u, 0); | |
| if (cap < len || !len || len > UTF_SIZE) | |
| return 0; | |
| for (i = len - 1; i != 0; --i) { | |
| s[i] = utf_encode_byte(u, 0); | |
| u >>= 6; | |
| } s[0] = utf_encode_byte(u, len); | |
| return len; | |
| } | |
| static int | |
| utf_len(const char *s, int len) | |
| { | |
| int result = 0; | |
| int rune_len = 0; | |
| unsigned long rune = 0; | |
| if (!s) return 0; | |
| while ((rune_len = utf_decode(&rune, s, len))) { | |
| len = max(0,len-rune_len); | |
| s += rune_len; | |
| result++; | |
| } return result; | |
| } | |
| static const char* | |
| utf_at(unsigned long *rune, int *rune_len, | |
| const char *s, int len, int idx) | |
| { | |
| int runes = 0; | |
| if (!s) return 0; | |
| while ((*rune_len = utf_decode(rune, s, len))) { | |
| if (runes++ == idx) return s; | |
| len = max(0,len-*rune_len); | |
| s += *rune_len; | |
| } return 0; | |
| } | |
| static void | |
| transform_init(struct transform *t) | |
| { | |
| t->x = t->y = 0; | |
| t->sx = t->sy = 1; | |
| } | |
| static void | |
| list_init(struct list_hook *list) | |
| { | |
| list->next = list->prev = list; | |
| } | |
| static void | |
| list__add(struct list_hook *n, | |
| struct list_hook *prev, struct list_hook *next) | |
| { | |
| next->prev = n; | |
| n->next = next; | |
| n->prev = prev; | |
| prev->next = n; | |
| } | |
| static void | |
| list_add_head(struct list_hook *list, struct list_hook *n) | |
| { | |
| list__add(n, list, list->next); | |
| } | |
| static void | |
| list_add_tail(struct list_hook *list, struct list_hook *n) | |
| { | |
| list__add(n, list->prev, list); | |
| } | |
| static void | |
| list__del(struct list_hook *prev, struct list_hook *next) | |
| { | |
| next->prev = prev; | |
| prev->next = next; | |
| } | |
| static void | |
| list_del(struct list_hook *entry) | |
| { | |
| list__del(entry->prev, entry->next); | |
| entry->next = entry; | |
| entry->prev = entry; | |
| } | |
| static void | |
| list_move_head(struct list_hook *list, struct list_hook *entry) | |
| { | |
| list_del(entry); | |
| list_add_head(list, entry); | |
| } | |
| static void | |
| list_move_tail(struct list_hook *entry, struct list_hook *list) | |
| { | |
| list_del(entry); | |
| list_add_tail(list, entry); | |
| } | |
| static void | |
| list__splice(const struct list_hook *list, | |
| struct list_hook *prev, struct list_hook *next) | |
| { | |
| struct list_hook *first = list->next; | |
| struct list_hook *last = list->prev; | |
| first->prev = prev; | |
| prev->next = first; | |
| last->next = next; | |
| next->prev = last; | |
| } | |
| static void | |
| list_splice_head(struct list_hook *dst, | |
| struct list_hook *list) | |
| { | |
| if (!list_empty(list)) { | |
| list__splice(list, dst, dst->next); | |
| list_init(list); | |
| } | |
| } | |
| static void | |
| list_splice_tail(struct list_hook *dst, | |
| struct list_hook *list) | |
| { | |
| if (!list_empty(list)) { | |
| list__splice(list, dst->prev, dst); | |
| list_init(list); | |
| } | |
| } | |
| static void | |
| block_alloc_init(struct block_allocator *a) | |
| { | |
| list_init(&a->blks); | |
| list_init(&a->freelist); | |
| } | |
| static struct memory_block* | |
| block_alloc(struct block_allocator *a, long blksz) | |
| { | |
| struct memory_block *blk = 0; | |
| blksz = max(blksz, DEFAULT_MEMORY_BLOCK_SIZE); | |
| if (blksz == DEFAULT_MEMORY_BLOCK_SIZE && !list_empty(&a->freelist)) { | |
| /* allocate from freelist */ | |
| blk = list_entry(a->freelist.next, struct memory_block, hook); | |
| list_del(&blk->hook); | |
| } else blk = qalloc(a->mem, blksz); | |
| /* setup block */ | |
| zero(blk, szof(*blk)); | |
| blk->size = blksz - szof(*blk); | |
| blk->base = cast(unsigned char*, (blk+1)); | |
| /* add block into list */ | |
| a->blkcnt++; | |
| list_init(&blk->hook); | |
| list_add_head(&a->blks, &blk->hook); | |
| return blk; | |
| } | |
| static void | |
| block_dealloc(struct block_allocator *a, struct memory_block *blk) | |
| { | |
| list_del(&blk->hook); | |
| if ((blk->size + szof(*blk)) == DEFAULT_MEMORY_BLOCK_SIZE) | |
| list_add_head(&a->freelist, &blk->hook); | |
| else qdealloc(a->mem, blk); | |
| a->blkcnt--; | |
| } | |
| static void | |
| free_blocks(struct block_allocator *a) | |
| { | |
| struct list_hook *i, *n = 0; | |
| list_foreach_s(i, n, &a->freelist) { | |
| struct memory_block *blk = 0; | |
| blk = list_entry(i, struct memory_block, hook); | |
| list_del(&blk->hook); | |
| qdealloc(a->mem, blk); | |
| } | |
| list_foreach_s(i, n, &a->blks) { | |
| struct memory_block *blk = 0; | |
| blk = list_entry(i, struct memory_block, hook); | |
| list_del(&blk->hook); | |
| qdealloc(a->mem, blk); | |
| } a->blkcnt = 0; | |
| } | |
| static void* | |
| arena_push(struct memory_arena *a, long size, long align) | |
| { | |
| assert(a); | |
| if (!a || !size) return 0; | |
| align = max(align,1); | |
| if (!a->blk || (a->blk->used + size + align) > a->blk->size) { | |
| long minsz = size + szof(struct memory_block) + align; | |
| long blksz = max(minsz, DEFAULT_MEMORY_BLOCK_SIZE); | |
| struct memory_block *blk = block_alloc(a->mem, blksz); | |
| blk->prev = a->blk; | |
| a->blk = blk; | |
| a->blkcnt++; | |
| } | |
| {struct memory_block *blk = a->blk; | |
| unsigned char *raw = blk->base + blk->used; | |
| unsigned char *res = align(raw, align); | |
| blk->used += size + (res - raw); | |
| zero(res, size); | |
| return res;} | |
| } | |
| static void | |
| arena_free_last_blk(struct memory_arena *a) | |
| { | |
| struct memory_block *blk = a->blk; | |
| a->blk = blk->prev; | |
| block_dealloc(a->mem, blk); | |
| a->blkcnt--; | |
| } | |
| static void | |
| arena_clear(struct memory_arena *a) | |
| { | |
| while (a->blk) | |
| arena_free_last_blk(a); | |
| } | |
| static struct temp_memory | |
| temp_memory_begin(struct memory_arena *a) | |
| { | |
| struct temp_memory res; | |
| res.used = a->blk ? a->blk->used: 0; | |
| res.blk = a->blk; | |
| res.arena = a; | |
| a->tmpcnt++; | |
| return res; | |
| } | |
| static void | |
| temp_memory_end(struct temp_memory tmp) | |
| { | |
| struct memory_arena *a = tmp.arena; | |
| while (a->blk != tmp.blk) | |
| arena_free_last_blk(a); | |
| if (a->blk) a->blk->used = tmp.used; | |
| a->tmpcnt--; | |
| } | |
| static void | |
| insert(struct table *tbl, uiid id, struct box *b) | |
| { | |
| uiid cnt = cast(uiid, tbl->cnt); | |
| uiid idx = id & (cnt-1), begin = idx; | |
| do {uiid key = tbl->keys[idx]; | |
| if (key) continue; | |
| tbl->keys[idx] = id; | |
| tbl->boxes[idx] = b; return; | |
| } while ((idx = ((idx+1) & (cnt-1))) != begin); | |
| } | |
| static struct box* | |
| lookup(struct table *tbl, uiid id) | |
| { | |
| uiid key = 0; | |
| uiid cnt = cast(uiid, tbl->cnt); | |
| uiid idx = id & (cnt-1), begin = idx; | |
| do {if (!(key = tbl->keys[idx])) return 0; | |
| if (key == id) return tbl->boxes[idx]; | |
| } while ((idx = ((idx+1) & (cnt-1))) != begin); | |
| return 0; | |
| } | |
| static int | |
| box_equal(const struct box *a, const struct box *b) | |
| { | |
| if (!a || !b) return 0; | |
| if (a->type_id != b->type_id) | |
| return 0; | |
| if (a->widget_id != b->widget_id) | |
| return 0; | |
| if (a->box_id != b->box_id) | |
| return 0; | |
| return 1; | |
| } | |
| static void | |
| box_shrink(struct box *d, const struct box *s, int pad) | |
| { | |
| d->local.x = s->local.x + pad; | |
| d->local.y = s->local.y + pad; | |
| d->local.w = max(0, s->local.w - 2*pad); | |
| d->local.h = max(0, s->local.h - 2*pad); | |
| } | |
| static void | |
| box_add_property(struct box *b, enum property_type type) | |
| { | |
| assert(b); | |
| if (!b) return; | |
| switch (type) { | |
| case PROPERTY_INTERACTIVE: b->interactive=1; break; | |
| case PROPERTY_MOVABLE_X: b->movable_x=1; break; | |
| case PROPERTY_MOVABLE_Y: b->movable_y=1; break; | |
| case PROPERTY_BACKGROUND: b->background=1; break; | |
| case PROPERTY_UNIFORM: b->uniform=1; break; | |
| case PROPERTY_SELECTABLE: b->selectable=1; break;} | |
| } | |
| static int | |
| box_intersect(const struct box *f, const struct box *s) | |
| { | |
| const struct rect *a = &f->screen; | |
| const struct rect *b = &s->screen; | |
| return intersect(a->x, a->y, a->w, a->h, b->x, b->y, b->w, b->h); | |
| } | |
| static void | |
| input_resize(struct context *ctx, int w, int h) | |
| { | |
| struct input *in; | |
| assert(ctx); | |
| if (!ctx) return; | |
| in = &ctx->input; | |
| in->resized = 1; | |
| in->width = w; | |
| in->height = h; | |
| } | |
| static void | |
| input_motion(struct context *ctx, int x, int y) | |
| { | |
| struct input *in; | |
| assert(ctx); | |
| if (!ctx) return; | |
| in = &ctx->input; | |
| in->mouse.x = x; | |
| in->mouse.y = y; | |
| in->mouse.dx = in->mouse.x - in->mouse.lx; | |
| in->mouse.dy = in->mouse.y - in->mouse.ly; | |
| } | |
| static void | |
| input_key(struct context *ctx, int key, int down) | |
| { | |
| struct input *in; | |
| assert(ctx); | |
| if (!ctx) return; | |
| in = &ctx->input; | |
| if (key < 0 || key >= cntof(in->keys) || (in->keys[key].down == down)) | |
| return; | |
| in->keys[key].transitions++; | |
| in->keys[key].down = !!down; | |
| } | |
| static void | |
| input_button(struct context *ctx, enum mouse_button idx, int down) | |
| { | |
| struct input *in; | |
| struct key *btn; | |
| assert(ctx); | |
| if (!ctx) return; | |
| in = &ctx->input; | |
| if (in->mouse.btn[idx].down == down) | |
| return; | |
| btn = in->mouse.btn + idx; | |
| btn->down = !!down; | |
| btn->transitions++; | |
| } | |
| static void | |
| input_shortcut(struct context *ctx, int shortcut, int down) | |
| { | |
| struct input *in; | |
| assert(ctx); | |
| if (!ctx) return; | |
| in = &ctx->input; | |
| assert(shortcut < cntof(in->shortcuts)); | |
| if (in->shortcuts[shortcut].down == down) | |
| return; | |
| in->shortcuts[shortcut].transitions++; | |
| in->shortcuts[shortcut].down = !!down; | |
| } | |
| static void | |
| input_scroll(struct context *ctx, int x, int y) | |
| { | |
| struct input *in; | |
| assert(ctx); | |
| if (!ctx) return; | |
| in = &ctx->input; | |
| in->mouse.wheelx += x; | |
| in->mouse.wheely += y; | |
| } | |
| static void | |
| input_text(struct context *ctx, const char *buf, int len) | |
| { | |
| struct input *in; | |
| assert(ctx); | |
| if (!ctx) return; | |
| in = &ctx->input; | |
| if (in->text_len + len + 1 >= cntof(in->text)) | |
| return; | |
| copy(in->text + in->text_len, buf, len); | |
| in->text_len += len; | |
| in->text[in->text_len] = 0; | |
| } | |
| static void | |
| input_char(struct context *ctx, char c) | |
| { | |
| input_text(ctx, &c, 1); | |
| } | |
| static void | |
| input_rune(struct context *ctx, unsigned long r) | |
| { | |
| int len = 0; | |
| char buf[UTF_SIZE]; | |
| len = utf_encode(buf, UTF_SIZE, r); | |
| input_text(ctx, buf, len); | |
| } | |
| static void | |
| reset(struct panel_state *s) | |
| { | |
| assert(s); | |
| if (!s) return; | |
| s->stkcnt = 0; | |
| } | |
| static void | |
| push(struct panel_state *s, struct box *b) | |
| { | |
| assert(s); | |
| if (!s) return; | |
| assert(s->stkcnt < cntof(s->stk)); | |
| s->stk[s->stkcnt++] = b; | |
| } | |
| static struct box* | |
| peek(struct panel_state *s) | |
| { | |
| struct repository *r; | |
| assert(s); | |
| if (!s) return 0; | |
| r = s->repo; | |
| if (!s->stkcnt) return r->tree; | |
| return s->stk[s->stkcnt-1]; | |
| } | |
| static void | |
| pop(struct panel_state *s) | |
| { | |
| assert(s); | |
| if (!s) return; | |
| assert(s->stkcnt); | |
| --s->stkcnt; | |
| } | |
| static void | |
| repository_init(struct repository *r, struct memory_arena *a) | |
| { | |
| zero(r, szof(*r)); | |
| list_init(&r->widgets); | |
| r->arena.mem = a->mem; | |
| } | |
| static struct panel* | |
| panel_create(struct memory_arena *a, uiid id, | |
| struct list_hook *freelist, struct list_hook *list) | |
| { | |
| int i = 0; | |
| struct panel *p = 0; | |
| if (!list_empty(freelist)) { | |
| /* allocate from freelist */ | |
| struct list_hook *h = freelist->next; | |
| list_del(freelist->next); | |
| p = list_entry(h, struct panel, hook); | |
| zero(p, szof(*p)); | |
| } else p = arena_push_type(a, struct panel); | |
| p->id = id; | |
| list_init(&p->hook); | |
| list_init(&p->root.node); | |
| list_init(&p->root.hook); | |
| list_init(&p->root.links); | |
| transform_init(&p->root.local_transform); | |
| transform_init(&p->root.screen_transform); | |
| list_add_tail(list, &p->hook); | |
| for (i = 0; i < cntof(p->repo); ++i) { | |
| struct repository *r = p->repo + i; | |
| repository_init(r, a); | |
| r->tree = &p->root; | |
| } return p; | |
| } | |
| static void | |
| panel_destroy(struct panel *p, struct list_hook *garbage, | |
| struct list_hook *list) | |
| { | |
| struct list_hook *i, *n; | |
| struct list_hook tmp; | |
| list_init(&tmp); | |
| /* unlink panel */ | |
| list_del(&p->root.node); | |
| list_move_tail(&tmp, &p->hook); | |
| /* remove sub-tree */ | |
| list_foreach_s(i,n,list) { | |
| struct list_hook *k; | |
| struct panel *sub = list_entry(i, struct panel, hook); | |
| list_foreach(k, &tmp) { | |
| struct panel *gb = list_entry(k, struct panel, hook); | |
| if (sub->parent == gb) { | |
| list_del(&sub->root.node); | |
| list_move_tail(&tmp, &sub->hook); | |
| break; | |
| } | |
| } | |
| } list_splice_tail(garbage, &tmp); | |
| } | |
| static struct panel* | |
| panel_find(struct context *ctx, uiid id) | |
| { | |
| struct list_hook *i = 0; | |
| list_foreach(i, &ctx->pan) { | |
| struct panel *p; | |
| p = list_entry(i, struct panel, hook); | |
| if (p->id == id) return p; | |
| } return 0; | |
| } | |
| static struct box* | |
| find(struct panel_state *s, uiid type_id, uiid id) | |
| { | |
| struct repository *r; | |
| struct panel *p; | |
| struct box *b; | |
| assert(s); | |
| if (!s) return 0; | |
| p = s->panel; | |
| r = &p->repo[p->act]; | |
| if (!r->cnt || !r->tbl.cnt) | |
| return 0; | |
| b = lookup(&r->tbl, id); | |
| if (b && b->type_id == type_id) | |
| return b; | |
| else return 0; | |
| } | |
| static struct box* | |
| query(struct context *ctx, uiid pid, uiid type_id, uiid id) | |
| { | |
| struct panel *p; | |
| assert(ctx); | |
| if (!ctx) return 0; | |
| p = panel_find(ctx, pid); | |
| if (!p) return 0; | |
| p->state.panel = p; | |
| return find(&p->state, type_id, id); | |
| } | |
| static struct box* | |
| widget_get_parent(struct widget_builder *w) | |
| { | |
| struct panel_state *s; | |
| struct repository *r; | |
| assert(w); | |
| assert(w->state); | |
| assert(w->state->repo); | |
| if (!w || !w->state || !w->state->repo) | |
| return 0; | |
| s = w->state; | |
| r = s->repo; | |
| if (!w->stkcnt) return r->tree; | |
| return w->stk[w->stkcnt-1]; | |
| } | |
| static struct widget_builder* | |
| widget_begin(struct panel_state *s, uiid type_id, uiid widget_id, | |
| int size, int alignment) | |
| { | |
| struct box *last; | |
| struct widget_builder *w; | |
| assert(s); | |
| if (!s) return 0; | |
| w = s->builder; | |
| zero(w, szof(*w)); | |
| last = find(s, type_id, widget_id); | |
| if (last) { | |
| last->alive = 1; | |
| w->widget_last = last->widget; | |
| } else w->widget_last = 0; | |
| w->widget = arena_push(s->arena, size, alignment); | |
| w->stk[w->stkcnt++] = peek(s); | |
| w->widget_id = widget_id; | |
| w->type_id = type_id; | |
| w->arena = s->arena; | |
| w->state = s; | |
| return w; | |
| } | |
| static struct box* | |
| widget_add_box(struct widget_builder *w, uiid box_id) | |
| { | |
| struct context *ctx; | |
| struct panel_state *st; | |
| struct repository *repo; | |
| struct box *p, *b; | |
| assert(w); | |
| if (!w) return 0; | |
| st = w->state; | |
| ctx = st->ctx; | |
| repo = st->repo; | |
| /* allocate and setup box */ | |
| b = arena_push_type(w->arena, struct box); | |
| b->widget_id = w->widget_id; | |
| b->type_id = w->type_id; | |
| b->widget = w->widget; | |
| b->box_id = box_id; | |
| list_init(&b->hook); | |
| list_init(&b->node); | |
| list_init(&b->links); | |
| transform_init(&b->local_transform); | |
| transform_init(&b->screen_transform); | |
| if (box_equal(ctx->active, b)) | |
| ctx->active = b; | |
| if (box_equal(ctx->origin, b)) | |
| ctx->origin = b; | |
| if (box_equal(ctx->hot, b)) | |
| ctx->hot = b; | |
| /* link box into parent node */ | |
| p = b->parent = widget_get_parent(w); | |
| list_add_tail(&p->links, &b->node); | |
| b->depth = p->depth + 1; | |
| repo->depth = max(repo->depth, b->depth); | |
| /* add widget box into list */ | |
| if (!w->cnt) { | |
| list_add_head(&repo->widgets, &b->hook); | |
| repo->tbl.cnt++; | |
| } w->cnt++; | |
| return b; | |
| } | |
| static struct box* | |
| widget_add_box_begin(struct widget_builder *w, uiid id) | |
| { | |
| struct box* b; | |
| assert(w); | |
| if (!w) return 0; | |
| b = widget_add_box(w, id); | |
| assert(w->stkcnt < cntof(w->stk)); | |
| w->stk[w->stkcnt++] = b; | |
| return b; | |
| } | |
| static void | |
| widget_add_box_end(struct widget_builder *w) | |
| { | |
| assert(w); | |
| if (!w) return; | |
| assert(w->stkcnt); | |
| --w->stkcnt; | |
| } | |
| static void | |
| widget_end(struct widget_builder *w) | |
| { | |
| struct panel_state *st; | |
| struct repository *repo; | |
| assert(w); | |
| if (!w) return; | |
| st = w->state; | |
| repo = st->repo; | |
| repo->cnt += w->cnt; | |
| assert(!(--w->stkcnt)); | |
| } | |
| static struct panel_state* | |
| begin(struct context *ctx, uiid id) | |
| { | |
| struct panel_state *s = 0; | |
| struct panel *parent = 0; | |
| struct panel *p = 0; | |
| struct box *root = 0; | |
| assert(ctx); | |
| if (!ctx) return 0; | |
| /* find or create panel */ | |
| p = panel_find(ctx, id); | |
| if (!p) { | |
| p = panel_create(&ctx->arena, id, &ctx->freelist, &ctx->pan); | |
| if (!p) return 0; | |
| assert(ctx->stkcnt < cntof(ctx->stk)); | |
| parent = (ctx->stkcnt) ? ctx->stk[ctx->stkcnt-1]: ctx->root; | |
| if (parent) { | |
| /* link panel into parent node */ | |
| struct box *pb = peek(&parent->state); | |
| list_add_tail(&pb->links, &p->root.node); | |
| p->root.parent = pb; | |
| p->parent = parent; | |
| } | |
| } else parent = p->parent; | |
| ctx->stk[ctx->stkcnt++] = p; | |
| /* setup panel state */ | |
| s = &p->state; | |
| zero(s, szof(*s)); | |
| s->repo = &p->repo[!p->act]; | |
| s->arena = &s->repo->arena; | |
| s->arena->mem = &ctx->blkmem; | |
| s->builder = &p->builder; | |
| s->panel = p; | |
| s->ctx = ctx; | |
| /* setup root node */ | |
| root = &p->root; | |
| root->uniform = 1; | |
| root->interactive = 1; | |
| s->repo->tree = root; | |
| if (parent) { | |
| /* reset node */ | |
| struct panel_state *ps = &parent->state; | |
| struct box *pb = peek(ps); | |
| root->cnt = 0; | |
| root->widget = p; | |
| root->widget_id = id; | |
| root->box_id = WIDGET_PANEL; | |
| root->type_id = WIDGET_PANEL; | |
| root->depth = pb->depth+1; | |
| s->repo->depth = root->depth; | |
| list_init(&root->links); | |
| list_init(&root->hook); | |
| } else { | |
| /* init tree root node */ | |
| root->widget_id = WIDGET_ROOT; | |
| root->type_id = WIDGET_ROOT; | |
| root->box_id = WIDGET_ROOT; | |
| root->widget = ctx; | |
| ctx->tree = root; | |
| } push(s, root); | |
| return s; | |
| } | |
| static void | |
| end(struct panel_state *s) | |
| { | |
| struct context *ctx; | |
| assert(s); | |
| if (!s) return; | |
| ctx = s->ctx; | |
| assert(ctx->stkcnt > 0); | |
| ctx->stkcnt--; | |
| } | |
| static void | |
| clear(struct context *ctx, uiid pid) | |
| { | |
| struct panel *p = 0; | |
| assert(ctx); | |
| if (!ctx) return; | |
| p = panel_find(ctx, pid); | |
| if (p) panel_destroy(p, &ctx->garbage, &ctx->pan); | |
| } | |
| static struct panel_state* | |
| popup_begin(struct context *ctx, uiid id) | |
| { | |
| struct panel_state *s = 0; | |
| struct panel *root = 0; | |
| struct box *blk = 0; | |
| assert(ctx); | |
| if (!ctx) return 0; | |
| root = ctx->root; | |
| s = &root->state; | |
| blk = ctx->blocking; | |
| ctx->stk[ctx->stkcnt++] = root; | |
| push(s, ctx->popup); | |
| blk->interactive = 0; | |
| return begin(ctx, id); | |
| } | |
| static void | |
| popup_close(struct panel_state *s) | |
| { | |
| struct context *ctx = 0; | |
| struct list_hook *i, *n; | |
| struct box *blk = 0; | |
| assert(s && s->ctx); | |
| if (!s) return; | |
| ctx = s->ctx; | |
| blk = ctx->blocking; | |
| list_foreach_s(i, n, &blk->links) { | |
| /* closes all popups */ | |
| struct box *b = list_entry(i,struct box,node); | |
| struct panel *p = b->widget; | |
| if (b == ctx->ui) continue; | |
| panel_destroy(p, &ctx->garbage, &ctx->pan); | |
| } | |
| } | |
| static void | |
| popup_end(struct panel_state *s) | |
| { | |
| struct panel_state *p = 0; | |
| struct context *ctx = 0; | |
| struct panel *root = 0; | |
| assert(s); | |
| if (!s) return; | |
| end(s); | |
| ctx = s->ctx; | |
| root = ctx->root; | |
| p = &root->state; | |
| assert(ctx->stkcnt > 0); | |
| ctx->stkcnt--; | |
| pop(p); | |
| } | |
| static struct context* | |
| create(const struct allocator *a) | |
| { | |
| struct context *ctx = 0; | |
| struct panel_state *s = 0; | |
| struct widget_builder *w = 0; | |
| a = (a) ? a: &default_allocator; | |
| ctx = qalloc(a, szof(*ctx)); | |
| zero(ctx, szof(*ctx)); | |
| ctx->mem = *a; | |
| ctx->blkmem.mem = &ctx->mem; | |
| ctx->arena.mem = &ctx->blkmem; | |
| block_alloc_init(&ctx->blkmem); | |
| list_init(&ctx->pan); | |
| list_init(&ctx->garbage); | |
| list_init(&ctx->freelist); | |
| s = begin(ctx, 0); | |
| w = widget_begin(s, WIDGET_ROOT, WIDGET_ROOT, 0,0); | |
| ctx->overlay = widget_add_box_begin(w, WIDGET_OVERLAY); | |
| box_add_property(ctx->overlay, PROPERTY_INTERACTIVE); | |
| box_add_property(ctx->overlay, PROPERTY_UNIFORM); | |
| ctx->popup = widget_add_box_begin(w, WIDGET_POPUP); | |
| box_add_property(ctx->popup, PROPERTY_INTERACTIVE); | |
| box_add_property(ctx->popup, PROPERTY_UNIFORM); | |
| ctx->contextual = widget_add_box_begin(w, WIDGET_CONTEXTUAL); | |
| box_add_property(ctx->contextual, PROPERTY_INTERACTIVE); | |
| box_add_property(ctx->contextual, PROPERTY_UNIFORM); | |
| ctx->unblocking = widget_add_box_begin(w, WIDGET_UNBLOCKING); | |
| box_add_property(ctx->unblocking, PROPERTY_INTERACTIVE); | |
| box_add_property(ctx->unblocking, PROPERTY_UNIFORM); | |
| ctx->blocking = widget_add_box_begin(w, WIDGET_BLOCKING); | |
| box_add_property(ctx->blocking, PROPERTY_INTERACTIVE); | |
| box_add_property(ctx->blocking, PROPERTY_UNIFORM); | |
| ctx->ui = widget_add_box(w, WIDGET_UI); | |
| box_add_property(ctx->ui, PROPERTY_INTERACTIVE); | |
| box_add_property(ctx->ui, PROPERTY_BACKGROUND); | |
| widget_add_box_end(w); | |
| widget_add_box_end(w); | |
| widget_add_box_end(w); | |
| widget_add_box_end(w); | |
| widget_add_box_end(w); | |
| widget_end(w); | |
| push(s, ctx->ui); | |
| end(s); | |
| ctx->hot = ctx->tree; | |
| ctx->root = s->panel; | |
| ctx->active = ctx->tree; | |
| ctx->origin = ctx->tree; | |
| return ctx; | |
| } | |
| static void | |
| destroy(struct context *ctx) | |
| { | |
| assert(ctx); | |
| if (!ctx) return; | |
| free_blocks(&ctx->blkmem); | |
| qdealloc(&ctx->mem, ctx); | |
| } | |
| static struct operation* | |
| operation_begin(enum operation_type type, | |
| struct context *ctx, struct memory_arena *arena) | |
| { | |
| struct temp_memory tmp; | |
| struct operation *res = 0; | |
| tmp = temp_memory_begin(arena); | |
| res = arena_push_type(arena, struct operation); | |
| res->arena = arena; | |
| res->type = type; | |
| res->ctx = ctx; | |
| res->tmp = tmp; | |
| list_init(&res->evts); | |
| list_init(&res->widgets); | |
| return res; | |
| } | |
| static void | |
| operation_end(struct operation *op) | |
| { | |
| assert(op); | |
| if (!op) return; | |
| temp_memory_end(op->tmp); | |
| } | |
| static struct box** | |
| bfs(struct box **buf, struct box *root) | |
| { | |
| struct list_hook *i; | |
| unsigned long head = 0, tail = 1; | |
| struct box **que = buf; que[tail] = root; | |
| while (head < tail) { | |
| struct box *b = que[++head]; | |
| list_foreach(i, &b->links) | |
| que[++tail] = list_entry(i,struct box,node); | |
| } return que+1; | |
| } | |
| static int | |
| dfs(struct box **buf, struct box **stk, struct box *root) | |
| { | |
| int tail = 0; | |
| struct list_hook *i; | |
| unsigned long head = 0; | |
| stk[head++] = root; | |
| while (head > 0) { | |
| struct box *b = stk[--head]; | |
| buf[tail++] = b; | |
| list_foreach(i, &b->links) { | |
| struct box *s = list_entry(i,struct box,node); | |
| if (s->hidden || !box_intersect(b, s)) continue; | |
| stk[head++] = s; | |
| } | |
| } return tail; | |
| } | |
| static void | |
| reorder(struct box *b) | |
| { | |
| while (b->parent) { | |
| struct list_hook *i; | |
| struct box *p = b->parent; | |
| if (p->uniform) goto nxt; | |
| list_foreach(i, &p->links) { | |
| struct box *n = list_entry(i, struct box, node); | |
| if (n != b || n->background) break; | |
| list_move_tail(&n->node, &p->links); | |
| break; | |
| } nxt: b = p; | |
| } return; | |
| } | |
| static struct box* | |
| at(struct box *b, int mx, int my) | |
| { | |
| struct list_hook *i; | |
| rn: list_foreach_rev(i, &b->links) { | |
| struct box *sub = list_entry(i, struct box, node); | |
| if (!sub->interactive || sub->hidden) continue; | |
| if (inbox(mx, my, sub->screen.x, sub->screen.y, sub->screen.w, sub->screen.h)) | |
| {b = sub; goto rn;} | |
| } return b; | |
| } | |
| static void | |
| event_add_box(struct event *evt, struct box *box) | |
| { | |
| assert(evt->cnt < evt->cap); | |
| if (evt->cnt >= evt->cap) return; | |
| evt->boxes[evt->cnt++] = box; | |
| } | |
| static struct event* | |
| event_begin(struct operation *op, enum event_type type, struct box *orig) | |
| { | |
| struct event *res; | |
| res = arena_push_type(op->arena, struct event); | |
| list_init(&res->hook); | |
| list_add_tail(&op->evts, &res->hook); | |
| res->type = type; | |
| res->origin = orig; | |
| res->cap = orig->depth + 1; | |
| res->boxes = arena_push_array(op->arena, res->cap, struct box*); | |
| event_add_box(res, orig); | |
| return res; | |
| } | |
| static void | |
| event_end(struct event *evt) | |
| { | |
| unused(evt); | |
| } | |
| static struct list_hook* | |
| it(struct list_hook *list, struct list_hook *iter) | |
| { | |
| if (iter && iter->next == list) | |
| iter = 0; | |
| else if (!iter) | |
| iter = list->next; | |
| else iter = iter->next; | |
| return iter; | |
| } | |
| static struct list_hook* | |
| itr(struct list_hook *list, struct list_hook *iter) | |
| { | |
| if (iter && iter->prev == list) | |
| iter = 0; | |
| else if (!iter) | |
| iter = list->prev; | |
| else iter = iter->prev; | |
| return iter; | |
| } | |
| static void | |
| swap_buffers(struct panel *p, struct repository *r, | |
| struct memory_arena *a) | |
| { | |
| arena_clear(&r->arena); | |
| repository_init(r, a); | |
| p->act = !p->act; | |
| } | |
| static struct operation* | |
| process_begin(struct context *ctx, unsigned int flags) | |
| { | |
| int i; | |
| #define jmpto(sm,s) do {(sm)->state = s; goto run;} while(0) | |
| struct state_machine *sm = 0; | |
| assert(ctx); | |
| assert(!ctx->stkcnt); | |
| if (!ctx) return 0; | |
| flags = (!flags) ? PROCESS_INPUT: flags; | |
| sm = &ctx->state_machine; | |
| run: switch(sm->state) { | |
| case STATE_PREPROCESS: { | |
| struct list_hook *pi = 0; | |
| list_foreach(pi, &ctx->pan) { | |
| struct panel *p = list_entry(pi, struct panel, hook); | |
| struct repository *r = &p->repo[!p->act]; | |
| if (!r->cnt) { | |
| /* explicitly reset input state */ | |
| if ((flags & flag(OP_INPUT))) { | |
| r = &p->repo[p->act]; | |
| for (i = 0; i < r->cnt; ++i) { | |
| struct box *b = r->bfs[i]; | |
| b->entered = b->exited = 0; | |
| b->drag_end = b->moved = 0; | |
| b->pressed = b->released = 0; | |
| b->clicked = b->scrolled = 0; | |
| b->drag_begin = b->dragged = 0; | |
| b->activated = b->deactivated = 0; | |
| } | |
| } continue; | |
| } sm->unbalanced = 1; | |
| /* allocate hash table */ | |
| {unsigned long tblcnt = 0; | |
| tblcnt = cast(unsigned long, (cast(float,r->tbl.cnt)*1.3f)); | |
| tblcnt = next_pow2(tblcnt); | |
| r->tbl.cnt = cast(int, tblcnt); | |
| r->tbl.keys = arena_push_array(&r->arena, r->tbl.cnt, uiid); | |
| r->tbl.boxes = arena_push_array(&r->arena, r->tbl.cnt, struct box*);} | |
| r->cnt++; | |
| /* insert each widget root box into table */ | |
| {struct list_hook *bi = 0; | |
| list_foreach(bi, &r->widgets) { | |
| struct box *w = list_entry(bi, struct box, hook); | |
| insert(&r->tbl, w->widget_id, w); | |
| }} | |
| /* generate list of boxes in bfs order */ | |
| r->bfs = arena_push_array(&r->arena, r->cnt+1, struct box*); | |
| r->bfs = bfs(r->bfs, r->tree); | |
| } jmpto(sm, STATE_GC); | |
| } | |
| case STATE_GC: { | |
| struct panel *p = 0; | |
| struct repository *r = 0; | |
| struct operation *op = 0; | |
| while (1) { | |
| do {sm->iter = it(&ctx->pan, sm->iter); | |
| if (!sm->iter) jmpto(sm, STATE_CLEANUP); | |
| p = list_entry(sm->iter, struct panel, hook); | |
| r = &p->repo[p->act]; | |
| } while (!p->repo[!p->act].cnt); | |
| if (flags & PROCESS_CLEANUP) break; | |
| swap_buffers(p,r,&ctx->arena); | |
| } | |
| op = operation_begin(OP_CLEANUP, ctx, &ctx->arena); | |
| op->panel = p; | |
| op->repo = r; | |
| /* gather up all "dead" widget boxes */ | |
| {struct list_hook *x, *n; | |
| list_foreach_s(x, n, &r->widgets) { | |
| struct box *b = 0; | |
| b = list_entry(x, struct box, hook); | |
| if (b->alive) continue; | |
| list_move_tail(&op->widgets, &b->hook); | |
| }} return op; | |
| } | |
| case STATE_CLEANUP: { | |
| struct panel *p = 0; | |
| struct repository *r = 0; | |
| struct operation *op = 0; | |
| struct list_hook *freelist = &ctx->freelist; | |
| if (!(flags & PROCESS_CLEANUP)) | |
| list_splice_head(freelist, &ctx->garbage); | |
| if (list_empty(&ctx->garbage)) { | |
| /* state transition table */ | |
| if (sm->unbalanced && (flags & PROCESS_BLUEPRINT)) | |
| jmpto(sm, STATE_BLUEPRINT); | |
| else if (flags & flag(OP_INPUT)) | |
| jmpto(sm, STATE_INPUT); | |
| else if (flags & flag(OP_PAINT)) | |
| jmpto(sm, STATE_PAINT); | |
| else return 0; | |
| } | |
| sm->iter = ctx->garbage.next; | |
| p = list_entry(sm->iter, struct panel, hook); | |
| r = &p->repo[ p->act]; | |
| if (!p->repo[!p->act].cnt) | |
| list_move_tail(freelist, &p->hook); | |
| op = operation_begin(OP_CLEANUP, ctx, &r->arena); | |
| op->widgets = r->widgets; | |
| op->panel = p; | |
| op->repo = r; | |
| return op; | |
| } | |
| case STATE_BLUEPRINT: { | |
| struct panel *p = 0; | |
| struct repository *r = 0; | |
| struct operation *op = 0; | |
| sm->iter = itr(&ctx->pan, sm->iter); | |
| if (!sm->iter) jmpto(sm, STATE_LAYOUTING); | |
| p = list_entry(sm->iter, struct panel, hook); | |
| r = &p->repo[p->act]; | |
| op = operation_begin(OP_BLUEPRINT, ctx, &r->arena); | |
| op->end = op->inc = -1; | |
| op->begin = r->cnt-1; | |
| op->boxes = r->bfs; | |
| op->panel = p; | |
| op->repo = r; | |
| return op; | |
| } | |
| case STATE_LAYOUTING: { | |
| struct panel *p = 0; | |
| struct repository *r = 0; | |
| struct operation *op = 0; | |
| sm->iter = it(&ctx->pan, sm->iter); | |
| if (!sm->iter) { | |
| sm->unbalanced = 0; | |
| /* state transition table */ | |
| if (flags & PROCESS_TRANSFORM) | |
| jmpto(sm, STATE_TRANSFORM); | |
| else if (flags & PROCESS_PAINT) | |
| jmpto(sm, STATE_PAINT); | |
| else return 0; | |
| } | |
| p = list_entry(sm->iter, struct panel, hook); | |
| r = &p->repo[p->act]; | |
| op = operation_begin(OP_LAYOUTING, ctx, &r->arena); | |
| op->begin = 0, op->inc = 1; | |
| op->end = max(0,r->cnt); | |
| op->boxes = r->bfs; | |
| op->panel = p; | |
| op->repo = r; | |
| return op; | |
| } | |
| case STATE_TRANSFORM: { | |
| /* calculate all box screen coordinates and transformations */ | |
| struct list_hook *pi = 0; | |
| list_foreach(pi, &ctx->pan) { | |
| struct panel *p = list_entry(pi, struct panel, hook); | |
| struct repository *r = &p->repo[p->act]; | |
| for (i = 0; i < r->cnt; ++i) { | |
| struct list_hook *bi; | |
| struct box *b = r->bfs[i]; | |
| struct transform *tb = &b->screen_transform; | |
| list_foreach(bi, &b->links) { | |
| struct box *sub = list_entry(bi, struct box, node); | |
| const struct transform *lt = &sub->local_transform; | |
| struct transform *st = &sub->screen_transform; | |
| struct rect *l = &sub->local; | |
| struct rect *s = &sub->screen; | |
| /* screen transformations */ | |
| st->x = tb->x + lt->x; | |
| st->y = tb->y + lt->y; | |
| st->sx = tb->sx * lt->sx; | |
| st->sy = tb->sy * lt->sy; | |
| /* screen coordinates */ | |
| s->x = floori(cast(float,(l->x + st->x))*st->sx); | |
| s->y = floori(cast(float,(l->y + st->y))*st->sy); | |
| s->w = ceili(cast(float,l->w) * st->sx); | |
| s->h = ceili(cast(float,l->h) * st->sy); | |
| } | |
| } | |
| } | |
| /* state transition table */ | |
| if (sm->unbalanced && (flags & PROCESS_BLUEPRINT)) | |
| jmpto(sm, STATE_BLUEPRINT); | |
| else if (flags & PROCESS_INPUT) | |
| jmpto(sm, STATE_INPUT); | |
| else if (flags & PROCESS_PAINT) | |
| jmpto(sm, STATE_PAINT); | |
| else return 0; | |
| } break; | |
| case STATE_INPUT: { | |
| struct operation *op = 0; | |
| struct input *in = &ctx->input; | |
| if (in->resized) { | |
| struct box *root = ctx->tree; | |
| in->resized = 0; | |
| root->local.w = root->screen.w = in->width; | |
| root->local.h = root->screen.h = in->height; | |
| if (root->local.w != in->width || root->local.h != in->height) | |
| jmpto(sm, STATE_BLUEPRINT); | |
| } | |
| op = operation_begin(OP_INPUT, ctx, &ctx->arena); | |
| op->in = in; | |
| if (in->mouse.dx || in->mouse.dy) { | |
| /* motion - entered, exited, dragged, moved */ | |
| int mx = in->mouse.x, my = in->mouse.y; | |
| int lx = in->mouse.lx, ly = in->mouse.ly; | |
| struct box *last = ctx->hot; | |
| ctx->hot = at(ctx->tree, mx, my); | |
| if (ctx->hot != last) { | |
| struct event *evt; | |
| struct box *prev = last; | |
| struct box *cur = ctx->hot; | |
| cur->entered = !inbox(lx, ly, cur->screen.x, cur->screen.y, cur->screen.w, cur->screen.h); | |
| prev->exited = !inbox(mx, my, prev->screen.x, prev->screen.y, prev->screen.w, prev->screen.h); | |
| /* exited */ | |
| evt = event_begin(op, EVT_EXITED, prev); | |
| evt->with.entered.cur = ctx->hot; | |
| evt->with.entered.last = last; | |
| while ((prev = prev->parent)) { | |
| int was = inbox(lx, ly, prev->screen.x, prev->screen.y, prev->screen.w, prev->screen.h); | |
| int isnt = !inbox(mx, my, prev->screen.x, prev->screen.y, prev->screen.w, prev->screen.h); | |
| prev->exited = was && isnt; | |
| event_add_box(evt, prev); | |
| } event_end(evt); | |
| /* entered */ | |
| evt = event_begin(op, EVT_ENTERED, cur); | |
| evt->with.exited.cur = ctx->hot; | |
| evt->with.exited.last = last; | |
| while ((cur = cur->parent)) { | |
| int wasnt = !inbox(lx, ly, cur->screen.x, cur->screen.y, cur->screen.w, cur->screen.h); | |
| int is = inbox(mx, my, cur->screen.x, cur->screen.y, cur->screen.w, cur->screen.h); | |
| cur->entered = wasnt && is; | |
| event_add_box(evt, cur); | |
| } event_end(evt); | |
| } | |
| if (ctx->active == ctx->origin) { | |
| /* dragged, moved */ | |
| struct box *a = ctx->active; | |
| struct event *evt; | |
| struct box *act; | |
| /* moved */ | |
| if (a->movable_x || a->movable_y) { | |
| evt = event_begin(op, EVT_EXITED, act = a); | |
| if (a->movable_x) | |
| a->local.x += evt->with.moved.x = in->mouse.dx; | |
| if (a->movable_y) | |
| a->local.y += evt->with.moved.y = in->mouse.dy; | |
| a->moved = 1; | |
| while ((act = act->parent)) | |
| event_add_box(evt, act); | |
| event_end(evt); | |
| sm->unbalanced = 1; | |
| } | |
| /* dragged */ | |
| evt = event_begin(op, EVT_DRAGGED, act = a); | |
| evt->with.dragged.x = in->mouse.dx; | |
| evt->with.dragged.y = in->mouse.dy; | |
| while ((act = act->parent)) | |
| event_add_box(evt, act); | |
| event_end(evt); | |
| a->dragged = 1; | |
| } | |
| /* reset */ | |
| in->mouse.dx = in->mouse.dy = 0; | |
| in->mouse.lx = in->mouse.x; | |
| in->mouse.ly = in->mouse.y; | |
| } | |
| for (i = 0; i < cntof(in->mouse.btn); ++i) { | |
| /* button - pressed, released, clicked */ | |
| struct key *btn = in->mouse.btn + i; | |
| struct box *a = ctx->active; | |
| struct box *h = ctx->hot; | |
| struct event *evt = 0; | |
| struct box *act = 0; | |
| if (!btn->transitions) continue; | |
| ctx->active = (btn->down && btn->transitions) ? ctx->hot: ctx->active; | |
| ctx->origin = (btn->down && btn->transitions) ? ctx->hot: ctx->tree; | |
| if (btn->down && btn->transitions) | |
| reorder(a); | |
| /* pressed */ | |
| h->pressed = btn->down && btn->transitions; | |
| if (h->pressed) { | |
| evt = event_begin(op, EVT_PRESSED, act = a); | |
| evt->with.pressed.x = in->mouse.x; | |
| evt->with.pressed.y = in->mouse.y; | |
| while ((act = act->parent)) { | |
| event_add_box(evt, act); | |
| act->pressed = 1; | |
| } event_end(evt); | |
| /* drag_begin */ | |
| evt = event_begin(op, EVT_DRAG_BEGIN, act = a); | |
| evt->with.drag_begin.x = in->mouse.x; | |
| evt->with.drag_begin.y = in->mouse.y; | |
| while ((act = act->parent)) | |
| event_add_box(evt, act); | |
| event_end(evt); | |
| a->drag_begin = 1; | |
| } | |
| /* released */ | |
| h->released = !btn->down && btn->transitions; | |
| if (h->released) { | |
| evt = event_begin(op, EVT_RELEASED, act = a); | |
| evt->with.released.x = in->mouse.x; | |
| evt->with.released.y = in->mouse.y; | |
| while ((act = act->parent)) { | |
| event_add_box(evt, act); | |
| act->released = 1; | |
| } event_end(evt); | |
| if (ctx->hot == ctx->active) { | |
| /* clicked */ | |
| evt = event_begin(op, EVT_CLICKED, act = a); | |
| evt->with.clicked.x = in->mouse.x; | |
| evt->with.clicked.y = in->mouse.y; | |
| while ((act = act->parent)) { | |
| event_add_box(evt, act); | |
| act->clicked = 1; | |
| } event_end(evt); | |
| } | |
| /* drag_end */ | |
| evt = event_begin(op, EVT_DRAG_END, act = a); | |
| evt->with.drag_end.x = in->mouse.x; | |
| evt->with.drag_end.y = in->mouse.y; | |
| while ((act = act->parent)) | |
| event_add_box(evt, act); | |
| event_end(evt); | |
| a->drag_end = 1; | |
| } btn->transitions = 0; | |
| } | |
| if (in->mouse.wheelx || in->mouse.wheely) { | |
| /* scroll */ | |
| struct event *evt = 0; | |
| struct box *a = ctx->active; | |
| evt = event_begin(op, EVT_KEY, a); | |
| evt->with.scroll.x = in->mouse.wheelx; | |
| evt->with.scroll.y = in->mouse.wheely; | |
| while ((a = a->parent)) { | |
| event_add_box(evt, a); | |
| a->scrolled = 1; | |
| } event_end(evt); | |
| in->mouse.wheelx = 0; | |
| in->mouse.wheely = 0; | |
| } | |
| for (i = 0; i < cntof(in->keys); ++i) { | |
| /* key - up, down */ | |
| struct event *evt = 0; | |
| struct box *a = ctx->active; | |
| if (!in->keys[i].transitions) | |
| continue; | |
| evt = event_begin(op, EVT_KEY, a); | |
| evt->with.key.pressed = in->keys[i].down && in->keys[i].transitions; | |
| evt->with.key.released = !in->keys[i].down && in->keys[i].transitions; | |
| evt->with.key.shift = in->shift; | |
| evt->with.key.super = in->super; | |
| evt->with.key.ctrl = in->ctrl; | |
| evt->with.key.alt = in->alt; | |
| evt->with.key.code = i; | |
| while ((a = a->parent)) | |
| event_add_box(evt, a); | |
| event_end(evt); | |
| in->keys[i].transitions = 0; | |
| } | |
| for (i = 0; i < cntof(in->shortcuts); ++i) { | |
| struct key *key = in->shortcuts + i; | |
| key->transitions = 0; | |
| } | |
| if (in->text_len) { | |
| /* text */ | |
| struct box *a = ctx->active; | |
| struct event *evt = event_begin(op, EVT_TEXT, a); | |
| evt->with.text.buf = in->text; | |
| evt->with.text.len = in->text_len; | |
| evt->with.text.shift = in->shift; | |
| evt->with.text.super = in->super; | |
| evt->with.text.ctrl = in->ctrl; | |
| evt->with.text.alt = in->alt; | |
| while ((a = a->parent)) | |
| event_add_box(evt, a); | |
| event_end(evt); | |
| } | |
| /* state transition table */ | |
| if (sm->unbalanced && (flags & PROCESS_BLUEPRINT)) | |
| sm->state = STATE_BLUEPRINT; | |
| else if (flags & flag(OP_PAINT)) { | |
| if (list_empty(&op->evts)) { | |
| operation_end(op); | |
| jmpto(sm, STATE_PAINT); | |
| } sm->state = STATE_PAINT; | |
| } else sm->state = STATE_POSTPROCESS; | |
| return op; | |
| } | |
| case STATE_PAINT: { | |
| int depth = 0; | |
| struct box **stk = 0; | |
| struct temp_memory tmp; | |
| struct list_hook *pi = 0; | |
| struct operation *op; | |
| op = operation_begin(OP_PAINT, ctx, &ctx->arena); | |
| /* caculate tree depth and number of boxes */ | |
| op->cnt = depth = 0; | |
| list_foreach(pi, &ctx->pan) { | |
| struct panel *p = list_entry(pi, struct panel, hook); | |
| struct repository *r = &p->repo[p->act]; | |
| depth = max(depth, r->depth+1); | |
| op->cnt += r->cnt; | |
| } | |
| /* generate list of boxes in dfs order */ | |
| op->surfaces = arena_push_array(&ctx->arena, op->cnt+1, struct box*); | |
| tmp = temp_memory_begin(&ctx->arena); | |
| stk = arena_push_array(&ctx->arena, depth+1, struct box*); | |
| op->cnt = dfs(op->surfaces, stk, ctx->tree); | |
| temp_memory_end(tmp); | |
| sm->state = STATE_POSTPROCESS; | |
| return op; | |
| } | |
| case STATE_POSTPROCESS: { | |
| struct box *blk = ctx->blocking; | |
| if (blk->links.prev == blk->links.next) | |
| blk->interactive = 1; | |
| sm->state = STATE_PREPROCESS; | |
| return 0; | |
| }} return 0; | |
| #undef call | |
| } | |
| static void | |
| process_end(struct operation *op) | |
| { | |
| switch (op->type) { | |
| case OP_INPUT: { | |
| struct context *ctx = op->ctx; | |
| struct input *in = &ctx->input; | |
| in->text_len = 0; | |
| } break; | |
| case OP_CLEANUP: { | |
| struct context *ctx = op->ctx; | |
| struct panel *p = op->panel; | |
| struct repository *r = op->repo; | |
| /* swap buffers */ | |
| arena_clear(&r->arena); | |
| repository_init(r, &ctx->arena); | |
| p->act = !p->act; | |
| } break;} | |
| operation_end(op); | |
| } | |
| static void | |
| measure(struct box *b, int pad) | |
| { | |
| struct list_hook *i; | |
| list_foreach(i, &b->links) { | |
| const struct box *n = list_entry(i, struct box, node); | |
| b->dw = max(b->dw, n->dw + 2*pad); | |
| b->dh = max(b->dh, n->dh + 2*pad); | |
| } | |
| } | |
| static void | |
| blueprint(struct operation *op, struct box *b) | |
| { | |
| unused(op); | |
| if (b->type_id & WIDGET_INTERNAL_BEGIN) { | |
| switch (b->type_id) { | |
| case WIDGET_ROOT: { | |
| switch (b->box_id) { | |
| case WIDGET_OVERLAY: | |
| case WIDGET_POPUP: | |
| case WIDGET_CONTEXTUAL: | |
| case WIDGET_UNBLOCKING: | |
| case WIDGET_BLOCKING: | |
| case WIDGET_UI: break; | |
| case WIDGET_ROOT: { | |
| struct context *ctx = op->ctx; | |
| b->dw = ctx->input.width; | |
| b->dh = ctx->input.height; | |
| } break;} | |
| } break; | |
| case WIDGET_PANEL: { | |
| measure(b,0); | |
| } break;} | |
| } else measure(b,0); | |
| } | |
| static void | |
| compute(struct box *b, int pad) | |
| { | |
| struct list_hook *i; | |
| list_foreach(i, &b->links) { | |
| struct box *n = list_entry(i, struct box, node); | |
| n->local.x = b->local.x + pad; | |
| n->local.y = b->local.y + pad; | |
| n->local.w = max(b->local.w - 2*pad, 0); | |
| n->local.h = max(b->local.h - 2*pad, 0); | |
| } | |
| } | |
| static void | |
| layout_copy(struct box *b) | |
| { | |
| struct list_hook *i; | |
| list_foreach(i, &b->links) { | |
| struct box *n = 0; | |
| n = list_entry(i, struct box, node); | |
| n->local.w = b->local.w; | |
| n->local.h = b->local.h; | |
| } | |
| } | |
| static void | |
| layout_default(struct box *b) | |
| { | |
| struct list_hook *i; | |
| list_foreach(i, &b->links) { | |
| struct box *n = list_entry(i, struct box, node); | |
| n->local.w = min(n->dw, (b->local.x+b->local.w)-n->local.x); | |
| n->local.h = min(n->dh, (b->local.y+b->local.h)-n->local.y); | |
| } | |
| } | |
| static void | |
| layout(struct operation *op, struct box *b) | |
| { | |
| unused(op); | |
| if ((b->type_id & WIDGET_INTERNAL_BEGIN)) { | |
| switch (b->type_id) { | |
| case WIDGET_PANEL: | |
| layout_default(b); break; | |
| case WIDGET_ROOT: { | |
| switch (b->box_id) { | |
| case WIDGET_ROOT: { | |
| struct context *ctx = op->ctx; | |
| b->local.w = ctx->input.width; | |
| b->local.h = ctx->input.height; | |
| layout_copy(b); | |
| } break; | |
| case WIDGET_OVERLAY: | |
| case WIDGET_POPUP: | |
| case WIDGET_CONTEXTUAL: | |
| case WIDGET_UNBLOCKING: | |
| case WIDGET_BLOCKING: | |
| layout_copy(b); break; | |
| case WIDGET_UI: | |
| layout_default(b); break; | |
| } | |
| } break;} | |
| return; | |
| } else compute(b, 0); | |
| } | |
| /* =========================================================================== | |
| * | |
| * | |
| * WIDGETS | |
| * | |
| * | |
| * =========================================================================== */ | |
| enum widget_type { | |
| WIDGET_LABEL, | |
| WIDGET_CHECKBOX, | |
| WIDGET_BUTTON, | |
| WIDGET_SCROLL, | |
| WIDGET_HBOX | |
| }; | |
| /* --------------------------------------------------------------------------- | |
| * LABEL | |
| * --------------------------------------------------------------------------- */ | |
| struct label { | |
| struct box *bounds; | |
| int ubegin, ulen; | |
| int selb, sele, len; | |
| char buf[1]; | |
| }; | |
| static struct label* | |
| label(struct panel_state *ctx, const char *txt, int len, uiid id) | |
| { | |
| static const int alignment = alignof(struct label); | |
| static const int size = szof(struct label); | |
| struct widget_builder *w; | |
| struct label *lbl; | |
| assert(ctx); | |
| if (!ctx) return 0; | |
| w = widget_begin(ctx, WIDGET_LABEL, id, size + max(len-1,0), alignment); | |
| lbl = w->widget; | |
| copy(lbl->buf, txt, len); | |
| lbl->len = len; | |
| lbl->bounds = widget_add_box(w, 0); | |
| box_add_property(lbl->bounds, PROPERTY_INTERACTIVE); | |
| widget_end(w); | |
| return lbl; | |
| } | |
| /* --------------------------------------------------------------------------- | |
| * CHECKBOX | |
| * --------------------------------------------------------------------------- */ | |
| enum checkbox_component { | |
| CHECKBOX_BOUNDS, | |
| CHECKBOX_CURSOR | |
| }; | |
| struct checkbox { | |
| int padding; | |
| struct box *bounds; | |
| struct box *cursor; | |
| unsigned checked:1; | |
| unsigned hovered:1; | |
| unsigned entered:1; | |
| unsigned exited:1; | |
| unsigned pressed:1; | |
| unsigned released:1; | |
| unsigned clicked:1; | |
| unsigned toggled:1; | |
| unsigned activated:1; | |
| unsigned deactivated:1; | |
| }; | |
| static void | |
| checkbox_poll(struct checkbox *chk, const struct box *b) | |
| { | |
| chk->exited = b->exited; | |
| chk->entered = b->entered; | |
| chk->pressed = b->pressed; | |
| chk->clicked = b->clicked; | |
| chk->released = b->released; | |
| chk->activated = b->activated; | |
| chk->deactivated = b->deactivated; | |
| chk->toggled = b->clicked; | |
| } | |
| static struct checkbox* | |
| checkbox_begin(struct panel_state *ctx, uiid id) | |
| { | |
| static const int alignment = alignof(struct label); | |
| static const int size = szof(struct label); | |
| static const int pad = 3; | |
| struct checkbox *chk = 0; | |
| assert(ctx); | |
| if (!ctx) return 0; | |
| {struct widget_builder *w; | |
| w = widget_begin(ctx, WIDGET_CHECKBOX, id, size, alignment); | |
| chk = w->widget; | |
| if (w->widget_last) { | |
| struct checkbox *prev = w->widget_last; | |
| copy(chk, prev, szof(*chk)); | |
| checkbox_poll(chk, prev->bounds); | |
| } else chk->padding = pad; | |
| chk->bounds = widget_add_box_begin(w, CHECKBOX_BOUNDS); | |
| box_add_property(chk->bounds, PROPERTY_INTERACTIVE); | |
| chk->cursor = widget_add_box(w, CHECKBOX_CURSOR); | |
| box_add_property(chk->cursor, PROPERTY_INTERACTIVE); | |
| widget_add_box_end(w); | |
| widget_end(w);} | |
| push(ctx, chk->cursor); | |
| return chk; | |
| } | |
| static void | |
| checkbox_end(struct panel_state *ctx) | |
| { | |
| assert(ctx); | |
| if (!ctx) return; | |
| pop(ctx); | |
| } | |
| static struct checkbox* | |
| checkbox(struct panel_state *ctx, uiid id) | |
| { | |
| struct checkbox *chk; | |
| chk = checkbox_begin(ctx, id); | |
| checkbox_end(ctx); | |
| return chk; | |
| } | |
| /* --------------------------------------------------------------------------- | |
| * BUTTON | |
| * --------------------------------------------------------------------------- */ | |
| enum button_component { | |
| BUTTON_BOUNDS, | |
| BUTTON_CONTENT | |
| }; | |
| struct button { | |
| int padding; | |
| struct box *bounds; | |
| struct box *content; | |
| unsigned hovered:1; | |
| unsigned entered:1; | |
| unsigned exited:1; | |
| unsigned pressed:1; | |
| unsigned released:1; | |
| unsigned clicked:1; | |
| unsigned activated:1; | |
| unsigned deactivated:1; | |
| }; | |
| static void | |
| button_poll(struct button *btn, const struct box *b) | |
| { | |
| btn->exited = b->exited; | |
| btn->entered = b->entered; | |
| btn->pressed = b->pressed; | |
| btn->clicked = b->clicked; | |
| btn->released = b->released; | |
| btn->activated = b->activated; | |
| btn->deactivated = b->deactivated; | |
| } | |
| static struct button* | |
| button_begin(struct panel_state *ctx, uiid id) | |
| { | |
| static const int alignment = alignof(struct button); | |
| static const int size = szof(struct button); | |
| static const int pad = 4; | |
| struct button *btn; | |
| assert(ctx); | |
| if (!ctx) return 0; | |
| {struct widget_builder *w; | |
| w = widget_begin(ctx, WIDGET_BUTTON, id, size, alignment); | |
| btn = w->widget; | |
| if (w->widget_last) { | |
| struct button *prev = w->widget_last; | |
| copy(btn, prev, szof(*btn)); | |
| button_poll(btn, prev->bounds); | |
| } else btn->padding = pad; | |
| btn->bounds = widget_add_box_begin(w, BUTTON_BOUNDS); | |
| box_add_property(btn->bounds, PROPERTY_INTERACTIVE); | |
| btn->content = widget_add_box(w, BUTTON_CONTENT); | |
| box_add_property(btn->content, PROPERTY_INTERACTIVE); | |
| widget_add_box_end(w); | |
| widget_end(w);} | |
| push(ctx, btn->content); | |
| return btn; | |
| } | |
| static const struct button* | |
| button_query(struct context *ctx, uiid pid, uiid widget_id) | |
| { | |
| struct box *b = 0; | |
| struct button *btn = 0; | |
| if ((b = query(ctx, pid, WIDGET_BUTTON, widget_id))) | |
| button_poll(b->widget, b); | |
| return btn; | |
| } | |
| static void | |
| button_end(struct panel_state *ctx) | |
| { | |
| assert(ctx); | |
| if (!ctx) return; | |
| pop(ctx); | |
| } | |
| /* --------------------------------------------------------------------------- | |
| * HORIZONTAL BOX | |
| * --------------------------------------------------------------------------- */ | |
| enum hbox_slot_type { | |
| HBOX_SLOT_DYNAMIC, | |
| HBOX_SLOT_STATIC, | |
| HBOX_SLOT_VARIABLE, | |
| HBOX_SLOT_FITTING | |
| }; | |
| struct hbox_slot { | |
| enum hbox_slot_type type; | |
| int pixel; | |
| struct box *bounds; | |
| }; | |
| struct hbox { | |
| unsigned char padding; | |
| unsigned char spacing; | |
| struct box *bounds; | |
| int cnt; | |
| struct hbox_slot slots[1]; | |
| }; | |
| static struct hbox* | |
| hbox_begin(struct panel_state *ctx, uiid id, int cnt) | |
| { | |
| static const int alignment = alignof(struct hbox); | |
| static const int size = szof(struct hbox); | |
| static const int slot_size = szof(struct hbox_slot); | |
| static const unsigned char spacing = 4; | |
| static const unsigned char pad = 4; | |
| struct hbox *h = 0; | |
| int i; | |
| assert(ctx); | |
| assert(cnt > 0); | |
| if (!ctx) return 0; | |
| cnt = max(cnt, 1); | |
| {struct widget_builder *w; | |
| w = widget_begin(ctx, WIDGET_HBOX, id, size+max(cnt-1,0)*slot_size, alignment); | |
| h = w->widget; | |
| if (!w->widget_last) | |
| h->spacing = spacing, h->padding = pad; | |
| else copy(h, w->widget_last, size); | |
| h->cnt = cnt; | |
| h->bounds = widget_add_box_begin(w, 0); | |
| box_add_property(h->bounds, PROPERTY_INTERACTIVE); | |
| for (i = 0; i < cnt; ++i) { | |
| struct hbox_slot *s = h->slots + i; | |
| s->bounds = widget_add_box(w, cast(uiid,i+1)); | |
| box_add_property(s->bounds, PROPERTY_INTERACTIVE); | |
| s->type = HBOX_SLOT_DYNAMIC; | |
| s->pixel = 0; | |
| } widget_add_box_end(w); | |
| widget_end(w);} | |
| push(ctx, h->bounds); | |
| return h; | |
| } | |
| static void | |
| hbox_slot_begin(struct panel_state *ctx, | |
| enum hbox_slot_type type, | |
| int pixel, int idx) | |
| { | |
| struct hbox *h = 0; | |
| struct box *bounds = 0; | |
| struct hbox_slot *slot = 0; | |
| assert(ctx); | |
| assert(idx >= 0); | |
| if (!ctx || idx < 0) | |
| return; | |
| bounds = peek(ctx); | |
| h = bounds->widget; | |
| assert(idx < h->cnt); | |
| slot = h->slots + idx; | |
| slot->type = type; | |
| slot->pixel = pixel; | |
| push(ctx, slot->bounds); | |
| } | |
| static void | |
| hbox_slot_dynamic_begin(struct panel_state *ctx, int idx) | |
| { | |
| hbox_slot_begin(ctx, HBOX_SLOT_DYNAMIC, 0, idx); | |
| } | |
| static void | |
| hbox_slot_static_begin(struct panel_state *ctx, int pixel_width, int idx) | |
| { | |
| hbox_slot_begin(ctx, HBOX_SLOT_STATIC, pixel_width, idx); | |
| } | |
| static void | |
| hbox_slot_variable_begin(struct panel_state *ctx, | |
| int min_pixel_width, int idx) | |
| { | |
| hbox_slot_begin(ctx, HBOX_SLOT_VARIABLE, min_pixel_width, idx); | |
| } | |
| static void | |
| hbox_slot_fitting_begin(struct panel_state *ctx, int idx) | |
| { | |
| hbox_slot_begin(ctx, HBOX_SLOT_FITTING, 0, idx); | |
| } | |
| static void | |
| hbox_slot_end(struct panel_state *ctx) | |
| { | |
| assert(ctx); | |
| if (!ctx) return; | |
| pop(ctx); | |
| } | |
| static void | |
| hbox_end(struct panel_state *ctx) | |
| { | |
| assert(ctx); | |
| if (!ctx) return; | |
| pop(ctx); | |
| } | |
| /* =========================================================================== | |
| * APP | |
| * =========================================================================== */ | |
| #include <stdio.h> | |
| #include "SDL2/SDL.h" | |
| #define len(s) (cntof(s)-1) | |
| #define h1(s,i,x) (x*65599lu+(unsigned char)(s)[(i)<len(s)?len(s)-1-(i):len(s)]) | |
| #define h4(s,i,x) h1(s,i,h1(s,i+1,h1(s,i+2,h1(s,i+3,x)))) | |
| #define h16(s,i,x) h4(s,i,h4(s,i+4,h4(s,i+8,h4(s,i+12,x)))) | |
| #define hash(s,i) ((unsigned long)(h16(s,0,i)^(h16(s,0,i)>>16))) | |
| #define idx(s,i) hash(s,(unsigned long)i) | |
| #define id(s) idx(s,0) | |
| #define lit(s) s,len(s) | |
| #define txt(s) s,((int)strlen(s)) | |
| enum shortcut { | |
| KEY_COPY, | |
| KEY_CUT, | |
| KEY_PASTE, | |
| KEY_SCROLL_START, | |
| KEY_SCROLL_END, | |
| KEY_SCROLL_DOWN, | |
| KEY_SCROLL_UP, | |
| KEY_COUNT | |
| }; | |
| struct color {unsigned char r,g,b,a;}; | |
| struct canvas {SDL_Renderer *ren; int penx, peny;}; | |
| static struct color | |
| rgb(int r, int g, int b) | |
| { | |
| struct color res; | |
| res.r = cast(unsigned char, clamp(0,r,255)); | |
| res.g = cast(unsigned char, clamp(0,g,255)); | |
| res.b = cast(unsigned char, clamp(0,b,255)); | |
| res.a = 255; | |
| return res; | |
| } | |
| static void | |
| fill_rect(struct canvas *c, int x, int y, int w, int h, struct color col) | |
| { | |
| SDL_Rect a; | |
| a.w = w, a.h = h; | |
| a.x = x+c->penx, a.y = y+c->peny; | |
| SDL_SetRenderDrawColor(c->ren, col.r,col.g,col.b,col.a); | |
| SDL_RenderFillRect(c->ren, &a); | |
| } | |
| static void | |
| stroke_line(struct canvas *c, int x0, int y0, int x1, int y1, struct color col) | |
| { | |
| SDL_SetRenderDrawColor(c->ren, col.r,col.g,col.b,col.a); | |
| SDL_RenderDrawLine(c->ren, c->penx+x0, c->peny+y0, c->penx+x1, c->peny+y1); | |
| } | |
| static void | |
| stroke_rect(struct canvas *c, int x, int y, int w, int h, struct color col) | |
| { | |
| SDL_Rect a; | |
| a.x = c->penx+x, a.y = c->peny+y; | |
| a.w = w, a.h = h; | |
| SDL_SetRenderDrawColor(c->ren, col.r,col.g,col.b,col.a); | |
| SDL_RenderDrawRect(c->ren, &a); | |
| } | |
| static struct canvas | |
| canvas_create(SDL_Window *win) | |
| { | |
| struct canvas res; | |
| zero(&res, szof(res)); | |
| res.ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED); | |
| SDL_SetRenderDrawColor(res.ren, 255,255,255,255); | |
| SDL_RenderClear(res.ren); | |
| return res; | |
| } | |
| static void | |
| canvas_move_to(struct canvas *c, int x, int y) | |
| { | |
| c->penx = x; | |
| c->peny = y; | |
| } | |
| static void | |
| canvas_reset(struct canvas *c) | |
| { | |
| canvas_move_to(c, 0, 0); | |
| } | |
| int main(void) | |
| { | |
| SDL_Window *win; | |
| struct canvas can; | |
| struct context *ctx = create(DEFAULT_ALLOCATOR); | |
| SDL_Init(SDL_INIT_VIDEO); | |
| win = SDL_CreateWindow("GUI", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, | |
| 800, 600, SDL_WINDOW_SHOWN); | |
| can = canvas_create(win); | |
| input_resize(ctx, 800, 600); | |
| {int quit = 0; | |
| while (!quit) { | |
| /* ----------------------------- UI ------------------------------------*/ | |
| {struct panel_state *s = 0; | |
| if ((s = begin(ctx, id("panel")))) { | |
| struct hbox *h = 0; | |
| if ((h = hbox_begin(s, id("hbox"), 3))) { | |
| /* button */ | |
| {struct button *b = 0; | |
| hbox_slot_static_begin(s, 120, 0); | |
| if ((b = button_begin(s, id("#button")))) { | |
| if (b->clicked) printf("button clicked!\n"); | |
| label(s, lit("label"), id("#label0")); | |
| button_end(s); | |
| } hbox_slot_end(s);} | |
| /* checkbox */ | |
| {struct hbox *hb = 0; | |
| hbox_slot_fitting_begin(s, 1); | |
| if ((hb = hbox_begin(s, id("#hbox0"), 2))) { | |
| struct checkbox *chk = 0; | |
| hb->padding = 0, hb->spacing = 2; | |
| hbox_slot_fitting_begin(s, 0); | |
| chk = checkbox(s, id("#checkbox0")); | |
| if (chk->toggled) printf("checkbox: %d\n", chk->checked); | |
| hbox_slot_end(s); | |
| hbox_slot_fitting_begin(s, 1); | |
| label(s, lit("checkbox"), id("#label1")); | |
| hbox_slot_end(s); | |
| hbox_end(s); | |
| } hbox_slot_end(s);} | |
| /* checkbox-button */ | |
| {struct button *b = 0; | |
| hbox_slot_static_begin(s, 150, 2); | |
| if ((b = button_begin(s, id("#button2")))) { | |
| struct hbox *hb = 0; | |
| if ((hb = hbox_begin(s, id("#hbox1"), 2))) { | |
| hb->padding = 0; | |
| {struct checkbox *chk = 0; | |
| hbox_slot_fitting_begin(s, 0); | |
| chk = checkbox(s, id("#checkbox1")); | |
| if (b->clicked && !chk->toggled) | |
| chk->checked = !chk->checked; | |
| hbox_slot_end(s);} | |
| hbox_slot_fitting_begin(s, 1); | |
| label(s, lit("Label"), id("#label2")); | |
| hbox_slot_end(s); | |
| hbox_end(s); | |
| } button_end(s); | |
| } hbox_slot_end(s);} | |
| hbox_end(s); | |
| } end(s); | |
| }} | |
| /* ---------------------------------------------------------------------*/ | |
| /* input */ | |
| {SDL_Event evt; | |
| while (SDL_PollEvent(&evt)) { | |
| switch (evt.type) { | |
| case SDL_QUIT: quit = 1; break; | |
| case SDL_MOUSEMOTION: input_motion(ctx, evt.motion.x, evt.motion.y); break; | |
| case SDL_MOUSEWHEEL: input_scroll(ctx, evt.wheel.x, evt.wheel.y); break; | |
| case SDL_TEXTINPUT: input_text(ctx, txt(evt.text.text)); break; | |
| case SDL_WINDOWEVENT: { | |
| if (evt.window.event == SDL_WINDOWEVENT_RESIZED || | |
| evt.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) | |
| input_resize(ctx, evt.window.data1, evt.window.data2); | |
| } break; | |
| case SDL_MOUSEBUTTONUP: | |
| case SDL_MOUSEBUTTONDOWN: { | |
| int down = (evt.type == SDL_MOUSEBUTTONDOWN); | |
| if (evt.button.button == SDL_BUTTON_LEFT) | |
| input_button(ctx, MOUSE_BUTTON_LEFT, down); | |
| else if (evt.button.button == SDL_BUTTON_RIGHT) | |
| input_button(ctx, MOUSE_BUTTON_RIGHT, down); | |
| } break; | |
| case SDL_KEYUP: | |
| case SDL_KEYDOWN: { | |
| int down = (evt.type == SDL_KEYDOWN); | |
| SDL_Keycode sym = evt.key.keysym.sym; | |
| input_key(ctx, sym, down); | |
| } break;} | |
| }} | |
| /* process: input */ | |
| {struct operation *op = 0; int i; | |
| while ((op = process_begin(ctx, PROCESS_INPUT))) { | |
| switch (op->type) { | |
| default: break; | |
| case OP_BLUEPRINT: { | |
| for (i = op->begin; i != op->end; i += op->inc) { | |
| struct box *b = op->boxes[i]; | |
| switch (b->type_id) { | |
| default: blueprint(op, b); break; | |
| case WIDGET_LABEL: { | |
| struct label *lbl = b->widget; | |
| b->dw = lbl->len * 8 + max(lbl->len-1,0) + 2; | |
| b->dh = 16 + 2; | |
| } break; | |
| case WIDGET_CHECKBOX: { | |
| struct checkbox *chk = b->widget; | |
| if (b->box_id == CHECKBOX_CURSOR) | |
| b->dw = b->dh = 16 + 2; | |
| else measure(b, chk->padding); | |
| } break; | |
| case WIDGET_BUTTON: { | |
| struct button *btn = b->widget; | |
| if (b->box_id == BUTTON_BOUNDS) | |
| measure(b, btn->padding); | |
| else if (b->box_id == BUTTON_CONTENT) | |
| measure(b, 0); | |
| } break; | |
| case WIDGET_HBOX: { | |
| int si = 0; struct hbox *h = b->widget; | |
| if (b->box_id != 0) {measure(b, 0); break;} | |
| for (si = 0; si < h->cnt; ++si) { | |
| struct hbox_slot *s = h->slots + si; | |
| struct box *sb = s->bounds; | |
| switch (s->type) { | |
| case HBOX_SLOT_DYNAMIC: | |
| b->dw += sb->dw; break; | |
| case HBOX_SLOT_STATIC: | |
| b->dw += s->pixel; break; | |
| case HBOX_SLOT_FITTING: | |
| case HBOX_SLOT_VARIABLE: | |
| b->dw += max(sb->dw, s->pixel); break; | |
| } b->dh = max(sb->dh + 2*h->padding, b->dh); | |
| } b->dw += h->spacing*(h->cnt-1) + h->padding*2; | |
| } break;} | |
| } | |
| } break; | |
| case OP_LAYOUTING: { | |
| for (i = op->begin; i != op->end; i += op->inc) { | |
| struct box *b = op->boxes[i]; | |
| switch (b->type_id) { | |
| default: layout(op, b); break; | |
| case WIDGET_CHECKBOX: { | |
| struct checkbox *chk = b->widget; | |
| if (b->box_id == BUTTON_BOUNDS) { | |
| if (b->local.w > b->local.h) { | |
| b->local.x += (b->local.w - b->local.h) / 2; | |
| b->local.w = b->local.h; | |
| } else if (b->local.h > b->local.w) { | |
| b->local.y += (b->local.h - b->local.w) / 2; | |
| b->local.h = b->local.w; | |
| } box_shrink(chk->cursor, b, chk->padding); | |
| } else compute(b, 0); | |
| } break; | |
| case WIDGET_BUTTON: { | |
| struct button *btn = b->widget; | |
| if (b->box_id == BUTTON_BOUNDS) | |
| box_shrink(btn->content, b, btn->padding); | |
| else if (b->box_id == BUTTON_CONTENT) | |
| compute(b, 0); | |
| } break; | |
| case WIDGET_HBOX: { | |
| int si; struct hbox *h = b->widget; | |
| if (b->box_id != 0) | |
| {compute(b, h->padding); break;} | |
| {int enough_space, x = 0; | |
| int var_cnt = 0, var_width = 0; | |
| int min_var_cnt = 0, min_fix_w = 0; | |
| int total_fix_w = 0, max_var_w = 0; | |
| int space = max(b->local.w - (h->cnt-1) * h->spacing, 0); | |
| space = max(space - h->padding, 0); | |
| /* calculate metrics */ | |
| for (si = 0; si < h->cnt; ++si) { | |
| struct hbox_slot *s = h->slots + si; | |
| struct box *sb = s->bounds; | |
| sb->local.y = b->local.y + h->padding; | |
| sb->local.h = b->local.h; | |
| switch (s->type) { | |
| case HBOX_SLOT_DYNAMIC: { | |
| min_var_cnt++; | |
| var_cnt++; | |
| } break; | |
| case HBOX_SLOT_STATIC: { | |
| total_fix_w += s->pixel; | |
| min_fix_w += s->pixel; | |
| } break; | |
| case HBOX_SLOT_FITTING: { | |
| s->pixel = max(sb->dh, sb->dw); | |
| } /* fall through */ | |
| case HBOX_SLOT_VARIABLE: { | |
| total_fix_w += s->pixel; | |
| max_var_w = max(max_var_w, s->pixel); | |
| var_cnt++; | |
| } break; } | |
| } | |
| var_width = max(space-min_fix_w,0) / max(var_cnt,1); | |
| enough_space = var_width >= max_var_w; | |
| if (!enough_space) | |
| var_width = max(space-total_fix_w,0) / max(min_var_cnt,1); | |
| /* set position and width */ | |
| x = b->local.x + h->padding; | |
| for (si = 0; si < h->cnt; ++si) { | |
| struct hbox_slot *s = h->slots + si; | |
| struct box *sb = s->bounds; | |
| sb->local.x = x; | |
| switch (s->type) { | |
| case HBOX_SLOT_DYNAMIC: | |
| sb->local.w = var_width; break; | |
| case HBOX_SLOT_STATIC: | |
| sb->local.w = s->pixel; break; | |
| case HBOX_SLOT_FITTING: | |
| sb->local.w = s->pixel; break; | |
| case HBOX_SLOT_VARIABLE: | |
| sb->local.w = (enough_space) ? var_width: s->pixel; break; | |
| } x += sb->local.w + h->spacing; | |
| }} | |
| } break;} | |
| } | |
| } break; | |
| case OP_INPUT: { | |
| struct list_hook *it = 0; | |
| list_foreach(it, &op->evts) { | |
| struct event *evt = list_entry(it, struct event, hook); | |
| for (i = 0; i < evt->cnt; i++) { | |
| struct box *b = evt->boxes[i]; | |
| switch (b->type_id) { | |
| case WIDGET_CHECKBOX: { | |
| struct checkbox *chk = b->widget; | |
| if (b->clicked && evt->type == EVT_CLICKED) | |
| {chk->toggled = 1; chk->checked = !chk->checked;} | |
| b = chk->bounds; | |
| if (b->entered && evt->type == EVT_ENTERED) | |
| chk->hovered = 1; | |
| if (b->exited && evt->type == EVT_EXITED) | |
| chk->hovered = 0; | |
| } break; | |
| case WIDGET_BUTTON: { | |
| struct button *btn = b->widget; | |
| b = btn->bounds; | |
| if (b->entered && evt->type == EVT_ENTERED) | |
| btn->hovered = 1; | |
| if (b->exited && evt->type == EVT_EXITED) | |
| btn->hovered = 0; | |
| } break;} | |
| } | |
| } | |
| } break;} | |
| process_end(op); | |
| }} | |
| /* query */ | |
| {const struct button *b = 0; | |
| if ((b = button_query(ctx, id("panel"), id("#button")))) { | |
| if (b->entered) printf("Button: entered!\n"); | |
| if (b->exited) printf("Button: exited!\n"); | |
| if (b->pressed) printf("Button: pressed!\n"); | |
| if (b->released) printf("Button: released\n"); | |
| if (b->clicked) printf("Button: clicked!\n"); | |
| if (b->activated) printf("Button: activated!\n"); | |
| if (b->deactivated) printf("Button: deactivated!\n"); | |
| }} | |
| /* process: paint */ | |
| {struct operation *op = 0; int i; | |
| SDL_SetRenderDrawColor(can.ren, 255,255,255,255); | |
| SDL_RenderClear(can.ren); | |
| while ((op = process_begin(ctx, PROCESS_PAINT))) { | |
| assert(op->type == OP_PAINT); | |
| for (i = 0; i < op->cnt; ++i) { | |
| struct box *b = op->surfaces[i]; | |
| switch (b->type_id) { | |
| default: break; | |
| case WIDGET_LABEL: { | |
| int j = 0, x = 0; | |
| struct label *lbl = b->widget; | |
| const struct color c = rgb(0,0,0); | |
| canvas_move_to(&can, b->screen.x+1, b->screen.y+1); | |
| for (j = 0; j < lbl->len; ++j, x+=9) | |
| stroke_rect(&can, x, 0, 8, 16, c); | |
| canvas_reset(&can); | |
| } break; | |
| case WIDGET_CHECKBOX: { | |
| struct checkbox *chk = b->widget; | |
| canvas_move_to(&can, b->screen.x, b->screen.y); | |
| if (b->box_id == CHECKBOX_BOUNDS) | |
| stroke_rect(&can, 0,0, b->screen.w, b->screen.h, rgb(0,0,0)); | |
| else if (chk->checked) | |
| fill_rect(&can, 0,0, b->screen.w, b->screen.h, rgb(0,0,0)); | |
| canvas_reset(&can); | |
| } break; | |
| case WIDGET_BUTTON: { | |
| if (b->box_id != BUTTON_BOUNDS) break; | |
| canvas_move_to(&can, b->screen.x, b->screen.y); | |
| stroke_rect(&can, 0,0, b->screen.w, b->screen.h, rgb(0,0,0)); | |
| canvas_reset(&can); | |
| } break;} | |
| } process_end(op); | |
| }} | |
| SDL_RenderPresent(can.ren); | |
| SDL_Delay(33); | |
| }} | |
| destroy(ctx); | |
| SDL_DestroyRenderer(can.ren); | |
| SDL_DestroyWindow(win); | |
| SDL_Quit(); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment