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.widgets.controller;
10 import creator.widgets;
11 import inochi2d;
12 
13 struct EditableAxisPoint {
14     int origIndex;
15     bool fixed;
16     float value;
17     float normValue;
18 };
19 
20 /**
21     A Parameter controller
22 */
23 bool incController(string strId, ref Parameter param, ImVec2 size, bool forceSnap = false) {
24     ImGuiWindow* window = igGetCurrentWindow();
25     if (window.SkipItems) return false;
26 
27     ImGuiID id = igGetID(strId.ptr, strId.ptr+strId.length);
28 
29     ImVec2 avail;
30     igGetContentRegionAvail(&avail);
31     if (size.x <= 0) size.x = avail.x-size.x;
32     if (!param.isVec2) size.y = 32;
33     else if (size.y <= 0) size.y = avail.y-size.y;
34 
35     ImGuiContext* ctx = igGetCurrentContext();
36     ImGuiStyle* style = &ctx.Style;
37     ImGuiStorage* storage = igGetStateStorage();
38     ImGuiIO* io = igGetIO();
39 
40 
41     ImVec2 mPos;
42     ImVec2 vPos;
43     igGetCursorScreenPos(&vPos);
44     bool bModified = false;
45     
46     if (param.isVec2) {
47         float oRectOffsetX = 24;
48         float oRectOffsetY = 12;
49         ImRect fRect = ImRect(
50             vPos,
51             ImVec2(vPos.x + size.x, vPos.y + size.y)
52         );
53 
54         ImRect oRect = ImRect(
55             ImVec2(vPos.x+oRectOffsetX, vPos.y+oRectOffsetY), 
56             ImVec2((vPos.x + size.x)-oRectOffsetX, (vPos.y + size.y)-oRectOffsetY)
57         );
58 
59         igPushID(id);
60 
61             igRenderFrame(oRect.Min, oRect.Max, igGetColorU32(ImGuiCol.FrameBg));
62 
63             float sDeltaX = param.max.x-param.min.x;
64             float sDeltaY = param.max.y-param.min.y;
65             
66             ImVec2 vSecurity = ImVec2(15, 15);
67             ImRect frameBB = ImRect(ImVec2(oRect.Min.x - vSecurity.x, oRect.Min.y - vSecurity.y), ImVec2(oRect.Max.x + vSecurity.x, oRect.Max.y + vSecurity.y));
68 
69             bool shouldSnap = forceSnap || io.KeyShift;
70             bool hovered;
71             bool held;
72             bool pressed = igButtonBehavior(frameBB, igGetID("##Zone"), &hovered, &held);
73             if (hovered && igIsMouseDown(ImGuiMouseButton.Right)) {
74                 held = true;
75             }
76             if (hovered && held) {
77                 igGetMousePos(&mPos);
78                 ImVec2 vCursorPos = ImVec2(mPos.x - oRect.Min.x, mPos.y - oRect.Min.y);
79 
80                 param.value = vec2(
81                     clamp(vCursorPos.x / (oRect.Max.x - oRect.Min.x) * sDeltaX + param.min.x, param.min.x, param.max.x),
82                     clamp(vCursorPos.y / (oRect.Max.y - oRect.Min.y) * -sDeltaY + param.max.y, param.min.y, param.max.y)
83                 );
84 
85                 // Snap to closest point mode
86                 if (shouldSnap) param.value = param.getClosestKeypointValue();
87 
88                 bModified = true;
89             }
90 
91             float fXLimit = 10f / ImRect_GetWidth(&oRect);
92             float fYLimit = 10f / ImRect_GetHeight(&oRect);
93             float fScaleX;
94             float fScaleY;
95             ImVec2 vCursorPos;
96 
97             ImDrawList* drawList = igGetWindowDrawList();
98             
99             ImS32 uDotColor = igGetColorU32(ImVec4(1f, 0f, 0f, 1f));
100             ImS32 uLineColor = igGetColorU32(style.Colors[ImGuiCol.Text]);
101             ImS32 uDotKeyColor = igGetColorU32(style.Colors[ImGuiCol.TextDisabled]);
102             ImS32 uDotKeyPartial = igGetColorU32(ImVec4(1f, 1f, 0f, 1f));
103             ImS32 uDotKeyComplete = igGetColorU32(ImVec4(0f, 1f, 0f, 1f));
104 
105             // AXES LINES
106             foreach(xIdx; 0..param.axisPoints[0].length) {
107                 float xVal = param.axisPoints[0][xIdx];
108                 float xPos = (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x;
109                 
110                 ImDrawList_AddLineDashed(
111                     drawList, 
112                     ImVec2(
113                         xPos, 
114                         oRect.Min.y
115                     ), 
116                     ImVec2(
117                         xPos, 
118                         oRect.Max.y
119                     ), 
120                     uDotKeyColor, 
121                     1f, 
122                     24, 
123                     1.2f
124                 );
125             
126             }
127 
128             foreach(yIdx; 0..param.axisPoints[1].length) {
129                 float yVal = 1 - param.axisPoints[1][yIdx];
130                 float yPos = (oRect.Max.y - oRect.Min.y) * yVal + oRect.Min.y;
131                 
132                 ImDrawList_AddLineDashed(
133                     drawList, 
134                     ImVec2(
135                         oRect.Min.x,
136                         yPos, 
137                     ), 
138                     ImVec2(
139                         oRect.Max.x,
140                         yPos, 
141                     ), 
142                     uDotKeyColor, 
143                     1f, 
144                     40, 
145                     1.2f
146                 );
147             }
148 
149             // OUTSIDE FRAME
150             ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, oRect.Min.y), ImVec2(oRect.Max.x, oRect.Min.y), uLineColor, 2f);
151             ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, oRect.Max.y), ImVec2(oRect.Max.x, oRect.Max.y), uLineColor, 2f);
152             ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, oRect.Min.y), ImVec2(oRect.Min.x, oRect.Max.y), uLineColor, 2f);
153             ImDrawList_AddLine(drawList, ImVec2(oRect.Max.x, oRect.Min.y), ImVec2(oRect.Max.x, oRect.Max.y), uLineColor, 2f);
154             
155             // AXES POINTS
156             foreach(xIdx; 0..param.axisPoints[0].length) {
157                 float xVal = param.axisPoints[0][xIdx];
158                 foreach(yIdx; 0..param.axisPoints[1].length) {
159                     float yVal = 1 - param.axisPoints[1][yIdx];
160 
161                     vCursorPos = ImVec2(
162                         (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x, 
163                         (oRect.Max.y - oRect.Min.y) * yVal + oRect.Min.y
164                     );
165 
166                     ImDrawList_AddCircleFilled(drawList, vCursorPos, 6.0f, uDotKeyColor, 16);
167 
168                     bool isPartial = false;
169                     bool isComplete = true;
170                     foreach(binding; param.bindings) {
171                         if (binding.getIsSet()[xIdx][yIdx]) {
172                             isPartial = true;
173                         } else {
174                             isComplete = false;
175                         }
176                     }
177 
178                     if (isComplete)
179                         ImDrawList_AddCircleFilled(drawList, vCursorPos, 4f, uDotKeyComplete, 16);
180                     else if (isPartial)
181                         ImDrawList_AddCircleFilled(drawList, vCursorPos, 4f, uDotKeyPartial, 16);
182                 }
183             }
184 
185             // PARAM VALUE
186             fScaleX = (param.value.x - param.min.x) / sDeltaX;
187             fScaleY = 1 - (param.value.y - param.min.y) / sDeltaY;
188             vCursorPos = ImVec2(
189                 (oRect.Max.x - oRect.Min.x) * fScaleX + oRect.Min.x, 
190                 (oRect.Max.y - oRect.Min.y) * fScaleY + oRect.Min.y
191             );
192             
193             ImDrawList_AddCircleFilled(drawList, vCursorPos, 4f, uDotColor, 16);
194         
195         igPopID(); 
196 
197         igItemAdd(fRect, id);
198         igItemSize(size);
199     } else {
200         const float lineHeight = 16;
201 
202         float oRectOffsetX = 24;
203         float oRectOffsetY = 12;
204         ImRect fRect = ImRect(
205             vPos,
206             ImVec2(vPos.x + size.x, vPos.y + size.y)
207         );
208 
209         ImRect oRect = ImRect(
210             ImVec2(vPos.x+oRectOffsetX, vPos.y+oRectOffsetY), 
211             ImVec2((vPos.x + size.x)-oRectOffsetX, (vPos.y + size.y)-oRectOffsetY)
212         );
213 
214         igPushID(id);
215 
216             igRenderFrame(oRect.Min, oRect.Max, igGetColorU32(ImGuiCol.FrameBg));
217             float sDeltaX = param.max.x-param.min.x;
218             
219             ImVec2 vSecurity = ImVec2(15, 15);
220             ImRect frameBB = ImRect(ImVec2(oRect.Min.x - vSecurity.x, oRect.Min.y - vSecurity.y), ImVec2(oRect.Max.x + vSecurity.x, oRect.Max.y + vSecurity.y));
221 
222             bool shouldSnap = forceSnap || io.KeyShift;
223             bool hovered;
224             bool held;
225             bool pressed = igButtonBehavior(frameBB, igGetID("##Zone"), &hovered, &held);
226             if (hovered && igIsMouseDown(ImGuiMouseButton.Right)) {
227                 held = true;
228             }
229             if (hovered && held) {
230                 igGetMousePos(&mPos);
231                 ImVec2 vCursorPos = ImVec2(mPos.x - oRect.Min.x, mPos.y - oRect.Min.y);
232 
233                 param.value.x = clamp(vCursorPos.x / (oRect.Max.x - oRect.Min.x) * sDeltaX + param.min.x, param.min.x, param.max.x);
234 
235                 // Snap to closest point mode
236                 if (shouldSnap) {
237                     vec2 closestPoint = param.value;
238                     float closestDist = float.infinity;
239                     foreach(xIdx; 0..param.axisPoints[0].length) {
240                         vec2 pos = vec2(
241                             (param.max.x - param.min.x) * param.axisPoints[0][xIdx] + param.min.x,
242                             0
243                         );
244 
245                         float dist = param.value.distance(pos);
246                         if (dist < closestDist) {
247                             closestDist = dist;
248                             closestPoint = pos;
249                         }
250                     }
251 
252                     // clamp to closest point
253                     param.value = closestPoint;
254                 }
255 
256                 bModified = true;
257             }
258 
259             float fYCenter = oRect.Min.y+(ImRect_GetHeight(&oRect)/2);
260             float fYCenterLineLen1th = lineHeight/2;
261             float fScaleX;
262             float fScaleY;
263             ImVec2 vCursorPos;
264 
265             ImDrawList* drawList = igGetWindowDrawList();
266             
267             ImS32 uDotColor = igGetColorU32(ImVec4(1f, 0f, 0f, 1f));
268             ImS32 uLineColor = igGetColorU32(style.Colors[ImGuiCol.Text]);
269             ImS32 uDotKeyColor = igGetColorU32(style.Colors[ImGuiCol.TextDisabled]);
270             ImS32 uDotKeyFilled = igGetColorU32(ImVec4(0f, 1f, 0f, 1f));
271 
272             // AXES LINES
273             foreach(xIdx; 0..param.axisPoints[0].length) {
274                 float xVal = param.axisPoints[0][xIdx];
275                 float xPos = (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x;
276                 
277                 ImDrawList_AddLine(
278                     drawList, 
279                     ImVec2(
280                         xPos, 
281                         fYCenter-fYCenterLineLen1th-(fYCenterLineLen1th/4)
282                     ), 
283                     ImVec2(
284                         xPos, 
285                         fYCenter+fYCenterLineLen1th
286                     ), 
287                     uLineColor, 
288                     2f, 
289                 );
290             
291             }
292 
293             // REF LINE
294             ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, fYCenter), ImVec2(oRect.Max.x, fYCenter), uLineColor, 2f);
295             
296             // AXES POINTS
297             foreach(xIdx; 0..param.axisPoints[0].length) {
298                 float xVal = param.axisPoints[0][xIdx];
299 
300                 vCursorPos = ImVec2(
301                     (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x, 
302                     fYCenter
303                 );
304 
305                 ImDrawList_AddCircleFilled(drawList, vCursorPos, 6.0f, uDotKeyColor, 16);
306                 foreach(binding; param.bindings) {
307                     if (binding.getIsSet()[xIdx][0]) {
308                         ImDrawList_AddCircleFilled(drawList, vCursorPos, 4f, uDotKeyFilled, 16);
309                         break;
310                     }
311                 }
312             }
313 
314             // PARAM VALUE
315             fScaleX = (param.value.x - param.min.x) / sDeltaX;
316             vCursorPos = ImVec2(
317                 (oRect.Max.x - oRect.Min.x) * fScaleX + oRect.Min.x, 
318                 fYCenter
319             );
320             
321             ImDrawList_AddCircleFilled(drawList, vCursorPos, 4f, uDotColor, 16);
322         
323         igPopID(); 
324 
325         igItemAdd(fRect, id);
326         igItemSize(size);
327     }
328 
329     return bModified;
330 }
331 
332 /**
333     A fake controller that lets you demonstrate additional axis points
334 */
335 void incControllerAxisDemo(string strId, ref Parameter param, ref EditableAxisPoint[][2] axisPoints, ImVec2 size) {
336     ImGuiWindow* window = igGetCurrentWindow();
337     if (window.SkipItems) return;
338 
339     ImGuiID id = igGetID(strId.ptr, strId.ptr+strId.length);
340 
341     ImVec2 avail;
342     igGetContentRegionAvail(&avail);
343     if (size.x <= 0) size.x = avail.x-size.x;
344     if (!param.isVec2) size.y = 32;
345     else if (size.y <= 0) size.y = avail.y-size.y;
346 
347     ImGuiContext* ctx = igGetCurrentContext();
348     ImGuiStyle* style = &ctx.Style;
349     ImGuiStorage* storage = igGetStateStorage();
350     ImGuiIO* io = igGetIO();
351 
352 
353     ImVec2 mPos;
354     ImVec2 vPos;
355     igGetCursorScreenPos(&vPos);
356     
357     if (param.isVec2) {
358         float oRectOffsetX = 24;
359         float oRectOffsetY = 12;
360         ImRect fRect = ImRect(
361             vPos,
362             ImVec2(vPos.x + size.x, vPos.y + size.y)
363         );
364 
365         ImRect oRect = ImRect(
366             ImVec2(vPos.x+oRectOffsetX, vPos.y+oRectOffsetY), 
367             ImVec2((vPos.x + size.x)-oRectOffsetX, (vPos.y + size.y)-oRectOffsetY)
368         );
369 
370         igPushID(id);
371 
372             igRenderFrame(oRect.Min, oRect.Max, igGetColorU32(ImGuiCol.FrameBg));
373             ImVec2 vCursorPos;
374             ImDrawList* drawList = igGetWindowDrawList();
375             
376             ImS32 uLineColor = igGetColorU32(style.Colors[ImGuiCol.Text]);
377             ImS32 uDotKeyColor = igGetColorU32(style.Colors[ImGuiCol.TextDisabled]);
378             ImS32 uDotKeyFilled = igGetColorU32(ImVec4(0f, 1f, 0f, 1f));
379 
380             // AXES LINES
381             foreach(xIdx; 0..axisPoints[0].length) {
382                 float xVal = axisPoints[0][xIdx].normValue;
383                 float xPos = (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x;
384                 
385                 ImDrawList_AddLineDashed(
386                     drawList, 
387                     ImVec2(
388                         xPos, 
389                         oRect.Min.y
390                     ), 
391                     ImVec2(
392                         xPos, 
393                         oRect.Max.y
394                     ), 
395                     uDotKeyColor, 
396                     1f, 
397                     24, 
398                     1.2f
399                 );
400             }
401 
402             foreach(yIdx; 0..axisPoints[1].length) {
403                 float yVal = 1 - axisPoints[1][yIdx].normValue;
404                 float yPos = (oRect.Max.y - oRect.Min.y) * yVal + oRect.Min.y;
405                 
406                 ImDrawList_AddLineDashed(
407                     drawList, 
408                     ImVec2(
409                         oRect.Min.x,
410                         yPos, 
411                     ), 
412                     ImVec2(
413                         oRect.Max.x,
414                         yPos, 
415                     ), 
416                     uDotKeyColor, 
417                     1f, 
418                     40, 
419                     1.2f
420                 );
421             }
422 
423             // OUTSIDE FRAME
424             ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, oRect.Min.y), ImVec2(oRect.Max.x, oRect.Min.y), uLineColor, 2f);
425             ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, oRect.Max.y), ImVec2(oRect.Max.x, oRect.Max.y), uLineColor, 2f);
426             ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, oRect.Min.y), ImVec2(oRect.Min.x, oRect.Max.y), uLineColor, 2f);
427             ImDrawList_AddLine(drawList, ImVec2(oRect.Max.x, oRect.Min.y), ImVec2(oRect.Max.x, oRect.Max.y), uLineColor, 2f);
428             
429             // AXES POINTS
430             foreach(xIdx; 0..axisPoints[0].length) {
431                 float xVal = axisPoints[0][xIdx].normValue;
432                 foreach(yIdx; 0..axisPoints[1].length) {
433                     float yVal = 1 - axisPoints[1][yIdx].normValue;
434 
435                     vCursorPos = ImVec2(
436                         (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x, 
437                         (oRect.Max.y - oRect.Min.y) * yVal + oRect.Min.y
438                     );
439 
440                     ImDrawList_AddCircleFilled(drawList, vCursorPos, 6.0f, uDotKeyColor, 16);
441                 }
442             }        
443         igPopID(); 
444 
445         igItemAdd(fRect, id);
446         igItemSize(size);
447     } else {
448         const float lineHeight = 16;
449 
450         float oRectOffsetX = 24;
451         float oRectOffsetY = 12;
452         ImRect fRect = ImRect(
453             vPos,
454             ImVec2(vPos.x + size.x, vPos.y + size.y)
455         );
456 
457         ImRect oRect = ImRect(
458             ImVec2(vPos.x+oRectOffsetX, vPos.y+oRectOffsetY), 
459             ImVec2((vPos.x + size.x)-oRectOffsetX, (vPos.y + size.y)-oRectOffsetY)
460         );
461 
462         igPushID(id);
463 
464             igRenderFrame(oRect.Min, oRect.Max, igGetColorU32(ImGuiCol.FrameBg));
465             float sDeltaX = param.max.x-param.min.x;
466             
467             ImVec2 vSecurity = ImVec2(15, 15);
468             ImRect frameBB = ImRect(ImVec2(oRect.Min.x - vSecurity.x, oRect.Min.y - vSecurity.y), ImVec2(oRect.Max.x + vSecurity.x, oRect.Max.y + vSecurity.y));
469             float fYCenter = oRect.Min.y+(ImRect_GetHeight(&oRect)/2);
470             float fYCenterLineLen1th = lineHeight/2;
471             ImVec2 vCursorPos;
472 
473             ImDrawList* drawList = igGetWindowDrawList();
474             
475             ImS32 uLineColor = igGetColorU32(style.Colors[ImGuiCol.Text]);
476             ImS32 uDotKeyColor = igGetColorU32(style.Colors[ImGuiCol.TextDisabled]);
477             ImS32 uDotKeyFilled = igGetColorU32(ImVec4(0f, 1f, 0f, 1f));
478 
479             // AXES LINES
480             foreach(xIdx; 0..axisPoints[0].length) {
481                 float xVal = axisPoints[0][xIdx].normValue;
482                 float xPos = (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x;
483                 
484                 ImDrawList_AddLine(
485                     drawList, 
486                     ImVec2(
487                         xPos, 
488                         fYCenter-fYCenterLineLen1th-(fYCenterLineLen1th/4)
489                     ), 
490                     ImVec2(
491                         xPos, 
492                         fYCenter+fYCenterLineLen1th
493                     ), 
494                     uLineColor, 
495                     2f, 
496                 );
497             
498             }
499 
500             // REF LINE
501             ImDrawList_AddLine(drawList, ImVec2(oRect.Min.x, fYCenter), ImVec2(oRect.Max.x, fYCenter), uLineColor, 2f);
502             
503             // AXES POINTS
504             foreach(xIdx; 0..axisPoints[0].length) {
505                 float xVal = axisPoints[0][xIdx].normValue;
506 
507                 vCursorPos = ImVec2(
508                     (oRect.Max.x - oRect.Min.x) * xVal + oRect.Min.x, 
509                     fYCenter
510                 );
511 
512                 ImDrawList_AddCircleFilled(drawList, vCursorPos, 6.0f, uDotKeyColor, 16);
513             }
514 
515         igPopID(); 
516 
517         igItemAdd(fRect, id);
518         igItemSize(size);
519     }
520 }
521 
522 
523 /**
524     Draws dashed lines
525 */
526 void ImDrawList_AddLineDashed(ImDrawList* self, ImVec2 a, ImVec2 b, ImU32 col, float thickness = 1f, int segments = 50, float lineScale = 1f) {
527     if ((col >> 24) == 0)
528         return;
529 
530     ImVec2 dir = ImVec2(
531         (b.x - a.x) / segments, 
532         (b.y - a.y) / segments
533     );
534 
535     bool on = true;
536     ImVec2[2] points;
537     foreach(i; 0..segments) {
538         points[i%2] = ImVec2(a.x + dir.x * i, a.y + dir.y * i);
539 
540         if (i != 0 && i%2 == 0) {
541             if (on) {
542                 ImDrawList_PathLineTo(self, ImVec2(points[0].x-(dir.x*lineScale), points[0].y-(dir.y*lineScale)));
543                 ImDrawList_PathLineTo(self, ImVec2(points[1].x+(dir.x*lineScale), points[1].y+(dir.y*lineScale)));
544                 ImDrawList_PathStroke(self, col, ImDrawFlags.None, thickness);
545             }
546 
547             on = !on;
548         }
549     }
550     ImDrawList_PathClear(self);
551     
552 }