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.engine;
19 
20 import std.math;
21 import std.stdio;
22 import std..string;
23 
24 import imgui.api;
25 import imgui.gl3_renderer;
26 
27 package:
28 
29 /** Globals start. */
30 
31 __gshared imguiGfxCmd[GFXCMD_QUEUE_SIZE] g_gfxCmdQueue;
32 __gshared uint g_gfxCmdQueueSize = 0;
33 __gshared int  g_scrollTop        = 0;
34 __gshared int  g_scrollBottom     = 0;
35 __gshared int  g_scrollRight      = 0;
36 __gshared int  g_scrollAreaTop    = 0;
37 __gshared int* g_scrollVal        = null;
38 __gshared int  g_focusTop         = 0;
39 __gshared int  g_focusBottom      = 0;
40 __gshared uint g_scrollId = 0;
41 __gshared bool g_insideScrollArea = false;
42 __gshared GuiState g_state;
43 
44 /** Globals end. */
45 
46 enum GFXCMD_QUEUE_SIZE   = 5000;
47 enum BUTTON_HEIGHT       = 20;
48 enum SLIDER_HEIGHT       = 20;
49 enum SLIDER_MARKER_WIDTH = 10;
50 enum CHECK_SIZE          = 8;
51 enum DEFAULT_SPACING     = 4;
52 enum TEXT_HEIGHT         = 8;
53 enum SCROLL_AREA_PADDING = 6;
54 enum INDENT_SIZE         = 16;
55 enum AREA_HEADER         = 28;
56 enum MAX_SCROLL_AREA     = 512;
57 
58 // Pull render interface.
59 alias imguiGfxCmdType = int;
60 enum : imguiGfxCmdType
61 {
62     IMGUI_GFXCMD_RECT,
63     IMGUI_GFXCMD_TRIANGLE,
64     IMGUI_GFXCMD_LINE,
65     IMGUI_GFXCMD_TEXT,
66     IMGUI_GFXCMD_SCISSOR,
67 }
68 
69 struct imguiGfxRect
70 {
71     short x, y, w, h, r;
72 }
73 
74 struct imguiGfxText
75 {
76     short x, y, align_;
77     const(char)[] text;
78 }
79 
80 struct imguiGfxLine
81 {
82     short x0, y0, x1, y1, r;
83 }
84 
85 struct imguiGfxCmd
86 {
87     char type;
88     char flags;
89     byte[2] pad;
90     uint col;
91     uint areaId;
92 
93     union
94     {
95         imguiGfxLine line;
96         imguiGfxRect rect;
97         imguiGfxText text;
98     }
99 }
100 
101 struct ScrollArea
102 {
103     imguiGfxRect clipRect;
104 }
105 
106 void resetGfxCmdQueue()
107 {
108     g_gfxCmdQueueSize = 0;
109 }
110 
111 public void addGfxCmdScissor(int x, int y, int w, int h)
112 {
113     if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE)
114         return;
115     auto cmd = &g_gfxCmdQueue[g_gfxCmdQueueSize++];
116     cmd.type   = IMGUI_GFXCMD_SCISSOR;
117     cmd.areaId = g_state.inScrollArea ? g_state.areaId : 0;
118     cmd.flags  = x < 0 ? 0 : 1;         // on/off flag.
119     cmd.col    = 0;
120     cmd.rect.x = cast(short)x;
121     cmd.rect.y = cast(short)y;
122     cmd.rect.w = cast(short)w;
123     cmd.rect.h = cast(short)h;
124 }
125 
126 public void addGfxCmdRect(float x, float y, float w, float h, RGBA color)
127 {
128     if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE)
129         return;
130     auto cmd = &g_gfxCmdQueue[g_gfxCmdQueueSize++];
131     cmd.type   = IMGUI_GFXCMD_RECT;
132     cmd.areaId = g_state.inScrollArea ? g_state.areaId : 0;
133     cmd.flags  = 0;
134     cmd.col    = color.toPackedRGBA();
135     cmd.rect.x = cast(short)(x * 8.0f);
136     cmd.rect.y = cast(short)(y * 8.0f);
137     cmd.rect.w = cast(short)(w * 8.0f);
138     cmd.rect.h = cast(short)(h * 8.0f);
139     cmd.rect.r = 0;
140 }
141 
142 public void addGfxCmdLine(float x0, float y0, float x1, float y1, float r, RGBA color)
143 {
144     if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE)
145         return;
146     auto cmd = &g_gfxCmdQueue[g_gfxCmdQueueSize++];
147     cmd.type    = IMGUI_GFXCMD_LINE;
148     cmd.areaId  = g_state.inScrollArea ? g_state.areaId : 0;
149     cmd.flags   = 0;
150     cmd.col     = color.toPackedRGBA();
151     cmd.line.x0 = cast(short)(x0 * 8.0f);
152     cmd.line.y0 = cast(short)(y0 * 8.0f);
153     cmd.line.x1 = cast(short)(x1 * 8.0f);
154     cmd.line.y1 = cast(short)(y1 * 8.0f);
155     cmd.line.r  = cast(short)(r * 8.0f);
156 }
157 
158 public void addGfxCmdRoundedRect(float x, float y, float w, float h, float r, RGBA color)
159 {
160     if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE)
161         return;
162     auto cmd = &g_gfxCmdQueue[g_gfxCmdQueueSize++];
163     cmd.type   = IMGUI_GFXCMD_RECT;
164     cmd.areaId = g_state.inScrollArea ? g_state.areaId : 0;
165     cmd.flags  = 0;
166     cmd.col    = color.toPackedRGBA();
167     cmd.rect.x = cast(short)(x * 8.0f);
168     cmd.rect.y = cast(short)(y * 8.0f);
169     cmd.rect.w = cast(short)(w * 8.0f);
170     cmd.rect.h = cast(short)(h * 8.0f);
171     cmd.rect.r = cast(short)(r * 8.0f);
172 }
173 
174 public void addGfxCmdTriangle(int x, int y, int w, int h, int flags, RGBA color)
175 {
176     if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE)
177         return;
178     auto cmd = &g_gfxCmdQueue[g_gfxCmdQueueSize++];
179     cmd.type   = IMGUI_GFXCMD_TRIANGLE;
180     cmd.areaId = g_state.inScrollArea ? g_state.areaId : 0;
181     cmd.flags  = cast(byte)flags;
182     cmd.col    = color.toPackedRGBA();
183     cmd.rect.x = cast(short)(x * 8.0f);
184     cmd.rect.y = cast(short)(y * 8.0f);
185     cmd.rect.w = cast(short)(w * 8.0f);
186     cmd.rect.h = cast(short)(h * 8.0f);
187 }
188 
189 public void addGfxCmdText(int x, int y, int align_, const(char)[] text, RGBA color)
190 {
191     if (g_gfxCmdQueueSize >= GFXCMD_QUEUE_SIZE)
192         return;
193     auto cmd = &g_gfxCmdQueue[g_gfxCmdQueueSize++];
194     cmd.type       = IMGUI_GFXCMD_TEXT;
195     cmd.areaId     = g_state.inScrollArea ? g_state.areaId : 0;
196     cmd.flags      = 0;
197     cmd.col        = color.toPackedRGBA();
198     cmd.text.x     = cast(short)x;
199     cmd.text.y     = cast(short)y;
200     cmd.text.align_ = cast(short)align_;
201     cmd.text.text  = text;
202 }
203 
204 struct GuiState
205 {
206     bool left;
207     bool leftPressed, leftReleased;
208     int mx = -1, my = -1;
209     int scroll;
210     // 'unicode' value passed to updateInput.
211     dchar unicode;
212     // 'unicode' value passed to updateInput on previous frame.
213     //
214     // Used to detect that unicode (text) input has changed.
215     dchar lastUnicode;
216     // ID of the 'inputable' widget (widget we're entering input into, e.g. text input).
217     //
218     // A text input becomes 'inputable' when it is 'hot' and left-clicked.
219     //
220     // 0 if no widget is inputable
221     uint inputable;
222     uint active;
223     // The 'hot' widget (hovered over input widget).
224     //
225     // 0 if no widget is inputable
226     uint hot;
227     // The widget that will be 'hot' in the next frame.
228     uint hotToBe;
229     // These two are probably unused? (set but not read?)
230     bool isHot;
231     bool isActive;
232 
233     bool wentActive;
234     int dragX, dragY;
235     float dragOrig;
236     int widgetX, widgetY, widgetW = 100;
237     bool insideCurrentScroll;
238 
239     bool inScrollArea;
240     uint areaId;
241     uint widgetId;
242     ScrollArea[MAX_SCROLL_AREA] scrollArea;
243 }
244 
245 bool anyActive()
246 {
247     return g_state.active != 0;
248 }
249 
250 bool isActive(uint id)
251 {
252     return g_state.active == id;
253 }
254 
255 /// Is the widget with specified ID 'inputable' for e.g. text input?
256 bool isInputable(uint id)
257 {
258     return g_state.inputable == id;
259 }
260 
261 bool isHot(uint id)
262 {
263     return g_state.hot == id;
264 }
265 
266 bool inRect(int x, int y, int w, int h, bool checkScroll = true)
267 {
268     return (!checkScroll || g_state.insideCurrentScroll) && g_state.mx >= x && g_state.mx <= x + w && g_state.my >= y && g_state.my <= y + h;
269 }
270 
271 void clearInput()
272 {
273     g_state.leftPressed  = false;
274     g_state.leftReleased = false;
275     g_state.scroll       = 0;
276 }
277 
278 void clearActive()
279 {
280     g_state.active = 0;
281 
282     // mark all UI for this frame as processed
283     clearInput();
284 }
285 
286 void setActive(uint id)
287 {
288     g_state.active     = id;
289     g_state.inputable  = 0;
290     g_state.wentActive = true;
291 }
292 
293 // Set the inputable widget to the widget with specified ID.
294 //
295 // A text input becomes 'inputable' when it is 'hot' and left-clicked.
296 //
297 // 0 if no widget is inputable
298 void setInputable(uint id)
299 {
300     g_state.inputable = id;
301 }
302 
303 void setHot(uint id)
304 {
305     g_state.hotToBe = id;
306 }
307 
308 bool buttonLogic(uint id, bool over)
309 {
310     bool res = false;
311 
312     // process down
313     if (!anyActive())
314     {
315         if (over)
316             setHot(id);
317 
318         if (isHot(id) && g_state.leftPressed)
319             setActive(id);
320     }
321 
322     // if button is active, then react on left up
323     if (isActive(id))
324     {
325         g_state.isActive = true;
326 
327         if (over)
328             setHot(id);
329 
330         if (g_state.leftReleased)
331         {
332             if (isHot(id))
333                 res = true;
334             clearActive();
335         }
336     }
337 
338     // Not sure if this does anything (g_state.isHot doesn't seem to be used).
339     if (isHot(id))
340         g_state.isHot = true;
341 
342     return res;
343 }
344 
345 /** Input logic for text input fields.
346  *
347  * Params:
348  *
349  * id             = ID of the text input widget
350  * over           = Is the mouse hovering over the text input widget?
351  * forceInputable = Force the text input widget to be inputable regardless of whether it's
352  *                  hovered and clicked by the mouse or not.
353  */
354 void textInputLogic(uint id, bool over, bool forceInputable)
355 {
356     // If nothing else is active, we check for mouse over to make the widget hot in the 
357     // next frame, and if both hot and LMB is pressed (or forced), make it inputable.
358     if (!anyActive())
359     {
360         if (over)                                               { setHot(id); }
361         if (forceInputable || isHot(id) && g_state.leftPressed) { setInputable(id); }
362     }
363     // Not sure if this does anything (g_state.isHot doesn't seem to be used).
364     if (isHot(id)) { g_state.isHot = true; }
365 }
366 
367 /* Update user input on the beginning of a frame.
368  *
369  * Params:
370  *
371  * mx          = Mouse X position.
372  * my          = Mouse Y position.
373  * mbut        = Mouse buttons pressed (a combination of values of a $(D MouseButton)).
374  * scroll      = Mouse wheel movement.
375  * unicodeChar = Unicode text input from the keyboard (usually the unicode result of last
376  *               keypress).
377  */
378 void updateInput(int mx, int my, ubyte mbut, int scroll, dchar unicodeChar)
379 {
380     bool left = (mbut & MouseButton.left) != 0;
381 
382     g_state.mx = mx;
383     g_state.my = my;
384     g_state.leftPressed  = !g_state.left && left;
385     g_state.leftReleased = g_state.left && !left;
386     g_state.left         = left;
387 
388     g_state.scroll = scroll;
389 
390     // Ignore characters we can't draw
391     if(unicodeChar > maxCharacterCount()) { unicodeChar = 0; }
392     g_state.lastUnicode = g_state.unicode;
393     g_state.unicode     = unicodeChar;
394 }
395 
396 // Separate from gl3_renderer.getTextLength so api doesn't directly call renderer.
397 float getTextLength(const(char)[] text)
398 {
399     return imgui.gl3_renderer.getTextLength(text);
400 }
401 
402 const(imguiGfxCmd*) imguiGetRenderQueue()
403 {
404     return g_gfxCmdQueue.ptr;
405 }
406 
407 int imguiGetRenderQueueSize()
408 {
409     return g_gfxCmdQueueSize;
410 }