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.actions.node; 8 import creator.core.actionstack; 9 import creator.actions; 10 import creator; 11 import inochi2d; 12 import std.format; 13 import i18n; 14 import std.exception; 15 16 /** 17 An action that happens when a node is changed 18 */ 19 class NodeMoveAction : Action { 20 public: 21 22 /** 23 Descriptive name 24 */ 25 string descrName; 26 27 /** 28 Which index in to the parent the nodes should be placed 29 */ 30 size_t parentOffset; 31 32 /** 33 Previous parent of node 34 */ 35 Node[uint] prevParents; 36 size_t[uint] prevOffsets; 37 38 /** 39 Nodes that was moved 40 */ 41 Node[] nodes; 42 43 /** 44 New parent of node 45 */ 46 Node newParent; 47 48 /** 49 The original transform of the node 50 */ 51 Transform originalTransform; 52 53 /** 54 The new transform of the node 55 */ 56 Transform newTransform; 57 58 /** 59 Creates a new node change action 60 */ 61 this(Node[] nodes, Node new_, size_t pOffset = 0) { 62 this.newParent = new_; 63 this.nodes = nodes; 64 this.parentOffset = pOffset; 65 66 // Enforce reparenting rules 67 foreach(sn; nodes) enforce(sn.canReparent(new_), _("%s can not be reparented in to %s due to a circular dependency.").format(sn.name, new_.name)); 68 69 // Reparent 70 foreach(ref sn; nodes) { 71 72 // Store ref to prev parent 73 if (sn.parent) { 74 prevParents[sn.uuid] = sn.parent; 75 prevOffsets[sn.uuid] = sn.getIndexInParent(); 76 } 77 78 // Set relative position 79 sn.setRelativeTo(new_); 80 sn.parent = new_; 81 sn.insertInto(new_, pOffset); 82 } 83 incActivePuppet().rescanNodes(); 84 85 // Set visual name 86 if (nodes.length == 1) descrName = nodes[0].name; 87 else descrName = _("nodes"); 88 } 89 90 /** 91 Rollback 92 */ 93 void rollback() { 94 foreach(ref sn; nodes) { 95 sn.setRelativeTo(prevParents[sn.uuid]); 96 sn.insertInto(prevParents[sn.uuid], prevOffsets[sn.uuid]); 97 } 98 incActivePuppet().rescanNodes(); 99 } 100 101 /** 102 Redo 103 */ 104 void redo() { 105 foreach(sn; nodes) { 106 sn.setRelativeTo(newParent); 107 sn.insertInto(newParent, parentOffset); 108 } 109 incActivePuppet().rescanNodes(); 110 } 111 112 /** 113 Describe the action 114 */ 115 string describe() { 116 if (prevParents.length == 0) return _("Created %s").format(descrName); 117 if (newParent is null) return _("Deleted %s").format(descrName); 118 return _("Moved %s to %s").format(descrName, newParent.name); 119 } 120 121 /** 122 Describe the action 123 */ 124 string describeUndo() { 125 if (prevParents.length == 0) return _("Created %s").format(descrName); 126 if (nodes.length == 1 && prevParents.length == 1) return _("Moved %s from %s").format(descrName, prevParents[nodes[0].uuid].name); 127 return _("Moved %s from origin").format(descrName); 128 } 129 130 /** 131 Gets name of this action 132 */ 133 string getName() { 134 return this.stringof; 135 } 136 137 bool merge(Action other) { return false; } 138 bool canMerge(Action other) { return false; } 139 } 140 141 /** 142 Action for whether a node was activated or deactivated 143 */ 144 class NodeActiveAction : Action { 145 public: 146 Node self; 147 bool newState; 148 149 /** 150 Rollback 151 */ 152 void rollback() { 153 self.enabled = !newState; 154 } 155 156 /** 157 Redo 158 */ 159 void redo() { 160 self.enabled = newState; 161 } 162 163 /** 164 Describe the action 165 */ 166 string describe() { 167 return "%s %s".format(newState ? _("Enabled") : _("Disabled"), self.name); 168 } 169 170 /** 171 Describe the action 172 */ 173 string describeUndo() { 174 return _("%s was %s").format(self.name, !newState ? _("Enabled") : _("Disabled")); 175 } 176 177 /** 178 Gets name of this action 179 */ 180 string getName() { 181 return this.stringof; 182 } 183 184 bool merge(Action other) { return false; } 185 bool canMerge(Action other) { return false; } 186 } 187 188 /** 189 Moves multiple children with history 190 */ 191 void incMoveChildrenWithHistory(Node[] n, Node to, size_t offset) { 192 // Push action to stack 193 incActionPush(new NodeMoveAction( 194 n, 195 to, 196 offset 197 )); 198 } 199 200 /** 201 Moves child with history 202 */ 203 void incMoveChildWithHistory(Node n, Node to, size_t offset) { 204 incMoveChildrenWithHistory([n], to, offset); 205 } 206 207 /** 208 Adds child with history 209 */ 210 void incAddChildWithHistory(Node n, Node to, string name=null) { 211 if (to is null) to = incActivePuppet().root; 212 213 // Push action to stack 214 incActionPush(new NodeMoveAction( 215 [n], 216 to 217 )); 218 219 n.insertInto(to, Node.OFFSET_START); 220 if (name is null) n.name = _("Unnamed ")~_(n.typeId()); 221 else n.name = name; 222 incActivePuppet().rescanNodes(); 223 } 224 225 /** 226 Deletes child with history 227 */ 228 void incDeleteChildWithHistory(Node n) { 229 // Push action to stack 230 incActionPush(new NodeMoveAction( 231 [n], 232 null 233 )); 234 235 n.parent = null; 236 incActivePuppet().rescanNodes(); 237 } 238 239 /** 240 Node value changed action 241 */ 242 class NodeValueChangeAction(TNode, T) : Action if (is(TNode : Node)) { 243 public: 244 alias TSelf = typeof(this); 245 TNode node; 246 T oldValue; 247 T newValue; 248 T* valuePtr; 249 string name; 250 251 this(string name, TNode node, T oldValue, T newValue, T* valuePtr) { 252 this.name = name; 253 this.node = node; 254 this.oldValue = oldValue; 255 this.newValue = newValue; 256 this.valuePtr = valuePtr; 257 } 258 259 /** 260 Rollback 261 */ 262 void rollback() { 263 *valuePtr = oldValue; 264 } 265 266 /** 267 Redo 268 */ 269 void redo() { 270 *valuePtr = newValue; 271 } 272 273 /** 274 Describe the action 275 */ 276 string describe() { 277 return _("%s->%s changed to %s").format(node.name, name, newValue); 278 } 279 280 /** 281 Describe the action 282 */ 283 string describeUndo() { 284 return _("%s->%s changed from %s").format(node.name, name, oldValue); 285 } 286 287 /** 288 Gets name of this action 289 */ 290 string getName() { 291 return name; 292 } 293 294 /** 295 Merge 296 */ 297 bool merge(Action other) { 298 if (this.canMerge(other)) { 299 this.newValue = (cast(TSelf)other).newValue; 300 return true; 301 } 302 return false; 303 } 304 305 /** 306 Gets whether this node can merge with an other 307 */ 308 bool canMerge(Action other) { 309 TSelf otherChange = cast(TSelf) other; 310 return (otherChange !is null && otherChange.getName() == this.getName()); 311 } 312 }