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.areaId = 1; 401 g_state.widgetId = 1; 402 403 resetGfxCmdQueue(); 404 } 405 406 /** End the list of batched commands for the current frame. */ 407 void imguiEndFrame() 408 { 409 clearInput(); 410 } 411 412 /** Render all of the batched commands for the current frame. */ 413 void imguiRender(int width, int height) 414 { 415 imguiRenderGLDraw(width, height); 416 } 417 418 /** 419 Begin the definition of a new scrollable area. 420 421 Once elements within the scrollable area are defined 422 you must call $(D imguiEndScrollArea) to end the definition. 423 424 Params: 425 426 title = The title that will be displayed for this scroll area. 427 xPos = The X position of the scroll area. 428 yPos = The Y position of the scroll area. 429 width = The width of the scroll area. 430 height = The height of the scroll area. 431 scroll = A pointer to a variable which will hold the current scroll value of the widget. 432 colorScheme = Optionally override the current default color scheme when creating this element. 433 434 Returns: 435 436 $(D true) if the mouse was located inside the scrollable area. 437 */ 438 bool imguiBeginScrollArea(const(char)[] title, int xPos, int yPos, int width, int height, int* scroll, const ref ColorScheme colorScheme = defaultColorScheme) 439 { 440 g_state.areaId++; 441 g_state.widgetId = 0; 442 g_scrollId = (g_state.areaId << 16) | g_state.widgetId; 443 444 g_state.widgetX = xPos + SCROLL_AREA_PADDING; 445 g_state.widgetY = yPos + height - AREA_HEADER + (*scroll); 446 g_state.widgetW = width - SCROLL_AREA_PADDING * 4; 447 g_scrollTop = yPos - AREA_HEADER + height; 448 g_scrollBottom = yPos + SCROLL_AREA_PADDING; 449 g_scrollRight = xPos + width - SCROLL_AREA_PADDING * 3; 450 g_scrollVal = scroll; 451 452 g_scrollAreaTop = g_state.widgetY; 453 454 g_focusTop = yPos - AREA_HEADER; 455 g_focusBottom = yPos - AREA_HEADER + height; 456 457 g_insideScrollArea = inRect(xPos, yPos, width, height, false); 458 g_state.insideCurrentScroll = g_insideScrollArea; 459 460 addGfxCmdRoundedRect(cast(float)xPos, cast(float)yPos, cast(float)width, cast(float)height, 6, colorScheme.scroll.area.back); 461 462 addGfxCmdText(xPos + AREA_HEADER / 2, yPos + height - AREA_HEADER / 2 - TEXT_HEIGHT / 2, TextAlign.left, title, colorScheme.scroll.area.text); 463 464 // The max() ensures we never have zero- or negative-sized scissor rectangle when the window is very small, 465 // avoiding a segfault. 466 addGfxCmdScissor(xPos + SCROLL_AREA_PADDING, 467 yPos + SCROLL_AREA_PADDING, 468 max(1, width - SCROLL_AREA_PADDING * 4), 469 max(1, height - AREA_HEADER - SCROLL_AREA_PADDING)); 470 471 return g_insideScrollArea; 472 } 473 474 /** 475 End the definition of the last scrollable element. 476 477 Params: 478 479 colorScheme = Optionally override the current default color scheme when creating this element. 480 */ 481 void imguiEndScrollArea(const ref ColorScheme colorScheme = defaultColorScheme) 482 { 483 // Disable scissoring. 484 addGfxCmdScissor(-1, -1, -1, -1); 485 486 // Draw scroll bar 487 int x = g_scrollRight + SCROLL_AREA_PADDING / 2; 488 int y = g_scrollBottom; 489 int w = SCROLL_AREA_PADDING * 2; 490 int h = g_scrollTop - g_scrollBottom; 491 492 int stop = g_scrollAreaTop; 493 int sbot = g_state.widgetY; 494 int sh = stop - sbot; // The scrollable area height. 495 496 float barHeight = cast(float)h / cast(float)sh; 497 498 if (barHeight < 1) 499 { 500 float barY = cast(float)(y - sbot) / cast(float)sh; 501 502 if (barY < 0) 503 barY = 0; 504 505 if (barY > 1) 506 barY = 1; 507 508 // Handle scroll bar logic. 509 uint hid = g_scrollId; 510 int hx = x; 511 int hy = y + cast(int)(barY * h); 512 int hw = w; 513 int hh = cast(int)(barHeight * h); 514 515 const int range = h - (hh - 1); 516 bool over = inRect(hx, hy, hw, hh); 517 buttonLogic(hid, over); 518 519 if (isActive(hid)) 520 { 521 float u = cast(float)(hy - y) / cast(float)range; 522 523 if (g_state.wentActive) 524 { 525 g_state.dragY = g_state.my; 526 g_state.dragOrig = u; 527 } 528 529 if (g_state.dragY != g_state.my) 530 { 531 u = g_state.dragOrig + (g_state.my - g_state.dragY) / cast(float)range; 532 533 if (u < 0) 534 u = 0; 535 536 if (u > 1) 537 u = 1; 538 *g_scrollVal = cast(int)((1 - u) * (sh - h)); 539 } 540 } 541 542 // BG 543 addGfxCmdRoundedRect(cast(float)x, cast(float)y, cast(float)w, cast(float)h, cast(float)w / 2 - 1, colorScheme.scroll.bar.back); 544 545 // Bar 546 if (isActive(hid)) 547 addGfxCmdRoundedRect(cast(float)hx, cast(float)hy, cast(float)hw, cast(float)hh, cast(float)w / 2 - 1, colorScheme.scroll.bar.thumbPress); 548 else 549 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); 550 551 // Handle mouse scrolling. 552 if (g_insideScrollArea) // && !anyActive()) 553 { 554 if (g_state.scroll) 555 { 556 *g_scrollVal += 20 * g_state.scroll; 557 558 if (*g_scrollVal < 0) 559 *g_scrollVal = 0; 560 561 if (*g_scrollVal > (sh - h)) 562 *g_scrollVal = (sh - h); 563 } 564 } 565 } 566 g_state.insideCurrentScroll = false; 567 } 568 569 /** 570 Define a new button. 571 572 Params: 573 574 label = The text that will be displayed on the button. 575 enabled = Set whether the button can be pressed. 576 colorScheme = Optionally override the current default color scheme when creating this element. 577 578 Returns: 579 580 $(D true) if the button is enabled and was pressed. 581 Note that pressing a button implies pressing and releasing the 582 left mouse button while over the gui button. 583 584 Example: 585 ----- 586 void onPress() { } 587 if (imguiButton("Push me")) // button was pushed 588 onPress(); 589 ----- 590 */ 591 bool imguiButton(const(char)[] label, Enabled enabled = Enabled.yes, const ref ColorScheme colorScheme = defaultColorScheme) 592 { 593 g_state.widgetId++; 594 uint id = (g_state.areaId << 16) | g_state.widgetId; 595 596 int x = g_state.widgetX; 597 int y = g_state.widgetY - BUTTON_HEIGHT; 598 int w = g_state.widgetW; 599 int h = BUTTON_HEIGHT; 600 g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_SPACING; 601 602 bool over = enabled && inRect(x, y, w, h); 603 bool res = buttonLogic(id, over); 604 605 addGfxCmdRoundedRect(cast(float)x, cast(float)y, cast(float)w, cast(float)h, cast(float)BUTTON_HEIGHT / 2 - 1, 606 isActive(id) ? colorScheme.button.backPress : colorScheme.button.back); 607 608 if (enabled) 609 { 610 addGfxCmdText(x + BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, 611 isHot(id) ? colorScheme.button.textHover : colorScheme.button.text); 612 } 613 else 614 { 615 addGfxCmdText(x + BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, 616 colorScheme.button.textDisabled); 617 } 618 619 return res; 620 } 621 622 /** 623 Define a new checkbox. 624 625 Params: 626 627 label = The text that will be displayed on the button. 628 checkState = A pointer to a variable which holds the current state of the checkbox. 629 enabled = Set whether the checkbox can be pressed. 630 colorScheme = Optionally override the current default color scheme when creating this element. 631 632 Returns: 633 634 $(D true) if the checkbox was toggled on or off. 635 Note that toggling implies pressing and releasing the 636 left mouse button while over the checkbox. 637 638 Example: 639 ----- 640 bool checkState = false; // initially un-checked 641 if (imguiCheck("checkbox", &checkState)) // checkbox was toggled 642 writeln(checkState); // check the current state 643 ----- 644 */ 645 bool imguiCheck(const(char)[] label, bool* checkState, Enabled enabled = Enabled.yes, const ref ColorScheme colorScheme = defaultColorScheme) 646 { 647 g_state.widgetId++; 648 uint id = (g_state.areaId << 16) | g_state.widgetId; 649 650 int x = g_state.widgetX; 651 int y = g_state.widgetY - BUTTON_HEIGHT; 652 int w = g_state.widgetW; 653 int h = BUTTON_HEIGHT; 654 g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_SPACING; 655 656 bool over = enabled && inRect(x, y, w, h); 657 bool res = buttonLogic(id, over); 658 659 if (res) // toggle the state 660 *checkState ^= 1; 661 662 const int cx = x + BUTTON_HEIGHT / 2 - CHECK_SIZE / 2; 663 const int cy = y + BUTTON_HEIGHT / 2 - CHECK_SIZE / 2; 664 665 addGfxCmdRoundedRect(cast(float)cx - 3, cast(float)cy - 3, cast(float)CHECK_SIZE + 6, cast(float)CHECK_SIZE + 6, 4, 666 isActive(id) ? colorScheme.checkbox.press : colorScheme.checkbox.back); 667 668 if (*checkState) 669 { 670 if (enabled) 671 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); 672 else 673 addGfxCmdRoundedRect(cast(float)cx, cast(float)cy, cast(float)CHECK_SIZE, cast(float)CHECK_SIZE, cast(float)CHECK_SIZE / 2 - 1, colorScheme.checkbox.disabledChecked); 674 } 675 676 if (enabled) 677 addGfxCmdText(x + BUTTON_HEIGHT, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, isHot(id) ? colorScheme.checkbox.textHover : colorScheme.checkbox.text); 678 else 679 addGfxCmdText(x + BUTTON_HEIGHT, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.checkbox.textDisabled); 680 681 return res; 682 } 683 684 /** 685 Define a new item. 686 687 Params: 688 689 label = The text that will be displayed as the item. 690 enabled = Set whether the item can be pressed. 691 colorScheme = Optionally override the current default color scheme when creating this element. 692 693 Returns: 694 695 $(D true) if the item is enabled and was pressed. 696 Note that pressing an item implies pressing and releasing the 697 left mouse button while over the item. 698 */ 699 bool imguiItem(const(char)[] label, Enabled enabled = Enabled.yes, const ref ColorScheme colorScheme = defaultColorScheme) 700 { 701 g_state.widgetId++; 702 uint id = (g_state.areaId << 16) | g_state.widgetId; 703 704 int x = g_state.widgetX; 705 int y = g_state.widgetY - BUTTON_HEIGHT; 706 int w = g_state.widgetW; 707 int h = BUTTON_HEIGHT; 708 g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_SPACING; 709 710 bool over = enabled && inRect(x, y, w, h); 711 bool res = buttonLogic(id, over); 712 713 if (isHot(id)) 714 addGfxCmdRoundedRect(cast(float)x, cast(float)y, cast(float)w, cast(float)h, 2.0f, isActive(id) ? colorScheme.item.press : colorScheme.item.hover); 715 716 if (enabled) 717 addGfxCmdText(x + BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.item.text); 718 else 719 addGfxCmdText(x + BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.item.textDisabled); 720 721 return res; 722 } 723 724 /** 725 Define a new collapsable element. 726 727 Params: 728 729 label = The text that will be displayed as the item. 730 subtext = Additional text displayed on the right of the label. 731 checkState = A pointer to a variable which holds the current state of the collapsable element. 732 enabled = Set whether the element can be pressed. 733 colorScheme = Optionally override the current default color scheme when creating this element. 734 735 Returns: 736 737 $(D true) if the collapsable element is enabled and was pressed. 738 Note that pressing a collapsable element implies pressing and releasing the 739 left mouse button while over the collapsable element. 740 */ 741 bool imguiCollapse(const(char)[] label, const(char)[] subtext, bool* checkState, Enabled enabled = Enabled.yes, const ref ColorScheme colorScheme = defaultColorScheme) 742 { 743 g_state.widgetId++; 744 uint id = (g_state.areaId << 16) | g_state.widgetId; 745 746 int x = g_state.widgetX; 747 int y = g_state.widgetY - BUTTON_HEIGHT; 748 int w = g_state.widgetW; 749 int h = BUTTON_HEIGHT; 750 g_state.widgetY -= BUTTON_HEIGHT; // + DEFAULT_SPACING; 751 752 const int cx = x + BUTTON_HEIGHT / 2 - CHECK_SIZE / 2; 753 const int cy = y + BUTTON_HEIGHT / 2 - CHECK_SIZE / 2; 754 755 bool over = enabled && inRect(x, y, w, h); 756 bool res = buttonLogic(id, over); 757 758 if (res) // toggle the state 759 *checkState ^= 1; 760 761 if (*checkState) 762 addGfxCmdTriangle(cx, cy, CHECK_SIZE, CHECK_SIZE, 2, isActive(id) ? colorScheme.collapse.doHide : colorScheme.collapse.shown); 763 else 764 addGfxCmdTriangle(cx, cy, CHECK_SIZE, CHECK_SIZE, 1, isActive(id) ? colorScheme.collapse.doShow : colorScheme.collapse.hidden); 765 766 if (enabled) 767 addGfxCmdText(x + BUTTON_HEIGHT, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, isHot(id) ? colorScheme.collapse.textHover : colorScheme.collapse.text); 768 else 769 addGfxCmdText(x + BUTTON_HEIGHT, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.collapse.textDisabled); 770 771 if (subtext) 772 addGfxCmdText(x + w - BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.right, subtext, colorScheme.collapse.subtext); 773 774 return res; 775 } 776 777 /** 778 Define a new label. 779 780 Params: 781 782 label = The text that will be displayed as the label. 783 colorScheme = Optionally override the current default color scheme when creating this element. 784 */ 785 void imguiLabel(const(char)[] label, const ref ColorScheme colorScheme = defaultColorScheme) 786 { 787 int x = g_state.widgetX; 788 int y = g_state.widgetY - BUTTON_HEIGHT; 789 g_state.widgetY -= BUTTON_HEIGHT; 790 addGfxCmdText(x, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.label.text); 791 } 792 793 794 /** 795 Define a new value. 796 797 Params: 798 799 label = The text that will be displayed as the value. 800 colorScheme = Optionally override the current default color scheme when creating this element. 801 */ 802 void imguiValue(const(char)[] label, const ref ColorScheme colorScheme = defaultColorScheme) 803 { 804 const int x = g_state.widgetX; 805 const int y = g_state.widgetY - BUTTON_HEIGHT; 806 const int w = g_state.widgetW; 807 g_state.widgetY -= BUTTON_HEIGHT; 808 809 addGfxCmdText(x + w - BUTTON_HEIGHT / 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.right, label, colorScheme.value.text); 810 } 811 812 /** 813 Define a new slider. 814 815 Params: 816 817 label = The text that will be displayed above the slider. 818 sliderState = A pointer to a variable which holds the current slider value. 819 minValue = The minimum value that the slider can hold. 820 maxValue = The maximum value that the slider can hold. 821 stepValue = The step at which the value of the slider will increase or decrease. 822 enabled = Set whether the slider's value can can be changed with the mouse. 823 colorScheme = Optionally override the current default color scheme when creating this element. 824 825 Returns: 826 827 $(D true) if the slider is enabled and was pressed. 828 Note that pressing a slider implies pressing and releasing the 829 left mouse button while over the slider. 830 */ 831 bool imguiSlider(const(char)[] label, float* sliderState, float minValue, float maxValue, float stepValue, Enabled enabled = Enabled.yes, const ref ColorScheme colorScheme = defaultColorScheme) 832 { 833 g_state.widgetId++; 834 uint id = (g_state.areaId << 16) | g_state.widgetId; 835 836 int x = g_state.widgetX; 837 int y = g_state.widgetY - BUTTON_HEIGHT; 838 int w = g_state.widgetW; 839 int h = SLIDER_HEIGHT; 840 g_state.widgetY -= SLIDER_HEIGHT + DEFAULT_SPACING; 841 842 addGfxCmdRoundedRect(cast(float)x, cast(float)y, cast(float)w, cast(float)h, 4.0f, colorScheme.slider.back); 843 844 const int range = w - SLIDER_MARKER_WIDTH; 845 846 float u = (*sliderState - minValue) / (maxValue - minValue); 847 848 if (u < 0) 849 u = 0; 850 851 if (u > 1) 852 u = 1; 853 int m = cast(int)(u * range); 854 855 bool over = enabled && inRect(x + m, y, SLIDER_MARKER_WIDTH, SLIDER_HEIGHT); 856 bool res = buttonLogic(id, over); 857 bool valChanged = false; 858 859 if (isActive(id)) 860 { 861 if (g_state.wentActive) 862 { 863 g_state.dragX = g_state.mx; 864 g_state.dragOrig = u; 865 } 866 867 if (g_state.dragX != g_state.mx) 868 { 869 u = g_state.dragOrig + cast(float)(g_state.mx - g_state.dragX) / cast(float)range; 870 871 if (u < 0) 872 u = 0; 873 874 if (u > 1) 875 u = 1; 876 *sliderState = minValue + u * (maxValue - minValue); 877 *sliderState = floor(*sliderState / stepValue + 0.5f) * stepValue; // Snap to stepValue 878 m = cast(int)(u * range); 879 valChanged = true; 880 } 881 } 882 883 if (isActive(id)) 884 addGfxCmdRoundedRect(cast(float)(x + m), cast(float)y, cast(float)SLIDER_MARKER_WIDTH, cast(float)SLIDER_HEIGHT, 4.0f, colorScheme.slider.thumbPress); 885 else 886 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); 887 888 // TODO: fix this, take a look at 'nicenum'. 889 // todo: this should display sub 0.1 if the step is low enough. 890 int digits = cast(int)(ceil(log10(stepValue))); 891 char[16] fmtBuf; 892 auto fmt = sformat(fmtBuf, "%%.%df", digits >= 0 ? 0 : -digits); 893 char[32] msgBuf; 894 string msg = sformat(msgBuf, fmt, *sliderState).idup; 895 896 if (enabled) 897 { 898 addGfxCmdText(x + SLIDER_HEIGHT / 2, y + SLIDER_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, isHot(id) ? colorScheme.slider.textHover : colorScheme.slider.text); 899 addGfxCmdText(x + w - SLIDER_HEIGHT / 2, y + SLIDER_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.right, msg, isHot(id) ? colorScheme.slider.valueHover : colorScheme.slider.value); 900 } 901 else 902 { 903 addGfxCmdText(x + SLIDER_HEIGHT / 2, y + SLIDER_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, colorScheme.slider.textDisabled); 904 addGfxCmdText(x + w - SLIDER_HEIGHT / 2, y + SLIDER_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.right, msg, colorScheme.slider.valueDisabled); 905 } 906 907 return res || valChanged; 908 } 909 910 /** Define a text input field. 911 * 912 * Params: 913 * 914 * text = Label that will be placed beside the text input field. 915 * buffer = Buffer to store entered text. 916 * usedSlice = Slice of buffer that stores text entered so far. 917 * forceInputable = Force the text input field to be inputable regardless of whether it 918 * has been selected by the user? Useful to e.g. make a text field 919 * inputable immediately after it appears in a newly opened dialog. 920 * colorScheme = Optionally override the current default color scheme for this element. 921 * 922 * Returns: true if the user has entered and confirmed the text (by pressing Enter), false 923 * otherwise. 924 * 925 * Example (using GLFW): 926 * -------------------- 927 * static dchar staticUnicode; 928 * // Buffer to store text input 929 * char[128] textInputBuffer; 930 * // Slice of textInputBuffer 931 * char[] textEntered; 932 * 933 * extern(C) static void getUnicode(GLFWwindow* w, uint unicode) 934 * { 935 * staticUnicode = unicode; 936 * } 937 * 938 * extern(C) static void getKey(GLFWwindow* w, int key, int scancode, int action, int mods) 939 * { 940 * if(action != GLFW_PRESS) { return; } 941 * if(key == GLFW_KEY_ENTER) { staticUnicode = 0x0D; } 942 * else if(key == GLFW_KEY_BACKSPACE) { staticUnicode = 0x08; } 943 * } 944 * 945 * void init() 946 * { 947 * GLFWwindow* window; 948 * 949 * // ... init the window here ... 950 * 951 * // Not really needed, but makes it obvious what we're doing 952 * textEntered = textInputBuffer[0 .. 0]; 953 * glfwSetCharCallback(window, &getUnicode); 954 * glfwSetKeyCallback(window, &getKey); 955 * } 956 * 957 * void frame() 958 * { 959 * // These should be defined somewhere 960 * int mouseX, mouseY, mouseScroll; 961 * ubyte mousebutton; 962 * 963 * // .. code here .. 964 * 965 * // Pass text input to imgui 966 * imguiBeginFrame(cast(int)mouseX, cast(int)mouseY, mousebutton, mouseScroll, staticUnicode); 967 * // reset staticUnicode for the next frame 968 * 969 * staticUnicode = 0; 970 * 971 * if(imguiTextInput("Text input:", textInputBuffer, textEntered)) 972 * { 973 * import std.stdio; 974 * writeln("Entered text is: ", textEntered); 975 * // Reset entered text for next input (use e.g. textEntered.dup if you need a copy). 976 * textEntered = textInputBuffer[0 .. 0]; 977 * } 978 * 979 * // .. more code here .. 980 * } 981 * -------------------- 982 */ 983 bool imguiTextInput(const(char)[] label, char[] buffer, ref char[] usedSlice, 984 bool forceInputable = false, const ref ColorScheme colorScheme = defaultColorScheme) 985 { 986 assert(buffer.ptr == usedSlice.ptr && buffer.length >= usedSlice.length, 987 "The usedSlice parameter on imguiTextInput must be a slice to the buffer " ~ 988 "parameter"); 989 990 // Label 991 g_state.widgetId++; 992 uint id = (g_state.areaId << 16) | g_state.widgetId; 993 int x = g_state.widgetX; 994 int y = g_state.widgetY - BUTTON_HEIGHT; 995 addGfxCmdText(x, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, TextAlign.left, label, 996 colorScheme.textInput.label); 997 998 bool res = false; 999 // Handle control input if any (Backspace to erase characters, Enter to confirm). 1000 // Backspace 1001 if(isInputable(id) && g_state.unicode == 0x08 && 1002 g_state.unicode != g_state.lastUnicode && !usedSlice.empty) 1003 { 1004 usedSlice = usedSlice[0 .. $ - 1]; 1005 } 1006 // Pressing Enter "confirms" the input. 1007 else if(isInputable(id) && g_state.unicode == 0x0D && g_state.unicode != g_state.lastUnicode) 1008 { 1009 g_state.inputable = 0; 1010 res = true; 1011 } 1012 else if(isInputable(id) && g_state.unicode != 0 && g_state.unicode != g_state.lastUnicode) 1013 { 1014 import std.utf; 1015 char[4] codePoints; 1016 const codePointCount = std.utf.encode(codePoints, g_state.unicode); 1017 // Only add the character into the buffer if we can fit it there. 1018 if(buffer.length - usedSlice.length >= codePointCount) 1019 { 1020 usedSlice = buffer[0 .. usedSlice.length + codePointCount]; 1021 usedSlice[$ - codePointCount .. $] = codePoints[0 .. codePointCount]; 1022 } 1023 } 1024 1025 // Draw buffer data 1026 uint labelLen = cast(uint)(imgui.engine.getTextLength(label) + 0.5f); 1027 x += labelLen; 1028 int w = g_state.widgetW - labelLen - DEFAULT_SPACING * 2; 1029 int h = BUTTON_HEIGHT; 1030 bool over = inRect(x, y, w, h); 1031 textInputLogic(id, over, forceInputable); 1032 addGfxCmdRoundedRect(cast(float)(x + DEFAULT_SPACING), cast(float)y, 1033 cast(float)w, cast(float)h, 1034 cast(float)BUTTON_HEIGHT / 2 - 1, 1035 isInputable(id) ? colorScheme.textInput.back 1036 : colorScheme.textInput.backDisabled); 1037 addGfxCmdText(x + DEFAULT_SPACING * 2, y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2, 1038 TextAlign.left, usedSlice, 1039 isInputable(id) ? colorScheme.textInput.text 1040 : colorScheme.textInput.textDisabled); 1041 1042 g_state.widgetY -= BUTTON_HEIGHT + DEFAULT_SPACING; 1043 return res; 1044 } 1045 1046 /** Add horizontal indentation for elements to be added. */ 1047 void imguiIndent() 1048 { 1049 g_state.widgetX += INDENT_SIZE; 1050 g_state.widgetW -= INDENT_SIZE; 1051 } 1052 1053 /** Remove horizontal indentation for elements to be added. */ 1054 void imguiUnindent() 1055 { 1056 g_state.widgetX -= INDENT_SIZE; 1057 g_state.widgetW += INDENT_SIZE; 1058 } 1059 1060 /** Add vertical space as a separator below the last element. */ 1061 void imguiSeparator() 1062 { 1063 g_state.widgetY -= DEFAULT_SPACING * 3; 1064 } 1065 1066 /** 1067 Add a horizontal line as a separator below the last element. 1068 1069 Params: 1070 colorScheme = Optionally override the current default color scheme when creating this element. 1071 */ 1072 void imguiSeparatorLine(const ref ColorScheme colorScheme = defaultColorScheme) 1073 { 1074 int x = g_state.widgetX; 1075 int y = g_state.widgetY - DEFAULT_SPACING * 2; 1076 int w = g_state.widgetW; 1077 int h = 1; 1078 g_state.widgetY -= DEFAULT_SPACING * 4; 1079 1080 addGfxCmdRect(cast(float)x, cast(float)y, cast(float)w, cast(float)h, colorScheme.separator); 1081 } 1082 1083 /** 1084 Draw text. 1085 1086 Params: 1087 color = Optionally override the current default text color when creating this element. 1088 */ 1089 void imguiDrawText(int xPos, int yPos, TextAlign textAlign, const(char)[] text, RGBA color = defaultColorScheme.generic.text) 1090 { 1091 addGfxCmdText(xPos, yPos, textAlign, text, color); 1092 } 1093 1094 /** 1095 Draw a line. 1096 1097 Params: 1098 colorScheme = Optionally override the current default color scheme when creating this element. 1099 */ 1100 void imguiDrawLine(float x0, float y0, float x1, float y1, float r, RGBA color = defaultColorScheme.generic.line) 1101 { 1102 addGfxCmdLine(x0, y0, x1, y1, r, color); 1103 } 1104 1105 /** 1106 Draw a rectangle. 1107 1108 Params: 1109 colorScheme = Optionally override the current default color scheme when creating this element. 1110 */ 1111 void imguiDrawRect(float xPos, float yPos, float width, float height, RGBA color = defaultColorScheme.generic.rect) 1112 { 1113 addGfxCmdRect(xPos, yPos, width, height, color); 1114 } 1115 1116 /** 1117 Draw a rounded rectangle. 1118 1119 Params: 1120 colorScheme = Optionally override the current default color scheme when creating this element. 1121 */ 1122 void imguiDrawRoundedRect(float xPos, float yPos, float width, float height, float r, RGBA color = defaultColorScheme.generic.roundRect) 1123 { 1124 addGfxCmdRoundedRect(xPos, yPos, width, height, r, color); 1125 }