1 /* 2 * Copyright (c) 2009-2010 Mikko Mononen memon@inside.org 3 * 4 * This software is provided 'as-is', without any express or implied 5 * warranty. In no event will the authors be held liable for any damages 6 * arising from the use of this software. 7 * Permission is granted to anyone to use this software for any purpose, 8 * including commercial applications, and to alter it and redistribute it 9 * freely, subject to the following restrictions: 10 * 1. The origin of this software must not be misrepresented; you must not 11 * claim that you wrote the original software. If you use this software 12 * in a product, an acknowledgment in the product documentation would be 13 * appreciated but is not required. 14 * 2. Altered source versions must be plainly marked as such, and must not be 15 * misrepresented as being the original software. 16 * 3. This notice may not be removed or altered from any source distribution. 17 */ 18 module imgui.api; 19 20 /** 21 imgui is an immediate mode GUI. See also: 22 http://sol.gfxile.net/imgui/ 23 24 This module contains the API of the library. 25 */ 26 27 import std.algorithm; 28 import std.math; 29 import std.stdio; 30 import std.string; 31 import std.range; 32 33 import imgui.engine; 34 import imgui.gl3_renderer; 35 /* import imgui.util; */ 36 37 // todo: opApply to allow changing brightness on all colors. 38 // todo: check CairoD samples for brightness settingsroutines. 39 40 /** A color scheme contains all the configurable GUI element colors. */ 41 struct ColorScheme 42 { 43 /** 44 Return a range of all colors. This gives you ref access, 45 which means you can modify the values. 46 */ 47 auto walkColors() 48 { 49 return chain( 50 (&generic.text).only, 51 (&generic.line).only, 52 (&generic.rect).only, 53 (&generic.roundRect).only, 54 (&scroll.area.back).only, 55 (&scroll.area.text).only, 56 (&scroll.bar.back).only, 57 (&scroll.bar.thumb).only, 58 (&scroll.bar.thumbHover).only, 59 (&scroll.bar.thumbPress).only, 60 (&button.text).only, 61 (&button.textHover).only, 62 (&button.textDisabled).only, 63 (&button.back).only, 64 (&button.backPress).only, 65 (&checkbox.back).only, 66 (&checkbox.press).only, 67 (&checkbox.checked).only, 68 (&checkbox.doUncheck).only, 69 (&checkbox.disabledChecked).only, 70 (&checkbox.text).only, 71 (&checkbox.textHover).only, 72 (&checkbox.textDisabled).only, 73 (&item.hover).only, 74 (&item.press).only, 75 (&item.text).only, 76 (&item.textDisabled).only, 77 (&collapse.shown).only, 78 (&collapse.hidden).only, 79 (&collapse.doShow).only, 80 (&collapse.doHide).only, 81 (&collapse.textHover).only, 82 (&collapse.text).only, 83 (&collapse.textDisabled).only, 84 (&collapse.subtext).only, 85 (&label.text).only, 86 (&value.text).only, 87 (&slider.back).only, 88 (&slider.thumb).only, 89 (&slider.thumbHover).only, 90 (&slider.thumbPress).only, 91 (&slider.text).only, 92 (&slider.textHover).only, 93 (&slider.textDisabled).only, 94 (&slider.value).only, 95 (&slider.valueHover).only, 96 (&slider.valueDisabled).only, 97 (&separator).only); 98 } 99 100 /// 101 static struct Generic 102 { 103 RGBA text; /// Used by imguiDrawText. 104 RGBA line; /// Used by imguiDrawLine. 105 RGBA rect; /// Used by imguiDrawRect. 106 RGBA roundRect; /// Used by imguiDrawRoundedRect. 107 } 108 109 /// 110 static struct Scroll 111 { 112 /// 113 static struct Area 114 { 115 RGBA back = RGBA(0, 0, 0, 192); 116 RGBA text = RGBA(255, 255, 255, 128); 117 } 118 119 /// 120 static struct Bar 121 { 122 RGBA back = RGBA(0, 0, 0, 196); 123 RGBA thumb = RGBA(255, 255, 255, 64); 124 RGBA thumbHover = RGBA(255, 196, 0, 96); 125 RGBA thumbPress = RGBA(255, 196, 0, 196); 126 } 127 128 Area area; /// 129 Bar bar; /// 130 } 131 132 /// 133 static struct Button 134 { 135 RGBA text = RGBA(255, 255, 255, 200); 136 RGBA textHover = RGBA(255, 196, 0, 255); 137 RGBA textDisabled = RGBA(128, 128, 128, 200); 138 RGBA back = RGBA(128, 128, 128, 96); 139 RGBA backPress = RGBA(128, 128, 128, 196); 140 } 141 142 /// 143 static struct TextInput 144 { 145 RGBA label = RGBA(255, 255, 255, 255); 146 RGBA text = RGBA(0, 0, 0, 255); 147 RGBA textDisabled = RGBA(255, 255, 255, 255); 148 RGBA back = RGBA(255, 196, 0, 255); 149 RGBA backDisabled = RGBA(128, 128, 128, 96); 150 } 151 152 /// 153 static struct Checkbox 154 { 155 /// Checkbox background. 156 RGBA back = RGBA(128, 128, 128, 96); 157 158 /// Checkbox background when it's pressed. 159 RGBA press = RGBA(128, 128, 128, 196); 160 161 /// An enabled and checked checkbox. 162 RGBA checked = RGBA(255, 255, 255, 255); 163 164 /// An enabled and checked checkbox which was just pressed to be disabled. 165 RGBA doUncheck = RGBA(255, 255, 255, 200); 166 167 /// A disabled but checked checkbox. 168 RGBA disabledChecked = RGBA(128, 128, 128, 200); 169 170 /// Label color of the checkbox. 171 RGBA text = RGBA(255, 255, 255, 200); 172 173 /// Label color of a hovered checkbox. 174 RGBA textHover = RGBA(255, 196, 0, 255); 175 176 /// Label color of an disabled checkbox. 177 RGBA textDisabled = RGBA(128, 128, 128, 200); 178 } 179 180 /// 181 static struct Item 182 { 183 RGBA hover = RGBA(255, 196, 0, 96); 184 RGBA press = RGBA(255, 196, 0, 196); 185 RGBA text = RGBA(255, 255, 255, 200); 186 RGBA textDisabled = RGBA(128, 128, 128, 200); 187 } 188 189 /// 190 static struct Collapse 191 { 192 RGBA shown = RGBA(255, 255, 255, 200); 193 RGBA hidden = RGBA(255, 255, 255, 200); 194 195 RGBA doShow = RGBA(255, 255, 255, 255); 196 RGBA doHide = RGBA(255, 255, 255, 255); 197 198 RGBA text = RGBA(255, 255, 255, 200); 199 RGBA textHover = RGBA(255, 196, 0, 255); 200 RGBA textDisabled = RGBA(128, 128, 128, 200); 201 202 RGBA subtext = RGBA(255, 255, 255, 128); 203 } 204 205 /// 206 static struct Label 207 { 208 RGBA text = RGBA(255, 255, 255, 255); 209 } 210 211 /// 212 static struct Value 213 { 214 RGBA text = RGBA(255, 255, 255, 200); 215 } 216 217 /// 218 static struct Slider 219 { 220 RGBA back = RGBA(0, 0, 0, 128); 221 RGBA thumb = RGBA(255, 255, 255, 64); 222 RGBA thumbHover = RGBA(255, 196, 0, 128); 223 RGBA thumbPress = RGBA(255, 255, 255, 255); 224 225 RGBA text = RGBA(255, 255, 255, 200); 226 RGBA textHover = RGBA(255, 196, 0, 255); 227 RGBA textDisabled = RGBA(128, 128, 128, 200); 228 229 RGBA value = RGBA(255, 255, 255, 200); 230 RGBA valueHover = RGBA(255, 196, 0, 255); 231 RGBA valueDisabled = RGBA(128, 128, 128, 200); 232 } 233 234 /// Colors for the generic imguiDraw* functions. 235 Generic generic; 236 237 /// Colors for the scrollable area. 238 Scroll scroll; 239 240 /// Colors for button elements. 241 Button button; 242 243 /// Colors for text input elements. 244 TextInput textInput; 245 246 /// Colors for checkbox elements. 247 Checkbox checkbox; 248 249 /// Colors for item elements. 250 Item item; 251 252 /// Colors for collapse elements. 253 Collapse collapse; 254 255 /// Colors for label elements. 256 Label label; 257 258 /// Colors for value elements. 259 Value value; 260 261 /// Colors for slider elements. 262 Slider slider; 263 264 /// Color for the separator line. 265 RGBA separator = RGBA(255, 255, 255, 32); 266 } 267 268 /** 269 The current default color scheme. 270 271 You can configure this scheme, it will be used by 272 default by GUI element creation functions unless 273 you explicitly pass a custom color scheme. 274 */ 275 __gshared ColorScheme defaultColorScheme; 276 277 /// 278 struct RGBA 279 { 280 ubyte r; 281 ubyte g; 282 ubyte b; 283 ubyte a = 255; 284 285 RGBA opBinary(string op)(RGBA rgba) 286 { 287 RGBA res = this; 288 289 mixin("res.r = cast(ubyte)res.r " ~ op ~ " rgba.r;"); 290 mixin("res.g = cast(ubyte)res.g " ~ op ~ " rgba.g;"); 291 mixin("res.b = cast(ubyte)res.b " ~ op ~ " rgba.b;"); 292 mixin("res.a = cast(ubyte)res.a " ~ op ~ " rgba.a;"); 293 294 return res; 295 } 296 } 297 298 /// 299 enum TextAlign 300 { 301 left, 302 center, 303 right, 304 } 305 306 /** The possible mouse buttons. These can be used as bitflags. */ 307 enum MouseButton : ubyte 308 { 309 left = 0x01, 310 right = 0x02, 311 } 312 313 /// 314 enum Enabled : bool 315 { 316 no, 317 yes, 318 } 319 320 /** Initialize the imgui library. 321 322 Params: 323 324 fontPath = Path to a TrueType font file to use to draw text. 325 fontTextureSize = Size of the texture to store font glyphs in. The actual texture 326 size is a square of this value. 327 328 A bigger texture allows to draw more Unicode characters (if the 329 font supports them). 256 (62.5kiB) should be enough for ASCII, 330 1024 (1MB) should be enough for most European scripts. 331 332 Returns: True on success, false on failure. 333 */ 334 bool imguiInit(const(char)[] fontPath, uint fontTextureSize = 1024) 335 { 336 return imguiRenderGLInit(fontPath, fontTextureSize); 337 } 338 339 /** Destroy the imgui library. */ 340 void imguiDestroy() 341 { 342 imguiRenderGLDestroy(); 343 } 344 345 /** 346 Begin a new frame. All batched commands after the call to 347 $(D imguiBeginFrame) will be rendered as a single frame once 348 $(D imguiRender) is called. 349 350 Note: You should call $(D imguiEndFrame) after batching all 351 commands to reset the input handling for the next frame. 352 353 Example: 354 ----- 355 int cursorX, cursorY; 356 ubyte mouseButtons; 357 int mouseScroll; 358 359 /// start a new batch of commands for this frame (the batched commands) 360 imguiBeginFrame(cursorX, cursorY, mouseButtons, mouseScroll); 361 362 /// define your UI elements here 363 imguiLabel("some text here"); 364 365 /// end the frame (this just resets the input control state, e.g. mouse button states) 366 imguiEndFrame(); 367 368 /// now render the batched commands 369 imguiRender(); 370 ----- 371 372 Params: 373 374 cursorX = The cursor's last X position. 375 cursorY = The cursor's last Y position. 376 mouseButtons = The last mouse buttons pressed (a value or a combination of values of a $(D MouseButton)). 377 mouseScroll = The last scroll value emitted by the mouse. 378 unicodeChar = Unicode text input from the keyboard (usually the unicode result of last keypress). 379 '0' means 'no text input'. Note that for text input to work, even Enter 380 and backspace must be passed (encoded as 0x0D and 0x08, respectively), 381 which may not be automatically handled by your input library's text 382 input functionality (e.g. GLFW's getUnicode() does not do this). 383 */ 384 void imguiBeginFrame(int cursorX, int cursorY, ubyte mouseButtons, int mouseScroll, 385 dchar unicodeChar = 0) 386 { 387 updateInput(cursorX, cursorY, mouseButtons, mouseScroll, unicodeChar); 388 389 g_state.hot = g_state.hotToBe; 390 g_state.hotToBe = 0; 391 392 g_state.wentActive = false; 393 g_state.isActive = false; 394 g_state.isHot = false; 395 396 g_state.widgetX = 0; 397 g_state.widgetY = 0; 398 g_state.widgetW = 0; 399 400 g_state.inScrollArea = false; 401 g_state.areaId = 1; 402 g_state.widgetId = 1; 403 404 resetGfxCmdQueue(); 405 } 406 407 /** End the list of batched commands for the current frame. */ 408 void imguiEndFrame() 409 { 410 clearInput(); 411 } 412 413 /** Render all of the batched commands for the current frame. */ 414 void imguiRender(int width, int height) 415 { 416 with(g_state.scrollArea[0].clipRect) 417 { 418 x = cast(short) 0; 419 y = cast(short) 0; 420 w = cast(short) width; 421 h = cast(short) height; 422 r = 0; 423 } 424 425 imguiRenderGLDraw(width, height); 426 } 427 428 /** 429 Begin the definition of a new scrollable area. 430 431 Once elements within the scrollable area are defined 432 you must call $(D imguiEndScrollArea) to end the definition. 433 434 Params: 435 436 title = The title that will be displayed for this scroll area. 437 xPos = The X position of the scroll area. 438 yPos = The Y position of the scroll area. 439 width = The width of the scroll area. 440 height = The height of the scroll area. 441 scroll = A pointer to a variable which will hold the current scroll value of the widget. 442 colorScheme = Optionally override the current default color scheme when creating this element. 443 444 Returns: 445 446 $(D true) if the mouse was located inside the scrollable area. 447 */ 448 bool imguiBeginScrollArea(const(char)[] title, int xPos, int yPos, int width, int height, int* scroll, const ref ColorScheme colorScheme = defaultColorScheme) 449 { 450 g_state.inScrollArea = true; 451 g_state.areaId++; 452 with(g_state.scrollArea[g_state.areaId].clipRect) 453 { 454 x = cast(short) xPos; 455 y = cast(short) yPos; 456 w = cast(short) width; 457 h = cast(short) height; 458 r = 0; 459 } 460 g_state.widgetId = 0; 461 g_scrollId = (g_state.areaId << 16) | g_state.widgetId; 462 463 g_state.widgetX = xPos + SCROLL_AREA_PADDING; 464 g_state.widgetY = yPos + height - AREA_HEADER + (*scroll); 465 g_state.widgetW = width - SCROLL_AREA_PADDING * 4; 466 g_scrollTop = yPos - AREA_HEADER + height; 467 g_scrollBottom = yPos + SCROLL_AREA_PADDING; 468 g_scrollRight = xPos + width - SCROLL_AREA_PADDING * 3; 469 g_scrollVal = scroll; 470 471 g_scrollAreaTop = g_state.widgetY; 472 473 g_focusTop = yPos - AREA_HEADER; 474 g_focusBottom = yPos - AREA_HEADER + height; 475 476 g_insideScrollArea = inRect(xPos, yPos, width, height, false); 477 g_state.insideCurrentScroll = g_insideScrollArea; 478 479 addGfxCmdRoundedRect(cast(float)xPos, cast(float)yPos, cast(float)width, cast(float)height, 6, colorScheme.scroll.area.back); 480 481 addGfxCmdText(xPos + AREA_HEADER / 2, yPos + height - AREA_HEADER / 2 - TEXT_HEIGHT / 2, TextAlign.left, title, colorScheme.scroll.area.text); 482 483 // The max() ensures we never have zero- or negative-sized scissor rectangle when the window is very small, 484 // avoiding a segfault. 485 addGfxCmdScissor(xPos + SCROLL_AREA_PADDING, 486 yPos + SCROLL_AREA_PADDING, 487 max(1, width - SCROLL_AREA_PADDING * 4), 488 max(1, height - AREA_HEADER - SCROLL_AREA_PADDING)); 489 490 return g_insideScrollArea; 491 } 492 493 /** 494 End the definition of the last scrollable element. 495 496 Params: 497 498 colorScheme = Optionally override the current default color scheme when creating this element. 499 */ 500 void imguiEndScrollArea(const ref ColorScheme colorScheme = defaultColorScheme) 501 { 502 scope(exit) { g_state.inScrollArea = false; } 503 // Disable scissoring. 504 addGfxCmdScissor(-1, -1, -1, -1); 505 506 // Draw scroll bar 507 int x = g_scrollRight + SCROLL_AREA_PADDING / 2; 508 int y = g_scrollBottom; 509 int w = SCROLL_AREA_PADDING * 2; 510 int h = g_scrollTop - g_scrollBottom; 511 512 int stop = g_scrollAreaTop; 513 int sbot = g_state.widgetY; 514 int sh = stop - sbot; // The scrollable area height. 515 516 float barHeight = cast(float)h / cast(float)sh; 517 518 if (barHeight < 1) 519 { 520 float barY = cast(float)(y - sbot) / cast(float)sh; 521 522 if (barY < 0) 523 barY = 0; 524 525 if (barY > 1) 526 barY = 1; 527 528 // Handle scroll bar logic. 529 uint hid = g_scrollId; 530 int hx = x; 531 int hy = y + cast(int)(barY * h); 532 int hw = w; 533 int hh = cast(int)(barHeight * h); 534 535 const int range = h - (hh - 1); 536 bool over = inRect(hx, hy, hw, hh); 537 buttonLogic(hid, over); 538 539 if (isActive(hid)) 540 { 541 float u = cast(float)(hy - y) / cast(float)range; 542 543 if (g_state.wentActive) 544 { 545 g_state.dragY = g_state.my; 546 g_state.dragOrig = u; 547 } 548 549 if (g_state.dragY != g_state.my) 550 { 551 u = g_state.dragOrig + (g_state.my - g_state.dragY) / cast(float)range; 552 553 if (u < 0) 554 u = 0; 555 556 if (u > 1) 557 u = 1; 558 *g_scrollVal = cast(int)((1 - u) * (sh - h)); 559 } 560 } 561 562 // BG 563 addGfxCmdRoundedRect(cast(float)x, cast(float)y, cast(float)w, cast(float)h, cast(float)w / 2 - 1, colorScheme.scroll.bar.back); 564 565 // Bar 566 if (isActive(hid)) 567 addGfxCmdRoundedRect(cast(float)hx, cast(float)hy, cast(float)hw, cast(float)hh, cast(float)w / 2 - 1, colorScheme.scroll.bar.thumbPress); 568 else 569 addGfxCmdRoundedRect(cast(float)hx, cast(float)hy, cast(float)hw, cast(float)hh, cast(float)w / 2 - 1, isHot(hid) ? colorScheme.scroll.bar.thumbHover : colorScheme.scroll.bar.thumb); 570 571 // Handle mouse scrolling. 572 if (g_insideScrollArea) // && !anyActive()) 573 { 574 if (g_state.scroll) 575 { 576 *g_scrollVal += 20 * g_state.scroll; 577 578 if (*g_scrollVal < 0) 579 *g_scrollVal = 0; 580 581 if (*g_scrollVal > (sh - h)) 582 *g_scrollVal = (sh - h); 583 } 584 } 585 } 586 g_state.insideCurrentScroll = false; 587 } 588 589 /** 590 Define a new button. 591 592 Params: 593 594 label = The text that will be displayed on the button. 595 enabled = Set whether the button can be pressed. 596 colorScheme = Optionally override the current default color scheme when creating this element. 597 598 Returns: 599 600 $(D true) if the button is enabled and was pressed. 601 Note that pressing a button implies pressing and releasing the 602 left mouse button while over the gui button. 603 604 Example: 605 ----- 606 void onPress() { } 607 if (imguiButton("Push me")) // button was pushed 608 onPress(); 609 ----- 610 */ 611 bool imguiButton(const(char)[] label, Enabled enabled = Enabled.yes, const ref ColorScheme colorScheme = defaultColorScheme) 612 { 613 g_state.widgetId++; 614 uint id = (g_state.areaId << 16) | g_state.widgetId; 615 616 int x = g_state.widgetX; 617 int y = g_state.widgetY - BUTTON_HEIGHT; 618 int w = g_state.widgetW; 619 int h = BUTTON_HEIGHT; 620 g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_SPACING; 621 622 bool over = enabled && inRect(x, y, w, h); 623 bool res = buttonLogic(id, over); 624 625 addGfxCmdRoundedRect(cast(float)x, cast(float)y, cast(float)w, cast(float)h, cast(float)BUTTON_HEIGHT / 2 - 1, 626 isActive(id) ? colorScheme.button.backPress : colorScheme.button.back); 627 628 if (enabled) 629 { 630 addGfxCmdText(x + BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, 631 isHot(id) ? colorScheme.button.textHover : colorScheme.button.text); 632 } 633 else 634 { 635 addGfxCmdText(x + BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, 636 colorScheme.button.textDisabled); 637 } 638 639 return res; 640 } 641 642 /** 643 Define a new checkbox. 644 645 Params: 646 647 label = The text that will be displayed on the button. 648 checkState = A pointer to a variable which holds the current state of the checkbox. 649 enabled = Set whether the checkbox can be pressed. 650 colorScheme = Optionally override the current default color scheme when creating this element. 651 652 Returns: 653 654 $(D true) if the checkbox was toggled on or off. 655 Note that toggling implies pressing and releasing the 656 left mouse button while over the checkbox. 657 658 Example: 659 ----- 660 bool checkState = false; // initially un-checked 661 if (imguiCheck("checkbox", &checkState)) // checkbox was toggled 662 writeln(checkState); // check the current state 663 ----- 664 */ 665 bool imguiCheck(const(char)[] label, bool* checkState, Enabled enabled = Enabled.yes, const ref ColorScheme colorScheme = defaultColorScheme) 666 { 667 g_state.widgetId++; 668 uint id = (g_state.areaId << 16) | g_state.widgetId; 669 670 int x = g_state.widgetX; 671 int y = g_state.widgetY - BUTTON_HEIGHT; 672 int w = g_state.widgetW; 673 int h = BUTTON_HEIGHT; 674 g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_SPACING; 675 676 bool over = enabled && inRect(x, y, w, h); 677 bool res = buttonLogic(id, over); 678 679 if (res) // toggle the state 680 *checkState ^= 1; 681 682 const int cx = x + BUTTON_HEIGHT / 2 - CHECK_SIZE / 2; 683 const int cy = y + BUTTON_HEIGHT / 2 - CHECK_SIZE / 2; 684 685 addGfxCmdRoundedRect(cast(float)cx - 3, cast(float)cy - 3, cast(float)CHECK_SIZE + 6, cast(float)CHECK_SIZE + 6, 4, 686 isActive(id) ? colorScheme.checkbox.press : colorScheme.checkbox.back); 687 688 if (*checkState) 689 { 690 if (enabled) 691 addGfxCmdRoundedRect(cast(float)cx, cast(float)cy, cast(float)CHECK_SIZE, cast(float)CHECK_SIZE, cast(float)CHECK_SIZE / 2 - 1, isActive(id) ? colorScheme.checkbox.checked : colorScheme.checkbox.doUncheck); 692 else 693 addGfxCmdRoundedRect(cast(float)cx, cast(float)cy, cast(float)CHECK_SIZE, cast(float)CHECK_SIZE, cast(float)CHECK_SIZE / 2 - 1, colorScheme.checkbox.disabledChecked); 694 } 695 696 if (enabled) 697 addGfxCmdText(x + BUTTON_HEIGHT, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, isHot(id) ? colorScheme.checkbox.textHover : colorScheme.checkbox.text); 698 else 699 addGfxCmdText(x + BUTTON_HEIGHT, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.checkbox.textDisabled); 700 701 return res; 702 } 703 704 /** 705 Define a new item. 706 707 Params: 708 709 label = The text that will be displayed as the item. 710 enabled = Set whether the item can be pressed. 711 colorScheme = Optionally override the current default color scheme when creating this element. 712 713 Returns: 714 715 $(D true) if the item is enabled and was pressed. 716 Note that pressing an item implies pressing and releasing the 717 left mouse button while over the item. 718 */ 719 bool imguiItem(const(char)[] label, Enabled enabled = Enabled.yes, const ref ColorScheme colorScheme = defaultColorScheme) 720 { 721 g_state.widgetId++; 722 uint id = (g_state.areaId << 16) | g_state.widgetId; 723 724 int x = g_state.widgetX; 725 int y = g_state.widgetY - BUTTON_HEIGHT; 726 int w = g_state.widgetW; 727 int h = BUTTON_HEIGHT; 728 g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_SPACING; 729 730 bool over = enabled && inRect(x, y, w, h); 731 bool res = buttonLogic(id, over); 732 733 if (isHot(id)) 734 addGfxCmdRoundedRect(cast(float)x, cast(float)y, cast(float)w, cast(float)h, 2.0f, isActive(id) ? colorScheme.item.press : colorScheme.item.hover); 735 736 if (enabled) 737 addGfxCmdText(x + BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.item.text); 738 else 739 addGfxCmdText(x + BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.item.textDisabled); 740 741 return res; 742 } 743 744 /** 745 Define a new collapsable element. 746 747 Params: 748 749 label = The text that will be displayed as the item. 750 subtext = Additional text displayed on the right of the label. 751 checkState = A pointer to a variable which holds the current state of the collapsable element. 752 enabled = Set whether the element can be pressed. 753 colorScheme = Optionally override the current default color scheme when creating this element. 754 755 Returns: 756 757 $(D true) if the collapsable element is enabled and was pressed. 758 Note that pressing a collapsable element implies pressing and releasing the 759 left mouse button while over the collapsable element. 760 */ 761 bool imguiCollapse(const(char)[] label, const(char)[] subtext, bool* checkState, Enabled enabled = Enabled.yes, const ref ColorScheme colorScheme = defaultColorScheme) 762 { 763 g_state.widgetId++; 764 uint id = (g_state.areaId << 16) | g_state.widgetId; 765 766 int x = g_state.widgetX; 767 int y = g_state.widgetY - BUTTON_HEIGHT; 768 int w = g_state.widgetW; 769 int h = BUTTON_HEIGHT; 770 g_state.widgetY -= BUTTON_HEIGHT; // + DEFAULT_SPACING; 771 772 const int cx = x + BUTTON_HEIGHT / 2 - CHECK_SIZE / 2; 773 const int cy = y + BUTTON_HEIGHT / 2 - CHECK_SIZE / 2; 774 775 bool over = enabled && inRect(x, y, w, h); 776 bool res = buttonLogic(id, over); 777 778 if (res) // toggle the state 779 *checkState ^= 1; 780 781 if (*checkState) 782 addGfxCmdTriangle(cx, cy, CHECK_SIZE, CHECK_SIZE, 2, isActive(id) ? colorScheme.collapse.doHide : colorScheme.collapse.shown); 783 else 784 addGfxCmdTriangle(cx, cy, CHECK_SIZE, CHECK_SIZE, 1, isActive(id) ? colorScheme.collapse.doShow : colorScheme.collapse.hidden); 785 786 if (enabled) 787 addGfxCmdText(x + BUTTON_HEIGHT, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, isHot(id) ? colorScheme.collapse.textHover : colorScheme.collapse.text); 788 else 789 addGfxCmdText(x + BUTTON_HEIGHT, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.collapse.textDisabled); 790 791 if (subtext) 792 addGfxCmdText(x + w - BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.right, subtext, colorScheme.collapse.subtext); 793 794 return res; 795 } 796 797 /** 798 Define a new label. 799 800 Params: 801 802 label = The text that will be displayed as the label. 803 colorScheme = Optionally override the current default color scheme when creating this element. 804 */ 805 void imguiLabel(const(char)[] label, const ref ColorScheme colorScheme = defaultColorScheme) 806 { 807 int x = g_state.widgetX; 808 int y = g_state.widgetY - BUTTON_HEIGHT; 809 g_state.widgetY -= BUTTON_HEIGHT; 810 addGfxCmdText(x, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.label.text); 811 } 812 813 814 /** 815 Define a new value. 816 817 Params: 818 819 label = The text that will be displayed as the value. 820 colorScheme = Optionally override the current default color scheme when creating this element. 821 */ 822 void imguiValue(const(char)[] label, const ref ColorScheme colorScheme = defaultColorScheme) 823 { 824 const int x = g_state.widgetX; 825 const int y = g_state.widgetY - BUTTON_HEIGHT; 826 const int w = g_state.widgetW; 827 g_state.widgetY -= BUTTON_HEIGHT; 828 829 addGfxCmdText(x + w - BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.right, label, colorScheme.value.text); 830 } 831 832 /** 833 Define a new slider. 834 835 Params: 836 837 label = The text that will be displayed above the slider. 838 sliderState = A pointer to a variable which holds the current slider value. 839 minValue = The minimum value that the slider can hold. 840 maxValue = The maximum value that the slider can hold. 841 stepValue = The step at which the value of the slider will increase or decrease. 842 enabled = Set whether the slider's value can can be changed with the mouse. 843 colorScheme = Optionally override the current default color scheme when creating this element. 844 845 Returns: 846 847 $(D true) if the slider is enabled and was pressed. 848 Note that pressing a slider implies pressing and releasing the 849 left mouse button while over the slider. 850 */ 851 bool imguiSlider(const(char)[] label, float* sliderState, float minValue, float maxValue, float stepValue, Enabled enabled = Enabled.yes, const ref ColorScheme colorScheme = defaultColorScheme) 852 { 853 g_state.widgetId++; 854 uint id = (g_state.areaId << 16) | g_state.widgetId; 855 856 int x = g_state.widgetX; 857 int y = g_state.widgetY - BUTTON_HEIGHT; 858 int w = g_state.widgetW; 859 int h = SLIDER_HEIGHT; 860 g_state.widgetY -= SLIDER_HEIGHT + DEFAULT_SPACING; 861 862 addGfxCmdRoundedRect(cast(float)x, cast(float)y, cast(float)w, cast(float)h, 4.0f, colorScheme.slider.back); 863 864 const int range = w - SLIDER_MARKER_WIDTH; 865 866 float u = (*sliderState - minValue) / (maxValue - minValue); 867 868 if (u < 0) 869 u = 0; 870 871 if (u > 1) 872 u = 1; 873 int m = cast(int)(u * range); 874 875 bool over = enabled && inRect(x + m, y, SLIDER_MARKER_WIDTH, SLIDER_HEIGHT); 876 bool res = buttonLogic(id, over); 877 bool valChanged = false; 878 879 if (isActive(id)) 880 { 881 if (g_state.wentActive) 882 { 883 g_state.dragX = g_state.mx; 884 g_state.dragOrig = u; 885 } 886 887 if (g_state.dragX != g_state.mx) 888 { 889 u = g_state.dragOrig + cast(float)(g_state.mx - g_state.dragX) / cast(float)range; 890 891 if (u < 0) 892 u = 0; 893 894 if (u > 1) 895 u = 1; 896 *sliderState = minValue + u * (maxValue - minValue); 897 *sliderState = floor(*sliderState / stepValue + 0.5f) * stepValue; // Snap to stepValue 898 m = cast(int)(u * range); 899 valChanged = true; 900 } 901 } 902 903 if (isActive(id)) 904 addGfxCmdRoundedRect(cast(float)(x + m), cast(float)y, cast(float)SLIDER_MARKER_WIDTH, cast(float)SLIDER_HEIGHT, 4.0f, colorScheme.slider.thumbPress); 905 else 906 addGfxCmdRoundedRect(cast(float)(x + m), cast(float)y, cast(float)SLIDER_MARKER_WIDTH, cast(float)SLIDER_HEIGHT, 4.0f, isHot(id) ? colorScheme.slider.thumbHover : colorScheme.slider.thumb); 907 908 // TODO: fix this, take a look at 'nicenum'. 909 // todo: this should display sub 0.1 if the step is low enough. 910 int digits = cast(int)(ceil(log10(stepValue))); 911 char[16] fmtBuf; 912 auto fmt = sformat(fmtBuf, "%%.%df", digits >= 0 ? 0 : -digits); 913 char[32] msgBuf; 914 string msg = sformat(msgBuf, fmt, *sliderState).idup; 915 916 if (enabled) 917 { 918 addGfxCmdText(x + SLIDER_HEIGHT / 2, y + SLIDER_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, isHot(id) ? colorScheme.slider.textHover : colorScheme.slider.text); 919 addGfxCmdText(x + w - SLIDER_HEIGHT / 2, y + SLIDER_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.right, msg, isHot(id) ? colorScheme.slider.valueHover : colorScheme.slider.value); 920 } 921 else 922 { 923 addGfxCmdText(x + SLIDER_HEIGHT / 2, y + SLIDER_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.slider.textDisabled); 924 addGfxCmdText(x + w - SLIDER_HEIGHT / 2, y + SLIDER_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.right, msg, colorScheme.slider.valueDisabled); 925 } 926 927 return res || valChanged; 928 } 929 930 /** Define a text input field. 931 * 932 * Params: 933 * 934 * text = Label that will be placed beside the text input field. 935 * buffer = Buffer to store entered text. 936 * usedSlice = Slice of buffer that stores text entered so far. 937 * forceInputable = Force the text input field to be inputable regardless of whether it 938 * has been selected by the user? Useful to e.g. make a text field 939 * inputable immediately after it appears in a newly opened dialog. 940 * colorScheme = Optionally override the current default color scheme for this element. 941 * 942 * Returns: true if the user has entered and confirmed the text (by pressing Enter), false 943 * otherwise. 944 * 945 * Example (using GLFW): 946 * -------------------- 947 * static dchar staticUnicode; 948 * // Buffer to store text input 949 * char[128] textInputBuffer; 950 * // Slice of textInputBuffer 951 * char[] textEntered; 952 * 953 * extern(C) static void getUnicode(GLFWwindow* w, uint unicode) 954 * { 955 * staticUnicode = unicode; 956 * } 957 * 958 * extern(C) static void getKey(GLFWwindow* w, int key, int scancode, int action, int mods) 959 * { 960 * if(action != GLFW_PRESS) { return; } 961 * if(key == GLFW_KEY_ENTER) { staticUnicode = 0x0D; } 962 * else if(key == GLFW_KEY_BACKSPACE) { staticUnicode = 0x08; } 963 * } 964 * 965 * void init() 966 * { 967 * GLFWwindow* window; 968 * 969 * // ... init the window here ... 970 * 971 * // Not really needed, but makes it obvious what we're doing 972 * textEntered = textInputBuffer[0 .. 0]; 973 * glfwSetCharCallback(window, &getUnicode); 974 * glfwSetKeyCallback(window, &getKey); 975 * } 976 * 977 * void frame() 978 * { 979 * // These should be defined somewhere 980 * int mouseX, mouseY, mouseScroll; 981 * ubyte mousebutton; 982 * 983 * // .. code here .. 984 * 985 * // Pass text input to imgui 986 * imguiBeginFrame(cast(int)mouseX, cast(int)mouseY, mousebutton, mouseScroll, staticUnicode); 987 * // reset staticUnicode for the next frame 988 * 989 * staticUnicode = 0; 990 * 991 * if(imguiTextInput("Text input:", textInputBuffer, textEntered)) 992 * { 993 * import std.stdio; 994 * writeln("Entered text is: ", textEntered); 995 * // Reset entered text for next input (use e.g. textEntered.dup if you need a copy). 996 * textEntered = textInputBuffer[0 .. 0]; 997 * } 998 * 999 * // .. more code here .. 1000 * } 1001 * -------------------- 1002 */ 1003 bool imguiTextInput(const(char)[] label, char[] buffer, ref char[] usedSlice, 1004 bool forceInputable = false, const ref ColorScheme colorScheme = defaultColorScheme) 1005 { 1006 assert(buffer.ptr == usedSlice.ptr && buffer.length >= usedSlice.length, 1007 "The usedSlice parameter on imguiTextInput must be a slice to the buffer " ~ 1008 "parameter"); 1009 1010 // Label 1011 g_state.widgetId++; 1012 uint id = (g_state.areaId << 16) | g_state.widgetId; 1013 int x = g_state.widgetX; 1014 int y = g_state.widgetY - BUTTON_HEIGHT; 1015 addGfxCmdText(x, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, 1016 colorScheme.textInput.label); 1017 1018 bool res = false; 1019 // Handle control input if any (Backspace to erase characters, Enter to confirm). 1020 // Backspace 1021 if(isInputable(id) && g_state.unicode == 0x08 && 1022 g_state.unicode != g_state.lastUnicode && !usedSlice.empty) 1023 { 1024 usedSlice = usedSlice[0 .. $ - 1]; 1025 } 1026 // Pressing Enter "confirms" the input. 1027 else if(isInputable(id) && g_state.unicode == 0x0D && g_state.unicode != g_state.lastUnicode) 1028 { 1029 g_state.inputable = 0; 1030 res = true; 1031 } 1032 else if(isInputable(id) && g_state.unicode != 0 && g_state.unicode != g_state.lastUnicode) 1033 { 1034 import std.utf; 1035 char[4] codePoints; 1036 const codePointCount = std.utf.encode(codePoints, g_state.unicode); 1037 // Only add the character into the buffer if we can fit it there. 1038 if(buffer.length - usedSlice.length >= codePointCount) 1039 { 1040 usedSlice = buffer[0 .. usedSlice.length + codePointCount]; 1041 usedSlice[$ - codePointCount .. $] = codePoints[0 .. codePointCount]; 1042 } 1043 } 1044 1045 // Draw buffer data 1046 uint labelLen = cast(uint)(imgui.engine.getTextLength(label) + 0.5f); 1047 x += labelLen; 1048 int w = g_state.widgetW - labelLen - DEFAULT_SPACING * 2; 1049 int h = BUTTON_HEIGHT; 1050 bool over = inRect(x, y, w, h); 1051 textInputLogic(id, over, forceInputable); 1052 addGfxCmdRoundedRect(cast(float)(x + DEFAULT_SPACING), cast(float)y, 1053 cast(float)w, cast(float)h, 1054 cast(float)BUTTON_HEIGHT / 2 - 1, 1055 isInputable(id) ? colorScheme.textInput.back 1056 : colorScheme.textInput.backDisabled); 1057 addGfxCmdText(x + DEFAULT_SPACING * 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, 1058 TextAlign.left, usedSlice, 1059 isInputable(id) ? colorScheme.textInput.text 1060 : colorScheme.textInput.textDisabled); 1061 1062 g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_SPACING; 1063 return res; 1064 } 1065 1066 /** Add horizontal indentation for elements to be added. */ 1067 void imguiIndent() 1068 { 1069 g_state.widgetX += INDENT_SIZE; 1070 g_state.widgetW -= INDENT_SIZE; 1071 } 1072 1073 /** Remove horizontal indentation for elements to be added. */ 1074 void imguiUnindent() 1075 { 1076 g_state.widgetX -= INDENT_SIZE; 1077 g_state.widgetW += INDENT_SIZE; 1078 } 1079 1080 /** Add vertical space as a separator below the last element. */ 1081 void imguiSeparator() 1082 { 1083 g_state.widgetY -= DEFAULT_SPACING * 3; 1084 } 1085 1086 /** 1087 Add a horizontal line as a separator below the last element. 1088 1089 Params: 1090 colorScheme = Optionally override the current default color scheme when creating this element. 1091 */ 1092 void imguiSeparatorLine(const ref ColorScheme colorScheme = defaultColorScheme) 1093 { 1094 int x = g_state.widgetX; 1095 int y = g_state.widgetY - DEFAULT_SPACING * 2; 1096 int w = g_state.widgetW; 1097 int h = 1; 1098 g_state.widgetY -= DEFAULT_SPACING * 4; 1099 1100 addGfxCmdRect(cast(float)x, cast(float)y, cast(float)w, cast(float)h, colorScheme.separator); 1101 } 1102 1103 /** 1104 Draw text. 1105 1106 Params: 1107 color = Optionally override the current default text color when creating this element. 1108 */ 1109 void imguiDrawText(int xPos, int yPos, TextAlign textAlign, const(char)[] text, RGBA color = defaultColorScheme.generic.text) 1110 { 1111 addGfxCmdText(xPos, yPos, textAlign, text, color); 1112 } 1113 1114 /** 1115 Draw a line. 1116 1117 Params: 1118 colorScheme = Optionally override the current default color scheme when creating this element. 1119 */ 1120 void imguiDrawLine(float x0, float y0, float x1, float y1, float r, RGBA color = defaultColorScheme.generic.line) 1121 { 1122 addGfxCmdLine(x0, y0, x1, y1, r, color); 1123 } 1124 1125 /** 1126 Draw a rectangle. 1127 1128 Params: 1129 colorScheme = Optionally override the current default color scheme when creating this element. 1130 */ 1131 void imguiDrawRect(float xPos, float yPos, float width, float height, RGBA color = defaultColorScheme.generic.rect) 1132 { 1133 addGfxCmdRect(xPos, yPos, width, height, color); 1134 } 1135 1136 /** 1137 Draw a rounded rectangle. 1138 1139 Params: 1140 colorScheme = Optionally override the current default color scheme when creating this element. 1141 */ 1142 void imguiDrawRoundedRect(float xPos, float yPos, float width, float height, float r, RGBA color = defaultColorScheme.generic.roundRect) 1143 { 1144 addGfxCmdRoundedRect(xPos, yPos, width, height, r, color); 1145 }