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 }