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