1 /* 2 Copyright © 2020, Inochi2D Project 3 Distributed under the 2-Clause BSD License, see LICENSE file. 4 5 Authors: 6 - Luna Nielsen 7 - Asahi Lina 8 */ 9 module creator.widgets.controller; 10 import creator.widgets; 11 import inochi2d; 12 import std.stdio; 13 14 struct EditableAxisPoint { 15 int origIndex; 16 bool fixed; 17 float value; 18 float normValue; 19 }; 20 21 /** 22 A Parameter controller 23 */ 24 bool incController(string strId, ref Parameter param, ImVec2 size, bool forceSnap = false, string grabParam = "") { 25 ImGuiWindow* window = igGetCurrentWindow(); 26 if (window.SkipItems) return false; 27 28 ImGuiID id = igGetID(strId.ptr, strId.ptr+strId.length); 29 30 ImVec2 avail; 31 igGetContentRegionAvail(&avail); 32 if (size.x <= 0) size.x = avail.x-size.x; 33 if (!param.isVec2) size.y = 32; 34 else if (size.y <= 0) size.y = avail.y-size.y; 35 36 ImGuiContext* ctx = igGetCurrentContext(); 37 ImGuiStyle* style = &ctx.Style; 38 ImGuiStorage* storage = igGetStateStorage(); 39 ImGuiIO* io = igGetIO(); 40 41 42 ImVec2 mPos; 43 ImVec2 vPos; 44 igGetCursorScreenPos(&vPos); 45 bool bModified = false; 46 47 if (param.isVec2) { 48 float oRectOffsetX = 24; 49 float oRectOffsetY = 12; 50 ImRect fRect = ImRect( 51 vPos, 52 ImVec2(vPos.x + size.x, vPos.y + size.y) 53 ); 54 55 ImRect oRect = ImRect( 56 ImVec2(vPos.x+oRectOffsetX, vPos.y+oRectOffsetY), 57 ImVec2((vPos.x + size.x)-oRectOffsetX, (vPos.y + size.y)-oRectOffsetY) 58 ); 59 60 igPushID(id); 61 62 igRenderFrame(oRect.Min, oRect.Max, igGetColorU32(ImGuiCol.FrameBg)); 63 64 float sDeltaX = param.max.x-param.min.x; 65 float sDeltaY = param.max.y-param.min.y; 66 67 ImVec2 vSecurity = ImVec2(15, 15); 68 ImRect frameBB = ImRect(ImVec2(oRect.Min.x - vSecurity.x, oRect.Min.y - vSecurity.y), ImVec2(oRect.Max.x + vSecurity.x, oRect.Max.y + vSecurity.y)); 69 70 bool shouldSnap = forceSnap || io.KeyShift; 71 bool hovered; 72 bool held; 73 bool pressed = igButtonBehavior(frameBB, igGetID("##Zone"), &hovered, &held); 74 if (hovered && igIsMouseDown(ImGuiMouseButton.Right)) { 75 held = true; 76 } 77 if ((grabParam == param.name) || (hovered && held)) { 78 igGetMousePos(&mPos); 79 ImVec2 vCursorPos = ImVec2(mPos.x - oRect.Min.x, mPos.y - oRect.Min.y); 80 81 param.value = vec2( 82 clamp(vCursorPos.x / (oRect.Max.x - oRect.Min.x) * sDeltaX + param.min.x, param.min.x, param.max.x), 83 clamp(vCursorPos.y / (oRect.Max.y - oRect.Min.y) * -sDeltaY + param.max.y, param.min.y, param.max.y) 84 ); 85 86 // Snap to closest point mode 87 if (shouldSnap) param.value = param.getClosestKeypointValue(); 88 89 bModified = true; 90 } 91 92 float fXLimit = 10f / ImRect_GetWidth(&oRect); 93 float fYLimit = 10f / ImRect_GetHeight(&oRect); 94 float fScaleX; 95 float fScaleY; 96 ImVec2 vCursorPos; 97 98 ImDrawList* drawList = igGetWindowDrawList(); 99 100 ImS32 uDotColor = igGetColorU32(ImVec4(1f, 0f, 0f, 1f)); 101 ImS32 uLineColor = igGetColorU32(style.Colors[ImGuiCol.Text]); 102 ImS32 uDotKeyColor = igGetColorU32(style.Colors[ImGuiCol.TextDisabled]); 103 ImS32 uDotKeyPartial = igGetColorU32(ImVec4(1f, 1f, 0f, 1f)); 104 ImS32 uDotKeyComplete = igGetColorU32(ImVec4(0f, 1f, 0f, 1f)); 105 106 // AXES LINES 107 foreach(xIdx; 0..param.axisPoints[0].length) { 108 float xVal = param.axisPoints[0][xIdx]; 109 float xPos = (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x; 110 111 ImDrawList_AddLineDashed( 112 drawList, 113 ImVec2( 114 xPos, 115 oRect.Min.y 116 ), 117 ImVec2( 118 xPos, 119 oRect.Max.y 120 ), 121 uDotKeyColor, 122 1f, 123 24, 124 1.2f 125 ); 126 127 } 128 129 foreach(yIdx; 0..param.axisPoints[1].length) { 130 float yVal = 1 - param.axisPoints[1][yIdx]; 131 float yPos = (oRect.Max.y - oRect.Min.y) * yVal + oRect.Min.y; 132 133 ImDrawList_AddLineDashed( 134 drawList, 135 ImVec2( 136 oRect.Min.x, 137 yPos, 138 ), 139 ImVec2( 140 oRect.Max.x, 141 yPos, 142 ), 143 uDotKeyColor, 144 1f, 145 40, 146 1.2f 147 ); 148 } 149 150 // OUTSIDE FRAME 151 ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, oRect.Min.y), ImVec2(oRect.Max.x, oRect.Min.y), uLineColor, 2f); 152 ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, oRect.Max.y), ImVec2(oRect.Max.x, oRect.Max.y), uLineColor, 2f); 153 ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, oRect.Min.y), ImVec2(oRect.Min.x, oRect.Max.y), uLineColor, 2f); 154 ImDrawList_AddLine(drawList, ImVec2(oRect.Max.x, oRect.Min.y), ImVec2(oRect.Max.x, oRect.Max.y), uLineColor, 2f); 155 156 // AXES POINTS 157 foreach(xIdx; 0..param.axisPoints[0].length) { 158 float xVal = param.axisPoints[0][xIdx]; 159 foreach(yIdx; 0..param.axisPoints[1].length) { 160 float yVal = 1 - param.axisPoints[1][yIdx]; 161 162 vCursorPos = ImVec2( 163 (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x, 164 (oRect.Max.y - oRect.Min.y) * yVal + oRect.Min.y 165 ); 166 167 ImDrawList_AddCircleFilled(drawList, vCursorPos, 6.0f, uDotKeyColor, 16); 168 169 bool isPartial = false; 170 bool isComplete = true; 171 foreach(binding; param.bindings) { 172 if (binding.getIsSet()[xIdx][yIdx]) { 173 isPartial = true; 174 } else { 175 isComplete = false; 176 } 177 } 178 179 if (isComplete && isPartial) 180 ImDrawList_AddCircleFilled(drawList, vCursorPos, 4f, uDotKeyComplete, 16); 181 else if (isPartial) 182 ImDrawList_AddCircleFilled(drawList, vCursorPos, 4f, uDotKeyPartial, 16); 183 } 184 } 185 186 // PARAM VALUE 187 fScaleX = (param.value.x - param.min.x) / sDeltaX; 188 fScaleY = 1 - (param.value.y - param.min.y) / sDeltaY; 189 vCursorPos = ImVec2( 190 (oRect.Max.x - oRect.Min.x) * fScaleX + oRect.Min.x, 191 (oRect.Max.y - oRect.Min.y) * fScaleY + oRect.Min.y 192 ); 193 194 ImDrawList_AddCircleFilled(drawList, vCursorPos, 4f, uDotColor, 16); 195 196 igPopID(); 197 198 igItemAdd(fRect, id); 199 igItemSize(size); 200 } else { 201 const float lineHeight = 16; 202 203 float oRectOffsetX = 24; 204 float oRectOffsetY = 12; 205 ImRect fRect = ImRect( 206 vPos, 207 ImVec2(vPos.x + size.x, vPos.y + size.y) 208 ); 209 210 ImRect oRect = ImRect( 211 ImVec2(vPos.x+oRectOffsetX, vPos.y+oRectOffsetY), 212 ImVec2((vPos.x + size.x)-oRectOffsetX, (vPos.y + size.y)-oRectOffsetY) 213 ); 214 215 igPushID(id); 216 217 igRenderFrame(oRect.Min, oRect.Max, igGetColorU32(ImGuiCol.FrameBg)); 218 float sDeltaX = param.max.x-param.min.x; 219 220 ImVec2 vSecurity = ImVec2(15, 15); 221 ImRect frameBB = ImRect(ImVec2(oRect.Min.x - vSecurity.x, oRect.Min.y - vSecurity.y), ImVec2(oRect.Max.x + vSecurity.x, oRect.Max.y + vSecurity.y)); 222 223 bool shouldSnap = forceSnap || io.KeyShift; 224 bool hovered; 225 bool held; 226 bool pressed = igButtonBehavior(frameBB, igGetID("##Zone"), &hovered, &held); 227 if (hovered && igIsMouseDown(ImGuiMouseButton.Right)) { 228 held = true; 229 } 230 if ((grabParam == param.name) || (hovered && held)) { 231 igGetMousePos(&mPos); 232 ImVec2 vCursorPos = ImVec2(mPos.x - oRect.Min.x, mPos.y - oRect.Min.y); 233 234 param.value.x = clamp(vCursorPos.x / (oRect.Max.x - oRect.Min.x) * sDeltaX + param.min.x, param.min.x, param.max.x); 235 236 // Snap to closest point mode 237 if (shouldSnap) { 238 vec2 closestPoint = param.value; 239 float closestDist = float.infinity; 240 foreach(xIdx; 0..param.axisPoints[0].length) { 241 vec2 pos = vec2( 242 (param.max.x - param.min.x) * param.axisPoints[0][xIdx] + param.min.x, 243 0 244 ); 245 246 float dist = param.value.distance(pos); 247 if (dist < closestDist) { 248 closestDist = dist; 249 closestPoint = pos; 250 } 251 } 252 253 // clamp to closest point 254 param.value = closestPoint; 255 } 256 257 bModified = true; 258 } 259 260 float fYCenter = oRect.Min.y+(ImRect_GetHeight(&oRect)/2); 261 float fYCenterLineLen1th = lineHeight/2; 262 float fScaleX; 263 float fScaleY; 264 ImVec2 vCursorPos; 265 266 ImDrawList* drawList = igGetWindowDrawList(); 267 268 ImS32 uDotColor = igGetColorU32(ImVec4(1f, 0f, 0f, 1f)); 269 ImS32 uLineColor = igGetColorU32(style.Colors[ImGuiCol.Text]); 270 ImS32 uDotKeyColor = igGetColorU32(style.Colors[ImGuiCol.TextDisabled]); 271 ImS32 uDotKeyFilled = igGetColorU32(ImVec4(0f, 1f, 0f, 1f)); 272 273 // AXES LINES 274 foreach(xIdx; 0..param.axisPoints[0].length) { 275 float xVal = param.axisPoints[0][xIdx]; 276 float xPos = (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x; 277 278 ImDrawList_AddLine( 279 drawList, 280 ImVec2( 281 xPos, 282 fYCenter-fYCenterLineLen1th-(fYCenterLineLen1th/4) 283 ), 284 ImVec2( 285 xPos, 286 fYCenter+fYCenterLineLen1th 287 ), 288 uLineColor, 289 2f, 290 ); 291 292 } 293 294 // REF LINE 295 ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, fYCenter), ImVec2(oRect.Max.x, fYCenter), uLineColor, 2f); 296 297 // AXES POINTS 298 foreach(xIdx; 0..param.axisPoints[0].length) { 299 float xVal = param.axisPoints[0][xIdx]; 300 301 vCursorPos = ImVec2( 302 (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x, 303 fYCenter 304 ); 305 306 ImDrawList_AddCircleFilled(drawList, vCursorPos, 6.0f, uDotKeyColor, 16); 307 foreach(binding; param.bindings) { 308 if (binding.getIsSet()[xIdx][0]) { 309 ImDrawList_AddCircleFilled(drawList, vCursorPos, 4f, uDotKeyFilled, 16); 310 break; 311 } 312 } 313 } 314 315 // PARAM VALUE 316 fScaleX = (param.value.x - param.min.x) / sDeltaX; 317 vCursorPos = ImVec2( 318 (oRect.Max.x - oRect.Min.x) * fScaleX + oRect.Min.x, 319 fYCenter 320 ); 321 322 ImDrawList_AddCircleFilled(drawList, vCursorPos, 4f, uDotColor, 16); 323 324 igPopID(); 325 326 igItemAdd(fRect, id); 327 igItemSize(size); 328 } 329 330 return bModified; 331 } 332 333 /** 334 A fake controller that lets you demonstrate additional axis points 335 */ 336 void incControllerAxisDemo(string strId, ref Parameter param, ref EditableAxisPoint[][2] axisPoints, ImVec2 size) { 337 ImGuiWindow* window = igGetCurrentWindow(); 338 if (window.SkipItems) return; 339 340 ImGuiID id = igGetID(strId.ptr, strId.ptr+strId.length); 341 342 ImVec2 avail; 343 igGetContentRegionAvail(&avail); 344 if (size.x <= 0) size.x = avail.x-size.x; 345 if (!param.isVec2) size.y = 32; 346 else if (size.y <= 0) size.y = avail.y-size.y; 347 348 ImGuiContext* ctx = igGetCurrentContext(); 349 ImGuiStyle* style = &ctx.Style; 350 ImGuiStorage* storage = igGetStateStorage(); 351 ImGuiIO* io = igGetIO(); 352 353 354 ImVec2 mPos; 355 ImVec2 vPos; 356 igGetCursorScreenPos(&vPos); 357 358 if (param.isVec2) { 359 float oRectOffsetX = 24; 360 float oRectOffsetY = 12; 361 ImRect fRect = ImRect( 362 vPos, 363 ImVec2(vPos.x + size.x, vPos.y + size.y) 364 ); 365 366 ImRect oRect = ImRect( 367 ImVec2(vPos.x+oRectOffsetX, vPos.y+oRectOffsetY), 368 ImVec2((vPos.x + size.x)-oRectOffsetX, (vPos.y + size.y)-oRectOffsetY) 369 ); 370 371 igPushID(id); 372 373 igRenderFrame(oRect.Min, oRect.Max, igGetColorU32(ImGuiCol.FrameBg)); 374 ImVec2 vCursorPos; 375 ImDrawList* drawList = igGetWindowDrawList(); 376 377 ImS32 uLineColor = igGetColorU32(style.Colors[ImGuiCol.Text]); 378 ImS32 uDotKeyColor = igGetColorU32(style.Colors[ImGuiCol.TextDisabled]); 379 ImS32 uDotKeyFilled = igGetColorU32(ImVec4(0f, 1f, 0f, 1f)); 380 381 // AXES LINES 382 foreach(xIdx; 0..axisPoints[0].length) { 383 float xVal = axisPoints[0][xIdx].normValue; 384 float xPos = (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x; 385 386 ImDrawList_AddLineDashed( 387 drawList, 388 ImVec2( 389 xPos, 390 oRect.Min.y 391 ), 392 ImVec2( 393 xPos, 394 oRect.Max.y 395 ), 396 uDotKeyColor, 397 1f, 398 24, 399 1.2f 400 ); 401 } 402 403 foreach(yIdx; 0..axisPoints[1].length) { 404 float yVal = 1 - axisPoints[1][yIdx].normValue; 405 float yPos = (oRect.Max.y - oRect.Min.y) * yVal + oRect.Min.y; 406 407 ImDrawList_AddLineDashed( 408 drawList, 409 ImVec2( 410 oRect.Min.x, 411 yPos, 412 ), 413 ImVec2( 414 oRect.Max.x, 415 yPos, 416 ), 417 uDotKeyColor, 418 1f, 419 40, 420 1.2f 421 ); 422 } 423 424 // OUTSIDE FRAME 425 ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, oRect.Min.y), ImVec2(oRect.Max.x, oRect.Min.y), uLineColor, 2f); 426 ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, oRect.Max.y), ImVec2(oRect.Max.x, oRect.Max.y), uLineColor, 2f); 427 ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, oRect.Min.y), ImVec2(oRect.Min.x, oRect.Max.y), uLineColor, 2f); 428 ImDrawList_AddLine(drawList, ImVec2(oRect.Max.x, oRect.Min.y), ImVec2(oRect.Max.x, oRect.Max.y), uLineColor, 2f); 429 430 // AXES POINTS 431 foreach(xIdx; 0..axisPoints[0].length) { 432 float xVal = axisPoints[0][xIdx].normValue; 433 foreach(yIdx; 0..axisPoints[1].length) { 434 float yVal = 1 - axisPoints[1][yIdx].normValue; 435 436 vCursorPos = ImVec2( 437 (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x, 438 (oRect.Max.y - oRect.Min.y) * yVal + oRect.Min.y 439 ); 440 441 ImDrawList_AddCircleFilled(drawList, vCursorPos, 6.0f, uDotKeyColor, 16); 442 } 443 } 444 igPopID(); 445 446 igItemAdd(fRect, id); 447 igItemSize(size); 448 } else { 449 const float lineHeight = 16; 450 451 float oRectOffsetX = 24; 452 float oRectOffsetY = 12; 453 ImRect fRect = ImRect( 454 vPos, 455 ImVec2(vPos.x + size.x, vPos.y + size.y) 456 ); 457 458 ImRect oRect = ImRect( 459 ImVec2(vPos.x+oRectOffsetX, vPos.y+oRectOffsetY), 460 ImVec2((vPos.x + size.x)-oRectOffsetX, (vPos.y + size.y)-oRectOffsetY) 461 ); 462 463 igPushID(id); 464 465 igRenderFrame(oRect.Min, oRect.Max, igGetColorU32(ImGuiCol.FrameBg)); 466 float sDeltaX = param.max.x-param.min.x; 467 468 ImVec2 vSecurity = ImVec2(15, 15); 469 ImRect frameBB = ImRect(ImVec2(oRect.Min.x - vSecurity.x, oRect.Min.y - vSecurity.y), ImVec2(oRect.Max.x + vSecurity.x, oRect.Max.y + vSecurity.y)); 470 float fYCenter = oRect.Min.y+(ImRect_GetHeight(&oRect)/2); 471 float fYCenterLineLen1th = lineHeight/2; 472 ImVec2 vCursorPos; 473 474 ImDrawList* drawList = igGetWindowDrawList(); 475 476 ImS32 uLineColor = igGetColorU32(style.Colors[ImGuiCol.Text]); 477 ImS32 uDotKeyColor = igGetColorU32(style.Colors[ImGuiCol.TextDisabled]); 478 ImS32 uDotKeyFilled = igGetColorU32(ImVec4(0f, 1f, 0f, 1f)); 479 480 // AXES LINES 481 foreach(xIdx; 0..axisPoints[0].length) { 482 float xVal = axisPoints[0][xIdx].normValue; 483 float xPos = (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x; 484 485 ImDrawList_AddLine( 486 drawList, 487 ImVec2( 488 xPos, 489 fYCenter-fYCenterLineLen1th-(fYCenterLineLen1th/4) 490 ), 491 ImVec2( 492 xPos, 493 fYCenter+fYCenterLineLen1th 494 ), 495 uLineColor, 496 2f, 497 ); 498 499 } 500 501 // REF LINE 502 ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, fYCenter), ImVec2(oRect.Max.x, fYCenter), uLineColor, 2f); 503 504 // AXES POINTS 505 foreach(xIdx; 0..axisPoints[0].length) { 506 float xVal = axisPoints[0][xIdx].normValue; 507 508 vCursorPos = ImVec2( 509 (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x, 510 fYCenter 511 ); 512 513 ImDrawList_AddCircleFilled(drawList, vCursorPos, 6.0f, uDotKeyColor, 16); 514 } 515 516 igPopID(); 517 518 igItemAdd(fRect, id); 519 igItemSize(size); 520 } 521 } 522 523 524 /** 525 Draws dashed lines 526 */ 527 void ImDrawList_AddLineDashed(ImDrawList* self, ImVec2 a, ImVec2 b, ImU32 col, float thickness = 1f, int segments = 50, float lineScale = 1f) { 528 if ((col >> 24) == 0) 529 return; 530 531 ImVec2 dir = ImVec2( 532 (b.x - a.x) / segments, 533 (b.y - a.y) / segments 534 ); 535 536 bool on = true; 537 ImVec2[2] points; 538 foreach(i; 0..segments) { 539 points[i%2] = ImVec2(a.x + dir.x * i, a.y + dir.y * i); 540 541 if (i != 0 && i%2 == 0) { 542 if (on) { 543 ImDrawList_PathLineTo(self, ImVec2(points[0].x-(dir.x*lineScale), points[0].y-(dir.y*lineScale))); 544 ImDrawList_PathLineTo(self, ImVec2(points[1].x+(dir.x*lineScale), points[1].y+(dir.y*lineScale))); 545 ImDrawList_PathStroke(self, col, ImDrawFlags.None, thickness); 546 } 547 548 on = !on; 549 } 550 } 551 ImDrawList_PathClear(self); 552 553 }