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