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