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 }