2 ** Copyright (c) 2024 rxi
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
28 #define unused(x) ((void) (x))
30 #define expect(x) do { \
32 fprintf(stderr, "Fatal error: %s:%d: assertion '%s' failed\n", \
33 __FILE__, __LINE__, #x); \
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 */ \
44 #define pop(stk) do { \
45 expect((stk).idx > 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 */
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
) {
83 mu_Rect
mu_rect(int x
, int y
, int w
, int h
) {
85 res
.x
= x
; res
.y
= y
; res
.w
= w
; res
.h
= h
;
90 mu_Color
mu_color(int r
, int g
, int b
, int a
) {
92 res
.r
= r
; res
.g
= g
; res
.b
= b
; res
.a
= a
;
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; }
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
;
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
) {
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 */
199 mu_Command
*cmd
= (mu_Command
*) ctx
->command_list
.items
;
200 cmd
->jump
.dst
= (char*) cnt
->head
+ sizeof(mu_JumpCommand
);
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 */
207 cnt
->tail
->jump
.dst
= ctx
->command_list
.items
+ ctx
->command_list
.idx
;
213 void mu_set_focus(mu_Context
*ctx
, mu_Id 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
;
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
);
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
) {
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; }
276 static void push_layout(mu_Context
*ctx
, mu_Rect body
, mu_Vec2 scroll
) {
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
);
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
) {
312 /* try to get existing container from pool */
313 int idx
= mu_pool_get(ctx
, ctx
->container_pool
, MU_CONTAINERPOOL_SIZE
, id
);
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
));
326 mu_bring_to_front(ctx
, 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 /*============================================================================
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
;
356 mu_pool_update(ctx
, items
, n
);
361 int mu_pool_get(mu_Context
*ctx
, mu_PoolItem
*items
, int len
, mu_Id id
) {
364 for (i
= 0; i
< len
; i
++) {
365 if (items
[i
].id
== id
) { return i
; }
371 void mu_pool_update(mu_Context
*ctx
, mu_PoolItem
*items
, int idx
) {
372 items
[idx
].last_update
= ctx
->frame
;
376 /*============================================================================
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 /*============================================================================
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
;
437 int mu_next_command(mu_Context
*ctx
, mu_Command
**cmd
) {
439 *cmd
= (mu_Command
*) (((char*) *cmd
) + (*cmd
)->base
.size
);
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
;
451 static mu_Command
* push_jump(mu_Context
*ctx
, mu_Command
*dst
) {
453 cmd
= mu_push_command(ctx
, MU_COMMAND_JUMP
, sizeof(mu_JumpCommand
));
459 void mu_set_clip(mu_Context
*ctx
, mu_Rect rect
) {
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
) {
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
)
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
)); }
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';
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
) {
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
));
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 /*============================================================================
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
) {
538 pop(ctx
->layout_stack
);
539 /* inherit position/next_row/max from child layout if they are greater */
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
);
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
);
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
;
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;
588 if (type
== ABSOLUTE
) { return (ctx
->last_rect
= res
); }
591 /* handle next row */
592 if (layout
->item_index
== layout
->items
) {
593 mu_layout_row(ctx
, layout
->items
, NULL
, layout
->size
.y
);
597 res
.x
= layout
->position
.x
;
598 res
.y
= layout
->position
.y
;
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 /*============================================================================
629 **============================================================================*/
631 static int in_hover_root(mu_Context
*ctx
) {
632 int i
= ctx
->container_stack
.idx
;
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; }
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
)
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
;
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
) &&
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
) {
701 void mu_text(mu_Context
*ctx
, const char *text
) {
702 const char *start
, *end
, *p
= text
;
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
));
709 mu_Rect r
= mu_layout_next(ctx
);
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);
719 } while (*end
&& *end
!= '\n');
720 mu_draw_text(ctx
, font
, start
, end
- start
, mu_vec2(r
.x
, r
.y
), color
);
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
) {
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
);
739 if (ctx
->mouse_pressed
== MU_MOUSE_LEFT
&& ctx
->focus
== id
) {
740 res
|= MU_RES_SUBMIT
;
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
]); }
750 int mu_checkbox(mu_Context
*ctx
, const char *label
, int *state
) {
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);
757 if (ctx
->mouse_pressed
== MU_MOUSE_LEFT
&& ctx
->focus
== id
) {
758 res
|= MU_RES_CHANGE
;
762 mu_draw_control_frame(ctx
, id
, box
, MU_COLOR_BASE
, 0);
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);
772 int mu_textbox_raw(mu_Context
*ctx
, char *buf
, int bufsz
, mu_Id id
, mu_Rect r
,
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
));
783 memcpy(buf
+ len
, ctx
->input_text
, n
);
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);
793 res
|= MU_RES_CHANGE
;
796 if (ctx
->key_pressed
& MU_KEY_RETURN
) {
797 mu_set_focus(ctx
, 0);
798 res
|= MU_RES_SUBMIT
;
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
);
817 mu_draw_control_text(ctx
, buf
, r
, MU_COLOR_TEXT
, opt
);
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
&&
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;
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];
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
);
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
; }
880 mu_draw_control_frame(ctx
, id
, base
, MU_COLOR_BASE
, opt
);
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
);
887 sprintf(buf
, fmt
, v
);
888 mu_draw_control_text(ctx
, buf
, base
, MU_COLOR_TEXT
, opt
);
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];
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
);
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
; }
917 mu_draw_control_frame(ctx
, id
, base
, MU_COLOR_BASE
, opt
);
919 sprintf(buf
, fmt
, *value
);
920 mu_draw_control_text(ctx
, buf
, base
, MU_COLOR_TEXT
, opt
);
926 static int header(mu_Context
*ctx
, const char *label
, int istreenode
, int opt
) {
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
);
932 mu_layout_row(ctx
, 1, &width
, 0);
935 expanded
= (opt
& MU_OPT_EXPANDED
) ? !active
: active
;
936 r
= mu_layout_next(ctx
);
937 mu_update_control(ctx
, id
, r
, 0);
940 active
^= (ctx
->mouse_pressed
== MU_MOUSE_LEFT
&& ctx
->focus
== id
);
942 /* update pool ref */
944 if (active
) { mu_pool_update(ctx
, ctx
->treenode_pool
, idx
); }
945 else { memset(&ctx
->treenode_pool
[idx
], 0, sizeof(mu_PoolItem
)); }
947 mu_pool_init(ctx
, ctx
->treenode_pool
, MU_TREENODEPOOL_SIZE
, id
);
952 if (ctx
->hover
== id
) { ctx
->draw_frame(ctx
, r
, MU_COLOR_BUTTONHOVER
); }
954 mu_draw_control_frame(ctx
, id
, r
, MU_COLOR_BUTTON
, 0);
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
);
982 void mu_end_treenode(mu_Context
*ctx
) {
983 get_layout(ctx
)->indent
-= ctx
->style
->indent
;
988 #define scrollbar(ctx, cnt, b, cs, x, y, w, h) \
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 */ \
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); \
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; } \
1021 cnt->scroll.y = 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
);
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
);
1083 int mu_begin_window_ex(mu_Context
*ctx
, const char *title
, mu_Rect rect
, int opt
) {
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
;
1095 if (~opt
& MU_OPT_NOFRAME
) {
1096 ctx
->draw_frame(ctx
, rect
, MU_COLOR_WINDOWBG
);
1100 if (~opt
& MU_OPT_NOTITLE
) {
1102 tr
.h
= ctx
->style
->title_height
;
1103 ctx
->draw_frame(ctx
, tr
, MU_COLOR_TITLEBG
);
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
;
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
);
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
) {
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
) {
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);
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
) {
1191 void mu_begin_panel_ex(mu_Context
*ctx
, const char *name
, int opt
) {
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
);