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 }