1 /*
2     Copyright © 2020,2022 Inochi2D Project
3     Distributed under the 2-Clause BSD License, see LICENSE file.
4 */
5 module creator.actions.binding;
6 
7 import creator.core.actionstack;
8 import creator.actions;
9 import creator;
10 import inochi2d;
11 import std.format;
12 import std.stdio;
13 import std.range;
14 import std.algorithm.mutation;
15 import i18n;
16 
17 private {
18     T[][] duplicate(T)(ref T[][] source) {
19         T[][] target = source.dup;
20         foreach (i, s; source) {
21             target[i] = s.dup;
22         }
23         return target;
24     }
25 
26     void copy(T)(ref T[][] source, ref T[][] target) {
27         foreach (sarray, tarray; zip(source, target)) {
28             foreach (i, s; sarray) {
29                 tarray[i] = s;
30             }
31         }
32     }
33 
34 }
35 
36 /**
37     Action for add / remove of binding
38 */
39 class ParameterBindingAddRemoveAction(bool added = true) : Action {
40 public:
41     Parameter        parent;
42     ParameterBinding self;
43 
44     this(Parameter parent, ParameterBinding self) {
45         this.parent = parent;
46         this.self   = self;
47     }
48 
49     /**
50         Rollback
51     */
52     void rollback() {
53         if (!added)
54             parent.bindings ~= self;
55         else
56             parent.removeBinding(self);
57     }
58 
59     /**
60         Redo
61     */
62     void redo() {
63         if (added)
64             parent.bindings ~= self;
65         else
66             parent.removeBinding(self);
67     }
68 
69     /**
70         Describe the action
71     */
72     string describe() {
73         if (added)
74             return _("Added binding %s").format(self.getName());
75         else
76             return _("Removed binding %s").format(self.getName());
77     }
78 
79     /**
80         Describe the action
81     */
82     string describeUndo() {
83         if (added)
84             return _("Binding %s was removed").format(self.getName());
85         else
86             return _("Binding %s was added").format(self.getName());
87     }
88 
89     /**
90         Gets name of this action
91     */
92     string getName() {
93         return this.stringof;
94     }
95     
96     bool merge(Action other) { return false; }
97     bool canMerge(Action other) { return false; }
98 }
99 
100 alias ParameterBindingAddAction    = ParameterBindingAddRemoveAction!true;
101 alias ParameterBindingRemoveAction = ParameterBindingAddRemoveAction!false;
102 
103 /**
104     Action for change of all of binding values (and isSet value) at once
105 */
106 class ParameterBindingAllValueChangeAction(T)  : LazyBoundAction {
107     alias TSelf    = typeof(this);
108     alias TBinding = ParameterBindingImpl!(T);
109     string   name;
110     TBinding self;
111     T[][] values;
112     bool[][] isSet;
113     bool undoable = true;
114 
115     this(string name, TBinding self, void delegate() update = null) {
116         this.name = name;
117         this.self = self;
118         values = duplicate!T(self.values);
119         isSet  = duplicate!bool(self.isSet_);
120         if (update !is null) {
121             update();
122             updateNewState();
123         }
124     }
125 
126     void updateNewState() {
127     }
128 
129     /**
130         Rollback
131     */
132     void rollback() {
133         if (undoable) {
134             swap(values, self.values);
135             swap(isSet, self.isSet_);
136             undoable = false;
137         }
138     }
139 
140     /**
141         Redo
142     */
143     void redo() {
144         if (!undoable) {
145             swap(values, self.values);
146             swap(isSet, self.isSet_);
147             undoable = true;
148         }
149     }
150 
151     /**
152         Describe the action
153     */
154     string describe() {
155         return _("%s->%s changed").format(self.getName(), name);
156     }
157 
158     /**
159         Describe the action
160     */
161     string describeUndo() {
162         return _("%s->%s change cancelled").format(self.getName(), name);
163     }
164 
165     /**
166         Gets name of this action
167     */
168     string getName() {
169         return this.stringof;
170     }
171     
172     /**
173         Merge
174     */
175     bool merge(Action other) {
176         if (this.canMerge(other)) {
177             return true;
178         }
179         return false;
180     }
181 
182     /**
183         Gets whether this node can merge with an other
184     */
185     bool canMerge(Action other) {
186         TSelf otherChange = cast(TSelf) other;
187         return (otherChange !is null && this.name == otherChange.name);
188     }
189 };
190 
191 
192 /**
193     Action to change binding value (and isSet) of specified keypoint.
194 */
195 class ParameterBindingValueChangeAction(T)  : LazyBoundAction {
196     alias TSelf    = typeof(this);
197     string name;
198     alias TBinding = ParameterBindingImpl!(T);
199     TBinding self;
200     int  pointx;
201     int  pointy;
202     T    value;
203     bool isSet;
204     bool undoable;
205 
206     this(string name, TBinding self, int pointx, int pointy, void delegate() update = null) {
207         this.name  = name;
208         this.self  = self;
209         this.pointx = pointx;
210         this.pointy = pointy;
211         this.value  = self.values[pointx][pointy];
212         this.isSet  = self.isSet_[pointx][pointy];
213         this.undoable = true;
214         if (update !is null) {
215             update();
216             updateNewState();
217         }
218     }
219 
220     void updateNewState() {
221     }
222 
223     /**
224         Rollback
225     */
226     void rollback() {
227         if (undoable) {
228             swap(self.values[pointx][pointy], value);
229             swap(self.isSet_[pointx][pointy], isSet);
230             import std.stdio;
231             self.reInterpolate();
232             undoable = false;
233         }
234     }
235 
236     /**
237         Redo
238     */
239     void redo() {
240         if (!undoable) {
241             swap(self.values[pointx][pointy], value);
242             swap(self.isSet_[pointx][pointy], isSet);
243             self.reInterpolate();
244             undoable = true;
245         }
246     }
247 
248     /**
249         Describe the action
250     */
251     string describe() {
252         return _("%s->%s changed").format(self.getName(), name);
253     }
254 
255     /**
256         Describe the action
257     */
258     string describeUndo() {
259         return _("%s->%s change cancelled").format(self.getName(), name);
260     }
261 
262     /**
263         Gets name of this action
264     */
265     string getName() {
266         return this.stringof;
267     }
268     
269     /**
270         Merge
271     */
272     bool merge(Action other) {
273         if (this.canMerge(other)) {
274             return true;
275         }
276         return false;
277     }
278 
279     /**
280         Gets whether this node can merge with an other
281     */
282     bool canMerge(Action other) {
283         TSelf otherChange = cast(TSelf) other;
284         return (otherChange !is null && this.name == otherChange.name && this.pointx == otherChange.pointx && this.pointy == otherChange.pointy);
285     }
286 };