.
[full-beans.git] / microui.c
blob0415e45b105484fcf06fe1d9f4f01875d98cd128
1 /*
2 ** Copyright (c) 2024 rxi
3 **
4 ** Permission is hereby granted, free of charge, to any person obtaining a copy
5 ** of this software and associated documentation files (the "Software"), to
6 ** deal in the Software without restriction, including without limitation the
7 ** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 ** sell copies of the Software, and to permit persons to whom the Software is
9 ** furnished to do so, subject to the following conditions:
11 ** The above copyright notice and this permission notice shall be included in
12 ** all copies or substantial portions of the Software.
14 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 ** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 ** IN THE SOFTWARE.
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include "microui.h"
28 #define unused(x) ((void) (x))
30 #define expect(x) do { \
31 if (!(x)) { \
32 fprintf(stderr, "Fatal error: %s:%d: assertion '%s' failed\n", \
33 __FILE__, __LINE__, #x); \
34 abort(); \
35 } \
36 } while (0)
38 #define push(stk, val) do { \
39 expect((stk).idx < (int) (sizeof((stk).items) / sizeof(*(stk).items))); \
40 (stk).items[(stk).idx] = (val); \
41 (stk).idx++; /* incremented after incase `val` uses this value */ \
42 } while (0)
44 #define pop(stk) do { \
45 expect((stk).idx > 0); \
46 (stk).idx--; \
47 } while (0)
50 static mu_Rect unclipped_rect = { 0, 0, 0x1000000, 0x1000000 };
52 static mu_Style default_style = {
53 /* font | size | padding | spacing | indent */
54 NULL, { 68, 10 }, 5, 4, 24,
55 /* title_height | scrollbar_size | thumb_size */
56 24, 12, 8,
58 { 230, 230, 230, 255 }, /* MU_COLOR_TEXT */
59 { 25, 25, 25, 255 }, /* MU_COLOR_BORDER */
60 { 50, 50, 50, 255 }, /* MU_COLOR_WINDOWBG */
61 { 25, 25, 25, 255 }, /* MU_COLOR_TITLEBG */
62 { 240, 240, 240, 255 }, /* MU_COLOR_TITLETEXT */
63 { 0, 0, 0, 0 }, /* MU_COLOR_PANELBG */
64 { 75, 75, 75, 255 }, /* MU_COLOR_BUTTON */
65 { 95, 95, 95, 255 }, /* MU_COLOR_BUTTONHOVER */
66 { 115, 115, 115, 255 }, /* MU_COLOR_BUTTONFOCUS */
67 { 30, 30, 30, 255 }, /* MU_COLOR_BASE */
68 { 35, 35, 35, 255 }, /* MU_COLOR_BASEHOVER */
69 { 40, 40, 40, 255 }, /* MU_COLOR_BASEFOCUS */
70 { 43, 43, 43, 255 }, /* MU_COLOR_SCROLLBASE */
71 { 30, 30, 30, 255 } /* MU_COLOR_SCROLLTHUMB */
76 mu_Vec2 mu_vec2(int x, int y) {
77 mu_Vec2 res;
78 res.x = x; res.y = y;
79 return res;
83 mu_Rect mu_rect(int x, int y, int w, int h) {
84 mu_Rect res;
85 res.x = x; res.y = y; res.w = w; res.h = h;
86 return res;
90 mu_Color mu_color(int r, int g, int b, int a) {
91 mu_Color res;
92 res.r = r; res.g = g; res.b = b; res.a = a;
93 return res;
97 static mu_Rect expand_rect(mu_Rect rect, int n) {
98 return mu_rect(rect.x - n, rect.y - n, rect.w + n * 2, rect.h + n * 2);
102 static mu_Rect intersect_rects(mu_Rect r1, mu_Rect r2) {
103 int x1 = mu_max(r1.x, r2.x);
104 int y1 = mu_max(r1.y, r2.y);
105 int x2 = mu_min(r1.x + r1.w, r2.x + r2.w);
106 int y2 = mu_min(r1.y + r1.h, r2.y + r2.h);
107 if (x2 < x1) { x2 = x1; }
108 if (y2 < y1) { y2 = y1; }
109 return mu_rect(x1, y1, x2 - x1, y2 - y1);
113 static int rect_overlaps_vec2(mu_Rect r, mu_Vec2 p) {
114 return p.x >= r.x && p.x < r.x + r.w && p.y >= r.y && p.y < r.y + r.h;
118 static void draw_frame(mu_Context *ctx, mu_Rect rect, int colorid) {
119 mu_draw_rect(ctx, rect, ctx->style->colors[colorid]);
120 if (colorid == MU_COLOR_SCROLLBASE ||
121 colorid == MU_COLOR_SCROLLTHUMB ||
122 colorid == MU_COLOR_TITLEBG) { return; }
123 /* draw border */
124 if (ctx->style->colors[MU_COLOR_BORDER].a) {
125 mu_draw_box(ctx, expand_rect(rect, 1), ctx->style->colors[MU_COLOR_BORDER]);
130 void mu_init(mu_Context *ctx) {
131 memset(ctx, 0, sizeof(*ctx));
132 ctx->draw_frame = draw_frame;
133 ctx->_style = default_style;
134 ctx->style = &ctx->_style;
138 void mu_begin(mu_Context *ctx) {
139 expect(ctx->text_width && ctx->text_height);
140 ctx->command_list.idx = 0;
141 ctx->root_list.idx = 0;
142 ctx->scroll_target = NULL;
143 ctx->hover_root = ctx->next_hover_root;
144 ctx->next_hover_root = NULL;
145 ctx->mouse_delta.x = ctx->mouse_pos.x - ctx->last_mouse_pos.x;
146 ctx->mouse_delta.y = ctx->mouse_pos.y - ctx->last_mouse_pos.y;
147 ctx->frame++;
151 static int compare_zindex(const void *a, const void *b) {
152 return (*(mu_Container**) a)->zindex - (*(mu_Container**) b)->zindex;
156 void mu_end(mu_Context *ctx) {
157 int i, n;
158 /* check stacks */
159 expect(ctx->container_stack.idx == 0);
160 expect(ctx->clip_stack.idx == 0);
161 expect(ctx->id_stack.idx == 0);
162 expect(ctx->layout_stack.idx == 0);
164 /* handle scroll input */
165 if (ctx->scroll_target) {
166 ctx->scroll_target->scroll.x += ctx->scroll_delta.x;
167 ctx->scroll_target->scroll.y += ctx->scroll_delta.y;
170 /* unset focus if focus id was not touched this frame */
171 if (!ctx->updated_focus) { ctx->focus = 0; }
172 ctx->updated_focus = 0;
174 /* bring hover root to front if mouse was pressed */
175 if (ctx->mouse_pressed && ctx->next_hover_root &&
176 ctx->next_hover_root->zindex < ctx->last_zindex &&
177 ctx->next_hover_root->zindex >= 0
179 mu_bring_to_front(ctx, ctx->next_hover_root);
182 /* reset input state */
183 ctx->key_pressed = 0;
184 ctx->input_text[0] = '\0';
185 ctx->mouse_pressed = 0;
186 ctx->scroll_delta = mu_vec2(0, 0);
187 ctx->last_mouse_pos = ctx->mouse_pos;
189 /* sort root containers by zindex */
190 n = ctx->root_list.idx;
191 qsort(ctx->root_list.items, n, sizeof(mu_Container*), compare_zindex);
193 /* set root container jump commands */
194 for (i = 0; i < n; i++) {
195 mu_Container *cnt = ctx->root_list.items[i];
196 /* if this is the first container then make the first command jump to it.
197 ** otherwise set the previous container's tail to jump to this one */
198 if (i == 0) {
199 mu_Command *cmd = (mu_Command*) ctx->command_list.items;
200 cmd->jump.dst = (char*) cnt->head + sizeof(mu_JumpCommand);
201 } else {
202 mu_Container *prev = ctx->root_list.items[i - 1];
203 prev->tail->jump.dst = (char*) cnt->head + sizeof(mu_JumpCommand);
205 /* make the last container's tail jump to the end of command list */
206 if (i == n - 1) {
207 cnt->tail->jump.dst = ctx->command_list.items + ctx->command_list.idx;
213 void mu_set_focus(mu_Context *ctx, mu_Id id) {
214 ctx->focus = id;
215 ctx->updated_focus = 1;
219 /* 32bit fnv-1a hash */
220 #define HASH_INITIAL 2166136261
222 static void hash(mu_Id *hash, const void *data, int size) {
223 const unsigned char *p = data;
224 while (size--) {
225 *hash = (*hash ^ *p++) * 16777619;
230 mu_Id mu_get_id(mu_Context *ctx, const void *data, int size) {
231 int idx = ctx->id_stack.idx;
232 mu_Id res = (idx > 0) ? ctx->id_stack.items[idx - 1] : HASH_INITIAL;
233 hash(&res, data, size);
234 ctx->last_id = res;
235 return res;
239 void mu_push_id(mu_Context *ctx, const void *data, int size) {
240 push(ctx->id_stack, mu_get_id(ctx, data, size));
244 void mu_pop_id(mu_Context *ctx) {
245 pop(ctx->id_stack);
249 void mu_push_clip_rect(mu_Context *ctx, mu_Rect rect) {
250 mu_Rect last = mu_get_clip_rect(ctx);
251 push(ctx->clip_stack, intersect_rects(rect, last));
255 void mu_pop_clip_rect(mu_Context *ctx) {
256 pop(ctx->clip_stack);
260 mu_Rect mu_get_clip_rect(mu_Context *ctx) {
261 expect(ctx->clip_stack.idx > 0);
262 return ctx->clip_stack.items[ctx->clip_stack.idx - 1];
266 int mu_check_clip(mu_Context *ctx, mu_Rect r) {
267 mu_Rect cr = mu_get_clip_rect(ctx);
268 if (r.x > cr.x + cr.w || r.x + r.w < cr.x ||
269 r.y > cr.y + cr.h || r.y + r.h < cr.y ) { return MU_CLIP_ALL; }
270 if (r.x >= cr.x && r.x + r.w <= cr.x + cr.w &&
271 r.y >= cr.y && r.y + r.h <= cr.y + cr.h ) { return 0; }
272 return MU_CLIP_PART;
276 static void push_layout(mu_Context *ctx, mu_Rect body, mu_Vec2 scroll) {
277 mu_Layout layout;
278 int width = 0;
279 memset(&layout, 0, sizeof(layout));
280 layout.body = mu_rect(body.x - scroll.x, body.y - scroll.y, body.w, body.h);
281 layout.max = mu_vec2(-0x1000000, -0x1000000);
282 push(ctx->layout_stack, layout);
283 mu_layout_row(ctx, 1, &width, 0);
287 static mu_Layout* get_layout(mu_Context *ctx) {
288 return &ctx->layout_stack.items[ctx->layout_stack.idx - 1];
292 static void pop_container(mu_Context *ctx) {
293 mu_Container *cnt = mu_get_current_container(ctx);
294 mu_Layout *layout = get_layout(ctx);
295 cnt->content_size.x = layout->max.x - layout->body.x;
296 cnt->content_size.y = layout->max.y - layout->body.y;
297 /* pop container, layout and id */
298 pop(ctx->container_stack);
299 pop(ctx->layout_stack);
300 mu_pop_id(ctx);
304 mu_Container* mu_get_current_container(mu_Context *ctx) {
305 expect(ctx->container_stack.idx > 0);
306 return ctx->container_stack.items[ ctx->container_stack.idx - 1 ];
310 static mu_Container* get_container(mu_Context *ctx, mu_Id id, int opt) {
311 mu_Container *cnt;
312 /* try to get existing container from pool */
313 int idx = mu_pool_get(ctx, ctx->container_pool, MU_CONTAINERPOOL_SIZE, id);
314 if (idx >= 0) {
315 if (ctx->containers[idx].open || ~opt & MU_OPT_CLOSED) {
316 mu_pool_update(ctx, ctx->container_pool, idx);
318 return &ctx->containers[idx];
320 if (opt & MU_OPT_CLOSED) { return NULL; }
321 /* container not found in pool: init new container */
322 idx = mu_pool_init(ctx, ctx->container_pool, MU_CONTAINERPOOL_SIZE, id);
323 cnt = &ctx->containers[idx];
324 memset(cnt, 0, sizeof(*cnt));
325 cnt->open = 1;
326 mu_bring_to_front(ctx, cnt);
327 return cnt;
331 mu_Container* mu_get_container(mu_Context *ctx, const char *name) {
332 mu_Id id = mu_get_id(ctx, name, strlen(name));
333 return get_container(ctx, id, 0);
337 void mu_bring_to_front(mu_Context *ctx, mu_Container *cnt) {
338 cnt->zindex = ++ctx->last_zindex;
342 /*============================================================================
343 ** pool
344 **============================================================================*/
346 int mu_pool_init(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id) {
347 int i, n = -1, f = ctx->frame;
348 for (i = 0; i < len; i++) {
349 if (items[i].last_update < f) {
350 f = items[i].last_update;
351 n = i;
354 expect(n > -1);
355 items[n].id = id;
356 mu_pool_update(ctx, items, n);
357 return n;
361 int mu_pool_get(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id) {
362 int i;
363 unused(ctx);
364 for (i = 0; i < len; i++) {
365 if (items[i].id == id) { return i; }
367 return -1;
371 void mu_pool_update(mu_Context *ctx, mu_PoolItem *items, int idx) {
372 items[idx].last_update = ctx->frame;
376 /*============================================================================
377 ** input handlers
378 **============================================================================*/
380 void mu_input_mousemove(mu_Context *ctx, int x, int y) {
381 ctx->mouse_pos = mu_vec2(x, y);
385 void mu_input_mousedown(mu_Context *ctx, int x, int y, int btn) {
386 mu_input_mousemove(ctx, x, y);
387 ctx->mouse_down |= btn;
388 ctx->mouse_pressed |= btn;
392 void mu_input_mouseup(mu_Context *ctx, int x, int y, int btn) {
393 mu_input_mousemove(ctx, x, y);
394 ctx->mouse_down &= ~btn;
398 void mu_input_scroll(mu_Context *ctx, int x, int y) {
399 ctx->scroll_delta.x += x;
400 ctx->scroll_delta.y += y;
404 void mu_input_keydown(mu_Context *ctx, int key) {
405 ctx->key_pressed |= key;
406 ctx->key_down |= key;
410 void mu_input_keyup(mu_Context *ctx, int key) {
411 ctx->key_down &= ~key;
415 void mu_input_text(mu_Context *ctx, const char *text) {
416 int len = strlen(ctx->input_text);
417 int size = strlen(text) + 1;
418 expect(len + size <= (int) sizeof(ctx->input_text));
419 memcpy(ctx->input_text + len, text, size);
423 /*============================================================================
424 ** commandlist
425 **============================================================================*/
427 mu_Command* mu_push_command(mu_Context *ctx, int type, int size) {
428 mu_Command *cmd = (mu_Command*) (ctx->command_list.items + ctx->command_list.idx);
429 expect(ctx->command_list.idx + size < MU_COMMANDLIST_SIZE);
430 cmd->base.type = type;
431 cmd->base.size = size;
432 ctx->command_list.idx += size;
433 return cmd;
437 int mu_next_command(mu_Context *ctx, mu_Command **cmd) {
438 if (*cmd) {
439 *cmd = (mu_Command*) (((char*) *cmd) + (*cmd)->base.size);
440 } else {
441 *cmd = (mu_Command*) ctx->command_list.items;
443 while ((char*) *cmd != ctx->command_list.items + ctx->command_list.idx) {
444 if ((*cmd)->type != MU_COMMAND_JUMP) { return 1; }
445 *cmd = (*cmd)->jump.dst;
447 return 0;
451 static mu_Command* push_jump(mu_Context *ctx, mu_Command *dst) {
452 mu_Command *cmd;
453 cmd = mu_push_command(ctx, MU_COMMAND_JUMP, sizeof(mu_JumpCommand));
454 cmd->jump.dst = dst;
455 return cmd;
459 void mu_set_clip(mu_Context *ctx, mu_Rect rect) {
460 mu_Command *cmd;
461 cmd = mu_push_command(ctx, MU_COMMAND_CLIP, sizeof(mu_ClipCommand));
462 cmd->clip.rect = rect;
466 void mu_draw_rect(mu_Context *ctx, mu_Rect rect, mu_Color color) {
467 mu_Command *cmd;
468 rect = intersect_rects(rect, mu_get_clip_rect(ctx));
469 if (rect.w > 0 && rect.h > 0) {
470 cmd = mu_push_command(ctx, MU_COMMAND_RECT, sizeof(mu_RectCommand));
471 cmd->rect.rect = rect;
472 cmd->rect.color = color;
477 void mu_draw_box(mu_Context *ctx, mu_Rect rect, mu_Color color) {
478 mu_draw_rect(ctx, mu_rect(rect.x + 1, rect.y, rect.w - 2, 1), color);
479 mu_draw_rect(ctx, mu_rect(rect.x + 1, rect.y + rect.h - 1, rect.w - 2, 1), color);
480 mu_draw_rect(ctx, mu_rect(rect.x, rect.y, 1, rect.h), color);
481 mu_draw_rect(ctx, mu_rect(rect.x + rect.w - 1, rect.y, 1, rect.h), color);
485 void mu_draw_text(mu_Context *ctx, mu_Font font, const char *str, int len,
486 mu_Vec2 pos, mu_Color color)
488 mu_Command *cmd;
489 mu_Rect rect = mu_rect(
490 pos.x, pos.y, ctx->text_width(font, str, len), ctx->text_height(font));
491 int clipped = mu_check_clip(ctx, rect);
492 if (clipped == MU_CLIP_ALL ) { return; }
493 if (clipped == MU_CLIP_PART) { mu_set_clip(ctx, mu_get_clip_rect(ctx)); }
494 /* add command */
495 if (len < 0) { len = strlen(str); }
496 cmd = mu_push_command(ctx, MU_COMMAND_TEXT, sizeof(mu_TextCommand) + len);
497 memcpy(cmd->text.str, str, len);
498 cmd->text.str[len] = '\0';
499 cmd->text.pos = pos;
500 cmd->text.color = color;
501 cmd->text.font = font;
502 /* reset clipping if it was set */
503 if (clipped) { mu_set_clip(ctx, unclipped_rect); }
507 void mu_draw_icon(mu_Context *ctx, int id, mu_Rect rect, mu_Color color) {
508 mu_Command *cmd;
509 /* do clip command if the rect isn't fully contained within the cliprect */
510 int clipped = mu_check_clip(ctx, rect);
511 if (clipped == MU_CLIP_ALL ) { return; }
512 if (clipped == MU_CLIP_PART) { mu_set_clip(ctx, mu_get_clip_rect(ctx)); }
513 /* do icon command */
514 cmd = mu_push_command(ctx, MU_COMMAND_ICON, sizeof(mu_IconCommand));
515 cmd->icon.id = id;
516 cmd->icon.rect = rect;
517 cmd->icon.color = color;
518 /* reset clipping if it was set */
519 if (clipped) { mu_set_clip(ctx, unclipped_rect); }
523 /*============================================================================
524 ** layout
525 **============================================================================*/
527 enum { RELATIVE = 1, ABSOLUTE = 2 };
530 void mu_layout_begin_column(mu_Context *ctx) {
531 push_layout(ctx, mu_layout_next(ctx), mu_vec2(0, 0));
535 void mu_layout_end_column(mu_Context *ctx) {
536 mu_Layout *a, *b;
537 b = get_layout(ctx);
538 pop(ctx->layout_stack);
539 /* inherit position/next_row/max from child layout if they are greater */
540 a = get_layout(ctx);
541 a->position.x = mu_max(a->position.x, b->position.x + b->body.x - a->body.x);
542 a->next_row = mu_max(a->next_row, b->next_row + b->body.y - a->body.y);
543 a->max.x = mu_max(a->max.x, b->max.x);
544 a->max.y = mu_max(a->max.y, b->max.y);
548 void mu_layout_row(mu_Context *ctx, int items, const int *widths, int height) {
549 mu_Layout *layout = get_layout(ctx);
550 if (widths) {
551 expect(items <= MU_MAX_WIDTHS);
552 memcpy(layout->widths, widths, items * sizeof(widths[0]));
554 layout->items = items;
555 layout->position = mu_vec2(layout->indent, layout->next_row);
556 layout->size.y = height;
557 layout->item_index = 0;
561 void mu_layout_width(mu_Context *ctx, int width) {
562 get_layout(ctx)->size.x = width;
566 void mu_layout_height(mu_Context *ctx, int height) {
567 get_layout(ctx)->size.y = height;
571 void mu_layout_set_next(mu_Context *ctx, mu_Rect r, int relative) {
572 mu_Layout *layout = get_layout(ctx);
573 layout->next = r;
574 layout->next_type = relative ? RELATIVE : ABSOLUTE;
578 mu_Rect mu_layout_next(mu_Context *ctx) {
579 mu_Layout *layout = get_layout(ctx);
580 mu_Style *style = ctx->style;
581 mu_Rect res;
583 if (layout->next_type) {
584 /* handle rect set by `mu_layout_set_next` */
585 int type = layout->next_type;
586 layout->next_type = 0;
587 res = layout->next;
588 if (type == ABSOLUTE) { return (ctx->last_rect = res); }
590 } else {
591 /* handle next row */
592 if (layout->item_index == layout->items) {
593 mu_layout_row(ctx, layout->items, NULL, layout->size.y);
596 /* position */
597 res.x = layout->position.x;
598 res.y = layout->position.y;
600 /* size */
601 res.w = layout->items > 0 ? layout->widths[layout->item_index] : layout->size.x;
602 res.h = layout->size.y;
603 if (res.w == 0) { res.w = style->size.x + style->padding * 2; }
604 if (res.h == 0) { res.h = style->size.y + style->padding * 2; }
605 if (res.w < 0) { res.w += layout->body.w - res.x + 1; }
606 if (res.h < 0) { res.h += layout->body.h - res.y + 1; }
608 layout->item_index++;
611 /* update position */
612 layout->position.x += res.w + style->spacing;
613 layout->next_row = mu_max(layout->next_row, res.y + res.h + style->spacing);
615 /* apply body offset */
616 res.x += layout->body.x;
617 res.y += layout->body.y;
619 /* update max position */
620 layout->max.x = mu_max(layout->max.x, res.x + res.w);
621 layout->max.y = mu_max(layout->max.y, res.y + res.h);
623 return (ctx->last_rect = res);
627 /*============================================================================
628 ** controls
629 **============================================================================*/
631 static int in_hover_root(mu_Context *ctx) {
632 int i = ctx->container_stack.idx;
633 while (i--) {
634 if (ctx->container_stack.items[i] == ctx->hover_root) { return 1; }
635 /* only root containers have their `head` field set; stop searching if we've
636 ** reached the current root container */
637 if (ctx->container_stack.items[i]->head) { break; }
639 return 0;
643 void mu_draw_control_frame(mu_Context *ctx, mu_Id id, mu_Rect rect,
644 int colorid, int opt)
646 if (opt & MU_OPT_NOFRAME) { return; }
647 colorid += (ctx->focus == id) ? 2 : (ctx->hover == id) ? 1 : 0;
648 ctx->draw_frame(ctx, rect, colorid);
652 void mu_draw_control_text(mu_Context *ctx, const char *str, mu_Rect rect,
653 int colorid, int opt)
655 mu_Vec2 pos;
656 mu_Font font = ctx->style->font;
657 int tw = ctx->text_width(font, str, -1);
658 mu_push_clip_rect(ctx, rect);
659 pos.y = rect.y + (rect.h - ctx->text_height(font)) / 2;
660 if (opt & MU_OPT_ALIGNCENTER) {
661 pos.x = rect.x + (rect.w - tw) / 2;
662 } else if (opt & MU_OPT_ALIGNRIGHT) {
663 pos.x = rect.x + rect.w - tw - ctx->style->padding;
664 } else {
665 pos.x = rect.x + ctx->style->padding;
667 mu_draw_text(ctx, font, str, -1, pos, ctx->style->colors[colorid]);
668 mu_pop_clip_rect(ctx);
672 int mu_mouse_over(mu_Context *ctx, mu_Rect rect) {
673 return rect_overlaps_vec2(rect, ctx->mouse_pos) &&
674 rect_overlaps_vec2(mu_get_clip_rect(ctx), ctx->mouse_pos) &&
675 in_hover_root(ctx);
679 void mu_update_control(mu_Context *ctx, mu_Id id, mu_Rect rect, int opt) {
680 int mouseover = mu_mouse_over(ctx, rect);
682 if (ctx->focus == id) { ctx->updated_focus = 1; }
683 if (opt & MU_OPT_NOINTERACT) { return; }
684 if (mouseover && !ctx->mouse_down) { ctx->hover = id; }
686 if (ctx->focus == id) {
687 if (ctx->mouse_pressed && !mouseover) { mu_set_focus(ctx, 0); }
688 if (!ctx->mouse_down && ~opt & MU_OPT_HOLDFOCUS) { mu_set_focus(ctx, 0); }
691 if (ctx->hover == id) {
692 if (ctx->mouse_pressed) {
693 mu_set_focus(ctx, id);
694 } else if (!mouseover) {
695 ctx->hover = 0;
701 void mu_text(mu_Context *ctx, const char *text) {
702 const char *start, *end, *p = text;
703 int width = -1;
704 mu_Font font = ctx->style->font;
705 mu_Color color = ctx->style->colors[MU_COLOR_TEXT];
706 mu_layout_begin_column(ctx);
707 mu_layout_row(ctx, 1, &width, ctx->text_height(font));
708 do {
709 mu_Rect r = mu_layout_next(ctx);
710 int w = 0;
711 start = end = p;
712 do {
713 const char* word = p;
714 while (*p && *p != ' ' && *p != '\n') { p++; }
715 w += ctx->text_width(font, word, p - word);
716 if (w > r.w && end != start) { break; }
717 w += ctx->text_width(font, p, 1);
718 end = p++;
719 } while (*end && *end != '\n');
720 mu_draw_text(ctx, font, start, end - start, mu_vec2(r.x, r.y), color);
721 p = end + 1;
722 } while (*end);
723 mu_layout_end_column(ctx);
727 void mu_label(mu_Context *ctx, const char *text) {
728 mu_draw_control_text(ctx, text, mu_layout_next(ctx), MU_COLOR_TEXT, 0);
732 int mu_button_ex(mu_Context *ctx, const char *label, int icon, int opt) {
733 int res = 0;
734 mu_Id id = label ? mu_get_id(ctx, label, strlen(label))
735 : mu_get_id(ctx, &icon, sizeof(icon));
736 mu_Rect r = mu_layout_next(ctx);
737 mu_update_control(ctx, id, r, opt);
738 /* handle click */
739 if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) {
740 res |= MU_RES_SUBMIT;
742 /* draw */
743 mu_draw_control_frame(ctx, id, r, MU_COLOR_BUTTON, opt);
744 if (label) { mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, opt); }
745 if (icon) { mu_draw_icon(ctx, icon, r, ctx->style->colors[MU_COLOR_TEXT]); }
746 return res;
750 int mu_checkbox(mu_Context *ctx, const char *label, int *state) {
751 int res = 0;
752 mu_Id id = mu_get_id(ctx, &state, sizeof(state));
753 mu_Rect r = mu_layout_next(ctx);
754 mu_Rect box = mu_rect(r.x, r.y, r.h, r.h);
755 mu_update_control(ctx, id, r, 0);
756 /* handle click */
757 if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) {
758 res |= MU_RES_CHANGE;
759 *state = !*state;
761 /* draw */
762 mu_draw_control_frame(ctx, id, box, MU_COLOR_BASE, 0);
763 if (*state) {
764 mu_draw_icon(ctx, MU_ICON_CHECK, box, ctx->style->colors[MU_COLOR_TEXT]);
766 r = mu_rect(r.x + box.w, r.y, r.w - box.w, r.h);
767 mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, 0);
768 return res;
772 int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, mu_Id id, mu_Rect r,
773 int opt)
775 int res = 0;
776 mu_update_control(ctx, id, r, opt | MU_OPT_HOLDFOCUS);
778 if (ctx->focus == id) {
779 /* handle text input */
780 int len = strlen(buf);
781 int n = mu_min(bufsz - len - 1, (int) strlen(ctx->input_text));
782 if (n > 0) {
783 memcpy(buf + len, ctx->input_text, n);
784 len += n;
785 buf[len] = '\0';
786 res |= MU_RES_CHANGE;
788 /* handle backspace */
789 if (ctx->key_pressed & MU_KEY_BACKSPACE && len > 0) {
790 /* skip utf-8 continuation bytes */
791 while ((buf[--len] & 0xc0) == 0x80 && len > 0);
792 buf[len] = '\0';
793 res |= MU_RES_CHANGE;
795 /* handle return */
796 if (ctx->key_pressed & MU_KEY_RETURN) {
797 mu_set_focus(ctx, 0);
798 res |= MU_RES_SUBMIT;
802 /* draw */
803 mu_draw_control_frame(ctx, id, r, MU_COLOR_BASE, opt);
804 if (ctx->focus == id) {
805 mu_Color color = ctx->style->colors[MU_COLOR_TEXT];
806 mu_Font font = ctx->style->font;
807 int textw = ctx->text_width(font, buf, -1);
808 int texth = ctx->text_height(font);
809 int ofx = r.w - ctx->style->padding - textw - 1;
810 int textx = r.x + mu_min(ofx, ctx->style->padding);
811 int texty = r.y + (r.h - texth) / 2;
812 mu_push_clip_rect(ctx, r);
813 mu_draw_text(ctx, font, buf, -1, mu_vec2(textx, texty), color);
814 mu_draw_rect(ctx, mu_rect(textx + textw, texty, 1, texth), color);
815 mu_pop_clip_rect(ctx);
816 } else {
817 mu_draw_control_text(ctx, buf, r, MU_COLOR_TEXT, opt);
820 return res;
824 static int number_textbox(mu_Context *ctx, mu_Real *value, mu_Rect r, mu_Id id) {
825 if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->key_down & MU_KEY_SHIFT &&
826 ctx->hover == id
828 ctx->number_edit = id;
829 sprintf(ctx->number_edit_buf, MU_REAL_FMT, *value);
831 if (ctx->number_edit == id) {
832 int res = mu_textbox_raw(
833 ctx, ctx->number_edit_buf, sizeof(ctx->number_edit_buf), id, r, 0);
834 if (res & MU_RES_SUBMIT || ctx->focus != id) {
835 *value = strtod(ctx->number_edit_buf, NULL);
836 ctx->number_edit = 0;
837 } else {
838 return 1;
841 return 0;
845 int mu_textbox_ex(mu_Context *ctx, char *buf, int bufsz, int opt) {
846 mu_Id id = mu_get_id(ctx, &buf, sizeof(buf));
847 mu_Rect r = mu_layout_next(ctx);
848 return mu_textbox_raw(ctx, buf, bufsz, id, r, opt);
852 int mu_slider_ex(mu_Context *ctx, mu_Real *value, mu_Real low, mu_Real high,
853 mu_Real step, const char *fmt, int opt)
855 char buf[MU_MAX_FMT + 1];
856 mu_Rect thumb;
857 int x, w, res = 0;
858 mu_Real last = *value, v = last;
859 mu_Id id = mu_get_id(ctx, &value, sizeof(value));
860 mu_Rect base = mu_layout_next(ctx);
862 /* handle text input mode */
863 if (number_textbox(ctx, &v, base, id)) { return res; }
865 /* handle normal mode */
866 mu_update_control(ctx, id, base, opt);
868 /* handle input */
869 if (ctx->focus == id &&
870 (ctx->mouse_down | ctx->mouse_pressed) == MU_MOUSE_LEFT)
872 v = low + (ctx->mouse_pos.x - base.x) * (high - low) / base.w;
873 if (step) { v = ((long long)((v + step / 2) / step)) * step; }
875 /* clamp and store value, update res */
876 *value = v = mu_clamp(v, low, high);
877 if (last != v) { res |= MU_RES_CHANGE; }
879 /* draw base */
880 mu_draw_control_frame(ctx, id, base, MU_COLOR_BASE, opt);
881 /* draw thumb */
882 w = ctx->style->thumb_size;
883 x = (v - low) * (base.w - w) / (high - low);
884 thumb = mu_rect(base.x + x, base.y, w, base.h);
885 mu_draw_control_frame(ctx, id, thumb, MU_COLOR_BUTTON, opt);
886 /* draw text */
887 sprintf(buf, fmt, v);
888 mu_draw_control_text(ctx, buf, base, MU_COLOR_TEXT, opt);
890 return res;
894 int mu_number_ex(mu_Context *ctx, mu_Real *value, mu_Real step,
895 const char *fmt, int opt)
897 char buf[MU_MAX_FMT + 1];
898 int res = 0;
899 mu_Id id = mu_get_id(ctx, &value, sizeof(value));
900 mu_Rect base = mu_layout_next(ctx);
901 mu_Real last = *value;
903 /* handle text input mode */
904 if (number_textbox(ctx, value, base, id)) { return res; }
906 /* handle normal mode */
907 mu_update_control(ctx, id, base, opt);
909 /* handle input */
910 if (ctx->focus == id && ctx->mouse_down == MU_MOUSE_LEFT) {
911 *value += ctx->mouse_delta.x * step;
913 /* set flag if value changed */
914 if (*value != last) { res |= MU_RES_CHANGE; }
916 /* draw base */
917 mu_draw_control_frame(ctx, id, base, MU_COLOR_BASE, opt);
918 /* draw text */
919 sprintf(buf, fmt, *value);
920 mu_draw_control_text(ctx, buf, base, MU_COLOR_TEXT, opt);
922 return res;
926 static int header(mu_Context *ctx, const char *label, int istreenode, int opt) {
927 mu_Rect r;
928 int active, expanded;
929 mu_Id id = mu_get_id(ctx, label, strlen(label));
930 int idx = mu_pool_get(ctx, ctx->treenode_pool, MU_TREENODEPOOL_SIZE, id);
931 int width = -1;
932 mu_layout_row(ctx, 1, &width, 0);
934 active = (idx >= 0);
935 expanded = (opt & MU_OPT_EXPANDED) ? !active : active;
936 r = mu_layout_next(ctx);
937 mu_update_control(ctx, id, r, 0);
939 /* handle click */
940 active ^= (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id);
942 /* update pool ref */
943 if (idx >= 0) {
944 if (active) { mu_pool_update(ctx, ctx->treenode_pool, idx); }
945 else { memset(&ctx->treenode_pool[idx], 0, sizeof(mu_PoolItem)); }
946 } else if (active) {
947 mu_pool_init(ctx, ctx->treenode_pool, MU_TREENODEPOOL_SIZE, id);
950 /* draw */
951 if (istreenode) {
952 if (ctx->hover == id) { ctx->draw_frame(ctx, r, MU_COLOR_BUTTONHOVER); }
953 } else {
954 mu_draw_control_frame(ctx, id, r, MU_COLOR_BUTTON, 0);
956 mu_draw_icon(
957 ctx, expanded ? MU_ICON_EXPANDED : MU_ICON_COLLAPSED,
958 mu_rect(r.x, r.y, r.h, r.h), ctx->style->colors[MU_COLOR_TEXT]);
959 r.x += r.h - ctx->style->padding;
960 r.w -= r.h - ctx->style->padding;
961 mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, 0);
963 return expanded ? MU_RES_ACTIVE : 0;
967 int mu_header_ex(mu_Context *ctx, const char *label, int opt) {
968 return header(ctx, label, 0, opt);
972 int mu_begin_treenode_ex(mu_Context *ctx, const char *label, int opt) {
973 int res = header(ctx, label, 1, opt);
974 if (res & MU_RES_ACTIVE) {
975 get_layout(ctx)->indent += ctx->style->indent;
976 push(ctx->id_stack, ctx->last_id);
978 return res;
982 void mu_end_treenode(mu_Context *ctx) {
983 get_layout(ctx)->indent -= ctx->style->indent;
984 mu_pop_id(ctx);
988 #define scrollbar(ctx, cnt, b, cs, x, y, w, h) \
989 do { \
990 /* only add scrollbar if content size is larger than body */ \
991 int maxscroll = cs.y - b->h; \
993 if (maxscroll > 0 && b->h > 0) { \
994 mu_Rect base, thumb; \
995 mu_Id id = mu_get_id(ctx, "!scrollbar" #y, 11); \
997 /* get sizing / positioning */ \
998 base = *b; \
999 base.x = b->x + b->w; \
1000 base.w = ctx->style->scrollbar_size; \
1002 /* handle input */ \
1003 mu_update_control(ctx, id, base, 0); \
1004 if (ctx->focus == id && ctx->mouse_down == MU_MOUSE_LEFT) { \
1005 cnt->scroll.y += ctx->mouse_delta.y * cs.y / base.h; \
1007 /* clamp scroll to limits */ \
1008 cnt->scroll.y = mu_clamp(cnt->scroll.y, 0, maxscroll); \
1010 /* draw base and thumb */ \
1011 ctx->draw_frame(ctx, base, MU_COLOR_SCROLLBASE); \
1012 thumb = base; \
1013 thumb.h = mu_max(ctx->style->thumb_size, base.h * b->h / cs.y); \
1014 thumb.y += cnt->scroll.y * (base.h - thumb.h) / maxscroll; \
1015 ctx->draw_frame(ctx, thumb, MU_COLOR_SCROLLTHUMB); \
1017 /* set this as the scroll_target (will get scrolled on mousewheel) */ \
1018 /* if the mouse is over it */ \
1019 if (mu_mouse_over(ctx, *b)) { ctx->scroll_target = cnt; } \
1020 } else { \
1021 cnt->scroll.y = 0; \
1023 } while (0)
1026 static void scrollbars(mu_Context *ctx, mu_Container *cnt, mu_Rect *body) {
1027 int sz = ctx->style->scrollbar_size;
1028 mu_Vec2 cs = cnt->content_size;
1029 cs.x += ctx->style->padding * 2;
1030 cs.y += ctx->style->padding * 2;
1031 mu_push_clip_rect(ctx, *body);
1032 /* resize body to make room for scrollbars */
1033 if (cs.y > cnt->body.h) { body->w -= sz; }
1034 if (cs.x > cnt->body.w) { body->h -= sz; }
1035 /* to create a horizontal or vertical scrollbar almost-identical code is
1036 ** used; only the references to `x|y` `w|h` need to be switched */
1037 scrollbar(ctx, cnt, body, cs, x, y, w, h);
1038 scrollbar(ctx, cnt, body, cs, y, x, h, w);
1039 mu_pop_clip_rect(ctx);
1043 static void push_container_body(
1044 mu_Context *ctx, mu_Container *cnt, mu_Rect body, int opt
1046 if (~opt & MU_OPT_NOSCROLL) { scrollbars(ctx, cnt, &body); }
1047 push_layout(ctx, expand_rect(body, -ctx->style->padding), cnt->scroll);
1048 cnt->body = body;
1052 static void begin_root_container(mu_Context *ctx, mu_Container *cnt) {
1053 push(ctx->container_stack, cnt);
1054 /* push container to roots list and push head command */
1055 push(ctx->root_list, cnt);
1056 cnt->head = push_jump(ctx, NULL);
1057 /* set as hover root if the mouse is overlapping this container and it has a
1058 ** higher zindex than the current hover root */
1059 if (rect_overlaps_vec2(cnt->rect, ctx->mouse_pos) &&
1060 (!ctx->next_hover_root || cnt->zindex > ctx->next_hover_root->zindex)
1062 ctx->next_hover_root = cnt;
1064 /* clipping is reset here in case a root-container is made within
1065 ** another root-containers's begin/end block; this prevents the inner
1066 ** root-container being clipped to the outer */
1067 push(ctx->clip_stack, unclipped_rect);
1071 static void end_root_container(mu_Context *ctx) {
1072 /* push tail 'goto' jump command and set head 'skip' command. the final steps
1073 ** on initing these are done in mu_end() */
1074 mu_Container *cnt = mu_get_current_container(ctx);
1075 cnt->tail = push_jump(ctx, NULL);
1076 cnt->head->jump.dst = ctx->command_list.items + ctx->command_list.idx;
1077 /* pop base clip rect and container */
1078 mu_pop_clip_rect(ctx);
1079 pop_container(ctx);
1083 int mu_begin_window_ex(mu_Context *ctx, const char *title, mu_Rect rect, int opt) {
1084 mu_Rect body;
1085 mu_Id id = mu_get_id(ctx, title, strlen(title));
1086 mu_Container *cnt = get_container(ctx, id, opt);
1087 if (!cnt || !cnt->open) { return 0; }
1088 push(ctx->id_stack, id);
1090 if (cnt->rect.w == 0) { cnt->rect = rect; }
1091 begin_root_container(ctx, cnt);
1092 rect = body = cnt->rect;
1094 /* draw frame */
1095 if (~opt & MU_OPT_NOFRAME) {
1096 ctx->draw_frame(ctx, rect, MU_COLOR_WINDOWBG);
1099 /* do title bar */
1100 if (~opt & MU_OPT_NOTITLE) {
1101 mu_Rect tr = rect;
1102 tr.h = ctx->style->title_height;
1103 ctx->draw_frame(ctx, tr, MU_COLOR_TITLEBG);
1105 /* do title text */
1106 if (~opt & MU_OPT_NOTITLE) {
1107 mu_Id id = mu_get_id(ctx, "!title", 6);
1108 mu_update_control(ctx, id, tr, opt);
1109 mu_draw_control_text(ctx, title, tr, MU_COLOR_TITLETEXT, opt);
1110 if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) {
1111 cnt->rect.x += ctx->mouse_delta.x;
1112 cnt->rect.y += ctx->mouse_delta.y;
1114 body.y += tr.h;
1115 body.h -= tr.h;
1118 /* do `close` button */
1119 if (~opt & MU_OPT_NOCLOSE) {
1120 mu_Id id = mu_get_id(ctx, "!close", 6);
1121 mu_Rect r = mu_rect(tr.x + tr.w - tr.h, tr.y, tr.h, tr.h);
1122 tr.w -= r.w;
1123 mu_draw_icon(ctx, MU_ICON_CLOSE, r, ctx->style->colors[MU_COLOR_TITLETEXT]);
1124 mu_update_control(ctx, id, r, opt);
1125 if (ctx->mouse_pressed == MU_MOUSE_LEFT && id == ctx->focus) {
1126 cnt->open = 0;
1131 push_container_body(ctx, cnt, body, opt);
1133 /* do `resize` handle */
1134 if (~opt & MU_OPT_NORESIZE) {
1135 int sz = ctx->style->title_height;
1136 mu_Id id = mu_get_id(ctx, "!resize", 7);
1137 mu_Rect r = mu_rect(rect.x + rect.w - sz, rect.y + rect.h - sz, sz, sz);
1138 mu_update_control(ctx, id, r, opt);
1139 if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) {
1140 cnt->rect.w = mu_max(96, cnt->rect.w + ctx->mouse_delta.x);
1141 cnt->rect.h = mu_max(64, cnt->rect.h + ctx->mouse_delta.y);
1145 /* resize to content size */
1146 if (opt & MU_OPT_AUTOSIZE) {
1147 mu_Rect r = get_layout(ctx)->body;
1148 cnt->rect.w = cnt->content_size.x + (cnt->rect.w - r.w);
1149 cnt->rect.h = cnt->content_size.y + (cnt->rect.h - r.h);
1152 /* close if this is a popup window and elsewhere was clicked */
1153 if (opt & MU_OPT_POPUP && ctx->mouse_pressed && ctx->hover_root != cnt) {
1154 cnt->open = 0;
1157 mu_push_clip_rect(ctx, cnt->body);
1158 return MU_RES_ACTIVE;
1162 void mu_end_window(mu_Context *ctx) {
1163 mu_pop_clip_rect(ctx);
1164 end_root_container(ctx);
1168 void mu_open_popup(mu_Context *ctx, const char *name) {
1169 mu_Container *cnt = mu_get_container(ctx, name);
1170 /* set as hover root so popup isn't closed in begin_window_ex() */
1171 ctx->hover_root = ctx->next_hover_root = cnt;
1172 /* position at mouse cursor, open and bring-to-front */
1173 cnt->rect = mu_rect(ctx->mouse_pos.x, ctx->mouse_pos.y, 1, 1);
1174 cnt->open = 1;
1175 mu_bring_to_front(ctx, cnt);
1179 int mu_begin_popup(mu_Context *ctx, const char *name) {
1180 int opt = MU_OPT_POPUP | MU_OPT_AUTOSIZE | MU_OPT_NORESIZE |
1181 MU_OPT_NOSCROLL | MU_OPT_NOTITLE | MU_OPT_CLOSED;
1182 return mu_begin_window_ex(ctx, name, mu_rect(0, 0, 0, 0), opt);
1186 void mu_end_popup(mu_Context *ctx) {
1187 mu_end_window(ctx);
1191 void mu_begin_panel_ex(mu_Context *ctx, const char *name, int opt) {
1192 mu_Container *cnt;
1193 mu_push_id(ctx, name, strlen(name));
1194 cnt = get_container(ctx, ctx->last_id, opt);
1195 cnt->rect = mu_layout_next(ctx);
1196 if (~opt & MU_OPT_NOFRAME) {
1197 ctx->draw_frame(ctx, cnt->rect, MU_COLOR_PANELBG);
1199 push(ctx->container_stack, cnt);
1200 push_container_body(ctx, cnt, cnt->rect, opt);
1201 mu_push_clip_rect(ctx, cnt->body);
1205 void mu_end_panel(mu_Context *ctx) {
1206 mu_pop_clip_rect(ctx);
1207 pop_container(ctx);