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.actions; 15 import creator.ext; 16 import creator; 17 import inochi2d; 18 import inochi2d.core.nodes.common; 19 import std.string; 20 import std.algorithm.searching; 21 import std.algorithm.mutation; 22 import std.conv; 23 import i18n; 24 25 // Drag drop data 26 import creator.panels.parameters; 27 28 import creator.actions.node; 29 30 /** 31 The inspector panel 32 */ 33 class InspectorPanel : Panel { 34 private: 35 36 37 protected: 38 override 39 void onUpdate() { 40 if (incEditMode == EditMode.VertexEdit) { 41 incLabelOver(_("In vertex edit mode..."), ImVec2(0, 0), true); 42 return; 43 } 44 45 auto nodes = incSelectedNodes(); 46 if (nodes.length == 1) { 47 Node node = nodes[0]; 48 if (node !is null && node != incActivePuppet().root) { 49 50 // Per-edit mode inspector drawers 51 switch(incEditMode()) { 52 case EditMode.ModelEdit: 53 if (incArmedParameter()) { 54 Parameter param = incArmedParameter(); 55 vec2u cursor = param.findClosestKeypoint(); 56 incCommonNonEditHeader(node); 57 incInspectorDeformTRS(node, param, cursor); 58 59 // Node Part Section 60 if (Part part = cast(Part)node) { 61 incInspectorDeformPart(part, param, cursor); 62 } 63 64 if (Composite composite = cast(Composite)node) { 65 incInspectorDeformComposite(composite, param, cursor); 66 } 67 68 } else { 69 incModelModeHeader(node); 70 incInspectorModelTRS(node); 71 72 // Node Camera Section 73 if (ExCamera camera = cast(ExCamera)node) { 74 incInspectorModelCamera(camera); 75 } 76 77 // Node Drawable Section 78 if (Composite composite = cast(Composite)node) { 79 incInspectorModelComposite(composite); 80 } 81 82 83 // Node Drawable Section 84 if (Drawable drawable = cast(Drawable)node) { 85 incInspectorModelDrawable(drawable); 86 } 87 88 // Node Part Section 89 if (Part part = cast(Part)node) { 90 incInspectorModelPart(part); 91 } 92 93 // Node SimplePhysics Section 94 if (SimplePhysics part = cast(SimplePhysics)node) { 95 incInspectorModelSimplePhysics(part); 96 } 97 } 98 99 break; 100 default: 101 incCommonNonEditHeader(node); 102 break; 103 } 104 } else incInspectorModelInfo(); 105 } else if (nodes.length == 0) { 106 incLabelOver(_("No nodes selected..."), ImVec2(0, 0), true); 107 } else { 108 incLabelOver(_("Can only inspect a single node..."), ImVec2(0, 0), true); 109 } 110 } 111 112 public: 113 this() { 114 super("Inspector", _("Inspector"), true); 115 } 116 } 117 118 /** 119 Generate logger frame 120 */ 121 mixin incPanel!InspectorPanel; 122 123 124 125 private: 126 127 // 128 // COMMON 129 // 130 131 void incCommonNonEditHeader(Node node) { 132 // Top level 133 igPushID(node.uuid); 134 string typeString = "%s".format(incTypeIdToIcon(node.typeId())); 135 auto len = incMeasureString(typeString); 136 incText(node.name); 137 igSameLine(0, 0); 138 incDummy(ImVec2(-len.x, len.y)); 139 igSameLine(0, 0); 140 incText(typeString); 141 igPopID(); 142 igSeparator(); 143 } 144 145 // 146 // MODEL MODE 147 // 148 149 void incInspectorModelInfo() { 150 auto rootNode = incActivePuppet().root; 151 auto puppet = incActivePuppet(); 152 153 // Top level 154 igPushID(rootNode.uuid); 155 string typeString = ""; 156 auto len = incMeasureString(typeString); 157 incText(_("Puppet")); 158 igSameLine(0, 0); 159 incDummy(ImVec2(-len.x, len.y)); 160 igSameLine(0, 0); 161 incText(typeString); 162 igPopID(); 163 igSeparator(); 164 165 igSpacing(); 166 igSpacing(); 167 168 // Version info 169 { 170 len = incMeasureString(_("Inochi2D Ver.")); 171 incText(puppet.meta.version_); 172 igSameLine(0, 0); 173 incDummy(ImVec2(-(len.x), len.y)); 174 igSameLine(0, 0); 175 incText(_("Inochi2D Ver.")); 176 } 177 178 igSpacing(); 179 igSpacing(); 180 181 if (incBeginCategory(__("General Info"))) { 182 igPushID("Name"); 183 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Name")); 184 incTooltip(_("Name of the puppet")); 185 incInputText("META_NAME", puppet.meta.name); 186 igPopID(); 187 igSpacing(); 188 189 igPushID("Artists"); 190 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Artist(s)")); 191 incTooltip(_("Artists who've drawn the puppet, seperated by comma")); 192 incInputText("META_ARTISTS", puppet.meta.artist); 193 igPopID(); 194 igSpacing(); 195 196 igPushID("Riggers"); 197 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Rigger(s)")); 198 incTooltip(_("Riggers who've rigged the puppet, seperated by comma")); 199 incInputText("META_RIGGERS", puppet.meta.rigger); 200 igPopID(); 201 igSpacing(); 202 203 igPushID("Contact"); 204 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Contact")); 205 incTooltip(_("Where to contact the main author of the puppet")); 206 incInputText("META_CONTACT", puppet.meta.contact); 207 igPopID(); 208 } 209 incEndCategory(); 210 211 if (incBeginCategory(__("Licensing"))) { 212 igPushID("LicenseURL"); 213 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("License URL")); 214 incTooltip(_("Link/URL to license")); 215 incInputText("META_LICENSEURL", puppet.meta.licenseURL); 216 igPopID(); 217 igSpacing(); 218 219 igPushID("Copyright"); 220 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Copyright")); 221 incTooltip(_("Copyright holder information of the puppet")); 222 incInputText("META_COPYRIGHT", puppet.meta.copyright); 223 igPopID(); 224 igSpacing(); 225 226 igPushID("Origin"); 227 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Origin")); 228 incTooltip(_("Where the model comes from on the internet.")); 229 incInputText("META_ORIGIN", puppet.meta.reference); 230 igPopID(); 231 } 232 incEndCategory(); 233 234 if (incBeginCategory(__("Physics Globals"))) { 235 igPushID("PixelsPerMeter"); 236 incText(_("Pixels per meter")); 237 incTooltip(_("Number of pixels that correspond to 1 meter in the physics engine.")); 238 incDragFloat("PixelsPerMeter", &puppet.physics.pixelsPerMeter, 1, 1, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat); 239 igPopID(); 240 igSpacing(); 241 242 igPushID("Gravity"); 243 incText(_("Gravity")); 244 incTooltip(_("Acceleration due to gravity, in m/s². Earth gravity is 9.8.")); 245 incDragFloat("Gravity", &puppet.physics.gravity, 0.01, 0, float.max, _("%.2f m/s²"), ImGuiSliderFlags.NoRoundToFormat); 246 igPopID(); 247 } 248 incEndCategory(); 249 250 if (incBeginCategory(__("Rendering Settings"))) { 251 igPushID("Filtering"); 252 if (igCheckbox(__("Use Point Filtering"), &incActivePuppet().meta.preservePixels)) { 253 incActivePuppet().populateTextureSlots(); 254 incActivePuppet().updateTextureState(); 255 } 256 incTooltip(_("Makes Inochi2D model use point filtering, removing blur for low-resolution models.")); 257 igPopID(); 258 } 259 incEndCategory(); 260 } 261 262 void incModelModeHeader(Node node) { 263 // Top level 264 igPushID(node.uuid); 265 string typeString = "%s".format(incTypeIdToIcon(node.typeId())); 266 auto len = incMeasureString(typeString); 267 incInputText("###MODEL_NODE_HEADER", incAvailableSpace().x-24, node.name); 268 igSameLine(0, 0); 269 incDummy(ImVec2(-len.x, len.y)); 270 igSameLine(0, 0); 271 incText(typeString); 272 igPopID(); 273 } 274 275 void incInspectorModelTRS(Node node) { 276 if (incBeginCategory(__("Transform"))) { 277 float adjustSpeed = 1; 278 // if (igIsKeyDown(igGetKeyIndex(ImGuiKeyModFlags_Shift))) { 279 // adjustSpeed = 0.1; 280 // } 281 282 ImVec2 avail; 283 igGetContentRegionAvail(&avail); 284 285 float fontSize = 16; 286 287 // 288 // Translation 289 // 290 291 // Translation portion of the transformation matrix. 292 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Translation")); 293 igPushItemWidth((avail.x-4f)/3f); 294 295 // Translation X 296 igPushID(0); 297 if (incDragFloat("translation_x", &node.localTransform.translation.vector[0], adjustSpeed, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 298 incActionPush( 299 new NodeValueChangeAction!(Node, float)( 300 "X", 301 node, 302 incGetDragFloatInitialValue("translation_x"), 303 node.localTransform.translation.vector[0], 304 &node.localTransform.translation.vector[0] 305 ) 306 ); 307 } 308 igPopID(); 309 310 igSameLine(0, 4); 311 312 // Translation Y 313 igPushID(1); 314 if (incDragFloat("translation_y", &node.localTransform.translation.vector[1], adjustSpeed, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 315 incActionPush( 316 new NodeValueChangeAction!(Node, float)( 317 "Y", 318 node, 319 incGetDragFloatInitialValue("translation_y"), 320 node.localTransform.translation.vector[1], 321 &node.localTransform.translation.vector[1] 322 ) 323 ); 324 } 325 igPopID(); 326 327 igSameLine(0, 4); 328 329 // Translation Z 330 igPushID(2); 331 if (incDragFloat("translation_z", &node.localTransform.translation.vector[2], adjustSpeed, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 332 incActionPush( 333 new NodeValueChangeAction!(Node, float)( 334 "Z", 335 node, 336 incGetDragFloatInitialValue("translation_z"), 337 node.localTransform.translation.vector[2], 338 &node.localTransform.translation.vector[2] 339 ) 340 ); 341 } 342 igPopID(); 343 344 345 346 // Padding 347 igSpacing(); 348 igSpacing(); 349 350 igBeginGroup(); 351 // Button which locks all transformation to be based off the root node 352 // of the puppet, this more or less makes the item stay in place 353 // even if the parent moves. 354 ImVec2 textLength = incMeasureString(_("Lock to Root Node")); 355 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Lock to Root Node")); 356 357 incSpacer(ImVec2(-12, 1)); 358 bool lockToRoot = node.lockToRoot; 359 if (incLockButton(&lockToRoot, "root_lk")) { 360 361 // TODO: Store this in undo history. 362 node.lockToRoot = lockToRoot; 363 } 364 igEndGroup(); 365 366 // Button which locks all transformation to be based off the root node 367 // of the puppet, this more or less makes the item stay in place 368 // even if the parent moves. 369 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.")); 370 371 // Padding 372 igSpacing(); 373 igSpacing(); 374 375 igPopItemWidth(); 376 377 378 // 379 // Rotation 380 // 381 igSpacing(); 382 383 // Rotation portion of the transformation matrix. 384 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Rotation")); 385 igPushItemWidth((avail.x-4f)/3f); 386 float rotationDegrees; 387 388 // Rotation X 389 igPushID(3); 390 rotationDegrees = degrees(node.localTransform.rotation.vector[0]); 391 if (incDragFloat("rotation_x", &rotationDegrees, adjustSpeed/100, -float.max, float.max, "%.2f°", ImGuiSliderFlags.NoRoundToFormat)) { 392 node.localTransform.rotation.vector[0] = radians(rotationDegrees); 393 394 incActionPush( 395 new NodeValueChangeAction!(Node, float)( 396 _("Rotation X"), 397 node, 398 incGetDragFloatInitialValue("rotation_x"), 399 node.localTransform.rotation.vector[0], 400 &node.localTransform.rotation.vector[0] 401 ) 402 ); 403 } 404 igPopID(); 405 406 igSameLine(0, 4); 407 408 // Rotation Y 409 igPushID(4); 410 rotationDegrees = degrees(node.localTransform.rotation.vector[1]); 411 if (incDragFloat("rotation_y", &rotationDegrees, adjustSpeed/100, -float.max, float.max, "%.2f°", ImGuiSliderFlags.NoRoundToFormat)) { 412 node.localTransform.rotation.vector[1] = radians(rotationDegrees); 413 414 incActionPush( 415 new NodeValueChangeAction!(Node, float)( 416 _("Rotation Y"), 417 node, 418 incGetDragFloatInitialValue("rotation_y"), 419 node.localTransform.rotation.vector[1], 420 &node.localTransform.rotation.vector[1] 421 ) 422 ); 423 } 424 igPopID(); 425 426 igSameLine(0, 4); 427 428 // Rotation Z 429 igPushID(5); 430 rotationDegrees = degrees(node.localTransform.rotation.vector[2]); 431 if (incDragFloat("rotation_z", &rotationDegrees, adjustSpeed/100, -float.max, float.max, "%.2f°", ImGuiSliderFlags.NoRoundToFormat)) { 432 node.localTransform.rotation.vector[2] = radians(rotationDegrees); 433 434 incActionPush( 435 new NodeValueChangeAction!(Node, float)( 436 _("Rotation Z"), 437 node, 438 incGetDragFloatInitialValue("rotation_z"), 439 node.localTransform.rotation.vector[2], 440 &node.localTransform.rotation.vector[2] 441 ) 442 ); 443 } 444 igPopID(); 445 446 igPopItemWidth(); 447 448 avail.x += igGetFontSize(); 449 450 // 451 // Scaling 452 // 453 igSpacing(); 454 455 // Scaling portion of the transformation matrix. 456 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Scale")); 457 igPushItemWidth((avail.x-14f)/2f); 458 459 // Scale X 460 igPushID(6); 461 if (incDragFloat("scale_x", &node.localTransform.scale.vector[0], adjustSpeed/100, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 462 incActionPush( 463 new NodeValueChangeAction!(Node, float)( 464 _("Scale X"), 465 node, 466 incGetDragFloatInitialValue("scale_x"), 467 node.localTransform.scale.vector[0], 468 &node.localTransform.scale.vector[0] 469 ) 470 ); 471 } 472 igPopID(); 473 474 igSameLine(0, 4); 475 476 // Scale Y 477 igPushID(7); 478 if (incDragFloat("scale_y", &node.localTransform.scale.vector[1], adjustSpeed/100, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 479 incActionPush( 480 new NodeValueChangeAction!(Node, float)( 481 _("Scale Y"), 482 node, 483 incGetDragFloatInitialValue("scale_y"), 484 node.localTransform.scale.vector[1], 485 &node.localTransform.scale.vector[1] 486 ) 487 ); 488 } 489 igPopID(); 490 491 igPopItemWidth(); 492 493 igSpacing(); 494 igSpacing(); 495 496 // An option in which positions will be snapped to whole integer values. 497 // In other words texture will always be on a pixel. 498 textLength = incMeasureString(_("Snap to Pixel")); 499 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Snap to Pixel")); 500 incSpacer(ImVec2(-12, 1)); 501 if (incLockButton(&node.localTransform.pixelSnap, "pix_lk")) { 502 incActionPush( 503 new NodeValueChangeAction!(Node, bool)( 504 _("Snap to Pixel"), 505 node, 506 !node.localTransform.pixelSnap, 507 node.localTransform.pixelSnap, 508 &node.localTransform.pixelSnap 509 ) 510 ); 511 } 512 513 // Padding 514 igSpacing(); 515 igSpacing(); 516 517 // The sorting order ID, which Inochi2D uses to sort 518 // Parts to draw in the user specified order. 519 // negative values = closer to camera 520 // positive values = further away from camera 521 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Sorting")); 522 float zsortV = node.relZSort; 523 if (igInputFloat("###ZSort", &zsortV, 0.01, 0.05, "%0.2f")) { 524 node.zSort = zsortV; 525 } 526 } 527 incEndCategory(); 528 } 529 530 void incInspectorModelDrawable(Drawable node) { 531 // The main type of anything that can be drawn to the screen 532 // in Inochi2D. 533 if (incBeginCategory(__("Drawable"))) { 534 float adjustSpeed = 1; 535 ImVec2 avail = incAvailableSpace(); 536 537 igBeginGroup(); 538 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Texture Offset")); 539 igPushItemWidth((avail.x-4f)/2f); 540 541 // Translation X 542 igPushID(42); 543 if (incDragFloat("offset_x", &node.getMesh().origin.vector[0], adjustSpeed, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 544 incActionPush( 545 new NodeValueChangeAction!(Node, float)( 546 "X", 547 node, 548 incGetDragFloatInitialValue("offset_x"), 549 node.getMesh().origin.vector[0], 550 &node.getMesh().origin.vector[0] 551 ) 552 ); 553 } 554 igPopID(); 555 556 igSameLine(0, 4); 557 558 // Translation Y 559 igPushID(43); 560 if (incDragFloat("offset_y", &node.getMesh().origin.vector[1], adjustSpeed, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 561 incActionPush( 562 new NodeValueChangeAction!(Node, float)( 563 "Y", 564 node, 565 incGetDragFloatInitialValue("offset_y"), 566 node.getMesh().origin.vector[1], 567 &node.getMesh().origin.vector[1] 568 ) 569 ); 570 } 571 igPopID(); 572 igPopItemWidth(); 573 igEndGroup(); 574 } 575 incEndCategory(); 576 } 577 578 void incInspectorTextureSlot(Part p, TextureUsage usage, string title, ImVec2 elemSize) { 579 igPushID(p.uuid); 580 igPushID(cast(uint)usage); 581 import std.path : baseName, extension, setExtension; 582 import std.uni : toLower; 583 incTextureSlot(title, p.textures[usage], elemSize); 584 585 // Only have dropdown if there's actually textures in the slot 586 if (p.textures[usage]) { 587 igOpenPopupOnItemClick("TEX_OPTIONS"); 588 if (igBeginPopup("TEX_OPTIONS")) { 589 590 // Allow saving texture to file 591 if (igMenuItem(__("Save to File"))) { 592 TFD_Filter[] filters = [ 593 {["*.png"], "PNG File"} 594 ]; 595 string file = incShowSaveDialog(filters, "texture.png"); 596 if (file) { 597 if (file.extension.empty) { 598 file = file.setExtension("png"); 599 } 600 p.textures[usage].save(file); 601 } 602 } 603 604 if (usage != TextureUsage.Albedo) { 605 if (igMenuItem(__("Remove"))) { 606 p.textures[usage] = null; 607 608 incActivePuppet().rescanNodes(); 609 incActivePuppet().populateTextureSlots(); 610 } 611 } else { 612 // Option which causes the Albedo color to be the emission color. 613 // The item will glow the same color as it, itself is. 614 if (igMenuItem(__("Make Emissive"))) { 615 p.textures[TextureUsage.Emissive] = new Texture( 616 ShallowTexture( 617 p.textures[usage].getTextureData(true), 618 p.textures[usage].width, 619 p.textures[usage].height, 620 4, // Input is RGBA 621 3 // Output should be RGB only 622 ) 623 ); 624 625 incActivePuppet().rescanNodes(); 626 incActivePuppet().populateTextureSlots(); 627 } 628 } 629 630 igEndPopup(); 631 } 632 } 633 634 // FILE DRAG & DROP 635 if (igBeginDragDropTarget()) { 636 const(ImGuiPayload)* payload = igAcceptDragDropPayload("__PARTS_DROP"); 637 if (payload !is null) { 638 string[] files = *cast(string[]*)payload.Data; 639 if (files.length > 0) { 640 string fname = files[0].baseName; 641 642 switch(fname.extension.toLower) { 643 case ".png", ".tga", ".jpeg", ".jpg": 644 645 try { 646 ShallowTexture tex; 647 switch(usage) { 648 case TextureUsage.Albedo: 649 tex = ShallowTexture(files[0], 4); 650 break; 651 case TextureUsage.Emissive: 652 tex = ShallowTexture(files[0], 3); 653 break; 654 case TextureUsage.Bumpmap: 655 tex = ShallowTexture(files[0], 3); 656 break; 657 default: 658 tex = ShallowTexture(files[0]); 659 break; 660 } 661 662 if (usage != TextureUsage.Albedo) { 663 664 // Error out if post processing textures don't match 665 if (tex.width != p.textures[0].width || tex.height != p.textures[0].height) { 666 incDialog(__("Error"), _("Size of given texture does not match the Albedo texture.")); 667 break; 668 } 669 } 670 671 if (tex.convChannels == 4) { 672 inTexPremultiply(tex.data); 673 } 674 p.textures[usage] = new Texture(tex); 675 } catch(Exception ex) { 676 if (ex.msg[0..11] == "unsupported") { 677 incDialog(__("Error"), _("%s is not supported").format(fname)); 678 } else incDialog(__("Error"), ex.msg); 679 } 680 681 682 // We've added new stuff, rescan nodes 683 incActivePuppet().rescanNodes(); 684 incActivePuppet().populateTextureSlots(); 685 break; 686 687 default: 688 incDialog(__("Error"), _("%s is not supported").format(fname)); 689 break; 690 } 691 } 692 693 // Finish the file drag 694 incFinishFileDrag(); 695 } 696 697 igEndDragDropTarget(); 698 } 699 igPopID(); 700 igPopID(); 701 } 702 703 void incInspectorModelPart(Part node) { 704 if (incBeginCategory(__("Part"))) { 705 706 if (!node.getMesh().isReady()) { 707 igSpacing(); 708 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Cannot inspect an unmeshed part")); 709 return; 710 } 711 igSpacing(); 712 713 // BLENDING MODE 714 import std.conv : text; 715 import std.string : toStringz; 716 717 ImVec2 avail = incAvailableSpace(); 718 float availForTextureSlots = round((avail.x/3.0)-2.0); 719 ImVec2 elemSize = ImVec2(availForTextureSlots, availForTextureSlots); 720 721 incInspectorTextureSlot(node, TextureUsage.Albedo, _("Albedo"), elemSize); 722 igSameLine(0, 4); 723 incInspectorTextureSlot(node, TextureUsage.Emissive, _("Emissive"), elemSize); 724 igSameLine(0, 4); 725 incInspectorTextureSlot(node, TextureUsage.Bumpmap, _("Bumpmap"), elemSize); 726 727 igSpacing(); 728 igSpacing(); 729 730 incText(_("Tint (Multiply)")); 731 igColorEdit3("###TINT", cast(float[3]*)node.tint.ptr); 732 733 incText(_("Tint (Screen)")); 734 igColorEdit3("###S_TINT", cast(float[3]*)node.screenTint.ptr); 735 736 // Padding 737 igSpacing(); 738 igSpacing(); 739 igSpacing(); 740 741 // Header for the Blending options for Parts 742 incText(_("Blending")); 743 if (igBeginCombo("###Blending", __(node.blendingMode.text))) { 744 745 // Normal blending mode as used in Photoshop, generally 746 // the default blending mode photoshop starts a layer out as. 747 if (igSelectable(__("Normal"), node.blendingMode == BlendMode.Normal)) node.blendingMode = BlendMode.Normal; 748 749 // Multiply blending mode, in which this texture's color data 750 // will be multiplied with the color data already in the framebuffer. 751 if (igSelectable(__("Multiply"), node.blendingMode == BlendMode.Multiply)) node.blendingMode = BlendMode.Multiply; 752 753 // Color Dodge blending mode 754 if (igSelectable(__("Color Dodge"), node.blendingMode == BlendMode.ColorDodge)) node.blendingMode = BlendMode.ColorDodge; 755 756 // Linear Dodge blending mode 757 if (igSelectable(__("Linear Dodge"), node.blendingMode == BlendMode.LinearDodge)) node.blendingMode = BlendMode.LinearDodge; 758 759 // Screen blending mode 760 if (igSelectable(__("Screen"), node.blendingMode == BlendMode.Screen)) node.blendingMode = BlendMode.Screen; 761 762 // Clip to Lower blending mode 763 if (igSelectable(__("Clip to Lower"), node.blendingMode == BlendMode.ClipToLower)) node.blendingMode = BlendMode.ClipToLower; 764 incTooltip(_("Special blending mode that causes (while respecting transparency) the part to be clipped to everything underneath")); 765 766 // Slice from Lower blending mode 767 if (igSelectable(__("Slice from Lower"), node.blendingMode == BlendMode.SliceFromLower)) node.blendingMode = BlendMode.SliceFromLower; 768 incTooltip(_("Special blending mode that causes (while respecting transparency) the part to be slice by everything underneath.\nBasically reverse Clip to Lower.")); 769 770 igEndCombo(); 771 } 772 773 igSpacing(); 774 775 incText(_("Opacity")); 776 igSliderFloat("###Opacity", &node.opacity, 0, 1f, "%0.2f"); 777 igSpacing(); 778 igSpacing(); 779 780 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Masks")); 781 igSpacing(); 782 783 // Threshold slider name for adjusting how transparent a pixel can be 784 // before it gets discarded. 785 incText(_("Threshold")); 786 igSliderFloat("###Threshold", &node.maskAlphaThreshold, 0.0, 1.0, "%.2f"); 787 788 igSpacing(); 789 790 // The sources that the part gets masked by. Depending on the masking mode 791 // either the sources will cut out things that don't overlap, or cut out 792 // things that do. 793 incText(_("Mask Sources")); 794 if (igBeginListBox("###MaskSources", ImVec2(0, 128))) { 795 if (node.masks.length == 0) { 796 incText(_("(Drag a Part or Mask Here)")); 797 } 798 799 foreach(i; 0..node.masks.length) { 800 MaskBinding* masker = &node.masks[i]; 801 igPushID(cast(int)i); 802 if (igBeginPopup("###MaskSettings")) { 803 if (igBeginMenu(__("Mode"))) { 804 if (igMenuItem(__("Mask"), null, masker.mode == MaskingMode.Mask)) masker.mode = MaskingMode.Mask; 805 if (igMenuItem(__("Dodge"), null, masker.mode == MaskingMode.DodgeMask)) masker.mode = MaskingMode.DodgeMask; 806 807 igEndMenu(); 808 } 809 810 if (igMenuItem(__("Delete"))) { 811 import std.algorithm.mutation : remove; 812 node.masks = node.masks.remove(i); 813 igEndPopup(); 814 igPopID(); 815 igEndListBox(); 816 incEndCategory(); 817 return; 818 } 819 820 igEndPopup(); 821 } 822 823 if (masker.mode == MaskingMode.Mask) igSelectable(_("%s (Mask)").format(masker.maskSrc.name).toStringz); 824 else igSelectable(_("%s (Dodge)").format(masker.maskSrc.name).toStringz); 825 826 827 if(igBeginDragDropTarget()) { 828 const(ImGuiPayload)* payload = igAcceptDragDropPayload("_MASKITEM"); 829 if (payload !is null) { 830 if (MaskBinding* binding = cast(MaskBinding*)payload.Data) { 831 ptrdiff_t maskIdx = node.getMaskIdx(binding.maskSrcUUID); 832 if (maskIdx >= 0) { 833 import std.algorithm.mutation : remove; 834 835 node.masks = node.masks.remove(maskIdx); 836 if (i == 0) node.masks = *binding ~ node.masks; 837 else if (i+1 >= node.masks.length) node.masks ~= *binding; 838 else node.masks = node.masks[0..i] ~ *binding ~ node.masks[i+1..$]; 839 } 840 } 841 } 842 843 igEndDragDropTarget(); 844 } 845 846 // TODO: We really should account for left vs. right handedness 847 if (igIsItemClicked(ImGuiMouseButton.Right)) { 848 igOpenPopup("###MaskSettings"); 849 } 850 851 if(igBeginDragDropSource(ImGuiDragDropFlags.SourceAllowNullID)) { 852 igSetDragDropPayload("_MASKITEM", cast(void*)masker, MaskBinding.sizeof, ImGuiCond.Always); 853 incText(masker.maskSrc.name); 854 igEndDragDropSource(); 855 } 856 igPopID(); 857 } 858 igEndListBox(); 859 } 860 861 if(igBeginDragDropTarget()) { 862 const(ImGuiPayload)* payload = igAcceptDragDropPayload("_PUPPETNTREE"); 863 if (payload !is null) { 864 if (Drawable payloadDrawable = cast(Drawable)*cast(Node*)payload.Data) { 865 866 // Make sure we don't mask against ourselves as well as don't double mask 867 if (payloadDrawable != node && !node.isMaskedBy(payloadDrawable)) { 868 node.masks ~= MaskBinding(payloadDrawable.uuid, MaskingMode.Mask, payloadDrawable); 869 } 870 } 871 } 872 873 igEndDragDropTarget(); 874 } 875 876 // Padding 877 igSpacing(); 878 igSpacing(); 879 } 880 incEndCategory(); 881 } 882 883 void incInspectorModelCamera(ExCamera node) { 884 if (incBeginCategory(__("Camera"))) { 885 886 incText(_("Viewport")); 887 igIndent(); 888 igSetNextItemWidth(incAvailableSpace().x); 889 igDragFloat2("###VIEWPORT", &node.getViewport().vector); 890 igUnindent(); 891 892 // Padding 893 igSpacing(); 894 igSpacing(); 895 } 896 incEndCategory(); 897 } 898 899 void incInspectorModelComposite(Composite node) { 900 if (incBeginCategory(__("Composite"))) { 901 902 903 igSpacing(); 904 905 // BLENDING MODE 906 import std.conv : text; 907 import std.string : toStringz; 908 909 910 incText(_("Tint (Multiply)")); 911 igColorEdit3("###TINT", cast(float[3]*)node.tint.ptr); 912 913 incText(_("Tint (Screen)")); 914 igColorEdit3("###S_TINT", cast(float[3]*)node.screenTint.ptr); 915 916 // Header for the Blending options for Parts 917 incText(_("Blending")); 918 if (igBeginCombo("###Blending", __(node.blendingMode.text))) { 919 920 // Normal blending mode as used in Photoshop, generally 921 // the default blending mode photoshop starts a layer out as. 922 if (igSelectable(__("Normal"), node.blendingMode == BlendMode.Normal)) node.blendingMode = BlendMode.Normal; 923 924 // Multiply blending mode, in which this texture's color data 925 // will be multiplied with the color data already in the framebuffer. 926 if (igSelectable(__("Multiply"), node.blendingMode == BlendMode.Multiply)) node.blendingMode = BlendMode.Multiply; 927 928 // Color Dodge blending mode 929 if (igSelectable(__("Color Dodge"), node.blendingMode == BlendMode.ColorDodge)) node.blendingMode = BlendMode.ColorDodge; 930 931 // Linear Dodge blending mode 932 if (igSelectable(__("Linear Dodge"), node.blendingMode == BlendMode.LinearDodge)) node.blendingMode = BlendMode.LinearDodge; 933 934 // Screen blending mode 935 if (igSelectable(__("Screen"), node.blendingMode == BlendMode.Screen)) node.blendingMode = BlendMode.Screen; 936 937 igEndCombo(); 938 } 939 940 igSpacing(); 941 942 incText(_("Opacity")); 943 igSliderFloat("###Opacity", &node.opacity, 0, 1f, "%0.2f"); 944 igSpacing(); 945 igSpacing(); 946 947 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Masks")); 948 igSpacing(); 949 950 // Threshold slider name for adjusting how transparent a pixel can be 951 // before it gets discarded. 952 incText(_("Threshold")); 953 igSliderFloat("###Threshold", &node.threshold, 0.0, 1.0, "%.2f"); 954 955 igSpacing(); 956 957 // Padding 958 igSpacing(); 959 igSpacing(); 960 } 961 incEndCategory(); 962 } 963 964 void incInspectorModelSimplePhysics(SimplePhysics node) { 965 if (incBeginCategory(__("SimplePhysics"))) { 966 float adjustSpeed = 1; 967 968 igSpacing(); 969 970 // BLENDING MODE 971 import std.conv : text; 972 import std.string : toStringz; 973 974 igPushID("TargetParam"); 975 if (igBeginPopup("TPARAM")) { 976 if (node.param) { 977 if (igMenuItem(__("Unmap"))) { 978 node.param = null; 979 incActivePuppet().rescanNodes(); 980 } 981 } else { 982 incDummyLabel(_("Unassigned"), ImVec2(128, 16)); 983 } 984 985 igEndPopup(); 986 } 987 988 incText(_("Parameter")); 989 string paramName = _("(unassigned)"); 990 if (node.param !is null) paramName = node.param.name; 991 igInputText("###TARGET_PARAM", cast(char*)paramName.toStringz, paramName.length, ImGuiInputTextFlags.ReadOnly); 992 igOpenPopupOnItemClick("TPARAM", ImGuiPopupFlags.MouseButtonRight); 993 994 if(igBeginDragDropTarget()) { 995 const(ImGuiPayload)* payload = igAcceptDragDropPayload("_PARAMETER"); 996 if (payload !is null) { 997 ParamDragDropData* payloadParam = *cast(ParamDragDropData**)payload.Data; 998 node.param = payloadParam.param; 999 incActivePuppet().rescanNodes(); 1000 } 1001 1002 igEndDragDropTarget(); 1003 } 1004 1005 igPopID(); 1006 1007 incText(_("Type")); 1008 if (igBeginCombo("###PhysType", __(node.modelType.text))) { 1009 1010 if (igSelectable(__("Pendulum"), node.modelType == PhysicsModel.Pendulum)) node.modelType = PhysicsModel.Pendulum; 1011 1012 if (igSelectable(__("SpringPendulum"), node.modelType == PhysicsModel.SpringPendulum)) node.modelType = PhysicsModel.SpringPendulum; 1013 1014 igEndCombo(); 1015 } 1016 1017 igSpacing(); 1018 1019 incText(_("Mapping mode")); 1020 if (igBeginCombo("###PhysMapMode", __(node.mapMode.text))) { 1021 1022 if (igSelectable(__("AngleLength"), node.mapMode == ParamMapMode.AngleLength)) node.mapMode = ParamMapMode.AngleLength; 1023 1024 if (igSelectable(__("XY"), node.mapMode == ParamMapMode.XY)) node.mapMode = ParamMapMode.XY; 1025 1026 igEndCombo(); 1027 } 1028 1029 igSpacing(); 1030 1031 igPushID("SimplePhysics"); 1032 1033 igPushID(0); 1034 incText(_("Gravity scale")); 1035 incDragFloat("gravity", &node.gravity, adjustSpeed/100, -float.max, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat); 1036 igSpacing(); 1037 igSpacing(); 1038 igPopID(); 1039 1040 igPushID(1); 1041 incText(_("Length")); 1042 incDragFloat("length", &node.length, adjustSpeed/100, 0, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat); 1043 igSpacing(); 1044 igSpacing(); 1045 igPopID(); 1046 1047 igPushID(2); 1048 incText(_("Resonant frequency")); 1049 incDragFloat("frequency", &node.frequency, adjustSpeed/100, 0.01, 30, "%.2f", ImGuiSliderFlags.NoRoundToFormat); 1050 igSpacing(); 1051 igSpacing(); 1052 igPopID(); 1053 1054 igPushID(3); 1055 incText(_("Damping")); 1056 incDragFloat("damping_angle", &node.angleDamping, adjustSpeed/100, 0, 5, "%.2f", ImGuiSliderFlags.NoRoundToFormat); 1057 igPopID(); 1058 1059 igPushID(4); 1060 incDragFloat("damping_length", &node.lengthDamping, adjustSpeed/100, 0, 5, "%.2f", ImGuiSliderFlags.NoRoundToFormat); 1061 igSpacing(); 1062 igSpacing(); 1063 igPopID(); 1064 1065 igPushID(5); 1066 incText(_("Output scale")); 1067 incDragFloat("output_scale.x", &node.outputScale.vector[0], adjustSpeed/100, 0, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat); 1068 igPopID(); 1069 1070 igPushID(6); 1071 incDragFloat("output_scale.y", &node.outputScale.vector[1], adjustSpeed/100, 0, float.max, "%.2f", ImGuiSliderFlags.NoRoundToFormat); 1072 igSpacing(); 1073 igSpacing(); 1074 igPopID(); 1075 1076 // Padding 1077 igSpacing(); 1078 igSpacing(); 1079 1080 igPopID(); 1081 } 1082 incEndCategory(); 1083 } 1084 1085 // 1086 // MODEL MODE ARMED 1087 // 1088 void incInspectorDeformFloatDragVal(string name, string paramName, float adjustSpeed, Node node, Parameter param, vec2u cursor, bool rotation=false) { 1089 float currFloat = node.getDefaultValue(paramName); 1090 if (ValueParameterBinding b = cast(ValueParameterBinding)param.getBinding(node, paramName)) { 1091 currFloat = b.getValue(cursor); 1092 } 1093 1094 // Convert to degrees for display 1095 if (rotation) currFloat = degrees(currFloat); 1096 1097 if (incDragFloat(name, &currFloat, adjustSpeed, -float.max, float.max, rotation ? "%.2f°" : "%.2f", ImGuiSliderFlags.NoRoundToFormat)) { 1098 1099 // Convert back to radians for data managment 1100 if (rotation) currFloat = radians(currFloat); 1101 1102 // Set binding 1103 GroupAction groupAction = null; 1104 ValueParameterBinding b = cast(ValueParameterBinding)param.getBinding(node, paramName); 1105 if (b is null) { 1106 b = cast(ValueParameterBinding)param.createBinding(node, paramName); 1107 param.addBinding(b); 1108 groupAction = new GroupAction(); 1109 auto addAction = new ParameterBindingAddAction(param, b); 1110 groupAction.addAction(addAction); 1111 } 1112 1113 // Push action 1114 auto action = new ParameterBindingValueChangeAction!(float)(b.getName(), b, cursor.x, cursor.y); 1115 b.setValue(cursor, currFloat); 1116 action.updateNewState(); 1117 if (groupAction) { 1118 groupAction.addAction(action); 1119 incActionPush(groupAction); 1120 } else { 1121 incActionPush(action); 1122 } 1123 } 1124 } 1125 1126 void incInspectorDeformInputFloat(string name, string paramName, float step, float stepFast, Node node, Parameter param, vec2u cursor) { 1127 float currFloat = node.getDefaultValue(paramName); 1128 if (ValueParameterBinding b = cast(ValueParameterBinding)param.getBinding(node, paramName)) { 1129 currFloat = b.getValue(cursor); 1130 } 1131 if (igInputFloat(name.toStringz, &currFloat, step, stepFast, "%.2f")) { 1132 GroupAction groupAction = null; 1133 ValueParameterBinding b = cast(ValueParameterBinding)param.getBinding(node, paramName); 1134 if (b is null) { 1135 b = cast(ValueParameterBinding)param.createBinding(node, paramName); 1136 param.addBinding(b); 1137 groupAction = new GroupAction(); 1138 auto addAction = new ParameterBindingAddAction(param, b); 1139 groupAction.addAction(addAction); 1140 } 1141 auto action = new ParameterBindingValueChangeAction!(float)(b.getName(), b, cursor.x, cursor.y); 1142 b.setValue(cursor, currFloat); 1143 action.updateNewState(); 1144 if (groupAction) { 1145 groupAction.addAction(action); 1146 incActionPush(groupAction); 1147 } else { 1148 incActionPush(action); 1149 } 1150 } 1151 } 1152 1153 void incInspectorDeformColorEdit3(string[3] paramNames, Node node, Parameter param, vec2u cursor) { 1154 import std.math : isNaN; 1155 float[3] rgb = [float.nan, float.nan, float.nan]; 1156 float[3] rgbadj = [1, 1, 1]; 1157 bool[3] rgbchange = [false, false, false]; 1158 ValueParameterBinding pbr = cast(ValueParameterBinding)param.getBinding(node, paramNames[0]); 1159 ValueParameterBinding pbg = cast(ValueParameterBinding)param.getBinding(node, paramNames[1]); 1160 ValueParameterBinding pbb = cast(ValueParameterBinding)param.getBinding(node, paramNames[2]); 1161 1162 if (pbr) { 1163 rgb[0] = pbr.getValue(cursor); 1164 rgbadj[0] = rgb[0]; 1165 } 1166 1167 if (pbg) { 1168 rgb[1] = pbg.getValue(cursor); 1169 rgbadj[1] = rgb[1]; 1170 } 1171 1172 if (pbb) { 1173 rgb[2] = pbb.getValue(cursor); 1174 rgbadj[2] = rgb[2]; 1175 } 1176 1177 if (igColorEdit3("###COLORADJ", &rgbadj)) { 1178 1179 // RED 1180 if (rgbadj[0] != 1) { 1181 auto b = cast(ValueParameterBinding)param.getOrAddBinding(node, paramNames[0]); 1182 b.setValue(cursor, rgbadj[0]); 1183 } else if (pbr) { 1184 pbr.setValue(cursor, rgbadj[0]); 1185 } 1186 1187 // GREEN 1188 if (rgbadj[1] != 1) { 1189 auto b = cast(ValueParameterBinding)param.getOrAddBinding(node, paramNames[1]); 1190 b.setValue(cursor, rgbadj[1]); 1191 } else if (pbg) { 1192 pbg.setValue(cursor, rgbadj[1]); 1193 } 1194 1195 // BLUE 1196 if (rgbadj[2] != 1) { 1197 auto b = cast(ValueParameterBinding)param.getOrAddBinding(node, paramNames[2]); 1198 b.setValue(cursor, rgbadj[2]); 1199 } else if (pbb) { 1200 pbb.setValue(cursor, rgbadj[2]); 1201 } 1202 } 1203 } 1204 1205 void incInspectorDeformSliderFloat(string name, string paramName, float min, float max, Node node, Parameter param, vec2u cursor) { 1206 float currFloat = node.getDefaultValue(paramName); 1207 if (ValueParameterBinding b = cast(ValueParameterBinding)param.getBinding(node, paramName)) { 1208 currFloat = b.getValue(cursor); 1209 } 1210 if (igSliderFloat(name.toStringz, &currFloat, min, max, "%.2f")) { 1211 GroupAction groupAction = null; 1212 ValueParameterBinding b = cast(ValueParameterBinding)param.getBinding(node, paramName); 1213 if (b is null) { 1214 b = cast(ValueParameterBinding)param.createBinding(node, paramName); 1215 param.addBinding(b); 1216 groupAction = new GroupAction(); 1217 auto addAction = new ParameterBindingAddAction(param, b); 1218 groupAction.addAction(addAction); 1219 } 1220 auto action = new ParameterBindingValueChangeAction!(float)(b.getName(), b, cursor.x, cursor.y); 1221 b.setValue(cursor, currFloat); 1222 action.updateNewState(); 1223 if (groupAction) { 1224 groupAction.addAction(action); 1225 incActionPush(groupAction); 1226 } else { 1227 incActionPush(action); 1228 } 1229 } 1230 } 1231 1232 void incInspectorDeformTRS(Node node, Parameter param, vec2u cursor) { 1233 if (incBeginCategory(__("Transform"))) { 1234 float adjustSpeed = 1; 1235 1236 ImVec2 avail; 1237 igGetContentRegionAvail(&avail); 1238 1239 float fontSize = 16; 1240 1241 // 1242 // Translation 1243 // 1244 1245 1246 1247 // Translation portion of the transformation matrix. 1248 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Translation")); 1249 igPushItemWidth((avail.x-4f)/3f); 1250 1251 // Translation X 1252 igPushID(0); 1253 incInspectorDeformFloatDragVal("translation_x", "transform.t.x", 1f, node, param, cursor); 1254 igPopID(); 1255 1256 igSameLine(0, 4); 1257 1258 // Translation Y 1259 igPushID(1); 1260 incInspectorDeformFloatDragVal("translation_y", "transform.t.y", 1f, node, param, cursor); 1261 igPopID(); 1262 1263 igSameLine(0, 4); 1264 1265 // Translation Z 1266 igPushID(2); 1267 incInspectorDeformFloatDragVal("translation_z", "transform.t.z", 1f, node, param, cursor); 1268 igPopID(); 1269 1270 1271 1272 // Padding 1273 igSpacing(); 1274 igSpacing(); 1275 1276 igPopItemWidth(); 1277 1278 1279 // 1280 // Rotation 1281 // 1282 igSpacing(); 1283 1284 // Rotation portion of the transformation matrix. 1285 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Rotation")); 1286 igPushItemWidth((avail.x-4f)/3f); 1287 1288 // Rotation X 1289 igPushID(3); 1290 incInspectorDeformFloatDragVal("rotation.x", "transform.r.x", 0.05f, node, param, cursor, true); 1291 igPopID(); 1292 1293 igSameLine(0, 4); 1294 1295 // Rotation Y 1296 igPushID(4); 1297 incInspectorDeformFloatDragVal("rotation.y", "transform.r.y", 0.05f, node, param, cursor, true); 1298 igPopID(); 1299 1300 igSameLine(0, 4); 1301 1302 // Rotation Z 1303 igPushID(5); 1304 incInspectorDeformFloatDragVal("rotation.z", "transform.r.z", 0.05f, node, param, cursor, true); 1305 igPopID(); 1306 1307 igPopItemWidth(); 1308 1309 avail.x += igGetFontSize(); 1310 1311 // 1312 // Scaling 1313 // 1314 igSpacing(); 1315 1316 // Scaling portion of the transformation matrix. 1317 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Scale")); 1318 igPushItemWidth((avail.x-14f)/2f); 1319 1320 // Scale X 1321 igPushID(6); 1322 incInspectorDeformFloatDragVal("scale.x", "transform.s.x", 0.1f, node, param, cursor); 1323 igPopID(); 1324 1325 igSameLine(0, 4); 1326 1327 // Scale Y 1328 igPushID(7); 1329 incInspectorDeformFloatDragVal("scale.y", "transform.s.y", 0.1f, node, param, cursor); 1330 igPopID(); 1331 1332 igPopItemWidth(); 1333 1334 igSpacing(); 1335 igSpacing(); 1336 1337 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Sorting")); 1338 incInspectorDeformInputFloat("zSort", "zSort", 0.01, 0.05, node, param, cursor); 1339 } 1340 incEndCategory(); 1341 } 1342 1343 void incInspectorDeformPart(Part node, Parameter param, vec2u cursor) { 1344 if (incBeginCategory(__("Part"))) { 1345 igBeginGroup(); 1346 igIndent(16); 1347 // Header for texture options 1348 if (incBeginCategory(__("Textures"))) { 1349 1350 incText(_("Tint (Multiply)")); 1351 1352 incInspectorDeformColorEdit3(["tint.r", "tint.g", "tint.b"], node, param, cursor); 1353 1354 incText(_("Tint (Screen)")); 1355 incInspectorDeformColorEdit3(["screenTint.r", "screenTint.g", "screenTint.b"], node, param, cursor); 1356 1357 // Padding 1358 igSeparator(); 1359 igSpacing(); 1360 igSpacing(); 1361 } 1362 incEndCategory(); 1363 igUnindent(); 1364 igEndGroup(); 1365 1366 incText(_("Opacity")); 1367 incInspectorDeformSliderFloat("###Opacity", "opacity", 0, 1f, node, param, cursor); 1368 igSpacing(); 1369 igSpacing(); 1370 1371 // Threshold slider name for adjusting how transparent a pixel can be 1372 // before it gets discarded. 1373 incText(_("Threshold")); 1374 incInspectorDeformSliderFloat("###Threshold", "alphaThreshold", 0.0, 1.0, node, param, cursor); 1375 } 1376 incEndCategory(); 1377 } 1378 1379 void incInspectorDeformComposite(Composite node, Parameter param, vec2u cursor) { 1380 if (incBeginCategory(__("Composite"))) { 1381 igBeginGroup(); 1382 igIndent(16); 1383 // Header for texture options 1384 if (incBeginCategory(__("Textures"))) { 1385 1386 incText(_("Tint (Multiply)")); 1387 1388 incInspectorDeformColorEdit3(["tint.r", "tint.g", "tint.b"], node, param, cursor); 1389 1390 incText(_("Tint (Screen)")); 1391 1392 incInspectorDeformColorEdit3(["screenTint.r", "screenTint.g", "screenTint.b"], node, param, cursor); 1393 1394 // Padding 1395 igSeparator(); 1396 igSpacing(); 1397 igSpacing(); 1398 } 1399 incEndCategory(); 1400 igUnindent(); 1401 igEndGroup(); 1402 1403 incText(_("Opacity")); 1404 incInspectorDeformSliderFloat("###Opacity", "opacity", 0, 1f, node, param, cursor); 1405 igSpacing(); 1406 igSpacing(); 1407 } 1408 incEndCategory(); 1409 }