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.inspector; 8 import creator.viewport.vertex; 9 import creator.core; 10 import creator.panels; 11 import creator.widgets; 12 import creator.utils; 13 import creator.windows; 14 import creator; 15 import inochi2d; 16 import std.string; 17 import std.algorithm.searching; 18 import std.algorithm.mutation; 19 import std.conv; 20 import i18n; 21 22 import creator.actions.node; 23 24 /** 25 The inspector panel 26 */ 27 class InspectorPanel : Panel { 28 private: 29 30 31 protected: 32 override 33 void onUpdate() { 34 auto nodes = incSelectedNodes(); 35 if (nodes.length == 1) { 36 Node node = nodes[0]; 37 if (node !is null && node != incActivePuppet().root) { 38 39 // Per-edit mode inspector drawers 40 switch(incEditMode()) { 41 case EditMode.ModelEdit: 42 if (incArmedParameter()) { 43 Parameter param = incArmedParameter(); 44 vec2u cursor = param.findClosestKeypoint(); 45 incCommonNonEditHeader(node); 46 incInspectorDeformTRS(node, param, cursor); 47 48 // Node Part Section 49 if (Part part = cast(Part)node) { 50 51 // Padding 52 igSpacing(); 53 igSpacing(); 54 igSpacing(); 55 igSpacing(); 56 incInspectorDeformPart(part, param, cursor); 57 } 58 59 } else { 60 incModelModeHeader(node); 61 incInspectorModelTRS(node); 62 63 // Node Drawable Section 64 if (Drawable drawable = cast(Drawable)node) { 65 66 // Padding 67 igSpacing(); 68 igSpacing(); 69 igSpacing(); 70 igSpacing(); 71 incInspectorModelDrawable(drawable); 72 } 73 74 // Node Part Section 75 if (Part part = cast(Part)node) { 76 77 // Padding 78 igSpacing(); 79 igSpacing(); 80 igSpacing(); 81 igSpacing(); 82 incInspectorModelPart(part); 83 } 84 } 85 86 break; 87 case EditMode.VertexEdit: 88 incCommonNonEditHeader(node); 89 incInspectorMeshEditDrawable(cast(Drawable)node); 90 break; 91 default: 92 incCommonNonEditHeader(node); 93 break; 94 } 95 } else incInspectorModelInfo(); 96 } else if (nodes.length == 0) { 97 igText(__("No nodes selected...")); 98 } else { 99 igText(__("Can only inspect a single node...")); 100 } 101 } 102 103 public: 104 this() { 105 super("Inspector", _("Inspector"), true); 106 } 107 } 108 109 /** 110 Generate logger frame 111 */ 112 mixin incPanel!InspectorPanel; 113 114 115 116 private: 117 118 // 119 // COMMON 120 // 121 122 void incCommonNonEditHeader(Node node) { 123 // Top level 124 igPushID(node.uuid); 125 string typeString = "%s\0".format(incTypeIdToIcon(node.typeId())); 126 auto len = incMeasureString(typeString); 127 igText(node.name.toStringz); 128 igSameLine(0, 0); 129 incDummy(ImVec2(-(len.x-14), len.y)); 130 igSameLine(0, 0); 131 igText(typeString.ptr); 132 igPopID(); 133 igSeparator(); 134 } 135 136 // 137 // MODEL MODE 138 // 139 140 void incInspectorModelInfo() { 141 auto rootNode = incActivePuppet().root; 142 auto puppet = incActivePuppet(); 143 144 // Top level 145 igPushID(rootNode.uuid); 146 string typeString = "\0"; 147 auto len = incMeasureString(typeString); 148 igText(__("Puppet")); 149 igSameLine(0, 0); 150 incDummy(ImVec2(-(len.x-14), len.y)); 151 igSameLine(0, 0); 152 igText(typeString.ptr); 153 igPopID(); 154 igSeparator(); 155 156 igSpacing(); 157 igSpacing(); 158 159 // Version info 160 { 161 len = incMeasureString(_("Inochi2D Ver.")); 162 igText(puppet.meta.version_.toStringz); 163 igSameLine(0, 0); 164 incDummy(ImVec2(-(len.x), len.y)); 165 igSameLine(0, 0); 166 igText(__("Inochi2D Ver.")); 167 } 168 169 igSpacing(); 170 igSpacing(); 171 172 if (igCollapsingHeader(__("General Info"), ImGuiTreeNodeFlags.DefaultOpen)) { 173 igPushID("Name"); 174 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Name")); 175 incTooltip(_("Name of the puppet")); 176 incInputText("", puppet.meta.name); 177 igPopID(); 178 igSpacing(); 179 180 igPushID("Artists"); 181 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Artist(s)")); 182 incTooltip(_("Artists who've drawn the puppet, seperated by comma")); 183 incInputText("", puppet.meta.artist); 184 igPopID(); 185 igSpacing(); 186 187 igPushID("Riggers"); 188 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Rigger(s)")); 189 incTooltip(_("Riggers who've rigged the puppet, seperated by comma")); 190 incInputText("", puppet.meta.rigger); 191 igPopID(); 192 igSpacing(); 193 194 igPushID("Contact"); 195 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Contact")); 196 incTooltip(_("Where to contact the main author of the puppet")); 197 incInputText("", puppet.meta.contact); 198 igPopID(); 199 igSpacing(); 200 } 201 202 if (igCollapsingHeader(__("Licensing"), ImGuiTreeNodeFlags.DefaultOpen)) { 203 igPushID("LicenseURL"); 204 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("License URL")); 205 incTooltip(_("Link/URL to license")); 206 incInputText("", puppet.meta.licenseURL); 207 igPopID(); 208 igSpacing(); 209 210 igPushID("Copyright"); 211 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Copyright")); 212 incTooltip(_("Copyright holder information of the puppet")); 213 incInputText("", puppet.meta.copyright); 214 igPopID(); 215 igSpacing(); 216 217 igPushID("Origin"); 218 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Origin")); 219 incTooltip(_("Where the model comes from on the internet.")); 220 incInputText("", puppet.meta.reference); 221 igPopID(); 222 } 223 } 224 225 void incModelModeHeader(Node node) { 226 // Top level 227 igPushID(node.uuid); 228 string typeString = "%s\0".format(incTypeIdToIcon(node.typeId())); 229 auto len = incMeasureString(typeString); 230 incInputText("", incAvailableSpace().x-24, node.name); 231 igSameLine(0, 0); 232 incDummy(ImVec2(-(len.x-14), len.y)); 233 igSameLine(0, 0); 234 igText(typeString.ptr); 235 igPopID(); 236 igSeparator(); 237 } 238 239 void incInspectorModelTRS(Node node) { 240 if (!igCollapsingHeader(__("Transform"), ImGuiTreeNodeFlags.DefaultOpen)) 241 return; 242 243 float adjustSpeed = 1; 244 // if (igIsKeyDown(igGetKeyIndex(ImGuiKeyModFlags_Shift))) { 245 // adjustSpeed = 0.1; 246 // } 247 248 ImVec2 avail; 249 igGetContentRegionAvail(&avail); 250 251 float fontSize = 16; 252 253 // 254 // Translation 255 // 256 257 // Translation portion of the transformation matrix. 258 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Translation")); 259 igPushItemWidth((avail.x-4f)/3f); 260 261 // Translation X 262 igPushID(0); 263 if (incDragFloat("translation_x", &node.localTransform.translation.vector[0], adjustSpeed, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 264 incActionPush( 265 new NodeValueChangeAction!(Node, float)( 266 "X", 267 node, 268 incGetDragFloatInitialValue("translation_x"), 269 node.localTransform.translation.vector[0], 270 &node.localTransform.translation.vector[0] 271 ) 272 ); 273 } 274 igPopID(); 275 276 igSameLine(0, 4); 277 278 // Translation Y 279 igPushID(1); 280 if (incDragFloat("translation_y", &node.localTransform.translation.vector[1], adjustSpeed, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 281 incActionPush( 282 new NodeValueChangeAction!(Node, float)( 283 "Y", 284 node, 285 incGetDragFloatInitialValue("translation_y"), 286 node.localTransform.translation.vector[1], 287 &node.localTransform.translation.vector[1] 288 ) 289 ); 290 } 291 igPopID(); 292 293 igSameLine(0, 4); 294 295 // Translation Z 296 igPushID(2); 297 if (incDragFloat("translation_z", &node.localTransform.translation.vector[2], adjustSpeed, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 298 incActionPush( 299 new NodeValueChangeAction!(Node, float)( 300 "Z", 301 node, 302 incGetDragFloatInitialValue("translation_z"), 303 node.localTransform.translation.vector[2], 304 &node.localTransform.translation.vector[2] 305 ) 306 ); 307 } 308 igPopID(); 309 310 311 312 // Padding 313 igSpacing(); 314 igSpacing(); 315 316 igBeginGroup(); 317 // Button which locks all transformation to be based off the root node 318 // of the puppet, this more or less makes the item stay in place 319 // even if the parent moves. 320 ImVec2 textLength = incMeasureString(_("Lock to Root Node")); 321 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Lock to Root Node")); 322 323 incSpacer(ImVec2(-12, 1)); 324 bool lockToRoot = node.lockToRoot; 325 if (incLockButton(&lockToRoot, "root_lk")) { 326 327 // TODO: Store this in undo history. 328 node.lockToRoot = lockToRoot; 329 } 330 igEndGroup(); 331 332 // Button which locks all transformation to be based off the root node 333 // of the puppet, this more or less makes the item stay in place 334 // even if the parent moves. 335 incTooltip(_("Makes so that the translation of this node is based off the root node, making it stay in place even if its parent moves.")); 336 337 // Padding 338 igSpacing(); 339 igSpacing(); 340 341 igPopItemWidth(); 342 343 344 // 345 // Rotation 346 // 347 igSpacing(); 348 349 // Rotation portion of the transformation matrix. 350 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Rotation")); 351 igPushItemWidth((avail.x-4f-(fontSize*3f))/3f); 352 353 // Rotation X 354 igPushID(3); 355 if (incDragFloat("rotation_x", &node.localTransform.rotation.vector[0], adjustSpeed/100, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 356 incActionPush( 357 new NodeValueChangeAction!(Node, float)( 358 _("Rotation X"), 359 node, 360 incGetDragFloatInitialValue("rotation_x"), 361 node.localTransform.rotation.vector[0], 362 &node.localTransform.rotation.vector[0] 363 ) 364 ); 365 } 366 igPopID(); 367 368 if (incLockButton(&node.localTransform.lockRotationX, "rot_x")) { 369 incActionPush( 370 new NodeValueChangeAction!(Node, bool)( 371 _("Lock Rotation X"), 372 node, 373 !node.localTransform.lockRotationX, 374 node.localTransform.lockRotationX, 375 &node.localTransform.lockRotationX 376 ) 377 ); 378 } 379 380 igSameLine(0, 4); 381 382 // Rotation Y 383 igPushID(4); 384 if (incDragFloat("rotation_y", &node.localTransform.rotation.vector[1], adjustSpeed/100, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 385 incActionPush( 386 new NodeValueChangeAction!(Node, float)( 387 _("Rotation Y"), 388 node, 389 incGetDragFloatInitialValue("rotation_y"), 390 node.localTransform.rotation.vector[1], 391 &node.localTransform.rotation.vector[1] 392 ) 393 ); 394 } 395 igPopID(); 396 397 if (incLockButton(&node.localTransform.lockRotationY, "rot_y")) { 398 incActionPush( 399 new NodeValueChangeAction!(Node, bool)( 400 _("Lock Rotation Y"), 401 node, 402 !node.localTransform.lockRotationY, 403 node.localTransform.lockRotationY, 404 &node.localTransform.lockRotationY 405 ) 406 ); 407 } 408 409 igSameLine(0, 4); 410 411 // Rotation Z 412 igPushID(5); 413 if (incDragFloat("rotation_z", &node.localTransform.rotation.vector[2], adjustSpeed/100, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 414 incActionPush( 415 new NodeValueChangeAction!(Node, float)( 416 _("Rotation Z"), 417 node, 418 incGetDragFloatInitialValue("rotation_z"), 419 node.localTransform.rotation.vector[2], 420 &node.localTransform.rotation.vector[2] 421 ) 422 ); 423 } 424 igPopID(); 425 426 if (incLockButton(&node.localTransform.lockRotationZ, "rot_z")) { 427 incActionPush( 428 new NodeValueChangeAction!(Node, bool)( 429 _("Lock Rotation Z"), 430 node, 431 !node.localTransform.lockRotationZ, 432 node.localTransform.lockRotationZ, 433 &node.localTransform.lockRotationZ 434 ) 435 ); 436 } 437 438 igPopItemWidth(); 439 440 avail.x += igGetFontSize(); 441 442 // 443 // Scaling 444 // 445 igSpacing(); 446 447 // Scaling portion of the transformation matrix. 448 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Scale")); 449 igPushItemWidth((avail.x-14f-(fontSize*2f))/2f); 450 451 // Scale X 452 igPushID(6); 453 if (incDragFloat("scale_x", &node.localTransform.scale.vector[0], adjustSpeed/100, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 454 incActionPush( 455 new NodeValueChangeAction!(Node, float)( 456 _("Scale X"), 457 node, 458 incGetDragFloatInitialValue("scale_x"), 459 node.localTransform.scale.vector[0], 460 &node.localTransform.scale.vector[0] 461 ) 462 ); 463 } 464 igPopID(); 465 if (incLockButton(&node.localTransform.lockScaleX, "sca_x")) { 466 incActionPush( 467 new NodeValueChangeAction!(Node, bool)( 468 _("Lock Scale X"), 469 node, 470 !node.localTransform.lockScaleX, 471 node.localTransform.lockScaleX, 472 &node.localTransform.lockScaleX 473 ) 474 ); 475 } 476 477 igSameLine(0, 4); 478 479 // Scale Y 480 igPushID(7); 481 if (incDragFloat("scale_y", &node.localTransform.scale.vector[1], adjustSpeed/100, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 482 incActionPush( 483 new NodeValueChangeAction!(Node, float)( 484 _("Scale Y"), 485 node, 486 incGetDragFloatInitialValue("scale_y"), 487 node.localTransform.scale.vector[1], 488 &node.localTransform.scale.vector[1] 489 ) 490 ); 491 } 492 igPopID(); 493 if (incLockButton(&node.localTransform.lockScaleY, "sca_y")) { 494 incActionPush( 495 new NodeValueChangeAction!(Node, bool)( 496 _("Lock Scale Y"), 497 node, 498 !node.localTransform.lockScaleY, 499 node.localTransform.lockScaleY, 500 &node.localTransform.lockScaleY 501 ) 502 ); 503 } 504 505 igPopItemWidth(); 506 507 igSpacing(); 508 igSpacing(); 509 510 // An option in which positions will be snapped to whole integer values. 511 // In other words texture will always be on a pixel. 512 textLength = incMeasureString(_("Snap to Pixel")); 513 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Snap to Pixel")); 514 incSpacer(ImVec2(-12, 1)); 515 if (incLockButton(&node.localTransform.pixelSnap, "pix_lk")) { 516 incActionPush( 517 new NodeValueChangeAction!(Node, bool)( 518 _("Snap to Pixel"), 519 node, 520 !node.localTransform.pixelSnap, 521 node.localTransform.pixelSnap, 522 &node.localTransform.pixelSnap 523 ) 524 ); 525 } 526 527 // Padding 528 igSpacing(); 529 igSpacing(); 530 531 // The sorting order ID, which Inochi2D uses to sort 532 // Parts to draw in the user specified order. 533 // negative values = closer to camera 534 // positive values = further away from camera 535 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Sorting")); 536 float zsortV = node.relZSort; 537 if (igInputFloat("###ZSort", &zsortV, 0.01, 0.05, "%0.2f")) { 538 node.zSort = zsortV; 539 } 540 } 541 542 void incInspectorModelDrawable(Drawable node) { 543 // The main type of anything that can be drawn to the screen 544 // in Inochi2D. 545 if (!igCollapsingHeader(__("Drawable"), ImGuiTreeNodeFlags.DefaultOpen)) 546 return; 547 548 ImVec2 avail = incAvailableSpace(); 549 550 igPushStyleVar_Vec2(ImGuiStyleVar.FramePadding, ImVec2(8, 8)); 551 igSpacing(); 552 igSpacing(); 553 554 if (igButton("", ImVec2(avail.x, 32))) { 555 incSetEditMode(EditMode.VertexEdit); 556 incSelectNode(node); 557 incVertexEditSetTarget(node); 558 incFocusCamera(node, vec2(0, 0)); 559 } 560 561 // Allow copying mesh data via drag n drop for now 562 if(igBeginDragDropTarget()) { 563 ImGuiPayload* payload = igAcceptDragDropPayload("_PUPPETNTREE"); 564 if (payload !is null) { 565 if (Drawable payloadDrawable = cast(Drawable)*cast(Node*)payload.Data) { 566 incSetEditMode(EditMode.VertexEdit); 567 incSelectNode(node); 568 incVertexEditSetTarget(node); 569 incFocusCamera(node, vec2(0, 0)); 570 incVertexEditCopyMeshDataToTarget(payloadDrawable.getMesh()); 571 } 572 } 573 574 igEndDragDropTarget(); 575 } else { 576 577 578 // Switches Inochi Creator over to Mesh Edit mode 579 // and selects the mesh that you had selected previously 580 // in Model Edit mode. 581 incTooltip(_("Edit Mesh")); 582 } 583 584 igSpacing(); 585 igSpacing(); 586 igPopStyleVar(); 587 } 588 589 void incInspectorModelPart(Part node) { 590 if (!igCollapsingHeader(__("Part"), ImGuiTreeNodeFlags.DefaultOpen)) 591 return; 592 593 if (!node.getMesh().isReady()) { 594 igSpacing(); 595 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Cannot inspect an unmeshed part")); 596 return; 597 } 598 igSpacing(); 599 600 // BLENDING MODE 601 import std.conv : text; 602 import std.string : toStringz; 603 604 igBeginGroup(); 605 igIndent(16); 606 // Header for texture options 607 if (igCollapsingHeader(__("Textures"))) { 608 609 igText("(TODO: Texture Select)"); 610 611 igSpacing(); 612 igSpacing(); 613 614 igText(__("Tint")); 615 igColorEdit3("", cast(float[3]*)node.tint.value_ptr); 616 617 // Padding 618 igSeparator(); 619 igSpacing(); 620 igSpacing(); 621 } 622 igUnindent(); 623 igEndGroup(); 624 625 // Header for the Blending options for Parts 626 igText(__("Blending")); 627 if (igBeginCombo("###Blending", __(node.blendingMode.text))) { 628 629 // Normal blending mode as used in Photoshop, generally 630 // the default blending mode photoshop starts a layer out as. 631 if (igSelectable(__("Normal"), node.blendingMode == BlendMode.Normal)) node.blendingMode = BlendMode.Normal; 632 633 // Multiply blending mode, in which this texture's color data 634 // will be multiplied with the color data already in the framebuffer. 635 if (igSelectable(__("Multiply"), node.blendingMode == BlendMode.Multiply)) node.blendingMode = BlendMode.Multiply; 636 637 // Color Dodge blending mode 638 if (igSelectable(__("Color Dodge"), node.blendingMode == BlendMode.ColorDodge)) node.blendingMode = BlendMode.ColorDodge; 639 640 // Linear Dodge blending mode 641 if (igSelectable(__("Linear Dodge"), node.blendingMode == BlendMode.LinearDodge)) node.blendingMode = BlendMode.LinearDodge; 642 643 // Screen blending mode 644 if (igSelectable(__("Screen"), node.blendingMode == BlendMode.Screen)) node.blendingMode = BlendMode.Screen; 645 646 igEndCombo(); 647 } 648 649 igSpacing(); 650 651 igText(__("Opacity")); 652 igSliderFloat("###Opacity", &node.opacity, 0, 1f, "%0.2f"); 653 igSpacing(); 654 igSpacing(); 655 656 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Masks")); 657 igSpacing(); 658 659 // The masking mode to apply if there's any source specified. 660 igText(__("Mode")); 661 if (igBeginCombo("###Mode", node.maskingMode ? __("Dodge") : __("Mask"))) { 662 663 // A masking mode where only areas of this Part that overlap the other 664 // source drawables gets drawn 665 if (igSelectable(__("Mask"), node.maskingMode == MaskingMode.Mask)) { 666 node.maskingMode = MaskingMode.Mask; 667 } 668 669 // A masking mode where areas of this Part that overlap the other 670 // source drawables gets discarded 671 if (igSelectable(__("Dodge"), node.maskingMode == MaskingMode.DodgeMask)) { 672 node.maskingMode = MaskingMode.DodgeMask; 673 } 674 igEndCombo(); 675 } 676 677 igSpacing(); 678 679 // Threshold slider name for adjusting how transparent a pixel can be 680 // before it gets discarded. 681 igText(__("Threshold")); 682 igSliderFloat("###Threshold", &node.maskAlphaThreshold, 0.0, 1.0, "%.2f"); 683 684 igSpacing(); 685 686 // The sources that the part gets masked by. Depending on the masking mode 687 // either the sources will cut out things that don't overlap, or cut out 688 // things that do. 689 igText(__("Mask Sources")); 690 if (igBeginListBox("###MaskSources", ImVec2(0, 128))) { 691 foreach(i, masker; node.mask) { 692 igPushID(cast(int)i); 693 igText(masker.name.toStringz); 694 if(igBeginDragDropSource(ImGuiDragDropFlags.SourceAllowNullID)) { 695 igSetDragDropPayload("_MASKITEM", cast(void*)&masker, (&masker).sizeof, ImGuiCond.Always); 696 igText(masker.name.toStringz); 697 igEndDragDropSource(); 698 } 699 igPopID(); 700 } 701 igEndListBox(); 702 } 703 704 if(igBeginDragDropTarget()) { 705 ImGuiPayload* payload = igAcceptDragDropPayload("_PUPPETNTREE"); 706 if (payload !is null) { 707 if (Drawable payloadDrawable = cast(Drawable)*cast(Node*)payload.Data) { 708 709 // Make sure we don't mask against ourselves as well as don't double mask 710 if (payloadDrawable != node && !node.mask.canFind(payloadDrawable)) { 711 node.mask ~= payloadDrawable; 712 } 713 } 714 } 715 716 igEndDragDropTarget(); 717 } 718 719 igButton("ー", ImVec2(0, 0)); 720 if(igBeginDragDropTarget()) { 721 ImGuiPayload* payload = igAcceptDragDropPayload("_MASKITEM"); 722 if (payload !is null) { 723 if (Drawable payloadDrawable = cast(Drawable)*cast(Node*)payload.Data) { 724 foreach(i; 0..node.mask.length) { 725 if (payloadDrawable.uuid == node.mask[i].uuid) { 726 node.mask = node.mask.remove(i); 727 break; 728 } 729 } 730 } 731 } 732 igEndDragDropTarget(); 733 } 734 735 // Padding 736 igSpacing(); 737 igSpacing(); 738 } 739 740 // 741 // MODEL MODE ARMED 742 // 743 void incInspectorDeformFloatDragVal(string name, string paramName, float adjustSpeed, Node node, Parameter param, vec2u cursor) { 744 float currFloat = node.getDefaultValue(paramName); 745 if (ValueParameterBinding b = cast(ValueParameterBinding)param.getBinding(node, paramName)) { 746 currFloat = b.getValue(cursor); 747 } 748 if (incDragFloat(name, &currFloat, adjustSpeed, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 749 ValueParameterBinding b = cast(ValueParameterBinding)param.getOrAddBinding(node, paramName); 750 b.setValue(cursor, currFloat); 751 } 752 } 753 754 void incInspectorDeformInputFloat(string name, string paramName, float step, float stepFast, Node node, Parameter param, vec2u cursor) { 755 float currFloat = node.getDefaultValue(paramName); 756 if (ValueParameterBinding b = cast(ValueParameterBinding)param.getBinding(node, paramName)) { 757 currFloat = b.getValue(cursor); 758 } 759 if (igInputFloat(name.toStringz, &currFloat, step, stepFast, "%.2f")) { 760 ValueParameterBinding b = cast(ValueParameterBinding)param.getOrAddBinding(node, paramName); 761 b.setValue(cursor, currFloat); 762 } 763 } 764 765 void incInspectorDeformColorEdit3(string[3] paramNames, Node node, Parameter param, vec2u cursor) { 766 import std.math : isNaN; 767 float[3] rgb = [float.nan, float.nan, float.nan]; 768 float[3] rgbadj = [1, 1, 1]; 769 bool[3] rgbchange = [false, false, false]; 770 ValueParameterBinding pbr = cast(ValueParameterBinding)param.getBinding(node, paramNames[0]); 771 ValueParameterBinding pbg = cast(ValueParameterBinding)param.getBinding(node, paramNames[1]); 772 ValueParameterBinding pbb = cast(ValueParameterBinding)param.getBinding(node, paramNames[2]); 773 774 if (pbr) { 775 rgb[0] = pbr.getValue(cursor); 776 rgbadj[0] = rgb[0]; 777 } 778 779 if (pbg) { 780 rgb[1] = pbg.getValue(cursor); 781 rgbadj[1] = rgb[1]; 782 } 783 784 if (pbb) { 785 rgb[2] = pbb.getValue(cursor); 786 rgbadj[2] = rgb[2]; 787 } 788 789 if (igColorEdit3("", &rgbadj)) { 790 791 // RED 792 if (rgbadj[0] != 1) { 793 auto b = cast(ValueParameterBinding)param.getOrAddBinding(node, paramNames[0]); 794 b.setValue(cursor, rgbadj[0]); 795 } else if (pbr) { 796 pbr.setValue(cursor, rgbadj[0]); 797 } 798 799 // GREEN 800 if (rgbadj[1] != 1) { 801 auto b = cast(ValueParameterBinding)param.getOrAddBinding(node, paramNames[1]); 802 b.setValue(cursor, rgbadj[1]); 803 } else if (pbg) { 804 pbg.setValue(cursor, rgbadj[1]); 805 } 806 807 // BLUE 808 if (rgbadj[2] != 1) { 809 auto b = cast(ValueParameterBinding)param.getOrAddBinding(node, paramNames[2]); 810 b.setValue(cursor, rgbadj[2]); 811 } else if (pbb) { 812 pbb.setValue(cursor, rgbadj[2]); 813 } 814 } 815 } 816 817 void incInspectorDeformSliderFloat(string name, string paramName, float min, float max, Node node, Parameter param, vec2u cursor) { 818 float currFloat = node.getDefaultValue(paramName); 819 if (ValueParameterBinding b = cast(ValueParameterBinding)param.getBinding(node, paramName)) { 820 currFloat = b.getValue(cursor); 821 } 822 if (igSliderFloat(name.toStringz, &currFloat, min, max, "%.2f")) { 823 ValueParameterBinding b = cast(ValueParameterBinding)param.getOrAddBinding(node, paramName); 824 b.setValue(cursor, currFloat); 825 } 826 } 827 828 void incInspectorDeformTRS(Node node, Parameter param, vec2u cursor) { 829 if (!igCollapsingHeader(__("Transform"), ImGuiTreeNodeFlags.DefaultOpen)) 830 return; 831 832 float adjustSpeed = 1; 833 834 ImVec2 avail; 835 igGetContentRegionAvail(&avail); 836 837 float fontSize = 16; 838 839 // 840 // Translation 841 // 842 843 844 845 // Translation portion of the transformation matrix. 846 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Translation")); 847 igPushItemWidth((avail.x-4f)/3f); 848 849 // Translation X 850 igPushID(0); 851 incInspectorDeformFloatDragVal("translation_x", "transform.t.x", 1f, node, param, cursor); 852 igPopID(); 853 854 igSameLine(0, 4); 855 856 // Translation Y 857 igPushID(1); 858 incInspectorDeformFloatDragVal("translation_y", "transform.t.y", 1f, node, param, cursor); 859 igPopID(); 860 861 igSameLine(0, 4); 862 863 // Translation Z 864 igPushID(2); 865 incInspectorDeformFloatDragVal("translation_z", "transform.t.z", 1f, node, param, cursor); 866 igPopID(); 867 868 869 870 // Padding 871 igSpacing(); 872 igSpacing(); 873 874 igPopItemWidth(); 875 876 877 // 878 // Rotation 879 // 880 igSpacing(); 881 882 // Rotation portion of the transformation matrix. 883 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Rotation")); 884 igPushItemWidth((avail.x-4f)/3f); 885 886 // Rotation X 887 igPushID(3); 888 incInspectorDeformFloatDragVal("rotation.x", "transform.r.x", 0.05f, node, param, cursor); 889 igPopID(); 890 891 igSameLine(0, 4); 892 893 // Rotation Y 894 igPushID(4); 895 incInspectorDeformFloatDragVal("rotation.y", "transform.r.y", 0.05f, node, param, cursor); 896 igPopID(); 897 898 igSameLine(0, 4); 899 900 // Rotation Z 901 igPushID(5); 902 incInspectorDeformFloatDragVal("rotation.z", "transform.r.z", 0.05f, node, param, cursor); 903 igPopID(); 904 905 igPopItemWidth(); 906 907 avail.x += igGetFontSize(); 908 909 // 910 // Scaling 911 // 912 igSpacing(); 913 914 // Scaling portion of the transformation matrix. 915 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Scale")); 916 igPushItemWidth((avail.x-14f)/2f); 917 918 // Scale X 919 igPushID(6); 920 incInspectorDeformFloatDragVal("scale.x", "transform.s.x", 0.1f, node, param, cursor); 921 igPopID(); 922 923 igSameLine(0, 4); 924 925 // Scale Y 926 igPushID(7); 927 incInspectorDeformFloatDragVal("scale.y", "transform.s.y", 0.1f, node, param, cursor); 928 igPopID(); 929 930 igPopItemWidth(); 931 932 igSpacing(); 933 igSpacing(); 934 935 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Sorting")); 936 incInspectorDeformInputFloat("zSort", "zSort", 0.01, 0.05, node, param, cursor); 937 } 938 939 void incInspectorDeformPart(Part node, Parameter param, vec2u cursor) { 940 if (!igCollapsingHeader(__("Part"), ImGuiTreeNodeFlags.DefaultOpen)) 941 return; 942 943 igBeginGroup(); 944 igIndent(16); 945 // Header for texture options 946 if (igCollapsingHeader(__("Textures"))) { 947 948 igText(__("Tint")); 949 950 incInspectorDeformColorEdit3(["tint.r", "tint.g", "tint.b"], node, param, cursor); 951 952 // Padding 953 igSeparator(); 954 igSpacing(); 955 igSpacing(); 956 } 957 igUnindent(); 958 igEndGroup(); 959 960 igText(__("Opacity")); 961 incInspectorDeformSliderFloat("###Opacity", "opacity", 0, 1f, node, param, cursor); 962 igSpacing(); 963 igSpacing(); 964 965 // Threshold slider name for adjusting how transparent a pixel can be 966 // before it gets discarded. 967 igText(__("Threshold")); 968 incInspectorDeformSliderFloat("###Threshold", "alphaThreshold", 0.0, 1.0, node, param, cursor); 969 } 970 971 // 972 // MESH EDIT MODE 973 // 974 void incInspectorMeshEditDrawable(Drawable node) { 975 igPushStyleVar_Vec2(ImGuiStyleVar.FramePadding, ImVec2(8, 8)); 976 igSpacing(); 977 igSpacing(); 978 979 incViewportVertexInspector(node); 980 981 ImVec2 avail = incAvailableSpace(); 982 incDummy(ImVec2(avail.x, avail.y-38)); 983 984 // Right align 985 incDummy(ImVec2(avail.x-72, 32)); 986 igSameLine(0, 0); 987 988 if (igButton("", ImVec2(32, 32))) { 989 if (igGetIO().KeyShift) { 990 incMeshEditReset(); 991 } else { 992 incMeshEditClear(); 993 } 994 995 incSetEditMode(EditMode.ModelEdit); 996 incSelectNode(node); 997 incFocusCamera(node); 998 } 999 incTooltip(_("Cancel")); 1000 1001 igSameLine(0, 8); 1002 1003 if (igButton("", ImVec2(32, 32))) { 1004 incMeshEditApply(); 1005 1006 incSetEditMode(EditMode.ModelEdit); 1007 incSelectNode(node); 1008 incFocusCamera(node); 1009 } 1010 incTooltip(_("Apply")); 1011 igPopStyleVar(); 1012 }