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