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