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.nodes;
8 import creator.viewport.vertex;
9 import creator.widgets.dragdrop;
10 import creator.actions;
11 import creator.panels;
12 import creator.ext;
13 import creator;
14 import creator.widgets;
15 import creator.ext;
16 import creator.core;
17 import creator.core.input;
18 import creator.utils;
19 import inochi2d;
20 import std.string;
21 import std.format;
22 import std.conv;
23 import i18n;
24 
25 /**
26     The logger frame
27 */
28 class NodesPanel : Panel {
29 protected:
30     void treeSetEnabled(Node n, bool enabled) {
31         n.enabled = enabled;
32         foreach(child; n.children) {
33             treeSetEnabled(child, enabled);
34         }
35     }
36 
37     void nodeActionsPopup(bool isRoot = false)(Node n) {
38         if (igIsItemClicked(ImGuiMouseButton.Right)) {
39             igOpenPopup("NodeActionsPopup");
40         }
41 
42         if (igBeginPopup("NodeActionsPopup")) {
43             
44             auto selected = incSelectedNodes();
45             
46             if (igBeginMenu(__("Add"), true)) {
47 
48                 incText(incTypeIdToIcon("Node"));
49                 igSameLine(0, 2);
50                 if (igMenuItem(__("Node"), "", false, true)) incAddChildWithHistory(new Node(n), n);
51                 
52                 incText(incTypeIdToIcon("Mask"));
53                 igSameLine(0, 2);
54                 if (igMenuItem(__("Mask"), "", false, true)) {
55                     MeshData empty;
56                     incAddChildWithHistory(new Mask(empty, n), n);
57                 }
58                 
59                 incText(incTypeIdToIcon("Composite"));
60                 igSameLine(0, 2);
61                 if (igMenuItem(__("Composite"), "", false, true)) {
62                     incAddChildWithHistory(new Composite(n), n);
63                 }
64                 
65                 incText(incTypeIdToIcon("SimplePhysics"));
66                 igSameLine(0, 2);
67                 if (igMenuItem(__("Simple Physics"), "", false, true)) incAddChildWithHistory(new SimplePhysics(n), n);
68 
69                 
70                 incText(incTypeIdToIcon("Camera"));
71                 igSameLine(0, 2);
72                 if (igMenuItem(__("Camera"), "", false, true)) incAddChildWithHistory(new ExCamera(n), n);
73 
74                 igEndMenu();
75             }
76 
77             static if (!isRoot) {
78 
79                 // Edit mesh option for drawables
80                 if (Drawable d = cast(Drawable)n) {
81                     if (!incArmedParameter()) {
82                         if (igMenuItem(__("Edit Mesh"))) {
83                             incVertexEditStartEditing(d);
84                         }
85                     }
86                 }
87                 
88                 if (igMenuItem(n.enabled ? /* Option to hide the node (and subnodes) */ __("Hide") :  /* Option to show the node (and subnodes) */ __("Show"))) {
89                     n.enabled = !n.enabled;
90                 }
91 
92                 if (igMenuItem(__("Delete"), "", false, !isRoot)) {
93 
94                     if (selected.length > 1) {
95                         foreach(sn; selected) {
96                             incDeleteChildWithHistory(sn);
97                         }
98                     } else {
99                         incDeleteChildWithHistory(n);
100                     }
101 
102                     // Make sure we don't keep selecting a node we've removed
103                     incSelectNode(null);
104                 }
105                 
106 
107                 if (igBeginMenu(__("More Info"), true)) {
108                     if (selected.length > 1) {
109                         foreach(sn; selected) {
110                             
111                             // %s is the name of the node in the More Info menu
112                             // %u is the UUID of the node in the More Info menu
113                             incText(_("%s ID: %u").format(sn.name, sn.uuid));
114 
115                             if (ExPart exp = cast(ExPart)sn) {
116                                 incText(_("%s Layer: %s").format(exp.name, exp.layerPath));
117                             }
118                         }
119                     } else {
120                         // %u is the UUID of the node in the More Info menu
121                         incText(_("ID: %u").format(n.uuid));
122 
123                         if (ExPart exp = cast(ExPart)n) {
124                             incText(_("Layer: %s").format(exp.layerPath));
125                         }
126                     }
127 
128                     igEndMenu();
129                 }
130             }
131             igEndPopup();
132         }
133     }
134 
135     void treeAddNode(bool isRoot = false)(ref Node n) {
136         igTableNextRow();
137 
138         auto io = igGetIO();
139 
140         // // Draw Enabler for this node first
141         // igTableSetColumnIndex(1);
142         // igPushFont(incIconFont());
143         //     incText(n.enabled ? "\ue8f4" : "\ue8f5");
144         // igPopFont();
145 
146 
147         // Prepare node flags
148         ImGuiTreeNodeFlags flags;
149         if (n.children.length == 0) flags |= ImGuiTreeNodeFlags.Leaf;
150         flags |= ImGuiTreeNodeFlags.DefaultOpen;
151         flags |= ImGuiTreeNodeFlags.OpenOnArrow;
152 
153 
154         // Then draw the node tree index
155         igTableSetColumnIndex(0);
156         igSetNextItemWidth(8);
157         bool open = igTreeNodeEx(cast(void*)n.uuid, flags, "");
158 
159             // Show node entry stuff
160             igSameLine(0, 4);
161 
162             auto selectedNodes = incSelectedNodes();
163             igPushID(n.uuid);
164                     bool selected = incNodeInSelection(n);
165 
166                     igBeginGroup();
167                         igIndent(4);
168 
169                         // Type Icon
170                         static if (!isRoot) {
171                             if (n.enabled) incText(incTypeIdToIcon(n.typeId));
172                             else incTextDisabled(incTypeIdToIcon(n.typeId));
173                             if (igIsItemClicked()) {
174                                 n.enabled = !n.enabled;
175                             }
176                         } else {
177                             incText("");
178                         }
179                         igSameLine(0, 2);
180 
181                         // Selectable
182                         if (igSelectable(isRoot ? __("Puppet") : n.name.toStringz, selected, ImGuiSelectableFlags.None, ImVec2(0, 0))) {
183                             switch(incEditMode) {
184                                 default:
185                                     if (selected) {
186                                         if (incSelectedNodes().length > 1) {
187                                             if (io.KeyCtrl) incRemoveSelectNode(n);
188                                             else incSelectNode(n);
189                                         } else {
190                                             incFocusCamera(n);
191                                         }
192                                     } else {
193                                         if (io.KeyCtrl) incAddSelectNode(n);
194                                         else incSelectNode(n);
195                                     }
196                                     break;
197                             }
198                         }
199                         this.nodeActionsPopup!isRoot(n);
200                     igEndGroup();
201 
202                     static if (!isRoot) {
203                         if(igBeginDragDropSource(ImGuiDragDropFlags.SourceAllowNullID)) {
204                             igSetDragDropPayload("_PUPPETNTREE", cast(void*)&n, (&n).sizeof, ImGuiCond.Always);
205                             if (selectedNodes.length > 1) {
206                                 incDragdropNodeList(selectedNodes);
207                             } else {
208                                 incDragdropNodeList(n);
209                             }
210                             igEndDragDropSource();
211                         }
212                     }
213             igPopID();
214 
215             if(igBeginDragDropTarget()) {
216                 const(ImGuiPayload)* payload = igAcceptDragDropPayload("_PUPPETNTREE");
217                 if (payload !is null) {
218                     Node payloadNode = *cast(Node*)payload.Data;
219                     
220                     try {
221                         if (selectedNodes.length > 1) incMoveChildrenWithHistory(selectedNodes, n, 0);
222                         else incMoveChildWithHistory(payloadNode, n, 0);
223                     } catch (Exception ex) {
224                         incDialog(__("Error"), ex.msg);
225                     }
226 
227                     if (open) igTreePop();
228                     igEndDragDropTarget();
229                     return;
230                 }
231                 igEndDragDropTarget();
232             }
233 
234         if (open) {
235             // Draw children
236             foreach(i, child; n.children) {
237                 igPushID(cast(int)i);
238                     igTableNextRow();
239                     igTableSetColumnIndex(0);
240                     igInvisibleButton("###TARGET", ImVec2(128, 4));
241 
242                     if(igBeginDragDropTarget()) {
243                         const(ImGuiPayload)* payload = igAcceptDragDropPayload("_PUPPETNTREE");
244                         if (payload !is null) {
245                             Node payloadNode = *cast(Node*)payload.Data;
246                             
247                             if (selectedNodes.length > 1) incMoveChildrenWithHistory(selectedNodes, n, i);
248                             else incMoveChildWithHistory(payloadNode, n, i);
249 
250                             // if (payloadNode.canReparent(n)) {
251                             //     auto idx = payloadNode.getIndexInNode(n);
252                             // }
253                             //     if (idx >= 0) {
254                             //         payloadNode.setRelativeTo(n);
255                             //         payloadNode.insertInto(n, clamp(idx < i ? i-1 : i, 0, n.children.length));
256                             //     } else {
257                             //         payloadNode.setRelativeTo(n);
258                             //         payloadNode.insertInto(n, clamp(cast(ptrdiff_t)i, 0, n.children.length));
259                             //     }
260                             // }
261                             
262                             igPopID();
263                             igTreePop();
264                             igEndDragDropTarget();
265                             return;
266                         }
267                         igEndDragDropTarget();
268                     }
269                 igPopID();
270 
271                 treeAddNode(child);
272             }
273             igTreePop();
274         }
275         
276 
277     }
278 
279     override
280     void onUpdate() {
281 
282         if (incEditMode == EditMode.ModelEdit) {
283             if (!incArmedParameter && (igIsWindowFocused(ImGuiFocusedFlags.ChildWindows) || igIsWindowHovered(ImGuiHoveredFlags.ChildWindows))) {
284                 if (incShortcut("Ctrl+A")) {
285                     incSelectAll();
286                 }
287             }
288         }
289 
290         if (incEditMode == EditMode.VertexEdit) {
291             incLabelOver(_("In vertex edit mode..."), ImVec2(0, 0), true);
292             return;
293         }
294 
295         if (igBeginChild("NodesMain", ImVec2(0, -30), false)) {
296             
297             // temp variables
298             float scrollDelta = 0;
299             auto avail = incAvailableSpace();
300 
301             // Get the screen position of our node window
302             // as well as the size for the drag/drop scroll
303             ImVec2 screenPos;
304             igGetCursorScreenPos(&screenPos);
305             ImRect crect = ImRect(
306                 screenPos,
307                 ImVec2(screenPos.x+avail.x, screenPos.y+avail.y)
308             );
309 
310             // Handle figuring out whether the user is trying to scroll the list via drag & drop
311             // We're only peeking in to the contents of the payload.
312             incBeginDragDropFake();
313                 auto data = igAcceptDragDropPayload("_PUPPETNTREE", ImGuiDragDropFlags.AcceptPeekOnly | ImGuiDragDropFlags.SourceAllowNullID);
314                 if (igIsMouseDragging(ImGuiMouseButton.Left) && data && data.Data) {
315                     ImVec2 mousePos;
316                     igGetMousePos(&mousePos);
317 
318                     // If mouse is inside the window
319                     if (mousePos.x > crect.Min.x && mousePos.x < crect.Max.x) {
320                         float scrollSpeed = (4*60)*deltaTime();
321 
322                         if (mousePos.y < crect.Min.y+32 && mousePos.y >= crect.Min.y) scrollDelta = -scrollSpeed;
323                         if (mousePos.y > crect.Max.y-32 && mousePos.y <= crect.Max.y) scrollDelta = scrollSpeed;
324                     }
325                 }
326             incEndDragDropFake();
327 
328             igPushStyleVar(ImGuiStyleVar.CellPadding, ImVec2(4, 1));
329             igPushStyleVar(ImGuiStyleVar.IndentSpacing, 14);
330 
331             if (igBeginTable("NodesContent", 2, ImGuiTableFlags.ScrollX, ImVec2(0, 0), 0)) {
332                 auto window = igGetCurrentWindow();
333                 igSetScrollY(window.Scroll.y+scrollDelta);
334                 igTableSetupColumn("Nodes", ImGuiTableColumnFlags.WidthFixed, 0, 0);
335                 //igTableSetupColumn("Visibility", ImGuiTableColumnFlags_WidthFixed, 32, 1);
336                 
337                 if (incEditMode == EditMode.ModelEdit) {
338                     igPushStyleVar(ImGuiStyleVar.ItemSpacing, ImVec2(4, 4));
339                         treeAddNode!true(incActivePuppet.root);
340                     igPopStyleVar();
341                 }
342 
343                 igEndTable();
344             }
345             if (igIsItemClicked(ImGuiMouseButton.Left)) {
346                 incSelectNode(null);
347             }
348             igPopStyleVar();
349             igPopStyleVar();
350         }
351         igEndChild();
352 
353         igSeparator();
354         igSpacing();
355         
356         if (incEditMode() == EditMode.ModelEdit) {
357             auto selected = incSelectedNodes();
358             if (igButton("", ImVec2(24, 24))) {
359                 foreach(payloadNode; selected) incDeleteChildWithHistory(payloadNode);
360             }
361 
362             if(igBeginDragDropTarget()) {
363                 const(ImGuiPayload)* payload = igAcceptDragDropPayload("_PUPPETNTREE");
364                 if (payload !is null) {
365                     Node payloadNode = *cast(Node*)payload.Data;
366 
367                     if (selected.length > 1) {
368                         foreach(pn; selected) incDeleteChildWithHistory(pn);
369                         incSelectNode(null);
370                     } else {
371 
372                         // Make sure we don't keep selecting a node we've removed
373                         if (incNodeInSelection(payloadNode)) {
374                             incSelectNode(null);
375                         }
376 
377                         incDeleteChildWithHistory(payloadNode);
378                     }
379                     
380                     igPopFont();
381                     return;
382                 }
383                 igEndDragDropTarget();
384             }
385         }
386 
387     }
388 
389 public:
390 
391     this() {
392         super("Nodes", _("Nodes"), true);
393         flags |= ImGuiWindowFlags.NoScrollbar;
394     }
395 }
396 
397 /**
398     Generate nodes frame
399 */
400 mixin incPanel!NodesPanel;
401 
402