1 /* 2 Copyright © 2020, Inochi2D Project 3 Distributed under the 2-Clause BSD License, see LICENSE file. 4 5 Authors: Luna Nielsen 6 */ 7 module creator.panels.parameters; 8 import creator.viewport.model.deform; 9 import creator.panels; 10 import creator.ext.param; 11 import creator.widgets; 12 import creator.windows; 13 import creator.core; 14 import creator.actions; 15 import creator.ext.param; 16 import creator; 17 import std.string; 18 import inochi2d; 19 import i18n; 20 import std.uni : toLower; 21 import std.stdio; 22 import creator.utils; 23 import std.algorithm.sorting : sort; 24 import std.algorithm.mutation : remove; 25 26 private { 27 28 ParameterBinding[][Node] cParamBindingEntries; 29 ParameterBinding[][Node] cParamBindingEntriesAll; 30 Node[] cAllBoundNodes; 31 ParameterBinding[BindTarget] cSelectedBindings; 32 Node[] cCompatibleNodes; 33 vec2u cParamPoint; 34 vec2u cClipboardPoint; 35 ParameterBinding[BindTarget] cClipboardBindings; 36 37 void refreshBindingList(Parameter param) { 38 // Filter selection to remove anything that went away 39 ParameterBinding[BindTarget] newSelectedBindings; 40 41 cParamBindingEntriesAll.clear(); 42 foreach(ParameterBinding binding; param.bindings) { 43 BindTarget target = binding.getTarget(); 44 if (target in cSelectedBindings) newSelectedBindings[target] = binding; 45 cParamBindingEntriesAll[binding.getNode()] ~= binding; 46 } 47 cAllBoundNodes = cParamBindingEntriesAll.keys.dup; 48 cAllBoundNodes.sort!((x, y) => x.name < y.name); 49 cSelectedBindings = newSelectedBindings; 50 paramPointChanged(param); 51 } 52 53 void paramPointChanged(Parameter param) { 54 cParamBindingEntries.clear(); 55 56 cParamPoint = param.findClosestKeypoint(); 57 foreach(ParameterBinding binding; param.bindings) { 58 if (binding.isSet(cParamPoint)) { 59 cParamBindingEntries[binding.getNode()] ~= binding; 60 } 61 } 62 } 63 64 void mirrorAll(Parameter param, uint axis) { 65 auto action = new ParameterChangeBindingsAction("Mirror All", param, null); 66 foreach(ParameterBinding binding; param.bindings) { 67 uint xCount = param.axisPointCount(0); 68 uint yCount = param.axisPointCount(1); 69 foreach(x; 0..xCount) { 70 foreach(y; 0..yCount) { 71 vec2u index = vec2u(x, y); 72 if (binding.isSet(index)) { 73 binding.scaleValueAt(index, axis, -1); 74 } 75 } 76 } 77 } 78 action.updateNewState(); 79 incActionPush(action); 80 } 81 82 void mirroredAutofill(Parameter param, uint axis, float min, float max) { 83 auto action = new ParameterChangeBindingsAction("Mirror Auto Fill", param, null); 84 foreach(ParameterBinding binding; param.bindings) { 85 uint xCount = param.axisPointCount(0); 86 uint yCount = param.axisPointCount(1); 87 foreach(x; 0..xCount) { 88 float offX = param.axisPoints[0][x]; 89 if (axis == 0 && (offX < min || offX > max)) continue; 90 foreach(y; 0..yCount) { 91 float offY = param.axisPoints[1][y]; 92 if (axis == 1 && (offY < min || offY > max)) continue; 93 94 vec2u index = vec2u(x, y); 95 if (!binding.isSet(index)) binding.extrapolateValueAt(index, axis); 96 } 97 } 98 } 99 action.updateNewState(); 100 incActionPush(action); 101 } 102 103 void fixScales(Parameter param) { 104 auto action = new ParameterChangeBindingsAction("Fix Scale", param, null); 105 foreach(ParameterBinding binding; param.bindings) { 106 switch(binding.getName()) { 107 case "transform.s.x": 108 case "transform.s.y": 109 if (ValueParameterBinding b = cast(ValueParameterBinding)binding) { 110 uint xCount = param.axisPointCount(0); 111 uint yCount = param.axisPointCount(1); 112 foreach(x; 0..xCount) { 113 foreach(y; 0..yCount) { 114 vec2u index = vec2u(x, y); 115 if (b.isSet(index)) { 116 b.values[x][y] += 1; 117 } 118 } 119 } 120 b.reInterpolate(); 121 } 122 break; 123 default: break; 124 } 125 } 126 action.updateNewState(); 127 incActionPush(action); 128 } 129 130 Node[] getCompatibleNodes() { 131 Node thisNode = null; 132 133 foreach(binding; cSelectedBindings.byValue()) { 134 if (thisNode is null) thisNode = binding.getNode(); 135 else if (!(binding.getNode() is thisNode)) return null; 136 } 137 if (thisNode is null) return null; 138 139 Node[] compatible; 140 nodeLoop: foreach(otherNode; cParamBindingEntriesAll.byKey()) { 141 if (otherNode is thisNode) continue; 142 143 foreach(binding; cSelectedBindings.byValue()) { 144 if (!binding.isCompatibleWithNode(otherNode)) 145 continue nodeLoop; 146 } 147 compatible ~= otherNode; 148 } 149 150 return compatible; 151 } 152 153 void copySelectionToNode(Parameter param, Node target) { 154 Node src = cSelectedBindings.keys[0].node; 155 156 foreach(binding; cSelectedBindings.byValue()) { 157 assert(binding.getNode() is src, "selection mismatch"); 158 159 ParameterBinding b = param.getOrAddBinding(target, binding.getName()); 160 binding.copyKeypointToBinding(cParamPoint, b, cParamPoint); 161 } 162 163 refreshBindingList(param); 164 } 165 166 void swapSelectionWithNode(Parameter param, Node target) { 167 Node src = cSelectedBindings.keys[0].node; 168 169 foreach(binding; cSelectedBindings.byValue()) { 170 assert(binding.getNode() is src, "selection mismatch"); 171 172 ParameterBinding b = param.getOrAddBinding(target, binding.getName()); 173 binding.swapKeypointWithBinding(cParamPoint, b, cParamPoint); 174 } 175 176 refreshBindingList(param); 177 } 178 179 void keypointActions(Parameter param, ParameterBinding[] bindings) { 180 if (igMenuItem(__("Unset"), "", false, true)) { 181 auto action = new ParameterChangeBindingsValueAction("unset", param, bindings, cParamPoint.x, cParamPoint.y); 182 foreach(binding; bindings) { 183 binding.unset(cParamPoint); 184 } 185 action.updateNewState(); 186 incActionPush(action); 187 incViewportNodeDeformNotifyParamValueChanged(); 188 } 189 if (igMenuItem(__("Set to current"), "", false, true)) { 190 auto action = new ParameterChangeBindingsValueAction("setCurrent", param, bindings, cParamPoint.x, cParamPoint.y); 191 foreach(binding; bindings) { 192 binding.setCurrent(cParamPoint); 193 } 194 action.updateNewState(); 195 incActionPush(action); 196 incViewportNodeDeformNotifyParamValueChanged(); 197 } 198 if (igMenuItem(__("Reset"), "", false, true)) { 199 auto action = new ParameterChangeBindingsValueAction("reset", param, bindings, cParamPoint.x, cParamPoint.y); 200 foreach(binding; bindings) { 201 binding.reset(cParamPoint); 202 } 203 action.updateNewState(); 204 incActionPush(action); 205 incViewportNodeDeformNotifyParamValueChanged(); 206 } 207 if (igMenuItem(__("Invert"), "", false, true)) { 208 auto action = new ParameterChangeBindingsValueAction("invert", param, bindings, cParamPoint.x, cParamPoint.y); 209 foreach(binding; bindings) { 210 binding.scaleValueAt(cParamPoint, -1, -1); 211 } 212 action.updateNewState(); 213 incActionPush(action); 214 incViewportNodeDeformNotifyParamValueChanged(); 215 } 216 if (igBeginMenu(__("Mirror"), true)) { 217 if (igMenuItem(__("Horizontally"), "", false, true)) { 218 auto action = new ParameterChangeBindingsValueAction("mirror Horizontally", param, bindings, cParamPoint.x, cParamPoint.y); 219 foreach(binding; bindings) { 220 binding.scaleValueAt(cParamPoint, 0, -1); 221 } 222 action.updateNewState(); 223 incActionPush(action); 224 incViewportNodeDeformNotifyParamValueChanged(); 225 } 226 if (igMenuItem(__("Vertically"), "", false, true)) { 227 auto action = new ParameterChangeBindingsValueAction("mirror Vertically", param, bindings, cParamPoint.x, cParamPoint.y); 228 foreach(binding; bindings) { 229 binding.scaleValueAt(cParamPoint, 1, -1); 230 } 231 action.updateNewState(); 232 incActionPush(action); 233 incViewportNodeDeformNotifyParamValueChanged(); 234 } 235 igEndMenu(); 236 } 237 if (param.isVec2) { 238 if (igBeginMenu(__("Set from mirror"), true)) { 239 if (igMenuItem(__("Horizontally"), "", false, true)) { 240 auto action = new ParameterChangeBindingsValueAction("set From Mirror (Horizontally)", param, bindings, cParamPoint.x, cParamPoint.y); 241 foreach(binding; bindings) { 242 binding.extrapolateValueAt(cParamPoint, 0); 243 } 244 action.updateNewState(); 245 incActionPush(action); 246 incViewportNodeDeformNotifyParamValueChanged(); 247 } 248 if (igMenuItem(__("Vertically"), "", false, true)) { 249 auto action = new ParameterChangeBindingsValueAction("set From Mirror (Vertically)", param, bindings, cParamPoint.x, cParamPoint.y); 250 foreach(binding; bindings) { 251 binding.extrapolateValueAt(cParamPoint, 1); 252 } 253 action.updateNewState(); 254 incActionPush(action); 255 incViewportNodeDeformNotifyParamValueChanged(); 256 } 257 if (igMenuItem(__("Diagonally"), "", false, true)) { 258 auto action = new ParameterChangeBindingsValueAction("set From Mirror (Diagonally)", param, bindings, cParamPoint.x, cParamPoint.y); 259 foreach(binding; bindings) { 260 binding.extrapolateValueAt(cParamPoint, -1); 261 } 262 action.updateNewState(); 263 incActionPush(action); 264 incViewportNodeDeformNotifyParamValueChanged(); 265 } 266 igEndMenu(); 267 } 268 } else { 269 if (igMenuItem(__("Set from mirror"), "", false, true)) { 270 auto action = new ParameterChangeBindingsValueAction("set From Mirror", param, bindings, cParamPoint.x, cParamPoint.y); 271 foreach(binding; bindings) { 272 binding.extrapolateValueAt(cParamPoint, 0); 273 } 274 action.updateNewState(); 275 incActionPush(action); 276 incViewportNodeDeformNotifyParamValueChanged(); 277 } 278 } 279 280 if (igMenuItem(__("Copy"), "", false, true)) { 281 cClipboardPoint = cParamPoint; 282 cClipboardBindings.clear(); 283 foreach(binding; bindings) { 284 cClipboardBindings[binding.getTarget()] = binding; 285 } 286 } 287 288 if (igMenuItem(__("Paste"), "", false, true)) { 289 if (bindings.length == 1 && cClipboardBindings.length == 1 && 290 bindings[0].isCompatibleWithNode(cClipboardBindings.values[0].getNode())) { 291 auto action = new ParameterChangeBindingsValueAction("set From Mirror", param, bindings,cParamPoint.x, cParamPoint.y); 292 cClipboardBindings.values[0].copyKeypointToBinding(cClipboardPoint, bindings[0], cParamPoint); 293 action.updateNewState(); 294 incActionPush(action); 295 } else { 296 foreach(binding; bindings) { 297 if (binding.getTarget() in cClipboardBindings) { 298 auto action = new ParameterChangeBindingsValueAction("set From Mirror", param, bindings, cParamPoint.x, cParamPoint.y); 299 ParameterBinding origBinding = cClipboardBindings[binding.getTarget()]; 300 origBinding.copyKeypointToBinding(cClipboardPoint, binding, cParamPoint); 301 } 302 } 303 } 304 } 305 306 } 307 308 void bindingList(Parameter param) { 309 if (incBeginCategory(__("Bindings"))) { 310 refreshBindingList(param); 311 312 auto io = igGetIO(); 313 auto style = igGetStyle(); 314 ImS32 inactiveColor = igGetColorU32(style.Colors[ImGuiCol.TextDisabled]); 315 316 igBeginChild("BindingList", ImVec2(0, 256), false); 317 igPushStyleVar(ImGuiStyleVar.CellPadding, ImVec2(4, 1)); 318 igPushStyleVar(ImGuiStyleVar.IndentSpacing, 14); 319 320 foreach(node; cAllBoundNodes) { 321 ParameterBinding[] allBindings = cParamBindingEntriesAll[node]; 322 ParameterBinding[] *bindings = (node in cParamBindingEntries); 323 324 // Figure out if node is selected ( == all bindings selected) 325 bool nodeSelected = true; 326 bool someSelected = false; 327 foreach(binding; allBindings) { 328 if ((binding.getTarget() in cSelectedBindings) is null) 329 nodeSelected = false; 330 else 331 someSelected = true; 332 } 333 334 ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.OpenOnArrow; 335 if (nodeSelected) 336 flags |= ImGuiTreeNodeFlags.Selected; 337 338 if (bindings is null) igPushStyleColor(ImGuiCol.Text, inactiveColor); 339 string nodeName = incTypeIdToIcon(node.typeId) ~ " " ~ node.name; 340 if (igTreeNodeEx(cast(void*)node.uuid, flags, nodeName.toStringz)) { 341 342 if (bindings is null) igPopStyleColor(); 343 if (igBeginPopup("###BindingPopup")) { 344 if (igMenuItem(__("Remove"), "", false, true)) { 345 auto action = new GroupAction(); 346 foreach(binding; cSelectedBindings.byValue()) { 347 action.addAction(new ParameterBindingRemoveAction(param, binding)); 348 param.removeBinding(binding); 349 } 350 incActionPush(action); 351 incViewportNodeDeformNotifyParamValueChanged(); 352 } 353 354 keypointActions(param, cSelectedBindings.values); 355 356 if (igBeginMenu(__("Interpolation Mode"), true)) { 357 if (igMenuItem(__("Nearest"), "", false, true)) { 358 foreach(binding; cSelectedBindings.values) { 359 binding.interpolateMode = InterpolateMode.Nearest; 360 } 361 incViewportNodeDeformNotifyParamValueChanged(); 362 } 363 if (igMenuItem(__("Linear"), "", false, true)) { 364 foreach(binding; cSelectedBindings.values) { 365 binding.interpolateMode = InterpolateMode.Linear; 366 } 367 incViewportNodeDeformNotifyParamValueChanged(); 368 } 369 igEndMenu(); 370 } 371 372 bool haveCompatible = cCompatibleNodes.length > 0; 373 if (igBeginMenu(__("Copy to"), haveCompatible)) { 374 foreach(cNode; cCompatibleNodes) { 375 if (igMenuItem(cNode.name.toStringz, "", false, true)) { 376 copySelectionToNode(param, cNode); 377 } 378 } 379 igEndMenu(); 380 } 381 if (igBeginMenu(__("Swap with"), haveCompatible)) { 382 foreach(cNode; cCompatibleNodes) { 383 if (igMenuItem(cNode.name.toStringz, "", false, true)) { 384 swapSelectionWithNode(param, cNode); 385 } 386 } 387 igEndMenu(); 388 } 389 390 igEndPopup(); 391 } 392 if (igIsItemClicked(ImGuiMouseButton.Right)) { 393 if (!someSelected) { 394 cSelectedBindings.clear(); 395 foreach(binding; allBindings) { 396 cSelectedBindings[binding.getTarget()] = binding; 397 } 398 } 399 cCompatibleNodes = getCompatibleNodes(); 400 igOpenPopup("###BindingPopup"); 401 } 402 403 // Node selection logic 404 if (igIsItemClicked(ImGuiMouseButton.Left) && !igIsItemToggledOpen()) { 405 406 // Select the node you've clicked in the bindings list 407 if (incNodeInSelection(node)) { 408 incFocusCamera(node); 409 } else incSelectNode(node); 410 411 if (!io.KeyCtrl) { 412 cSelectedBindings.clear(); 413 nodeSelected = false; 414 } 415 foreach(binding; allBindings) { 416 if (nodeSelected) cSelectedBindings.remove(binding.getTarget()); 417 else cSelectedBindings[binding.getTarget()] = binding; 418 } 419 } 420 421 // Iterate over bindings 422 foreach(binding; allBindings) { 423 ImGuiTreeNodeFlags flags2 = 424 ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.OpenOnArrow | 425 ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen; 426 427 bool selected = cast(bool)(binding.getTarget() in cSelectedBindings); 428 if (selected) flags2 |= ImGuiTreeNodeFlags.Selected; 429 430 // Style as inactive if not set at this keypoint 431 if (!binding.isSet(cParamPoint)) 432 igPushStyleColor(ImGuiCol.Text, inactiveColor); 433 434 435 // Binding entry 436 auto value = cast(ValueParameterBinding)binding; 437 string label; 438 if (value && binding.isSet(cParamPoint)) { 439 label = format("%s (%.02f)", binding.getName(), value.getValue(cParamPoint)); 440 } else { 441 label = binding.getName(); 442 } 443 444 // NOTE: This is a leaf node so it should NOT be popped. 445 const(char)* bid = binding.getName().toStringz; 446 igTreeNodeEx(bid, flags2, label.toStringz); 447 if (!binding.isSet(cParamPoint)) igPopStyleColor(); 448 449 // Binding selection logic 450 if (igIsItemClicked(ImGuiMouseButton.Right)) { 451 if (!selected) { 452 cSelectedBindings.clear(); 453 cSelectedBindings[binding.getTarget()] = binding; 454 } 455 cCompatibleNodes = getCompatibleNodes(); 456 igOpenPopup("###BindingPopup"); 457 } 458 if (igIsItemClicked(ImGuiMouseButton.Left)) { 459 if (!io.KeyCtrl) { 460 cSelectedBindings.clear(); 461 selected = false; 462 } 463 if (selected) cSelectedBindings.remove(binding.getTarget()); 464 else cSelectedBindings[binding.getTarget()] = binding; 465 } 466 } 467 468 igTreePop(); 469 } else if (bindings is null) igPopStyleColor(); 470 } 471 472 igPopStyleVar(); 473 igPopStyleVar(); 474 igEndChild(); 475 } 476 incEndCategory(); 477 } 478 479 void pushColorScheme(vec3 color) { 480 float h, s, v; 481 igColorConvertRGBtoHSV(color.r, color.g, color.b, &h, &s, &v); 482 483 float maxS = lerp(1, 0.60, v); 484 485 vec3 c = color; 486 igColorConvertHSVtoRGB( 487 h, 488 clamp(lerp(s, s-0.20, v), 0, maxS), 489 clamp(v-0.15, 0.15, 0.90), 490 &c.vector[0], &c.vector[1], &c.vector[2] 491 ); 492 igPushStyleColor(ImGuiCol.FrameBg, ImVec4(c.r, c.g, c.b, 1)); 493 494 495 maxS = lerp(1, 0.60, v); 496 igColorConvertHSVtoRGB( 497 h, 498 lerp( 499 clamp(s-0.25, 0, maxS), 500 clamp(s+0.25, 0, maxS), 501 s 502 ), 503 v <= 0.55 ? 504 clamp(v+0.25, 0.45, 0.95) : 505 clamp(v-(0.25*(1+v)), 0.30, 1), 506 &c.vector[0], &c.vector[1], &c.vector[2] 507 ); 508 igPushStyleColor(ImGuiCol.TextDisabled, ImVec4(c.r, c.g, c.b, 1)); 509 } 510 511 void popColorScheme() { 512 igPopStyleColor(2); 513 } 514 515 ptrdiff_t findParamIndex(ref Parameter[] paramArr, Parameter param) { 516 import std.algorithm.searching : countUntil; 517 ptrdiff_t idx = paramArr.countUntil(param); 518 return idx; 519 } 520 ParamDragDropData* dragDropData; 521 522 bool removeParameter(Parameter param) { 523 ExParameterGroup parent = null; 524 ptrdiff_t idx = -1; 525 526 mloop: foreach(i, iparam; incActivePuppet.parameters) { 527 if (iparam.uuid == param.uuid) { 528 idx = i; 529 break mloop; 530 } 531 532 if (ExParameterGroup group = cast(ExParameterGroup)iparam) { 533 foreach(x, ref xparam; group.children) { 534 if (xparam.uuid == param.uuid) { 535 idx = x; 536 parent = group; 537 break mloop; 538 } 539 } 540 } 541 } 542 543 if (idx < 0) return false; 544 545 if (parent) { 546 if (parent.children.length > 1) parent.children = parent.children.remove(idx); 547 else parent.children.length = 0; 548 } else { 549 if (incActivePuppet().parameters.length > 1) incActivePuppet().parameters = incActivePuppet().parameters.remove(idx); 550 else incActivePuppet().parameters.length = 0; 551 } 552 553 return true; 554 } 555 556 void moveParameter(Parameter from, ExParameterGroup to = null, int index = 0) { 557 if (!removeParameter(from)) return; 558 import std.array : insertInPlace; 559 560 // Map to bounds 561 if (index < 0) index = 0; 562 else if (to && index > to.children.length) index = cast(int)to.children.length-1; 563 else if (index > incActivePuppet().parameters.length) index = cast(int)incActivePuppet().parameters.length-1; 564 565 // Insert 566 if (to) to.children.insertInPlace(index, from); 567 else incActivePuppet().parameters.insertInPlace(index, from); 568 } 569 570 ExParameterGroup createParamGroup(int index = 0) { 571 import std.array : insertInPlace; 572 573 if (index < 0) index = 0; 574 else if (index > incActivePuppet().parameters.length) index = cast(int)incActivePuppet().parameters.length-1; 575 576 auto group = new ExParameterGroup(_("New Parameter Group")); 577 incActivePuppet().parameters.insertInPlace(index, group); 578 return group; 579 } 580 } 581 582 struct ParamDragDropData { 583 Parameter param; 584 } 585 586 /** 587 Generates a parameter view 588 */ 589 void incParameterView(bool armedParam=false)(size_t idx, Parameter param, string* grabParam, bool canGroup, ref Parameter[] paramArr, vec3 groupColor = vec3.init) { 590 igPushID(cast(void*)param); 591 scope(exit) igPopID(); 592 593 594 bool open; 595 if (!groupColor.isFinite) open = incBeginCategory(param.name.toStringz); 596 else open = incBeginCategory(param.name.toStringz, ImVec4(groupColor.r, groupColor.g, groupColor.b, 1)); 597 598 if(igBeginDragDropSource(ImGuiDragDropFlags.SourceAllowNullID)) { 599 if (!dragDropData) dragDropData = new ParamDragDropData; 600 601 dragDropData.param = param; 602 603 igSetDragDropPayload("_PARAMETER", cast(void*)&dragDropData, (&dragDropData).sizeof, ImGuiCond.Always); 604 incText(dragDropData.param.name); 605 igEndDragDropSource(); 606 } 607 608 if (canGroup) { 609 incBeginDragDropFake(); 610 auto peek = igAcceptDragDropPayload("_PARAMETER", ImGuiDragDropFlags.AcceptPeekOnly | ImGuiDragDropFlags.SourceAllowNullID); 611 if(peek && peek.Data && (*cast(ParamDragDropData**)peek.Data).param != param) { 612 if (igBeginDragDropTarget()) { 613 auto payload = igAcceptDragDropPayload("_PARAMETER"); 614 615 if (payload !is null) { 616 ParamDragDropData* payloadParam = *cast(ParamDragDropData**)payload.Data; 617 618 if (removeParameter(param)) { 619 auto group = createParamGroup(cast(int)idx); 620 group.children ~= param; 621 moveParameter(payloadParam.param, group); 622 } 623 } 624 igEndDragDropTarget(); 625 } 626 } 627 incEndDragDropFake(); 628 } 629 630 if (open) { 631 // Push color scheme 632 if (groupColor.isFinite) pushColorScheme(groupColor); 633 634 float reqSpace = param.isVec2 ? 144 : 52; 635 636 // Parameter Control 637 ImVec2 avail = incAvailableSpace(); 638 639 // We want to always show armed parameters but also make sure the child is begun. 640 bool childVisible = igBeginChild("###PARAM", ImVec2(avail.x-24, reqSpace)); 641 if (childVisible || armedParam) { 642 643 // Popup for rightclicking the controller 644 if (igBeginPopup("###ControlPopup")) { 645 if (incArmedParameter() == param) { 646 keypointActions(param, param.bindings); 647 } 648 igEndPopup(); 649 } 650 651 if (param.isVec2) incText("%.2f %.2f".format(param.value.x, param.value.y)); 652 else incText("%.2f".format(param.value.x)); 653 654 if (incController("###CONTROLLER", param, ImVec2(avail.x-24, reqSpace-24), incArmedParameter() == param, *grabParam)) { 655 if (incArmedParameter() == param) { 656 incViewportNodeDeformNotifyParamValueChanged(); 657 paramPointChanged(param); 658 } 659 if (igIsMouseDown(ImGuiMouseButton.Left)) { 660 if (*grabParam == null) 661 *grabParam = param.name; 662 } else { 663 *grabParam = ""; 664 } 665 } 666 if (igIsItemClicked(ImGuiMouseButton.Right)) { 667 if (incArmedParameter() == param) incViewportNodeDeformNotifyParamValueChanged(); 668 refreshBindingList(param); 669 igOpenPopup("###ControlPopup"); 670 } 671 } 672 igEndChild(); 673 674 675 if (incEditMode == EditMode.ModelEdit) { 676 igSameLine(0, 0); 677 678 // Parameter Setting Buttons 679 childVisible = igBeginChild("###SETTING", ImVec2(24, reqSpace), false); 680 if (childVisible || armedParam) { 681 if (igBeginPopup("###EditParam")) { 682 if (igMenuItem(__("Edit Properties"), "", false, true)) { 683 incPushWindowList(new ParamPropWindow(param)); 684 } 685 686 if (igMenuItem(__("Edit Axes Points"), "", false, true)) { 687 incPushWindowList(new ParamAxesWindow(param)); 688 } 689 690 if (igMenuItem(__("Split"), "", false, true)) { 691 incPushWindowList(new ParamSplitWindow(idx, param)); 692 } 693 694 if (param.isVec2) { 695 if (igMenuItem(__("Flip X"), "", false, true)) { 696 auto action = new ParameterChangeBindingsAction("Flip X", param, null); 697 param.reverseAxis(0); 698 action.updateNewState(); 699 incActionPush(action); 700 } 701 if (igMenuItem(__("Flip Y"), "", false, true)) { 702 auto action = new ParameterChangeBindingsAction("Flip Y", param, null); 703 param.reverseAxis(1); 704 action.updateNewState(); 705 incActionPush(action); 706 } 707 } else { 708 if (igMenuItem(__("Flip"), "", false, true)) { 709 auto action = new ParameterChangeBindingsAction("Flip", param, null); 710 param.reverseAxis(0); 711 action.updateNewState(); 712 incActionPush(action); 713 } 714 } 715 if (igBeginMenu(__("Mirror"), true)) { 716 if (igMenuItem(__("Horizontally"), "", false, true)) { 717 mirrorAll(param, 0); 718 incViewportNodeDeformNotifyParamValueChanged(); 719 } 720 if (igMenuItem(__("Vertically"), "", false, true)) { 721 mirrorAll(param, 1); 722 incViewportNodeDeformNotifyParamValueChanged(); 723 } 724 igEndMenu(); 725 } 726 if (igBeginMenu(__("Mirrored Autofill"), true)) { 727 if (igMenuItem("", "", false, true)) { 728 mirroredAutofill(param, 0, 0, 0.4999); 729 incViewportNodeDeformNotifyParamValueChanged(); 730 } 731 if (igMenuItem("", "", false, true)) { 732 mirroredAutofill(param, 0, 0.5001, 1); 733 incViewportNodeDeformNotifyParamValueChanged(); 734 } 735 if (param.isVec2) { 736 if (igMenuItem("", "", false, true)) { 737 mirroredAutofill(param, 1, 0.5001, 1); 738 incViewportNodeDeformNotifyParamValueChanged(); 739 } 740 if (igMenuItem("", "", false, true)) { 741 mirroredAutofill(param, 1, 0, 0.4999); 742 incViewportNodeDeformNotifyParamValueChanged(); 743 } 744 } 745 igEndMenu(); 746 } 747 748 igNewLine(); 749 igSeparator(); 750 751 if (igMenuItem(__("Duplicate"), "", false, true)) { 752 Parameter newParam = param.dup; 753 incActivePuppet().parameters ~= newParam; 754 incActionPush(new ParameterAddAction(newParam, ¶mArr)); 755 } 756 757 if (igMenuItem(__("Delete"), "", false, true)) { 758 if (incArmedParameter() == param) { 759 incDisarmParameter(); 760 } 761 incActivePuppet().removeParameter(param); 762 incActionPush(new ParameterRemoveAction(param, ¶mArr)); 763 } 764 765 igNewLine(); 766 igSeparator(); 767 768 // Sets the default value of the param 769 if (igMenuItem(__("Set Starting Position"), "", false, true)) { 770 auto action = new ParameterValueChangeAction!vec2("axis points", param, ¶m.defaults); 771 param.defaults = param.value; 772 action.updateNewState(); 773 incActionPush(action); 774 } 775 igEndPopup(); 776 } 777 778 if (igButton("", ImVec2(24, 24))) { 779 igOpenPopup("###EditParam"); 780 } 781 782 783 if (incButtonColored("", ImVec2(24, 24), incArmedParameter() == param ? ImVec4(1f, 0f, 0f, 1f) : *igGetStyleColorVec4(ImGuiCol.Text))) { 784 if (incArmedParameter() == param) { 785 incDisarmParameter(); 786 } else { 787 param.value = param.getClosestKeypointValue(); 788 paramPointChanged(param); 789 incArmParameter(idx, param); 790 } 791 } 792 793 // Arms the parameter for recording values. 794 incTooltip(_("Arm Parameter")); 795 } 796 igEndChild(); 797 } 798 if (incArmedParameter() == param) { 799 bindingList(param); 800 } 801 if (groupColor.isFinite) popColorScheme(); 802 } 803 804 incEndCategory(); 805 } 806 807 /** 808 The logger frame 809 */ 810 class ParametersPanel : Panel { 811 private: 812 string filter; 813 string grabParam = ""; 814 protected: 815 override 816 void onUpdate() { 817 if (incEditMode == EditMode.VertexEdit) { 818 incLabelOver(_("In vertex edit mode..."), ImVec2(0, 0), true); 819 return; 820 } 821 822 auto parameters = incActivePuppet().parameters; 823 824 if (igBeginPopup("###AddParameter")) { 825 if (igMenuItem(__("Add 1D Parameter (0..1)"), "", false, true)) { 826 Parameter param = new Parameter( 827 "Param #%d\0".format(parameters.length), 828 false 829 ); 830 incActivePuppet().parameters ~= param; 831 incActionPush(new ParameterAddAction(param, &incActivePuppet().parameters)); 832 } 833 if (igMenuItem(__("Add 1D Parameter (-1..1)"), "", false, true)) { 834 Parameter param = new Parameter( 835 "Param #%d\0".format(parameters.length), 836 false 837 ); 838 param.min.x = -1; 839 param.max.x = 1; 840 param.insertAxisPoint(0, 0.5); 841 incActivePuppet().parameters ~= param; 842 incActionPush(new ParameterAddAction(param, &incActivePuppet().parameters)); 843 } 844 if (igMenuItem(__("Add 2D Parameter (0..1)"), "", false, true)) { 845 Parameter param = new Parameter( 846 "Param #%d\0".format(parameters.length), 847 true 848 ); 849 incActivePuppet().parameters ~= param; 850 incActionPush(new ParameterAddAction(param, &incActivePuppet().parameters)); 851 } 852 if (igMenuItem(__("Add 2D Parameter (-1..+1)"), "", false, true)) { 853 Parameter param = new Parameter( 854 "Param #%d\0".format(parameters.length), 855 true 856 ); 857 param.min = vec2(-1, -1); 858 param.max = vec2(1, 1); 859 param.insertAxisPoint(0, 0.5); 860 param.insertAxisPoint(1, 0.5); 861 incActivePuppet().parameters ~= param; 862 incActionPush(new ParameterAddAction(param, &incActivePuppet().parameters)); 863 } 864 if (igMenuItem(__("Add Mouth Shape"), "", false, true)) { 865 Parameter param = new Parameter( 866 "Mouth #%d\0".format(parameters.length), 867 true 868 ); 869 param.min = vec2(-1, 0); 870 param.max = vec2(1, 1); 871 param.insertAxisPoint(0, 0.25); 872 param.insertAxisPoint(0, 0.5); 873 param.insertAxisPoint(0, 0.6); 874 param.insertAxisPoint(1, 0.3); 875 param.insertAxisPoint(1, 0.5); 876 param.insertAxisPoint(1, 0.6); 877 incActivePuppet().parameters ~= param; 878 incActionPush(new ParameterAddAction(param, &incActivePuppet().parameters)); 879 } 880 igEndPopup(); 881 } 882 if (igBeginChild("###FILTER", ImVec2(0, 32))) { 883 if (incInputText("Filter", filter)) { 884 filter = filter.toLower; 885 } 886 incTooltip(_("Filter, search for specific parameters")); 887 } 888 igEndChild(); 889 890 if (igBeginChild("ParametersList", ImVec2(0, -36))) { 891 892 // Always render the currently armed parameter on top 893 if (incArmedParameter()) { 894 incParameterView!true(incArmedParameterIdx(), incArmedParameter(), &grabParam, false, parameters); 895 } 896 897 // Render other parameters 898 foreach(i, ref param; parameters) { 899 if (incArmedParameter() == param) continue; 900 import std.algorithm.searching : canFind; 901 ExParameterGroup group = cast(ExParameterGroup)param; 902 bool found = filter.length == 0 || param.indexableName.canFind(filter); 903 if (group) { 904 foreach (ix, ref child; group.children) { 905 if (incArmedParameter() == child) continue; 906 if (child.indexableName.canFind(filter)) 907 found = true; 908 } 909 } 910 if (found) { 911 if (group) { 912 igPushID(group.uuid); 913 914 bool open; 915 if (group.color.isFinite) open = incBeginCategory(group.name.toStringz, ImVec4(group.color.r, group.color.g, group.color.b, 1)); 916 else open = incBeginCategory(group.name.toStringz); 917 918 if (igIsItemClicked(ImGuiMouseButton.Right)) { 919 igOpenPopup("###CategorySettings"); 920 } 921 922 // Popup 923 if (igBeginPopup("###CategorySettings")) { 924 if (igMenuItem(__("Rename"))) { 925 incPushWindow(new RenameWindow(group.name)); 926 } 927 928 if (igBeginMenu(__("Colors"))) { 929 auto flags = ImGuiColorEditFlags.NoLabel | ImGuiColorEditFlags.NoTooltip; 930 ImVec2 swatchSize = ImVec2(24, 24); 931 932 // COLOR SWATCHES 933 if (igColorButton("NONE", ImVec4(0, 0, 0, 0), flags | ImGuiColorEditFlags.AlphaPreview, swatchSize)) group.color = vec3(float.nan, float.nan, float.nan); 934 igSameLine(0, 4); 935 if (igColorButton("RED", ImVec4(1, 0, 0, 1), flags, swatchSize)) group.color = vec3(0.25, 0.15, 0.15); 936 igSameLine(0, 4); 937 if (igColorButton("GREEN", ImVec4(0, 1, 0, 1), flags, swatchSize)) group.color = vec3(0.15, 0.25, 0.15); 938 igSameLine(0, 4); 939 if (igColorButton("BLUE", ImVec4(0, 0, 1, 1), flags, swatchSize)) group.color = vec3(0.15, 0.15, 0.25); 940 igSameLine(0, 4); 941 if (igColorButton("PURPLE", ImVec4(1, 0, 1, 1), flags, swatchSize)) group.color = vec3(0.25, 0.15, 0.25); 942 igSameLine(0, 4); 943 if (igColorButton("CYAN", ImVec4(0, 1, 1, 1), flags, swatchSize)) group.color = vec3(0.15, 0.25, 0.25); 944 igSameLine(0, 4); 945 if (igColorButton("YELLOW", ImVec4(1, 1, 0, 1), flags, swatchSize)) group.color = vec3(0.25, 0.25, 0.15); 946 igSameLine(0, 4); 947 if (igColorButton("WHITE", ImVec4(1, 1, 1, 1), flags, swatchSize)) group.color = vec3(0.25, 0.25, 0.25); 948 949 igSpacing(); 950 951 // CUSTOM COLOR PICKER 952 // Allows user to select a custom color for parameter group. 953 igColorPicker3(__("Custom Color"), &group.color.vector, ImGuiColorEditFlags.InputRGB | ImGuiColorEditFlags.DisplayHSV); 954 igEndMenu(); 955 } 956 957 if (igMenuItem(__("Delete"))) { 958 if (i == 0) incActivePuppet().parameters = group.children ~ parameters[i+1..$]; 959 else if (i+1 == parameters.length) incActivePuppet().parameters = parameters[0..$-1] ~ group.children; 960 else incActivePuppet().parameters = parameters[0..i] ~ group.children ~ parameters[i+1..$]; 961 962 // End early. 963 igEndPopup(); 964 incEndCategory(); 965 igPopID(); 966 continue; 967 } 968 igEndPopup(); 969 } 970 971 // Allow drag/drop in to the category 972 if (igBeginDragDropTarget()) { 973 auto payload = igAcceptDragDropPayload("_PARAMETER"); 974 975 if (payload !is null) { 976 ParamDragDropData* payloadParam = *cast(ParamDragDropData**)payload.Data; 977 moveParameter(payloadParam.param, group); 978 } 979 igEndDragDropTarget(); 980 } 981 982 // Render children if open 983 if (open) { 984 foreach(ix, ref child; group.children) { 985 986 // Skip armed param 987 if (incArmedParameter() == child) continue; 988 if (child.indexableName.canFind(filter)) { 989 // Otherwise render it 990 incParameterView(ix, child, &grabParam, false, group.children, group.color); 991 } 992 } 993 } 994 incEndCategory(); 995 igPopID(); 996 } else { 997 incParameterView(i, param, &grabParam, true, incActivePuppet().parameters); 998 } 999 } 1000 } 1001 } 1002 igEndChild(); 1003 1004 // Allow drag/drop out of categories 1005 if (igBeginDragDropTarget()) { 1006 auto payload = igAcceptDragDropPayload("_PARAMETER"); 1007 1008 if (payload !is null) { 1009 ParamDragDropData* payloadParam = *cast(ParamDragDropData**)payload.Data; 1010 moveParameter(payloadParam.param, null); 1011 } 1012 igEndDragDropTarget(); 1013 } 1014 1015 // Right align add button 1016 ImVec2 avail = incAvailableSpace(); 1017 incDummy(ImVec2(avail.x-32, 32)); 1018 igSameLine(0, 0); 1019 1020 // Add button 1021 if (igButton("", ImVec2(32, 32))) { 1022 igOpenPopup("###AddParameter"); 1023 } 1024 incTooltip(_("Add Parameter")); 1025 } 1026 1027 public: 1028 this() { 1029 super("Parameters", _("Parameters"), false); 1030 } 1031 } 1032 1033 /** 1034 Generate logger frame 1035 */ 1036 mixin incPanel!ParametersPanel;