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.actions; 10 import creator.panels; 11 import creator; 12 import creator.widgets; 13 import creator.core; 14 import creator.utils; 15 import inochi2d; 16 import std.string; 17 import std.format; 18 import std.conv; 19 import i18n; 20 21 /** 22 The logger frame 23 */ 24 class NodesPanel : Panel { 25 protected: 26 void treeSetEnabled(Node n, bool enabled) { 27 n.enabled = enabled; 28 foreach(child; n.children) { 29 treeSetEnabled(child, enabled); 30 } 31 } 32 33 void nodeActionsPopup(bool isRoot = false)(Node n) { 34 if (igIsItemClicked(ImGuiMouseButton.Right)) { 35 igOpenPopup("NodeActionsPopup"); 36 } 37 38 if (igBeginPopup("NodeActionsPopup")) { 39 40 auto selected = incSelectedNodes(); 41 42 if (igBeginMenu(__("Add"), true)) { 43 44 igPushFont(incIconFont()); 45 igText(incTypeIdToIcon("Node").ptr); 46 igPopFont(); 47 igSameLine(0, 2); 48 if (igMenuItem(__("Node"), "", false, true)) incAddChildWithHistory(new Node(n), n); 49 50 igPushFont(incIconFont()); 51 igText(incTypeIdToIcon("Mask").ptr); 52 igPopFont(); 53 igSameLine(0, 2); 54 if (igMenuItem(__("Mask"), "", false, true)) { 55 MeshData empty; 56 incAddChildWithHistory(new Mask(empty, n), n); 57 } 58 59 igPushFont(incIconFont()); 60 igText(incTypeIdToIcon("Composite").ptr); 61 igPopFont(); 62 igSameLine(0, 2); 63 if (igMenuItem(__("Composite"), "", false, true)) { 64 incAddChildWithHistory(new Composite(n), n); 65 } 66 67 igPushFont(incIconFont()); 68 igText(incTypeIdToIcon("SimplePhysics").ptr); 69 igPopFont(); 70 igSameLine(0, 2); 71 if (igMenuItem(__("Simple Physics"), "", false, true)) incAddChildWithHistory(new SimplePhysics(n), n); 72 73 igEndMenu(); 74 } 75 76 static if (!isRoot) { 77 if (igMenuItem(n.enabled ? /* Option to hide the node (and subnodes) */ __("Hide") : /* Option to show the node (and subnodes) */ __("Show"))) { 78 n.enabled = !n.enabled; 79 } 80 81 if (igMenuItem(__("Delete"), "", false, !isRoot)) { 82 83 if (selected.length > 1) { 84 foreach(sn; selected) { 85 incDeleteChildWithHistory(sn); 86 } 87 } else { 88 incDeleteChildWithHistory(n); 89 } 90 91 // Make sure we don't keep selecting a node we've removed 92 incSelectNode(null); 93 } 94 95 96 if (igBeginMenu(__("More Info"), true)) { 97 if (selected.length > 1) { 98 foreach(sn; selected) { 99 100 // %s is the name of the node in the More Info menu 101 // %lu is the UUID of the node in the More Info menu 102 igText(__("%s ID: %lu"), sn.name.ptr, sn.uuid); 103 } 104 } else { 105 // %lu is the UUID of the node in the More Info menu 106 igText(__("ID: %lu"), n.uuid); 107 } 108 109 igEndMenu(); 110 } 111 } 112 igEndPopup(); 113 } 114 } 115 116 void treeAddNode(bool isRoot = false)(ref Node n) { 117 igTableNextRow(); 118 119 auto io = igGetIO(); 120 121 // // Draw Enabler for this node first 122 // igTableSetColumnIndex(1); 123 // igPushFont(incIconFont()); 124 // igText(n.enabled ? "\ue8f4" : "\ue8f5"); 125 // igPopFont(); 126 127 128 // Prepare node flags 129 ImGuiTreeNodeFlags flags; 130 if (n.children.length == 0) flags |= ImGuiTreeNodeFlags.Leaf; 131 flags |= ImGuiTreeNodeFlags.DefaultOpen; 132 flags |= ImGuiTreeNodeFlags.OpenOnArrow; 133 134 // Then draw the node tree index 135 igTableSetColumnIndex(0); 136 bool open = igTreeNodeEx(cast(void*)n.uuid, flags, ""); 137 138 // Show node entry stuff 139 igSameLine(0, 4); 140 141 auto selectedNodes = incSelectedNodes(); 142 igPushID(n.uuid); 143 bool selected = incNodeInSelection(n); 144 145 igPushFont(incIconFont()); 146 static if (!isRoot) { 147 if (n.enabled) igText(incTypeIdToIcon(n.typeId).ptr); 148 else igTextDisabled(incTypeIdToIcon(n.typeId).ptr); 149 } else { 150 igText(""); 151 } 152 igPopFont(); 153 igSameLine(0, 2); 154 155 if (igSelectable(isRoot ? __("Puppet") : n.name.toStringz, selected, ImGuiSelectableFlags.None, ImVec2(0, 0))) { 156 switch(incEditMode) { 157 default: 158 if (selected) { 159 if (incSelectedNodes().length > 1) { 160 if (io.KeyCtrl) incRemoveSelectNode(n); 161 else incSelectNode(n); 162 } else { 163 incFocusCamera(n); 164 } 165 } else { 166 if (io.KeyCtrl) incAddSelectNode(n); 167 else incSelectNode(n); 168 } 169 break; 170 } 171 } 172 this.nodeActionsPopup!isRoot(n); 173 174 static if (!isRoot) { 175 if(igBeginDragDropSource(ImGuiDragDropFlags.SourceAllowNullID)) { 176 igSetDragDropPayload("_PUPPETNTREE", cast(void*)&n, (&n).sizeof, ImGuiCond.Always); 177 if (selectedNodes.length > 1) { 178 foreach(node; selectedNodes) { 179 igText(node.name.toStringz); 180 } 181 } else { 182 igText(n.name.toStringz); 183 } 184 igEndDragDropSource(); 185 } 186 } 187 igPopID(); 188 189 // Only allow reparenting one node 190 if (selectedNodes.length < 2) { 191 if(igBeginDragDropTarget()) { 192 ImGuiPayload* payload = igAcceptDragDropPayload("_PUPPETNTREE"); 193 if (payload !is null) { 194 Node payloadNode = *cast(Node*)payload.Data; 195 196 if (payloadNode.canReparent(n)) { 197 incMoveChildWithHistory(payloadNode, n); 198 } 199 200 igTreePop(); 201 return; 202 } 203 igEndDragDropTarget(); 204 } 205 } 206 207 if (open) { 208 // Draw children 209 foreach(i, child; n.children) { 210 igPushID(cast(int)i); 211 igTableNextRow(); 212 igTableSetColumnIndex(0); 213 igInvisibleButton("###TARGET", ImVec2(128, 4)); 214 215 if(igBeginDragDropTarget()) { 216 ImGuiPayload* payload = igAcceptDragDropPayload("_PUPPETNTREE"); 217 if (payload !is null) { 218 Node payloadNode = *cast(Node*)payload.Data; 219 220 if (payloadNode.canReparent(n)) { 221 auto idx = payloadNode.getIndexInNode(n); 222 if (idx >= 0) { 223 payloadNode.insertInto(n, clamp(idx < i ? i-1 : i, 0, n.children.length)); 224 } else { 225 payloadNode.insertInto(n, clamp(cast(ptrdiff_t)i, 0, n.children.length)); 226 } 227 } 228 229 igPopID(); 230 igTreePop(); 231 return; 232 } 233 igEndDragDropTarget(); 234 } 235 igPopID(); 236 237 treeAddNode(child); 238 } 239 igTreePop(); 240 } 241 242 243 } 244 245 override 246 void onUpdate() { 247 248 if (incEditMode == EditMode.ModelEdit) { 249 if (!incArmedParameter) { 250 auto io = igGetIO(); 251 if (io.KeyCtrl && igIsKeyPressed(igGetKeyIndex(ImGuiKey.A), false)) { 252 incSelectAll(); 253 } 254 } 255 } 256 257 if (incEditMode == EditMode.VertexEdit) { 258 igText(__("In vertex edit mode...")); 259 return; 260 } 261 262 igBeginChild_Str("NodesMain", ImVec2(0, -30), false); 263 igPushStyleVar(ImGuiStyleVar.CellPadding, ImVec2(4, 1)); 264 igPushStyleVar(ImGuiStyleVar.IndentSpacing, 14); 265 266 if (igBeginTable("NodesContent", 2, ImGuiTableFlags.ScrollX, ImVec2(0, 0), 0)) { 267 igTableSetupColumn("Nodes", ImGuiTableColumnFlags.WidthFixed, 0, 0); 268 //igTableSetupColumn("Visibility", ImGuiTableColumnFlags_WidthFixed, 32, 1); 269 270 if (incEditMode == EditMode.ModelEdit) { 271 treeAddNode!true(incActivePuppet.root); 272 } 273 274 igEndTable(); 275 } 276 if (igIsItemClicked(ImGuiMouseButton.Left)) { 277 incSelectNode(null); 278 } 279 igPopStyleVar(); 280 igPopStyleVar(); 281 igEndChild(); 282 283 igSeparator(); 284 igSpacing(); 285 286 igPushFont(incIconFont()); 287 if (incEditMode() == EditMode.ModelEdit) { 288 auto selected = incSelectedNodes(); 289 if (igButton("", ImVec2(24, 24))) { 290 foreach(payloadNode; selected) incDeleteChildWithHistory(payloadNode); 291 } 292 293 if(igBeginDragDropTarget()) { 294 ImGuiPayload* payload = igAcceptDragDropPayload("_PUPPETNTREE"); 295 if (payload !is null) { 296 Node payloadNode = *cast(Node*)payload.Data; 297 298 if (selected.length > 1) { 299 foreach(pn; selected) incDeleteChildWithHistory(pn); 300 incSelectNode(null); 301 } else { 302 303 // Make sure we don't keep selecting a node we've removed 304 if (incNodeInSelection(payloadNode)) { 305 incSelectNode(null); 306 } 307 308 incDeleteChildWithHistory(payloadNode); 309 } 310 311 igPopFont(); 312 return; 313 } 314 igEndDragDropTarget(); 315 } 316 } 317 igPopFont(); 318 319 } 320 321 public: 322 323 this() { 324 super("Nodes", _("Nodes"), true); 325 flags |= ImGuiWindowFlags.NoScrollbar; 326 } 327 } 328 329 /** 330 Generate nodes frame 331 */ 332 mixin incPanel!NodesPanel; 333 334