1 /*
2     Copyright © 2020, Inochi2D Project
3     Distributed under the 2-Clause BSD License, see LICENSE file.
4     
5     Authors:
6     - Luna Nielsen
7     - Asahi Lina
8 */
9 module creator.windows.paramaxes;
10 import std.algorithm.mutation : remove;
11 import creator.windows;
12 import creator.widgets;
13 import creator.core;
14 import std.string;
15 import creator.utils.link;
16 import i18n;
17 import inochi2d;
18 import std.math;
19 
20 class ParamAxesWindow : Window {
21 private:
22     Parameter param;
23     EditableAxisPoint[][2] points;
24     vec2 endPoint;
25 
26     void findEndPoint() {
27         foreach(i, x; points[0]) {
28             if (!x.fixed) endPoint.x = i;
29         }
30 
31         foreach(i, y; points[1]) {
32             if (!y.fixed) endPoint.y = i;
33         }
34     }
35 
36 protected:
37     override
38     void onBeginUpdate() {
39         igSetNextWindowSize(ImVec2(384*2, 192*2), ImGuiCond.Appearing);
40         igSetNextWindowSizeConstraints(ImVec2(384*2, 192*2), ImVec2(float.max, float.max));
41         super.onBeginUpdate();
42     }
43 
44     void axisPointList(ulong axis, ImVec2 avail) {
45         int deleteIndex = -1;
46 
47         igIndent();
48             igPushID(cast(int)axis);
49                 if (igBeginChild("###AXIS_ADJ", ImVec2(0, avail.y))) {
50                     if (points[axis].length > 2) {
51                         int ix;
52                         foreach(i, ref pt; points[axis]) {
53                             ix++;
54                             if (pt.fixed) continue;
55 
56                             // Do not allow existing points to cross over
57                             vec2 range;
58                             if (pt.origIndex != -1) {
59                                 range = vec2(points[axis][i - 1].value, points[axis][i + 1].value);
60                             } else if (axis == 0) {
61                                 range = vec2(param.min.x, param.max.x);
62                             } else {
63                                 range = vec2(param.min.y, param.max.y);
64                             }
65 
66                             // Offset range so points cannot overlap
67                             range = range + vec2(0.01, -0.01);
68 
69                             igSetNextItemWidth(80);
70                             igPushID(cast(int)i);
71                                 if (incDragFloat(
72                                     "adj_offset", &pt.value, 0.01,
73                                     range.x, range.y, "%.2f", ImGuiSliderFlags.NoRoundToFormat)
74                                 ) {
75                                     pt.normValue = param.mapAxis(cast(uint)axis, pt.value);
76                                 }
77                                 igSameLine(0, 0);
78 
79                                 if (i == endPoint.vector[axis]) {
80                                     incDummy(ImVec2(-52, 32));
81                                     igSameLine(0, 0);
82                                     if (igButton("", ImVec2(24, 24))) {
83                                         deleteIndex = cast(int)i;
84                                     }
85                                     igSameLine(0, 0);
86                                     if (igButton("", ImVec2(24, 24))) {
87                                         createPoint(axis);
88                                     }
89 
90                                 } else {
91                                     incDummy(ImVec2(-28, 32));
92                                     igSameLine(0, 0);
93                                     if (igButton("", ImVec2(24, 24))) {
94                                         deleteIndex = cast(int)i;
95                                     }
96                                 }
97                             igPopID();
98                         }
99                     } else {
100                         incDummy(ImVec2(-28, 24));
101                         igSameLine(0, 0);
102                         if (igButton("", ImVec2(24, 24))) {
103                             createPoint(axis);
104                         }
105                     }
106                 }
107                 igEndChild();
108             igPopID();
109         igUnindent();
110 
111         if (deleteIndex != -1) {
112             points[axis] = points[axis].remove(cast(uint)deleteIndex);
113             this.findEndPoint();
114         }
115     }
116 
117     void createPoint(ulong axis) {
118         float normValue = (points[axis][0].normValue + points[axis][1].normValue) / 2;
119         float value = param.unmapAxis(cast(uint)axis, normValue);
120         points[axis] ~= EditableAxisPoint(-1, false, value, normValue);
121         this.findEndPoint();
122     }
123 
124     override
125     void onUpdate() {
126         igPushID(cast(void*)param);
127             ImVec2 avail = incAvailableSpace();
128             float reqSpace = param.isVec2 ? 128 : 32;
129 
130             if (igBeginChild("###ControllerView", ImVec2(192, avail.y))) {
131                 incDummy(ImVec2(0, (avail.y/2)-(reqSpace/2)));
132                 incControllerAxisDemo("###CONTROLLER", param, points, ImVec2(192, reqSpace));
133             }
134             igEndChild();
135 
136             igSameLine(0, 0);
137 
138             igBeginGroup();
139                 if (igBeginChild("###ControllerSettings", ImVec2(0, -(28)))) {
140                     avail = incAvailableSpace();
141                     if (param.isVec2) {
142 
143                         // Skip start and end point
144                         if (incBeginCategory("X", IncCategoryFlags.NoCollapse)) {
145                             axisPointList(0, ImVec2(avail.x, (avail.y/2)-42));
146                         }
147                         incEndCategory();
148 
149                         if (incBeginCategory("Y", IncCategoryFlags.NoCollapse)) {
150                             axisPointList(1, ImVec2(avail.x, (avail.y/2)-42));
151                         }
152                         incEndCategory();
153                     } else {
154 
155                         // Points where the user can set parameter values
156                         if (incBeginCategory(__("Breakpoints"), IncCategoryFlags.NoCollapse)) {
157                             axisPointList(0, ImVec2(avail.x, avail.y-38));
158                         }
159                         incEndCategory();
160                     }
161                 }
162                 igEndChild();
163 
164                 if (igBeginChild("###SettingsBtns", ImVec2(0, 0))) {
165                     incDummy(ImVec2(-132, 0));
166                     igSameLine(0, 0);
167 
168                     // Cancels the edited state for the axies points
169                     if (igButton(__("Cancel"), ImVec2(64, 24))) {
170                         this.close();
171                     }
172 
173                     igSameLine(0, 4);
174 
175                     // Actually saves the edited state for the axies points
176                     if (igButton(__("Save"), ImVec2(64, 24))) {
177                         bool success = true;
178                         
179                         // Make sure there isn't any invalid state
180                         iloop: foreach(axis; 0..points.length) {
181                             
182                             foreach(x; 0..points[0].length) {
183                                 foreach(xi; 0..points[0].length) {
184                                     if (x == xi) continue;
185 
186                                     if (points[0][x].normValue == points[0][xi].normValue) {
187                                         incDialog(__("Error"), _("One or more axes points are overlapping, this is not allowed."));
188                                         success = false;
189                                         break iloop;
190                                     }
191                                 }
192                             }
193                         }
194 
195                         if (success) {
196                             foreach (axis, axisPoints; points) {
197                                 int skew = 0;
198                                 foreach (i, ref point; axisPoints) {
199                                     if (point.origIndex != -1) {
200                                         // Update point
201 
202                                         // If we skipped over some original points, they were deleted,
203                                         // so delete them here
204                                         while (point.origIndex != -1 && (i + skew) < point.origIndex) {
205                                             param.deleteAxisPoint(cast(uint)axis, cast(uint)i);
206                                             skew++;
207                                         }
208 
209                                         // Do not touch fixed points
210                                         if (!point.fixed)
211                                             param.axisPoints[axis][i] = point.normValue;
212                                     } else {
213                                         // Add point
214                                         param.insertAxisPoint(cast(uint)axis, point.normValue);
215                                     }
216                                 }
217                             }
218                             this.close();
219                         }
220                     }
221                 }
222                 igEndChild();
223             igEndGroup();
224         igPopID();
225     }
226 
227 public:
228     this(ref Parameter param) {
229         this.param = param;
230         foreach(i, ref axisPoints; points) {
231             axisPoints.length = param.axisPoints[i].length;
232             foreach(j, ref point; axisPoints) {
233                 point.origIndex = cast(int)j;
234                 point.normValue = param.axisPoints[i][j];
235                 point.value = param.unmapAxis(cast(uint)i, point.normValue);
236             }
237             axisPoints[0].fixed = true;
238             axisPoints[$ - 1].fixed = true;
239         }
240         this.findEndPoint();
241 
242         // Title for the parameter axis points window
243         // This window allows adjusting axies in the
244         // Parameter it's attached to.
245         // Keypoints show up on every intersecting axis line.
246         super(_("Parameter Axes Points"));
247     }
248 }