1 /// Immediate-mode UI based on rxi/microui.
2 /// Authors: dd86k (dd@dax.moe)
3 /// Copyright: © 2020 rxi, © 2022 dd86k
4 /// License: BSD-3-Clause
5 module ddui;
6 
7 // Original Copyright:
8 /*
9 ** Copyright (c) 2020 rxi
10 **
11 ** Permission is hereby granted, free of charge, to any person obtaining a copy
12 ** of this software and associated documentation files (the "Software"), to
13 ** deal in the Software without restriction, including without limitation the
14 ** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
15 ** sell copies of the Software, and to permit persons to whom the Software is
16 ** furnished to do so, subject to the following conditions:
17 **
18 ** The above copyright notice and this permission notice shall be included in
19 ** all copies or substantial portions of the Software.
20 **
21 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 ** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
27 ** IN THE SOFTWARE.
28 */
29 
30 import core.stdc.stdio;
31 import core.stdc.stdlib;
32 import core.stdc.string;
33 
34 extern (C):
35 
36 //TODO: Consider ballooning command stack
37 //      e.g., if initial size isn't enough, allocate more (forever)
38 //      might not be viable since dynamic allocation is performed
39 
40 enum MU_VERSION = "0.0.1";
41 
42 /// Buffer size for text command.
43 /// This affects all labels and text inputs.
44 enum MU_TEXTSTACK_SIZE = 1024;
45 /// Maximum number of commands that the UI can generate.
46 /// Demo uses around 497 commands.
47 enum MU_COMMANDLIST_SIZE = 4096;
48 enum MU_ROOTLIST_SIZE = 32;
49 enum MU_CONTAINERSTACK_SIZE = 32;
50 enum MU_CLIPSTACK_SIZE = 32;
51 enum MU_IDSTACK_SIZE = 32;
52 enum MU_LAYOUTSTACK_SIZE = 16;
53 enum MU_CONTAINERPOOL_SIZE = 48;
54 enum MU_TREENODEPOOL_SIZE = 48;
55 enum MU_MAX_WIDTHS = 16;
56 enum MU_MAX_FMT = 64;
57 enum const(char)* MU_REAL_FMT = "%.3g";
58 enum const(char)* MU_SLIDER_FMT = "%.2f";
59 
60 /// Length of the text input buffer.
61 enum MU_TEXT_LEN = 32;
62 
63 /// Defines a stack with a maximum number of items that can be inserted.
64 /// Params: n = Maximum number of stack items allowed.
65 struct mu_Stack(T, size_t n)
66 {
67     size_t idx;
68     T[n] items;
69     
70     void push(T val)
71     {
72         assert(idx < n, "idx < n");
73         items[idx] = val;
74         ++idx;
75     }
76 
77     void pop()
78     {
79         assert(idx > 0, "idx > 0");
80         --idx;
81     }
82 }
83 
84 /// Returns minimum item.
85 auto mu_min(A, B)(A a, B b)
86 {
87     return a < b ? a : b;
88 }
89 /// Returns maximum item.
90 auto mu_max(A, B)(A a, B b)
91 {
92     return a > b ? a : b;
93 }
94 /// Returns a clamped value with a given minimum and maximum.
95 auto mu_clamp(X, A, B)(X x, A a, B b)
96 {
97     return mu_min(b, mu_max(a, x));
98 }
99 
100 alias mu_Id = uint;
101 alias mu_Real = float;
102 alias mu_Font = void*;
103 
104 enum
105 {
106     MU_CLIP_PART = 1,
107     MU_CLIP_ALL
108 }
109 
110 enum
111 {
112     MU_COMMAND_JUMP = 1,
113     MU_COMMAND_CLIP,
114     MU_COMMAND_RECT,
115     MU_COMMAND_TEXT,
116     MU_COMMAND_ICON,
117     MU_COMMAND_MAX
118 }
119 
120 enum
121 {
122     MU_COLOR_TEXT,
123     MU_COLOR_BORDER,
124     MU_COLOR_WINDOWBG,
125     MU_COLOR_TITLEBG,
126     MU_COLOR_TITLETEXT,
127     MU_COLOR_PANELBG,
128     MU_COLOR_BUTTON,
129     MU_COLOR_BUTTONHOVER,
130     MU_COLOR_BUTTONFOCUS,
131     MU_COLOR_BASE,
132     MU_COLOR_BASEHOVER,
133     MU_COLOR_BASEFOCUS,
134     MU_COLOR_SCROLLBASE,
135     MU_COLOR_SCROLLTHUMB,
136     MU_COLOR_MAX
137 }
138 
139 enum
140 {
141     MU_ICON_CLOSE = 1,
142     MU_ICON_CHECK,
143     MU_ICON_COLLAPSED,
144     MU_ICON_EXPANDED,
145     MU_ICON_MAX
146 }
147 
148 enum
149 {
150     MU_RES_ACTIVE = (1 << 0),
151     MU_RES_SUBMIT = (1 << 1),
152     MU_RES_CHANGE = (1 << 2)
153 }
154 
155 enum
156 {
157     MU_OPT_ALIGNCENTER = (1 << 0),
158     MU_OPT_ALIGNRIGHT = (1 << 1),
159     MU_OPT_NOINTERACT = (1 << 2),
160     MU_OPT_NOFRAME = (1 << 3),
161     MU_OPT_NORESIZE = (1 << 4),
162     MU_OPT_NOSCROLL = (1 << 5),
163     MU_OPT_NOCLOSE = (1 << 6),
164     MU_OPT_NOTITLE = (1 << 7),
165     MU_OPT_HOLDFOCUS = (1 << 8),
166     MU_OPT_AUTOSIZE = (1 << 9),
167     MU_OPT_POPUP = (1 << 10),
168     MU_OPT_CLOSED = (1 << 11),
169     MU_OPT_EXPANDED = (1 << 12)
170 }
171 
172 enum
173 {
174     MU_MOUSE_LEFT = (1 << 0),
175     MU_MOUSE_RIGHT = (1 << 1),
176     MU_MOUSE_MIDDLE = (1 << 2)
177 }
178 
179 enum
180 {
181     MU_KEY_SHIFT = (1 << 0),
182     MU_KEY_CTRL = (1 << 1),
183     MU_KEY_ALT = (1 << 2),
184     MU_KEY_BACKSPACE = (1 << 3),
185     MU_KEY_RETURN = (1 << 4)
186 }
187 
188 /// 2D vector point
189 struct mu_Vec2
190 {
191     int x, y;
192 }
193 
194 /// 2D rectangle
195 struct mu_Rect
196 {
197     int x, y, w, h;
198 }
199 
200 /// RGBA color
201 struct mu_Color
202 {
203     ubyte r, g, b, a;
204 }
205 
206 ///
207 struct mu_PoolItem
208 {
209     mu_Id id;
210     int last_update;
211 }
212 
213 ///
214 struct mu_BaseCommand
215 {
216     int type;
217 }
218 
219 /// These are used to skip command items to the next valid one.
220 struct mu_JumpCommand
221 {
222     mu_BaseCommand base;
223     int dst_idx;
224 }
225 
226 ///
227 struct mu_ClipCommand
228 {
229     mu_BaseCommand base;
230     mu_Rect rect;
231 }
232 
233 ///
234 struct mu_RectCommand
235 {
236     mu_BaseCommand base;
237     mu_Rect rect;
238     mu_Color color;
239 }
240 
241 ///
242 struct mu_TextCommand
243 {
244     mu_BaseCommand base;
245     mu_Font font;
246     mu_Vec2 pos;
247     mu_Color color;
248     //TODO: Consider adding size_t length
249     char[MU_TEXTSTACK_SIZE] str;
250 }
251 
252 ///
253 struct mu_IconCommand
254 {
255     mu_BaseCommand base;
256     mu_Rect rect;
257     int id;
258     mu_Color color;
259 }
260 
261 ///
262 union mu_Command
263 {
264     int type;
265     mu_BaseCommand base;
266     mu_JumpCommand jump;
267     mu_ClipCommand clip;
268     mu_RectCommand rect;
269     mu_TextCommand text;
270     mu_IconCommand icon;
271 }
272 
273 ///
274 struct mu_Layout
275 {
276     mu_Rect body_;
277     mu_Rect next;
278     mu_Vec2 position;
279     mu_Vec2 size;
280     mu_Vec2 max;
281     //TODO: Consider columns pointer with count
282     int[MU_MAX_WIDTHS] widths;
283     int items;
284     int item_index;
285     int next_row;
286     int next_type;
287     int indent;
288 }
289 
290 ///
291 struct mu_Container
292 {
293     int head_idx, tail_idx;
294     mu_Rect rect;
295     mu_Rect body_;
296     mu_Vec2 content_size;
297     mu_Vec2 scroll;
298     int zindex;
299     int open;
300 }
301 
302 ///
303 struct mu_Style
304 {
305     mu_Font font;
306     mu_Vec2 size;
307     int padding;
308     int spacing;
309     int indent;
310     int title_height;
311     int scrollbar_size;
312     int thumb_size;
313     mu_Color[MU_COLOR_MAX] colors;
314 }
315 
316 /// Main DDUI context structure.
317 /// The instance of which will be given to DDUI functions.
318 struct mu_Context
319 {
320     /// Text width callback.
321     int function(mu_Font font, const(char)* str, int len) text_width;
322     /// Text height callback.
323     int function(mu_Font font) text_height;
324     /// Callback for drawing boxes.
325     void function(mu_Context* ctx, mu_Rect rect, int colorid) mu_draw_frame;
326     
327     //
328     // core state
329     //
330     
331     mu_Style* style;
332     mu_Id hover;
333     mu_Id focus;
334     mu_Id last_id;
335     mu_Rect last_rect;
336     int last_zindex;
337     int updated_focus;
338     int frame;
339     mu_Container* hover_root;
340     mu_Container* next_hover_root;
341     mu_Container* scroll_target;
342     char[MU_MAX_FMT] number_edit_buf;
343     mu_Id number_edit;
344     
345     //
346     // stacks
347     //
348     
349     mu_Stack!(mu_Command,    MU_COMMANDLIST_SIZE)    command_list;
350     mu_Stack!(mu_Container*, MU_ROOTLIST_SIZE)       root_list;
351     mu_Stack!(mu_Container*, MU_CONTAINERSTACK_SIZE) container_stack;
352     mu_Stack!(mu_Rect,       MU_CLIPSTACK_SIZE)      clip_stack;
353     mu_Stack!(mu_Id,         MU_IDSTACK_SIZE)        id_stack;
354     mu_Stack!(mu_Layout,     MU_LAYOUTSTACK_SIZE)    layout_stack;
355     
356     //
357     // retained state pools
358     //
359     
360     mu_PoolItem [MU_CONTAINERPOOL_SIZE] container_pool;
361     mu_Container[MU_CONTAINERPOOL_SIZE] containers;
362     mu_PoolItem [MU_TREENODEPOOL_SIZE]  treenode_pool;
363     
364     //
365     // input state
366     //
367     
368     mu_Vec2 mouse_pos;
369     mu_Vec2 last_mouse_pos;
370     mu_Vec2 mouse_delta;
371     mu_Vec2 scroll_delta;
372     int mouse_down;
373     int mouse_pressed;
374     int key_down;
375     int key_pressed;
376     char[MU_TEXT_LEN] input_text; //TODO: Move into text_stack?
377 }
378 
379 /// Creates a button.
380 int mu_button(mu_Context* ctx, const(char)* label)
381 {
382     return mu_button_ex(ctx, label, 0, MU_OPT_ALIGNCENTER);
383 }
384 
385 /// Creates a editable textbox.
386 int mu_textbox(mu_Context* ctx, char* buf, int bufsz)
387 {
388     return mu_textbox_ex(ctx, buf, bufsz, 0);
389 }
390 
391 /// Creates a slider.
392 int mu_slider(mu_Context* ctx, mu_Real* value, mu_Real low, mu_Real high)
393 {
394     return mu_slider_ex(ctx, value, low, high, 0, MU_SLIDER_FMT, MU_OPT_ALIGNCENTER);
395 }
396 
397 /// Creates a number box.
398 int mu_number(mu_Context* ctx, mu_Real* value, mu_Real step)
399 {
400     return mu_number_ex(ctx, value, step, MU_SLIDER_FMT, MU_OPT_ALIGNCENTER);
401 }
402 
403 /// 
404 int mu_header(mu_Context* ctx, const(char)* label)
405 {
406     return mu_header_ex(ctx, label, 0);
407 }
408 
409 /// 
410 int mu_begin_treenode(mu_Context* ctx, const(char)* label)
411 {
412     return mu_begin_treenode_ex(ctx, label, 0);
413 }
414 
415 /// Creates a window.
416 int mu_begin_window(mu_Context* ctx, const(char)* title, mu_Rect rect)
417 {
418     return mu_begin_window_ex(ctx, title, rect, 0);
419 }
420 
421 /// Creates a panel.
422 mu_Container* mu_begin_panel(mu_Context* ctx, const(char)* name)
423 {
424     return mu_begin_panel_ex(ctx, name, 0);
425 }
426 
427 immutable mu_Rect unclipped_rect = { 0, 0, 0x1000000, 0x1000000 };
428 
429 /// Default style.
430 pragma(mangle, "mu_default_style")
431 __gshared mu_Style default_style = {
432     // Font
433     null,
434     // Size in pixels
435     { 68, 10 },
436     // Padding in pixels
437     5,
438     // Margin in pixels
439     4,
440     // Indentation
441     24,
442     // Title height
443     24,
444     // Scrollbar size
445     12,
446     // Thumb size
447     8,
448     [
449         // MU_COLOR_TEXT
450         { 230, 230, 230, 255 },
451         // MU_COLOR_BORDER
452         {  25,  25,  25, 255 },
453         // MU_COLOR_WINDOWBG
454         {  50,  50,  50, 255 },
455         // MU_COLOR_TITLEBG
456         {  25,  25,  25, 255 },
457         // MU_COLOR_TITLETEXT
458         { 240, 240, 240, 255 },
459         // MU_COLOR_PANELBG
460         {   0,   0,   0,   0 },
461         // MU_COLOR_BUTTON
462         {  75,  75,  75, 255 },
463         // MU_COLOR_BUTTONHOVER
464         {  95,  95,  95, 255 },
465         // MU_COLOR_BUTTONFOCUS
466         { 115, 115, 115, 255 },
467         // MU_COLOR_BASE
468         {  30,  30,  30, 255 },
469         // MU_COLOR_BASEHOVER
470         {  35,  35,  35, 255 },
471         // MU_COLOR_BASEFOCUS
472         {  40,  40,  40, 255 },
473         // MU_COLOR_SCROLLBASE
474         {  43,  43,  43, 255 },
475         // MU_COLOR_SCROLLTHUMB
476         {  30,  30,  30, 255 },
477     ]
478 };
479 
480 /// Expand a rectangle from its center by n pixels.
481 /// Params:
482 ///     rect = Rectangle
483 ///     n = Pixels
484 /// Returns: New rectangle
485 mu_Rect mu_expand_rect(mu_Rect rect, int n)
486 {
487     return mu_Rect(rect.x - n, rect.y - n, rect.w + n * 2, rect.h + n * 2);
488 }
489 
490 mu_Rect mu_intersect_rects(mu_Rect r1, mu_Rect r2)
491 {
492     int x1 = mu_max(r1.x, r2.x);
493     int y1 = mu_max(r1.y, r2.y);
494     int x2 = mu_min(r1.x + r1.w, r2.x + r2.w);
495     int y2 = mu_min(r1.y + r1.h, r2.y + r2.h);
496     if (x2 < x1)
497     {
498         x2 = x1;
499     }
500     if (y2 < y1)
501     {
502         y2 = y1;
503     }
504     return mu_Rect(x1, y1, x2 - x1, y2 - y1);
505 }
506 
507 int rect_overlaps_vec2(mu_Rect r, mu_Vec2 p)
508 {
509     return p.x >= r.x && p.x < r.x + r.w && p.y >= r.y && p.y < r.y + r.h;
510 }
511 
512 /// Draw a frame of a window
513 void mu_draw_frame(mu_Context* ctx, mu_Rect rect, int colorid)
514 {
515     mu_draw_rect(ctx, rect, ctx.style.colors[colorid]);
516     
517     if (colorid == MU_COLOR_SCROLLBASE ||
518         colorid == MU_COLOR_SCROLLTHUMB ||
519         colorid == MU_COLOR_TITLEBG)
520     {
521         return;
522     }
523     
524     // draw border
525     if (ctx.style.colors[MU_COLOR_BORDER].a)
526     {
527         mu_draw_box(ctx, mu_expand_rect(rect, 1), ctx.style.colors[MU_COLOR_BORDER]);
528     }
529 }
530 
531 void mu_init(mu_Context* ctx, mu_Style *style = null)
532 {
533     memset(ctx, 0, mu_Context.sizeof);
534     ctx.mu_draw_frame = &mu_draw_frame;
535     ctx.style = style ? style : cast(mu_Style*)&default_style;
536 }
537 
538 void mu_begin(mu_Context* ctx)
539 {
540     assert(ctx.text_width && ctx.text_height, "ctx.text_width && ctx.text_height");
541     ctx.command_list.idx = 0;
542     ctx.root_list.idx = 0;
543     ctx.scroll_target = null;
544     ctx.hover_root = ctx.next_hover_root;
545     ctx.next_hover_root = null;
546     ctx.mouse_delta.x = ctx.mouse_pos.x - ctx.last_mouse_pos.x;
547     ctx.mouse_delta.y = ctx.mouse_pos.y - ctx.last_mouse_pos.y;
548     ctx.frame++;
549 }
550 
551 int mu_compare_zindex(const void* a, const void* b)
552 {
553     mu_Container *A = cast(mu_Container*) a;
554     mu_Container *B = cast(mu_Container*) b;
555     return A.zindex - B.zindex;
556 }
557 
558 void mu_end(mu_Context* ctx)
559 {
560     // check stacks
561     assert(ctx.container_stack.idx == 0, "ctx.container_stack.idx == 0");
562     assert(ctx.clip_stack.idx == 0, "ctx.clip_stack.idx == 0");
563     assert(ctx.id_stack.idx == 0, "ctx.id_stack.idx == 0");
564     assert(ctx.layout_stack.idx == 0, "ctx.layout_stack.idx == 0");
565 
566     // handle scroll input
567     if (ctx.scroll_target)
568     {
569         ctx.scroll_target.scroll.x += ctx.scroll_delta.x;
570         ctx.scroll_target.scroll.y += ctx.scroll_delta.y;
571     }
572 
573     // unset focus if focus id was not touched this frame
574     if (!ctx.updated_focus)
575     {
576         ctx.focus = 0;
577     }
578     
579     ctx.updated_focus = 0;
580 
581     // bring hover root to front if mouse was pressed
582     if (ctx.mouse_pressed && ctx.next_hover_root &&
583         ctx.next_hover_root.zindex < ctx.last_zindex &&
584         ctx.next_hover_root.zindex)
585     {
586         mu_bring_to_front(ctx, ctx.next_hover_root);
587     }
588 
589     // reset input state
590     ctx.input_text[0] = '\0';
591     ctx.key_pressed = 0;
592     ctx.mouse_pressed = 0;
593     ctx.scroll_delta = mu_Vec2(0, 0);
594     ctx.last_mouse_pos = ctx.mouse_pos;
595 
596     // sort root containers by zindex
597     size_t n = ctx.root_list.idx;
598     // NOTE: May cause crashes across different C runtimes (e.g., MSVC)
599     //qsort(ctx.root_list.items.ptr, n, (mu_Container*).sizeof, &mu_compare_zindex);
600     
601     // Set root container jump commands
602     // First container should have the first command jump to it
603     mu_Command* cmd = cast(mu_Command*) ctx.command_list.items;
604     cmd.jump.dst_idx = ctx.root_list.items[0].head_idx + 1;
605     
606     // Otherwise set the previous container's tail to jump to this one
607     for (size_t i = 1; i < n; ++i)
608     {
609         mu_Container* cnt  = ctx.root_list.items[i];
610         mu_Container* prev = ctx.root_list.items[i - 1];
611         ctx.command_list.items[prev.tail_idx].jump.dst_idx = cnt.head_idx + 1;
612         if (i == n - 1)
613         {
614             ctx.command_list.items[cnt.tail_idx].jump.dst_idx = cast(int)ctx.command_list.idx;
615         }
616     }
617 }
618 
619 void mu_set_focus(mu_Context* ctx, mu_Id id)
620 {
621     ctx.focus = id;
622     ctx.updated_focus = 1;
623 }
624 
625 /// 32bit fnv-1a hash
626 private enum HASH_INITIAL = 0x811c_9dc5;
627 
628 private
629 void mu_hash(mu_Id* hash, const(void)* data, size_t size)
630 {
631     const(ubyte)* p = cast(const(ubyte)*) data;
632     while (size--)
633     {
634         *hash = (*hash ^ *p++) * 0x1000193;
635     }
636 }
637 
638 mu_Id mu_get_id(mu_Context* ctx, const(void)* data, size_t size)
639 {
640     int idx = cast(int)ctx.id_stack.idx;
641     mu_Id res = (idx > 0) ? ctx.id_stack.items[idx - 1] : HASH_INITIAL;
642     mu_hash(&res, data, size);
643     ctx.last_id = res;
644     return res;
645 }
646 
647 void mu_push_id(mu_Context* ctx, const void* data, int size)
648 {
649     ctx.id_stack.push(mu_get_id(ctx, data, size));
650 }
651 
652 void mu_pop_id(mu_Context* ctx)
653 {
654     ctx.id_stack.pop();
655 }
656 
657 void mu_push_clip_rect(mu_Context* ctx, mu_Rect rect)
658 {
659     mu_Rect last = mu_get_clip_rect(ctx);
660     ctx.clip_stack.push(mu_intersect_rects(rect, last));
661 }
662 
663 void mu_pop_clip_rect(mu_Context* ctx)
664 {
665     ctx.clip_stack.pop();
666 }
667 
668 mu_Rect mu_get_clip_rect(mu_Context* ctx)
669 {
670     assert(ctx.clip_stack.idx > 0, "ctx.clip_stack.idx > 0");
671     return ctx.clip_stack.items[ctx.clip_stack.idx - 1];
672 }
673 
674 int mu_check_clip(mu_Context* ctx, mu_Rect r)
675 {
676     mu_Rect cr = mu_get_clip_rect(ctx);
677     if (r.x > cr.x + cr.w || r.x + r.w < cr.x ||
678         r.y > cr.y + cr.h || r.y + r.h < cr.y)
679     {
680         return MU_CLIP_ALL;
681     }
682     if (r.x >= cr.x && r.x + r.w <= cr.x + cr.w &&
683         r.y >= cr.y && r.y + r.h <= cr.y + cr.h)
684     {
685         return 0;
686     }
687     return MU_CLIP_PART;
688 }
689 
690 void mu_push_layout(mu_Context* ctx, mu_Rect body_, mu_Vec2 scroll)
691 {
692     mu_Layout layout = void;
693     int width = 0;
694     memset(&layout, 0, layout.sizeof);
695     layout.body_ = mu_Rect(body_.x - scroll.x, body_.y - scroll.y, body_.w, body_.h);
696     layout.max = mu_Vec2(-0x1000000, -0x1000000); // ?
697     ctx.layout_stack.push(layout);
698     mu_layout_row(ctx, 1, &width, 0);
699 }
700 
701 mu_Layout* mu_get_layout(mu_Context* ctx)
702 {
703     return &ctx.layout_stack.items[ctx.layout_stack.idx - 1];
704 }
705 
706 void mu_pop_container(mu_Context* ctx)
707 {
708     mu_Container* cnt = mu_get_current_container(ctx);
709     mu_Layout* layout = mu_get_layout(ctx);
710     cnt.content_size.x = layout.max.x - layout.body_.x;
711     cnt.content_size.y = layout.max.y - layout.body_.y;
712     // pop container, layout and id
713     ctx.container_stack.pop();
714     ctx.layout_stack.pop();
715     mu_pop_id(ctx);
716 }
717 
718 mu_Container* mu_get_current_container(mu_Context* ctx)
719 {
720     assert(ctx.container_stack.idx > 0, "ctx.container_stack.idx > 0");
721     return ctx.container_stack.items[ctx.container_stack.idx - 1];
722 }
723 
724 mu_Container* mu_get_container2(mu_Context* ctx, mu_Id id, int opt)
725 {
726     // try to get existing container from pool
727     int idx = mu_pool_get(ctx, ctx.container_pool.ptr, MU_CONTAINERPOOL_SIZE, id);
728     if (idx >= 0)
729     {
730         if (ctx.containers[idx].open || ~opt & MU_OPT_CLOSED)
731         {
732             mu_pool_update(ctx, ctx.container_pool.ptr, idx);
733         }
734         return &ctx.containers[idx];
735     }
736     if (opt & MU_OPT_CLOSED)
737     {
738         return null;
739     }
740     
741     // container not found in pool: init new container
742     idx = mu_pool_init(ctx, ctx.container_pool.ptr, MU_CONTAINERPOOL_SIZE, id);
743     mu_Container* cnt = &ctx.containers[idx];
744     memset(cnt, 0, mu_Container.sizeof);
745     cnt.open = 1;
746     mu_bring_to_front(ctx, cnt);
747     return cnt;
748 }
749 
750 mu_Container* mu_get_container(mu_Context* ctx, const(char)* name)
751 {
752     mu_Id id = mu_get_id(ctx, name, cast(int) strlen(name));
753     return mu_get_container2(ctx, id, 0);
754 }
755 
756 void mu_bring_to_front(mu_Context* ctx, mu_Container* cnt)
757 {
758     cnt.zindex = ++ctx.last_zindex;
759 }
760 
761 /*============================================================================
762 ** pool
763 **============================================================================*/
764 
765 int mu_pool_init(mu_Context* ctx, mu_PoolItem* items, int len, mu_Id id)
766 {
767     int i, n = -1, f = ctx.frame;
768     for (i = 0; i < len; i++)
769     {
770         if (items[i].last_update < f)
771         {
772             f = items[i].last_update;
773             n = i;
774         }
775     }
776     assert(n > -1, "n > -1");
777     items[n].id = id;
778     mu_pool_update(ctx, items, n);
779     return n;
780 }
781 
782 int mu_pool_get(mu_Context* ctx, mu_PoolItem* items, int len, mu_Id id)
783 {
784     for (int i = 0; i < len; i++)
785     {
786         if (items[i].id == id)
787         {
788             return i;
789         }
790     }
791     return -1;
792 }
793 
794 void mu_pool_update(mu_Context* ctx, mu_PoolItem* items, int idx)
795 {
796     items[idx].last_update = ctx.frame;
797 }
798 
799 /*============================================================================
800 ** input handlers
801 **============================================================================*/
802 
803 void mu_input_mousemove(mu_Context* ctx, int x, int y)
804 {
805     ctx.mouse_pos = mu_Vec2(x, y);
806 }
807 
808 void mu_input_mousedown(mu_Context* ctx, int x, int y, int btn)
809 {
810     mu_input_mousemove(ctx, x, y);
811     ctx.mouse_down |= btn;
812     ctx.mouse_pressed |= btn;
813 }
814 
815 void mu_input_mouseup(mu_Context* ctx, int x, int y, int btn)
816 {
817     mu_input_mousemove(ctx, x, y);
818     ctx.mouse_down &= ~btn;
819 }
820 
821 void mu_input_scroll(mu_Context* ctx, int x, int y)
822 {
823     ctx.scroll_delta.x += x;
824     ctx.scroll_delta.y += y;
825 }
826 
827 void mu_input_keydown(mu_Context* ctx, int key)
828 {
829     ctx.key_pressed |= key;
830     ctx.key_down |= key;
831 }
832 
833 void mu_input_keyup(mu_Context* ctx, int key)
834 {
835     ctx.key_down &= ~key;
836 }
837 
838 void mu_input_text(mu_Context* ctx, const(char)* text)
839 {
840     size_t len  = strlen(ctx.input_text.ptr);
841     size_t size = strlen(text) + 1;
842     assert(len + size <= ctx.input_text.sizeof,
843         "len + size <= ctx.input_text.sizeof");
844     memcpy(ctx.input_text.ptr + len, text, size);
845 }
846 
847 /*============================================================================
848 ** commandlist
849 **============================================================================*/
850 
851 mu_Command* mu_push_command(mu_Context* ctx, int type)
852 {
853     mu_Command* cmd = &ctx.command_list.items[ctx.command_list.idx];
854     cmd.base.type = type;
855     ++ctx.command_list.idx;
856     return cmd;
857 }
858 
859 mu_Command[] mu_command_range(mu_Context* ctx)
860 {
861     return ctx.command_list.items.ptr[0 .. ctx.command_list.idx];
862 }
863 
864 deprecated("Use mu_get_next_command. This will be removed in the next version.")
865 int mu_next_command(mu_Context* ctx, mu_Command** cmd)
866 {
867     if (*cmd)
868     {
869         *cmd = *cmd + 1;
870     }
871     else // First
872     {
873         *cmd = ctx.command_list.items.ptr;
874     }
875     while (*cmd != &ctx.command_list.items[ctx.command_list.idx])
876     {
877         if ((*cmd).type != MU_COMMAND_JUMP)
878         {
879             return 1;
880         }
881         *cmd = &ctx.command_list.items[(*cmd).jump.dst_idx];
882     }
883     return 0;
884 }
885 
886 int mu_push_jump(mu_Context* ctx, int idx)
887 {
888     mu_Command *cmd = mu_push_command(ctx, MU_COMMAND_JUMP);
889     cmd.jump.dst_idx = idx;
890     assert(cmd == &ctx.command_list.items[ctx.command_list.idx - 1]);
891     return cast(int)(ctx.command_list.idx - 1);
892 }
893 
894 void mu_set_clip(mu_Context* ctx, mu_Rect rect)
895 {
896     mu_Command* cmd = mu_push_command(ctx, MU_COMMAND_CLIP/*, mu_ClipCommand.sizeof*/);
897     cmd.clip.rect = rect;
898 }
899 
900 void mu_draw_rect(mu_Context* ctx, mu_Rect rect, mu_Color color)
901 {
902     rect = mu_intersect_rects(rect, mu_get_clip_rect(ctx));
903     if (rect.w > 0 && rect.h > 0)
904     {
905         mu_Command* cmd = mu_push_command(ctx, MU_COMMAND_RECT/*, mu_RectCommand.sizeof*/);
906         cmd.rect.rect = rect;
907         cmd.rect.color = color;
908     }
909 }
910 
911 void mu_draw_box(mu_Context* ctx, mu_Rect rect, mu_Color color)
912 {
913     mu_draw_rect(ctx, mu_Rect(rect.x + 1, rect.y, rect.w - 2, 1), color);
914     mu_draw_rect(ctx, mu_Rect(rect.x + 1, rect.y + rect.h - 1, rect.w - 2, 1), color);
915     mu_draw_rect(ctx, mu_Rect(rect.x, rect.y, 1, rect.h), color);
916     mu_draw_rect(ctx, mu_Rect(rect.x + rect.w - 1, rect.y, 1, rect.h), color);
917 }
918 
919 void mu_draw_text(mu_Context* ctx, mu_Font font, const(char)* str, int len,
920     mu_Vec2 pos, mu_Color color)
921 {
922     mu_Rect rect = mu_Rect(pos.x, pos.y, ctx.text_width(font, str, len), ctx.text_height(font));
923     int clipped = mu_check_clip(ctx, rect);
924     if (clipped == MU_CLIP_ALL)
925     {
926         return;
927     }
928     if (clipped == MU_CLIP_PART)
929     {
930         mu_set_clip(ctx, mu_get_clip_rect(ctx));
931     }
932     
933     // add command
934     if (len < 0)
935     {
936         len = cast(int)strlen(str);
937     }
938     
939     mu_Command* cmd = mu_push_command(ctx, MU_COMMAND_TEXT/*, cast(int)(mu_TextCommand.sizeof + len)*/);
940     memcpy(cmd.text.str.ptr, str, len);
941     cmd.text.str[len] = '\0';
942     cmd.text.pos = pos;
943     cmd.text.color = color;
944     cmd.text.font = font;
945     
946     // reset clipping if it was set
947     if (clipped)
948     {
949         mu_set_clip(ctx, unclipped_rect);
950     }
951 }
952 
953 void mu_draw_icon(mu_Context* ctx, int id, mu_Rect rect, mu_Color color)
954 {
955     // do clip command if the rect isn't fully contained within the cliprect
956     int clipped = mu_check_clip(ctx, rect);
957     if (clipped == MU_CLIP_ALL)
958     {
959         return;
960     }
961     if (clipped == MU_CLIP_PART)
962     {
963         mu_set_clip(ctx, mu_get_clip_rect(ctx));
964     }
965     // do icon command
966     mu_Command* cmd = mu_push_command(ctx, MU_COMMAND_ICON/*, mu_IconCommand.sizeof*/);
967     cmd.icon.id = id;
968     cmd.icon.rect = rect;
969     cmd.icon.color = color;
970     // reset clipping if it was set
971     if (clipped)
972     {
973         mu_set_clip(ctx, unclipped_rect);
974     }
975 }
976 
977 /*============================================================================
978 ** layout
979 **============================================================================*/
980 
981 enum
982 {
983     RELATIVE = 1,
984     ABSOLUTE = 2
985 }
986 
987 void mu_layout_begin_column(mu_Context* ctx)
988 {
989     mu_push_layout(ctx, mu_layout_next(ctx), mu_Vec2(0, 0));
990 }
991 
992 void mu_layout_end_column(mu_Context* ctx)
993 {
994     mu_Layout* b = mu_get_layout(ctx);
995     ctx.layout_stack.pop();
996     // inherit position/next_row/max from child layout if they are greater
997     mu_Layout* a = mu_get_layout(ctx);
998     a.position.x = mu_max(a.position.x, b.position.x + b.body_.x - a.body_.x);
999     a.next_row = mu_max(a.next_row, b.next_row + b.body_.y - a.body_.y);
1000     a.max.x = mu_max(a.max.x, b.max.x);
1001     a.max.y = mu_max(a.max.y, b.max.y);
1002 }
1003 
1004 void mu_layout_row(mu_Context* ctx, int items, const(int)* widths, int height)
1005 {
1006     mu_Layout* layout = mu_get_layout(ctx);
1007     if (widths)
1008     {
1009         assert(items <= MU_MAX_WIDTHS, "items <= MU_MAX_WIDTHS");
1010         memcpy(layout.widths.ptr, widths, items * widths[0].sizeof);
1011     }
1012     layout.items = items;
1013     layout.position = mu_Vec2(layout.indent, layout.next_row);
1014     layout.size.y = height;
1015     layout.item_index = 0;
1016 }
1017 
1018 void mu_layout_width(mu_Context* ctx, int width)
1019 {
1020     mu_get_layout(ctx).size.x = width;
1021 }
1022 
1023 void mu_layout_height(mu_Context* ctx, int height)
1024 {
1025     mu_get_layout(ctx).size.y = height;
1026 }
1027 
1028 void mu_layout_set_next(mu_Context* ctx, mu_Rect r, int relative)
1029 {
1030     mu_Layout* layout = mu_get_layout(ctx);
1031     layout.next = r;
1032     layout.next_type = relative ? RELATIVE : ABSOLUTE;
1033 }
1034 
1035 mu_Rect mu_layout_next(mu_Context* ctx)
1036 {
1037     mu_Layout* layout = mu_get_layout(ctx);
1038     mu_Style* style = ctx.style;
1039     mu_Rect res = void;
1040 
1041     if (layout.next_type)
1042     {
1043         /* handle rect set by `mu_layout_set_next` */
1044         int type = layout.next_type;
1045         layout.next_type = 0;
1046         res = layout.next;
1047         if (type == ABSOLUTE)
1048         {
1049             return (ctx.last_rect = res);
1050         }
1051     }
1052     else
1053     {
1054         // handle next row
1055         if (layout.item_index == layout.items)
1056         {
1057             mu_layout_row(ctx, layout.items, null, layout.size.y);
1058         }
1059 
1060         // position
1061         res.x = layout.position.x;
1062         res.y = layout.position.y;
1063 
1064         // size
1065         res.w = layout.items > 0 ? layout.widths[layout.item_index] : layout.size.x;
1066         res.h = layout.size.y;
1067         if (res.w == 0)
1068         {
1069             res.w = style.size.x + style.padding * 2;
1070         }
1071         if (res.h == 0)
1072         {
1073             res.h = style.size.y + style.padding * 2;
1074         }
1075         if (res.w < 0)
1076         {
1077             res.w += layout.body_.w - res.x + 1;
1078         }
1079         if (res.h < 0)
1080         {
1081             res.h += layout.body_.h - res.y + 1;
1082         }
1083 
1084         layout.item_index++;
1085     }
1086 
1087     // update position
1088     layout.position.x += res.w + style.spacing;
1089     layout.next_row = mu_max(layout.next_row, res.y + res.h + style.spacing);
1090 
1091     // apply body_ offset
1092     res.x += layout.body_.x;
1093     res.y += layout.body_.y;
1094 
1095     // update max position
1096     layout.max.x = mu_max(layout.max.x, res.x + res.w);
1097     layout.max.y = mu_max(layout.max.y, res.y + res.h);
1098 
1099     return (ctx.last_rect = res);
1100 }
1101 
1102 /*============================================================================
1103 ** controls
1104 **============================================================================*/
1105 
1106 int mu_in_hover_root(mu_Context* ctx)
1107 {
1108     size_t i = ctx.container_stack.idx;
1109     while (i--)
1110     {
1111         if (ctx.container_stack.items[i] == ctx.hover_root)
1112         {
1113             return 1;
1114         }
1115         // only root containers have their `head` field set; stop searching if we've
1116         // reached the current root container
1117         if (ctx.container_stack.items[i].head_idx != -1)
1118         {
1119             break;
1120         }
1121     }
1122     return 0;
1123 }
1124 
1125 void mu_draw_control_frame(mu_Context* ctx, mu_Id id, mu_Rect rect,
1126     int colorid, int opt)
1127 {
1128     if (opt & MU_OPT_NOFRAME)
1129     {
1130         return;
1131     }
1132     colorid += (ctx.focus == id) ? 2 : (ctx.hover == id) ? 1 : 0;
1133     ctx.mu_draw_frame(ctx, rect, colorid);
1134 }
1135 
1136 void mu_draw_control_text(mu_Context* ctx, const(char)* str, mu_Rect rect,
1137     int colorid, int opt)
1138 {
1139     mu_Font font = ctx.style.font;
1140     int tw = ctx.text_width(font, str, -1);
1141     mu_push_clip_rect(ctx, rect);
1142     
1143     mu_Vec2 pos = void;
1144     pos.y = rect.y + (rect.h - ctx.text_height(font)) / 2;
1145     if (opt & MU_OPT_ALIGNCENTER)
1146     {
1147         pos.x = rect.x + (rect.w - tw) / 2;
1148     }
1149     else if (opt & MU_OPT_ALIGNRIGHT)
1150     {
1151         pos.x = rect.x + rect.w - tw - ctx.style.padding;
1152     }
1153     else
1154     {
1155         pos.x = rect.x + ctx.style.padding;
1156     }
1157     
1158     mu_draw_text(ctx, font, str, -1, pos, ctx.style.colors[colorid]);
1159     mu_pop_clip_rect(ctx);
1160 }
1161 
1162 int mu_mouse_over(mu_Context* ctx, mu_Rect rect)
1163 {
1164     return rect_overlaps_vec2(rect, ctx.mouse_pos) &&
1165         rect_overlaps_vec2(mu_get_clip_rect(ctx), ctx.mouse_pos) &&
1166         mu_in_hover_root(ctx);
1167 }
1168 
1169 void mu_update_control(mu_Context* ctx, mu_Id id, mu_Rect rect, int opt)
1170 {
1171     int mouseover = mu_mouse_over(ctx, rect);
1172 
1173     if (ctx.focus == id)
1174     {
1175         ctx.updated_focus = 1;
1176     }
1177     
1178     if (opt & MU_OPT_NOINTERACT)
1179     {
1180         return;
1181     }
1182     
1183     if (mouseover && !ctx.mouse_down)
1184     {
1185         ctx.hover = id;
1186     }
1187 
1188     if (ctx.focus == id)
1189     {
1190         if (ctx.mouse_pressed && !mouseover)
1191         {
1192             mu_set_focus(ctx, 0);
1193         }
1194         if (!ctx.mouse_down && ~opt & MU_OPT_HOLDFOCUS)
1195         {
1196             mu_set_focus(ctx, 0);
1197         }
1198     }
1199 
1200     if (ctx.hover == id)
1201     {
1202         if (ctx.mouse_pressed)
1203         {
1204             mu_set_focus(ctx, id);
1205         }
1206         else if (!mouseover)
1207         {
1208             ctx.hover = 0;
1209         }
1210     }
1211 }
1212 
1213 void mu_text(mu_Context* ctx, const(char)* text)
1214 {
1215     const(char)* start = void, end = void, p = text;
1216     int width = -1;
1217     mu_Font font = ctx.style.font;
1218     mu_Color color = ctx.style.colors[MU_COLOR_TEXT];
1219     mu_layout_begin_column(ctx);
1220     mu_layout_row(ctx, 1, &width, ctx.text_height(font));
1221     do
1222     {
1223         mu_Rect r = mu_layout_next(ctx);
1224         int w = 0;
1225         start = end = p;
1226         do
1227         {
1228             const(char)* word = p;
1229             while (*p && *p != ' ' && *p != '\n')
1230             {
1231                 ++p;
1232             }
1233             w += ctx.text_width(font, word, cast(int)(p - word));
1234             if (w > r.w && end != start)
1235             {
1236                 break;
1237             }
1238             w += ctx.text_width(font, p, 1);
1239             end = p++;
1240         } while (*end && *end != '\n');
1241         mu_draw_text(ctx, font, start, cast(int)(end - start), mu_Vec2(r.x, r.y), color);
1242         p = end + 1;
1243     } while (*end);
1244     mu_layout_end_column(ctx);
1245 }
1246 
1247 void mu_label(mu_Context* ctx, const(char)* text)
1248 {
1249     mu_draw_control_text(ctx, text, mu_layout_next(ctx), MU_COLOR_TEXT, 0);
1250 }
1251 
1252 int mu_button_ex(mu_Context* ctx, const(char)* label, int icon, int opt)
1253 {
1254     int res = 0;
1255     mu_Id id = label ?
1256         mu_get_id(ctx, label, strlen(label)) :
1257         mu_get_id(ctx, &icon, icon.sizeof);
1258     mu_Rect r = mu_layout_next(ctx);
1259     mu_update_control(ctx, id, r, opt);
1260     
1261     // handle click
1262     if (ctx.mouse_pressed == MU_MOUSE_LEFT && ctx.focus == id)
1263     {
1264         res |= MU_RES_SUBMIT;
1265     }
1266     
1267     // draw
1268     mu_draw_control_frame(ctx, id, r, MU_COLOR_BUTTON, opt);
1269     if (label)
1270     {
1271         mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, opt);
1272     }
1273     
1274     if (icon)
1275     {
1276         mu_draw_icon(ctx, icon, r, ctx.style.colors[MU_COLOR_TEXT]);
1277     }
1278     
1279     return res;
1280 }
1281 
1282 int mu_checkbox(mu_Context* ctx, const(char)* label, int* state)
1283 {
1284     int res = 0;
1285     mu_Id id = mu_get_id(ctx, &state, state.sizeof); // sizeof(state), so pointer?
1286     mu_Rect r = mu_layout_next(ctx);
1287     mu_Rect box = mu_Rect(r.x, r.y, r.h, r.h);
1288     mu_update_control(ctx, id, r, 0);
1289     
1290     // handle click
1291     if (ctx.mouse_pressed == MU_MOUSE_LEFT && ctx.focus == id)
1292     {
1293         res |= MU_RES_CHANGE;
1294         *state = !*state;
1295     }
1296     
1297     // draw
1298     mu_draw_control_frame(ctx, id, box, MU_COLOR_BASE, 0);
1299     if (*state)
1300     {
1301         mu_draw_icon(ctx, MU_ICON_CHECK, box, ctx.style.colors[MU_COLOR_TEXT]);
1302     }
1303     
1304     r = mu_Rect(r.x + box.w, r.y, r.w - box.w, r.h);
1305     mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, 0);
1306     return res;
1307 }
1308 
1309 int mu_textbox_raw(mu_Context* ctx, char* buf, int bufsz, mu_Id id, mu_Rect r,
1310     int opt)
1311 {
1312     int res = 0;
1313     mu_update_control(ctx, id, r, opt | MU_OPT_HOLDFOCUS);
1314 
1315     if (ctx.focus == id)
1316     {
1317         // handle text input
1318         size_t len = strlen(buf);
1319         size_t n = mu_min(bufsz - len - 1, strlen(ctx.input_text.ptr));
1320         if (n > 0)
1321         {
1322             memcpy(buf + len, ctx.input_text.ptr, n);
1323             len += n;
1324             buf[len] = '\0';
1325             res |= MU_RES_CHANGE;
1326         }
1327         
1328         // handle backspace
1329         if (ctx.key_pressed & MU_KEY_BACKSPACE && len > 0)
1330         {
1331             // skip utf-8 continuation bytes
1332             while ((buf[--len] & 0xc0) == 0x80 && len > 0) {}
1333             buf[len] = '\0';
1334             res |= MU_RES_CHANGE;
1335         }
1336         
1337         // handle return
1338         if (ctx.key_pressed & MU_KEY_RETURN)
1339         {
1340             mu_set_focus(ctx, 0);
1341             res |= MU_RES_SUBMIT;
1342         }
1343     }
1344 
1345     // draw
1346     mu_draw_control_frame(ctx, id, r, MU_COLOR_BASE, opt);
1347     if (ctx.focus == id)
1348     {
1349         mu_Color color = ctx.style.colors[MU_COLOR_TEXT];
1350         mu_Font font = ctx.style.font;
1351         int textw = ctx.text_width(font, buf, -1);
1352         int texth = ctx.text_height(font);
1353         int ofx = r.w - ctx.style.padding - textw - 1;
1354         int textx = r.x + mu_min(ofx, ctx.style.padding);
1355         int texty = r.y + (r.h - texth) / 2;
1356         mu_push_clip_rect(ctx, r);
1357         mu_draw_text(ctx, font, buf, -1, mu_Vec2(textx, texty), color);
1358         mu_draw_rect(ctx, mu_Rect(textx + textw, texty, 1, texth), color);
1359         mu_pop_clip_rect(ctx);
1360     }
1361     else
1362     {
1363         mu_draw_control_text(ctx, buf, r, MU_COLOR_TEXT, opt);
1364     }
1365 
1366     return res;
1367 }
1368 
1369 int mu_number_textbox(mu_Context* ctx, mu_Real* value, mu_Rect r, mu_Id id)
1370 {
1371     if (ctx.mouse_pressed == MU_MOUSE_LEFT &&
1372         ctx.key_down & MU_KEY_SHIFT &&
1373         ctx.hover == id)
1374     {
1375         ctx.number_edit = id;
1376         sprintf(ctx.number_edit_buf.ptr, MU_REAL_FMT, *value);
1377     }
1378     
1379     if (ctx.number_edit == id)
1380     {
1381         int res = mu_textbox_raw(
1382             ctx, ctx.number_edit_buf.ptr, (ctx.number_edit_buf).sizeof, id, r, 0);
1383         if (res & MU_RES_SUBMIT || ctx.focus != id)
1384         {
1385             *value = strtod(ctx.number_edit_buf.ptr, null);
1386             ctx.number_edit = 0;
1387         }
1388         else
1389         {
1390             return 1;
1391         }
1392     }
1393     
1394     return 0;
1395 }
1396 
1397 int mu_textbox_ex(mu_Context* ctx, char* buf, int bufsz, int opt)
1398 {
1399     mu_Id id = mu_get_id(ctx, &buf, buf.sizeof);
1400     mu_Rect r = mu_layout_next(ctx);
1401     return mu_textbox_raw(ctx, buf, bufsz, id, r, opt);
1402 }
1403 
1404 int mu_slider_ex(mu_Context* ctx, mu_Real* value, mu_Real low, mu_Real high,
1405     mu_Real step, const(char)* fmt, int opt)
1406 {
1407     int res = 0;
1408     mu_Real last = *value, v = last;
1409     mu_Id id = mu_get_id(ctx, &value, value.sizeof); // out of a pointer? not mu_Real.sizeof?
1410     mu_Rect base = mu_layout_next(ctx);
1411 
1412     // handle text input mode
1413     if (mu_number_textbox(ctx, &v, base, id))
1414     {
1415         return res;
1416     }
1417 
1418     // handle normal mode
1419     mu_update_control(ctx, id, base, opt);
1420 
1421     // handle input
1422     if (ctx.focus == id &&
1423         (ctx.mouse_down | ctx.mouse_pressed) == MU_MOUSE_LEFT)
1424     {
1425         v = low + (ctx.mouse_pos.x - base.x) * (high - low) / base.w;
1426         if (step)
1427         {
1428             v = ((v + step / 2) / step) * step;
1429         }
1430     }
1431     
1432     // clamp and store value, update res
1433     *value = v = mu_clamp(v, low, high);
1434     if (last != v)
1435     {
1436         res |= MU_RES_CHANGE;
1437     }
1438 
1439     // draw base
1440     mu_draw_control_frame(ctx, id, base, MU_COLOR_BASE, opt);
1441 
1442     // draw thumb
1443     int w = ctx.style.thumb_size;
1444     int x = cast(int)((v - low) * (base.w - w) / (high - low)); //TODO: fix float to int
1445     mu_Rect thumb = mu_Rect(base.x + x, base.y, w, base.h);
1446     mu_draw_control_frame(ctx, id, thumb, MU_COLOR_BUTTON, opt);
1447 
1448     // draw text
1449     char[MU_MAX_FMT] buf = void;
1450     sprintf(buf.ptr, fmt, v);
1451     mu_draw_control_text(ctx, buf.ptr, base, MU_COLOR_TEXT, opt);
1452 
1453     return res;
1454 }
1455 
1456 int mu_number_ex(mu_Context* ctx, mu_Real* value, mu_Real step,
1457     const(char)* fmt, int opt)
1458 {
1459     char[MU_MAX_FMT] buf = void;
1460     int res = 0;
1461     mu_Id id = mu_get_id(ctx, &value, value.sizeof); // sizeof(value)
1462     mu_Rect base = mu_layout_next(ctx);
1463     mu_Real last = *value;
1464 
1465     // handle text input mode
1466     if (mu_number_textbox(ctx, value, base, id))
1467     {
1468         return res;
1469     }
1470 
1471     // handle normal mode
1472     mu_update_control(ctx, id, base, opt);
1473 
1474     // handle input
1475     if (ctx.focus == id && ctx.mouse_down == MU_MOUSE_LEFT)
1476     {
1477         *value += ctx.mouse_delta.x * step;
1478     }
1479     // set flag if value changed
1480     if (*value != last)
1481     {
1482         res |= MU_RES_CHANGE;
1483     }
1484 
1485     // draw base
1486     mu_draw_control_frame(ctx, id, base, MU_COLOR_BASE, opt);
1487     // draw text
1488     sprintf(buf.ptr, fmt, *value);
1489     mu_draw_control_text(ctx, buf.ptr, base, MU_COLOR_TEXT, opt);
1490 
1491     return res;
1492 }
1493 
1494 int mu_header2(mu_Context* ctx, const(char)* label, int istreenode, int opt)
1495 {
1496     mu_Rect r;
1497     int active, expanded;
1498     mu_Id id = mu_get_id(ctx, label, cast(int)strlen(label));
1499     int idx = mu_pool_get(ctx, ctx.treenode_pool.ptr, MU_TREENODEPOOL_SIZE, id);
1500     int width = -1;
1501     mu_layout_row(ctx, 1, &width, 0);
1502 
1503     active = (idx >= 0);
1504     expanded = (opt & MU_OPT_EXPANDED) ? !active : active;
1505     r = mu_layout_next(ctx);
1506     mu_update_control(ctx, id, r, 0);
1507 
1508     // handle click
1509     active ^= (ctx.mouse_pressed == MU_MOUSE_LEFT && ctx.focus == id);
1510 
1511     // update pool ref
1512     if (idx >= 0)
1513     {
1514         if (active)
1515         {
1516             mu_pool_update(ctx, ctx.treenode_pool.ptr, idx);
1517         }
1518         else
1519         {
1520             memset(&ctx.treenode_pool[idx], 0, mu_PoolItem.sizeof);
1521         }
1522     }
1523     else if (active)
1524     {
1525         mu_pool_init(ctx, ctx.treenode_pool.ptr, MU_TREENODEPOOL_SIZE, id);
1526     }
1527 
1528     // draw
1529     if (istreenode)
1530     {
1531         if (ctx.hover == id)
1532         {
1533             ctx.mu_draw_frame(ctx, r, MU_COLOR_BUTTONHOVER);
1534         }
1535     }
1536     else
1537     {
1538         mu_draw_control_frame(ctx, id, r, MU_COLOR_BUTTON, 0);
1539     }
1540     mu_draw_icon(
1541         ctx, expanded ? MU_ICON_EXPANDED : MU_ICON_COLLAPSED,
1542         mu_Rect(r.x, r.y, r.h, r.h), ctx.style.colors[MU_COLOR_TEXT]);
1543     r.x += r.h - ctx.style.padding;
1544     r.w -= r.h - ctx.style.padding;
1545     mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, 0);
1546 
1547     return expanded ? MU_RES_ACTIVE : 0;
1548 }
1549 
1550 int mu_header_ex(mu_Context* ctx, const(char)* label, int opt)
1551 {
1552     return mu_header2(ctx, label, 0, opt);
1553 }
1554 
1555 int mu_begin_treenode_ex(mu_Context* ctx, const(char)* label, int opt)
1556 {
1557     int res = mu_header2(ctx, label, 1, opt);
1558     if (res & MU_RES_ACTIVE)
1559     {
1560         mu_get_layout(ctx).indent += ctx.style.indent;
1561         ctx.id_stack.push(ctx.last_id);
1562     }
1563     return res;
1564 }
1565 
1566 void mu_end_treenode(mu_Context* ctx)
1567 {
1568     mu_get_layout(ctx).indent -= ctx.style.indent;
1569     mu_pop_id(ctx);
1570 }
1571 
1572 private
1573 void mu_scrollbar(mu_Context* ctx, mu_Container* cnt,
1574     mu_Rect* b, ref mu_Vec2 cs, const(char)* name)
1575 {
1576     // only add scrollbar if content size is larger than body_
1577     int maxscroll = cs.y - b.h;
1578     if (maxscroll > 0 && b.h > 0)
1579     {
1580         mu_Rect base, thumb;
1581         // "!scrollbar" #y and "!scrollbar" #x
1582         mu_Id id = mu_get_id(ctx, name, 11);
1583 
1584         // get sizing / positioning
1585         base = *b;
1586         base.x = b.x + b.w;
1587         base.w = ctx.style.scrollbar_size;
1588 
1589         // handle input
1590         mu_update_control(ctx, id, base, 0);
1591         if (ctx.focus == id && ctx.mouse_down == MU_MOUSE_LEFT)
1592         {
1593             cnt.scroll.y += ctx.mouse_delta.y * cs.y / base.h;
1594         }
1595 
1596         // clamp scroll to limits
1597         cnt.scroll.y = mu_clamp(cnt.scroll.y, 0, maxscroll);
1598 
1599         // draw base and thumb
1600         ctx.mu_draw_frame(ctx, base, MU_COLOR_SCROLLBASE);
1601         thumb = base;
1602         thumb.h = mu_max(ctx.style.thumb_size, base.h * b.h / cs.y);
1603         thumb.y += cnt.scroll.y * (base.h - thumb.h) / maxscroll;
1604         ctx.mu_draw_frame(ctx, thumb, MU_COLOR_SCROLLTHUMB);
1605 
1606         // set this as the scroll_target (will get scrolled on mousewheel)
1607         // if the mouse is over it
1608         if (mu_mouse_over(ctx, *b))
1609         {
1610             ctx.scroll_target = cnt;
1611         }
1612     }
1613     else
1614     {
1615         cnt.scroll.y = 0;
1616     }
1617 }
1618 
1619 void mu_scrollbars(mu_Context* ctx, mu_Container* cnt, mu_Rect* body_)
1620 {
1621     int sz = ctx.style.scrollbar_size;
1622     mu_Vec2 cs = cnt.content_size;
1623     cs.x += ctx.style.padding * 2;
1624     cs.y += ctx.style.padding * 2;
1625     mu_push_clip_rect(ctx, *body_);
1626     // resize body_ to make room for scrollbars
1627     if (cs.y > cnt.body_.h)
1628     {
1629         body_.w -= sz;
1630     }
1631     if (cs.x > cnt.body_.w)
1632     {
1633         body_.h -= sz;
1634     }
1635     // to create a horizontal or vertical scrollbar almost-identical code is
1636     // used; only the references to `x|y` `w|h` need to be switched
1637     mu_scrollbar(ctx, cnt, body_, cs, "!scrollbarx");
1638     mu_scrollbar(ctx, cnt, body_, cs, "!scrollbary");
1639     mu_pop_clip_rect(ctx);
1640 }
1641 
1642 void mu_push_container_body(mu_Context* ctx, mu_Container* cnt, mu_Rect body_, int opt)
1643 {
1644     if (~opt & MU_OPT_NOSCROLL)
1645     {
1646         mu_scrollbars(ctx, cnt, &body_);
1647     }
1648     mu_push_layout(ctx, mu_expand_rect(body_, -ctx.style.padding), cnt.scroll);
1649     cnt.body_ = body_;
1650 }
1651 
1652 void mu_begin_root_container(mu_Context* ctx, mu_Container* cnt)
1653 {
1654     ctx.container_stack.push(cnt);
1655     // push container to roots list and push head command
1656     ctx.root_list.push(cnt);
1657     cnt.head_idx = mu_push_jump(ctx, -1);
1658     // set as hover root if the mouse is overlapping this container and it has a
1659     // higher zindex than the current hover root
1660     if (rect_overlaps_vec2(cnt.rect, ctx.mouse_pos) &&
1661         (!ctx.next_hover_root || cnt.zindex > ctx.next_hover_root.zindex))
1662     {
1663         ctx.next_hover_root = cnt;
1664     }
1665     // clipping is reset here in case a root-container is made within
1666     // another root-containers's begin/end block; this prevents the inner
1667     // root-container being clipped to the outer
1668     ctx.clip_stack.push(unclipped_rect);
1669 }
1670 
1671 void mu_end_root_container(mu_Context* ctx)
1672 {
1673     // push tail 'goto' jump command and set head 'skip' command. the final steps
1674     // on initing these are done in mu_end()
1675     mu_Container* cnt = mu_get_current_container(ctx);
1676     cnt.tail_idx = mu_push_jump(ctx, -1);
1677     ctx.command_list.items[cnt.head_idx].jump.dst_idx = cast(int)ctx.command_list.idx;
1678     // pop base clip rect and container
1679     mu_pop_clip_rect(ctx);
1680     mu_pop_container(ctx);
1681 }
1682 
1683 int mu_begin_window_ex(mu_Context* ctx, const(char)* title, mu_Rect rect, int opt)
1684 {
1685     mu_Rect body_;
1686     mu_Id id = mu_get_id(ctx, title, cast(int)strlen(title));
1687     mu_Container* cnt = mu_get_container2(ctx, id, opt);
1688     if (!cnt || !cnt.open)
1689     {
1690         return 0;
1691     }
1692     ctx.id_stack.push(id);
1693 
1694     if (cnt.rect.w == 0)
1695     {
1696         cnt.rect = rect;
1697     }
1698     mu_begin_root_container(ctx, cnt);
1699     rect = body_ = cnt.rect;
1700 
1701     // draw frame
1702     if (~opt & MU_OPT_NOFRAME)
1703     {
1704         ctx.mu_draw_frame(ctx, rect, MU_COLOR_WINDOWBG);
1705     }
1706 
1707     // do title bar
1708     if (~opt & MU_OPT_NOTITLE)
1709     {
1710         mu_Rect tr = rect;
1711         tr.h = ctx.style.title_height;
1712         ctx.mu_draw_frame(ctx, tr, MU_COLOR_TITLEBG);
1713 
1714         // do title text
1715         if (~opt & MU_OPT_NOTITLE)
1716         {
1717             mu_Id id3 = mu_get_id(ctx, cast(const(char)*)"!title", 6);
1718             mu_update_control(ctx, id3, tr, opt);
1719             mu_draw_control_text(ctx, title, tr, MU_COLOR_TITLETEXT, opt);
1720             if (id3 == ctx.focus && ctx.mouse_down == MU_MOUSE_LEFT)
1721             {
1722                 cnt.rect.x += ctx.mouse_delta.x;
1723                 cnt.rect.y += ctx.mouse_delta.y;
1724             }
1725             body_.y += tr.h;
1726             body_.h -= tr.h;
1727         }
1728 
1729         // do `close` button
1730         if (~opt & MU_OPT_NOCLOSE)
1731         {
1732             mu_Id id1 = mu_get_id(ctx, cast(const(char)*)"!close", 6);
1733             mu_Rect r = mu_Rect(tr.x + tr.w - tr.h, tr.y, tr.h, tr.h);
1734             tr.w -= r.w;
1735             mu_draw_icon(ctx, MU_ICON_CLOSE, r, ctx.style.colors[MU_COLOR_TITLETEXT]);
1736             mu_update_control(ctx, id1, r, opt);
1737             if (ctx.mouse_pressed == MU_MOUSE_LEFT && id1 == ctx.focus)
1738             {
1739                 cnt.open = 0;
1740             }
1741         }
1742     }
1743 
1744     mu_push_container_body(ctx, cnt, body_, opt);
1745 
1746     // do `resize` handle
1747     if (~opt & MU_OPT_NORESIZE)
1748     {
1749         int sz = ctx.style.title_height;
1750         mu_Id id2 = mu_get_id(ctx, cast(const(char)*)"!resize", 7);
1751         mu_Rect r = mu_Rect(rect.x + rect.w - sz, rect.y + rect.h - sz, sz, sz);
1752         mu_update_control(ctx, id2, r, opt);
1753         if (id2 == ctx.focus && ctx.mouse_down == MU_MOUSE_LEFT)
1754         {
1755             cnt.rect.w = mu_max(96, cnt.rect.w + ctx.mouse_delta.x);
1756             cnt.rect.h = mu_max(64, cnt.rect.h + ctx.mouse_delta.y);
1757         }
1758     }
1759 
1760     // resize to content size
1761     if (opt & MU_OPT_AUTOSIZE)
1762     {
1763         mu_Rect r = mu_get_layout(ctx).body_;
1764         cnt.rect.w = cnt.content_size.x + (cnt.rect.w - r.w);
1765         cnt.rect.h = cnt.content_size.y + (cnt.rect.h - r.h);
1766     }
1767 
1768     // close if this is a popup window and elsewhere was clicked
1769     if (opt & MU_OPT_POPUP && ctx.mouse_pressed && ctx.hover_root != cnt)
1770     {
1771         cnt.open = 0;
1772     }
1773 
1774     mu_push_clip_rect(ctx, cnt.body_);
1775     return MU_RES_ACTIVE;
1776 }
1777 
1778 void mu_end_window(mu_Context* ctx)
1779 {
1780     mu_pop_clip_rect(ctx);
1781     mu_end_root_container(ctx);
1782 }
1783 
1784 void mu_open_popup(mu_Context* ctx, const(char)* name)
1785 {
1786     mu_Container* cnt = mu_get_container(ctx, name);
1787     // set as hover root so popup isn't closed in begin_window_ex()
1788     ctx.hover_root = ctx.next_hover_root = cnt;
1789     // position at mouse cursor, open and bring-to-front
1790     cnt.rect = mu_Rect(ctx.mouse_pos.x, ctx.mouse_pos.y, 1, 1);
1791     cnt.open = 1;
1792     mu_bring_to_front(ctx, cnt);
1793 }
1794 
1795 int mu_begin_popup(mu_Context* ctx, const(char)* name)
1796 {
1797     return mu_begin_window_ex(ctx, name, mu_Rect(0, 0, 0, 0),
1798         MU_OPT_POPUP | MU_OPT_AUTOSIZE | MU_OPT_NORESIZE |
1799         MU_OPT_NOSCROLL | MU_OPT_NOTITLE | MU_OPT_CLOSED);
1800 }
1801 
1802 void mu_end_popup(mu_Context* ctx)
1803 {
1804     mu_end_window(ctx);
1805 }
1806 
1807 mu_Container* mu_begin_panel_ex(mu_Context* ctx, const(char)* name, int opt)
1808 {
1809     mu_Container* cnt;
1810     mu_push_id(ctx, name, cast(int)strlen(name));
1811     cnt = mu_get_container2(ctx, ctx.last_id, opt);
1812     cnt.rect = mu_layout_next(ctx);
1813     if (~opt & MU_OPT_NOFRAME)
1814     {
1815         ctx.mu_draw_frame(ctx, cnt.rect, MU_COLOR_PANELBG);
1816     }
1817     ctx.container_stack.push(cnt);
1818     mu_push_container_body(ctx, cnt, cnt.rect, opt);
1819     mu_push_clip_rect(ctx, cnt.body_);
1820     return cnt;
1821 }
1822 
1823 void mu_end_panel(mu_Context* ctx)
1824 {
1825     mu_pop_clip_rect(ctx);
1826     mu_pop_container(ctx);
1827 }