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 }