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 }