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 }