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 }