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.panels.parameters;
8 import creator.viewport.model.deform;
9 import creator.panels;
10 import creator.ext.param;
11 import creator.widgets;
12 import creator.windows;
13 import creator.core;
14 import creator.actions;
15 import creator.ext.param;
16 import creator;
17 import std.string;
18 import inochi2d;
19 import i18n;
20 import std.uni : toLower;
21 import std.stdio;
22 import creator.utils;
23 import std.algorithm.sorting : sort;
24 import std.algorithm.mutation : remove;
25 
26 private {
27 
28     ParameterBinding[][Node] cParamBindingEntries;
29     ParameterBinding[][Node] cParamBindingEntriesAll;
30     Node[] cAllBoundNodes;
31     ParameterBinding[BindTarget] cSelectedBindings;
32     Node[] cCompatibleNodes;
33     vec2u cParamPoint;
34     vec2u cClipboardPoint;
35     ParameterBinding[BindTarget] cClipboardBindings;
36 
37     void refreshBindingList(Parameter param) {
38         // Filter selection to remove anything that went away
39         ParameterBinding[BindTarget] newSelectedBindings;
40 
41         cParamBindingEntriesAll.clear();
42         foreach(ParameterBinding binding; param.bindings) {
43             BindTarget target = binding.getTarget();
44             if (target in cSelectedBindings) newSelectedBindings[target] = binding;
45             cParamBindingEntriesAll[binding.getNode()] ~= binding;
46         }
47         cAllBoundNodes = cParamBindingEntriesAll.keys.dup;
48         cAllBoundNodes.sort!((x, y) => x.name < y.name);
49         cSelectedBindings = newSelectedBindings;
50         paramPointChanged(param);
51     }
52 
53     void paramPointChanged(Parameter param) {
54         cParamBindingEntries.clear();
55 
56         cParamPoint = param.findClosestKeypoint();
57         foreach(ParameterBinding binding; param.bindings) {
58             if (binding.isSet(cParamPoint)) {
59                 cParamBindingEntries[binding.getNode()] ~= binding;
60             }
61         }
62     }
63 
64     void mirrorAll(Parameter param, uint axis) {
65         auto action = new ParameterChangeBindingsAction("Mirror All", param, null);
66         foreach(ParameterBinding binding; param.bindings) {
67             uint xCount = param.axisPointCount(0);
68             uint yCount = param.axisPointCount(1);
69             foreach(x; 0..xCount) {
70                 foreach(y; 0..yCount) {
71                     vec2u index = vec2u(x, y);
72                     if (binding.isSet(index)) {
73                         binding.scaleValueAt(index, axis, -1);
74                     }
75                 }
76             }
77         }
78         action.updateNewState();
79         incActionPush(action);
80     }
81 
82     void mirroredAutofill(Parameter param, uint axis, float min, float max) {
83         auto action = new ParameterChangeBindingsAction("Mirror Auto Fill", param, null);
84         foreach(ParameterBinding binding; param.bindings) {
85             uint xCount = param.axisPointCount(0);
86             uint yCount = param.axisPointCount(1);
87             foreach(x; 0..xCount) {
88                 float offX = param.axisPoints[0][x];
89                 if (axis == 0 && (offX < min || offX > max)) continue;
90                 foreach(y; 0..yCount) {
91                     float offY = param.axisPoints[1][y];
92                     if (axis == 1 && (offY < min || offY > max)) continue;
93 
94                     vec2u index = vec2u(x, y);
95                     if (!binding.isSet(index)) binding.extrapolateValueAt(index, axis);
96                 }
97             }
98         }
99         action.updateNewState();
100         incActionPush(action);
101     }
102 
103     void fixScales(Parameter param) {
104         auto action = new ParameterChangeBindingsAction("Fix Scale", param, null);
105         foreach(ParameterBinding binding; param.bindings) {
106             switch(binding.getName()) {
107                 case "transform.s.x":
108                 case "transform.s.y":
109                 if (ValueParameterBinding b = cast(ValueParameterBinding)binding) {
110                     uint xCount = param.axisPointCount(0);
111                     uint yCount = param.axisPointCount(1);
112                     foreach(x; 0..xCount) {
113                         foreach(y; 0..yCount) {
114                             vec2u index = vec2u(x, y);
115                             if (b.isSet(index)) {
116                                 b.values[x][y] += 1;
117                             }
118                         }
119                     }
120                     b.reInterpolate();
121                 }
122                 break;
123                 default: break;
124             }
125         }
126         action.updateNewState();
127         incActionPush(action);
128     }
129 
130     Node[] getCompatibleNodes() {
131         Node thisNode = null;
132 
133         foreach(binding; cSelectedBindings.byValue()) {
134             if (thisNode is null) thisNode = binding.getNode();
135             else if (!(binding.getNode() is thisNode)) return null;
136         }
137         if (thisNode is null) return null;
138 
139         Node[] compatible;
140         nodeLoop: foreach(otherNode; cParamBindingEntriesAll.byKey()) {
141             if (otherNode is thisNode) continue;
142 
143             foreach(binding; cSelectedBindings.byValue()) {
144                 if (!binding.isCompatibleWithNode(otherNode))
145                     continue nodeLoop;
146             }
147             compatible ~= otherNode;
148         }
149 
150         return compatible;
151     }
152 
153     void copySelectionToNode(Parameter param, Node target) {
154         Node src = cSelectedBindings.keys[0].node;
155 
156         foreach(binding; cSelectedBindings.byValue()) {
157             assert(binding.getNode() is src, "selection mismatch");
158 
159             ParameterBinding b = param.getOrAddBinding(target, binding.getName());
160             binding.copyKeypointToBinding(cParamPoint, b, cParamPoint);
161         }
162 
163         refreshBindingList(param);
164     }
165 
166     void swapSelectionWithNode(Parameter param, Node target) {
167         Node src = cSelectedBindings.keys[0].node;
168 
169         foreach(binding; cSelectedBindings.byValue()) {
170             assert(binding.getNode() is src, "selection mismatch");
171 
172             ParameterBinding b = param.getOrAddBinding(target, binding.getName());
173             binding.swapKeypointWithBinding(cParamPoint, b, cParamPoint);
174         }
175 
176         refreshBindingList(param);
177     }
178 
179     void keypointActions(Parameter param, ParameterBinding[] bindings) {
180         if (igMenuItem(__("Unset"), "", false, true)) {
181             auto action = new ParameterChangeBindingsValueAction("unset", param, bindings, cParamPoint.x, cParamPoint.y);
182             foreach(binding; bindings) {
183                 binding.unset(cParamPoint);
184             }
185             action.updateNewState();
186             incActionPush(action);
187             incViewportNodeDeformNotifyParamValueChanged();
188         }
189         if (igMenuItem(__("Set to current"), "", false, true)) {
190             auto action = new ParameterChangeBindingsValueAction("setCurrent", param, bindings, cParamPoint.x, cParamPoint.y);
191             foreach(binding; bindings) {
192                 binding.setCurrent(cParamPoint);
193             }
194             action.updateNewState();
195             incActionPush(action);
196             incViewportNodeDeformNotifyParamValueChanged();
197         }
198         if (igMenuItem(__("Reset"), "", false, true)) {
199             auto action = new ParameterChangeBindingsValueAction("reset", param, bindings, cParamPoint.x, cParamPoint.y);
200             foreach(binding; bindings) {
201                 binding.reset(cParamPoint);
202             }
203             action.updateNewState();
204             incActionPush(action);
205             incViewportNodeDeformNotifyParamValueChanged();
206         }
207         if (igMenuItem(__("Invert"), "", false, true)) {
208             auto action = new ParameterChangeBindingsValueAction("invert", param, bindings, cParamPoint.x, cParamPoint.y);
209             foreach(binding; bindings) {
210                 binding.scaleValueAt(cParamPoint, -1, -1);
211             }
212             action.updateNewState();
213             incActionPush(action);
214             incViewportNodeDeformNotifyParamValueChanged();
215         }
216         if (igBeginMenu(__("Mirror"), true)) {
217             if (igMenuItem(__("Horizontally"), "", false, true)) {
218                 auto action = new ParameterChangeBindingsValueAction("mirror Horizontally", param, bindings, cParamPoint.x, cParamPoint.y);
219                 foreach(binding; bindings) {
220                     binding.scaleValueAt(cParamPoint, 0, -1);
221                 }
222                 action.updateNewState();
223                 incActionPush(action);
224                 incViewportNodeDeformNotifyParamValueChanged();
225             }
226             if (igMenuItem(__("Vertically"), "", false, true)) {
227                 auto action = new ParameterChangeBindingsValueAction("mirror Vertically", param, bindings, cParamPoint.x, cParamPoint.y);
228                 foreach(binding; bindings) {
229                     binding.scaleValueAt(cParamPoint, 1, -1);
230                 }
231                 action.updateNewState();
232                 incActionPush(action);
233                 incViewportNodeDeformNotifyParamValueChanged();
234             }
235             igEndMenu();
236         }
237         if (param.isVec2) {
238             if (igBeginMenu(__("Set from mirror"), true)) {
239                 if (igMenuItem(__("Horizontally"), "", false, true)) {
240                     auto action = new ParameterChangeBindingsValueAction("set From Mirror (Horizontally)", param, bindings, cParamPoint.x, cParamPoint.y);
241                     foreach(binding; bindings) {
242                         binding.extrapolateValueAt(cParamPoint, 0);
243                     }
244                     action.updateNewState();
245                     incActionPush(action);
246                     incViewportNodeDeformNotifyParamValueChanged();
247                 }
248                 if (igMenuItem(__("Vertically"), "", false, true)) {
249                     auto action = new ParameterChangeBindingsValueAction("set From Mirror (Vertically)", param, bindings, cParamPoint.x, cParamPoint.y);
250                     foreach(binding; bindings) {
251                         binding.extrapolateValueAt(cParamPoint, 1);
252                     }
253                     action.updateNewState();
254                     incActionPush(action);
255                     incViewportNodeDeformNotifyParamValueChanged();
256                 }
257                 if (igMenuItem(__("Diagonally"), "", false, true)) {
258                     auto action = new ParameterChangeBindingsValueAction("set From Mirror (Diagonally)", param, bindings, cParamPoint.x, cParamPoint.y);
259                     foreach(binding; bindings) {
260                         binding.extrapolateValueAt(cParamPoint, -1);
261                     }
262                     action.updateNewState();
263                     incActionPush(action);
264                     incViewportNodeDeformNotifyParamValueChanged();
265                 }
266                 igEndMenu();
267             }
268         } else {
269             if (igMenuItem(__("Set from mirror"), "", false, true)) {
270                 auto action = new ParameterChangeBindingsValueAction("set From Mirror", param, bindings, cParamPoint.x, cParamPoint.y);
271                 foreach(binding; bindings) {
272                     binding.extrapolateValueAt(cParamPoint, 0);
273                 }
274                 action.updateNewState();
275                 incActionPush(action);
276                 incViewportNodeDeformNotifyParamValueChanged();
277             }
278         }
279 
280         if (igMenuItem(__("Copy"), "", false, true)) {
281             cClipboardPoint = cParamPoint;
282             cClipboardBindings.clear();
283             foreach(binding; bindings) {
284                 cClipboardBindings[binding.getTarget()] = binding;
285             }
286         }
287 
288         if (igMenuItem(__("Paste"), "", false,  true)) {
289             if (bindings.length == 1 && cClipboardBindings.length == 1 &&
290                 bindings[0].isCompatibleWithNode(cClipboardBindings.values[0].getNode())) {
291                 auto action = new ParameterChangeBindingsValueAction("set From Mirror", param, bindings,cParamPoint.x, cParamPoint.y);
292                 cClipboardBindings.values[0].copyKeypointToBinding(cClipboardPoint, bindings[0], cParamPoint);
293                 action.updateNewState();
294                 incActionPush(action);
295             } else {
296                 foreach(binding; bindings) {
297                     if (binding.getTarget() in cClipboardBindings) {
298                         auto action = new ParameterChangeBindingsValueAction("set From Mirror", param, bindings, cParamPoint.x, cParamPoint.y);
299                         ParameterBinding origBinding = cClipboardBindings[binding.getTarget()];
300                         origBinding.copyKeypointToBinding(cClipboardPoint, binding, cParamPoint);
301                     }
302                 }
303             }
304         }
305 
306     }
307 
308     void bindingList(Parameter param) {
309         if (incBeginCategory(__("Bindings"))) {
310             refreshBindingList(param);
311 
312             auto io = igGetIO();
313             auto style = igGetStyle();
314             ImS32 inactiveColor = igGetColorU32(style.Colors[ImGuiCol.TextDisabled]);
315 
316             igBeginChild("BindingList", ImVec2(0, 256), false);
317                 igPushStyleVar(ImGuiStyleVar.CellPadding, ImVec2(4, 1));
318                 igPushStyleVar(ImGuiStyleVar.IndentSpacing, 14);
319 
320                 foreach(node; cAllBoundNodes) {
321                     ParameterBinding[] allBindings = cParamBindingEntriesAll[node];
322                     ParameterBinding[] *bindings = (node in cParamBindingEntries);
323 
324                     // Figure out if node is selected ( == all bindings selected)
325                     bool nodeSelected = true;
326                     bool someSelected = false;
327                     foreach(binding; allBindings) {
328                         if ((binding.getTarget() in cSelectedBindings) is null)
329                             nodeSelected = false;
330                         else
331                             someSelected = true;
332                     }
333 
334                     ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.OpenOnArrow;
335                     if (nodeSelected)
336                         flags |= ImGuiTreeNodeFlags.Selected;
337 
338                     if (bindings is null) igPushStyleColor(ImGuiCol.Text, inactiveColor);
339                     string nodeName = incTypeIdToIcon(node.typeId) ~ " " ~ node.name;
340                     if (igTreeNodeEx(cast(void*)node.uuid, flags, nodeName.toStringz)) {
341 
342                         if (bindings is null) igPopStyleColor();
343                         if (igBeginPopup("###BindingPopup")) {
344                             if (igMenuItem(__("Remove"), "", false, true)) {
345                                 auto action = new GroupAction();
346                                 foreach(binding; cSelectedBindings.byValue()) {
347                                     action.addAction(new ParameterBindingRemoveAction(param, binding));
348                                     param.removeBinding(binding);
349                                 }
350                                 incActionPush(action);
351                                 incViewportNodeDeformNotifyParamValueChanged();
352                             }
353 
354                             keypointActions(param, cSelectedBindings.values);
355 
356                             if (igBeginMenu(__("Interpolation Mode"), true)) {
357                                 if (igMenuItem(__("Nearest"), "", false, true)) {
358                                     foreach(binding; cSelectedBindings.values) {
359                                         binding.interpolateMode = InterpolateMode.Nearest;
360                                     }
361                                     incViewportNodeDeformNotifyParamValueChanged();
362                                 }
363                                 if (igMenuItem(__("Linear"), "", false, true)) {
364                                     foreach(binding; cSelectedBindings.values) {
365                                         binding.interpolateMode = InterpolateMode.Linear;
366                                     }
367                                     incViewportNodeDeformNotifyParamValueChanged();
368                                 }
369                                 igEndMenu();
370                             }
371 
372                             bool haveCompatible = cCompatibleNodes.length > 0;
373                             if (igBeginMenu(__("Copy to"), haveCompatible)) {
374                                 foreach(cNode; cCompatibleNodes) {
375                                     if (igMenuItem(cNode.name.toStringz, "", false, true)) {
376                                         copySelectionToNode(param, cNode);
377                                     }
378                                 }
379                                 igEndMenu();
380                             }
381                             if (igBeginMenu(__("Swap with"), haveCompatible)) {
382                                 foreach(cNode; cCompatibleNodes) {
383                                     if (igMenuItem(cNode.name.toStringz, "", false, true)) {
384                                         swapSelectionWithNode(param, cNode);
385                                     }
386                                 }
387                                 igEndMenu();
388                             }
389 
390                             igEndPopup();
391                         }
392                         if (igIsItemClicked(ImGuiMouseButton.Right)) {
393                             if (!someSelected) {
394                                 cSelectedBindings.clear();
395                                 foreach(binding; allBindings) {
396                                     cSelectedBindings[binding.getTarget()] = binding;
397                                 }
398                             }
399                             cCompatibleNodes = getCompatibleNodes();
400                             igOpenPopup("###BindingPopup");
401                         }
402 
403                         // Node selection logic
404                         if (igIsItemClicked(ImGuiMouseButton.Left) && !igIsItemToggledOpen()) {
405                             
406                             // Select the node you've clicked in the bindings list
407                             if (incNodeInSelection(node)) {
408                                 incFocusCamera(node);
409                             } else incSelectNode(node);
410                             
411                             if (!io.KeyCtrl) {
412                                 cSelectedBindings.clear();
413                                 nodeSelected = false;
414                             }
415                             foreach(binding; allBindings) {
416                                 if (nodeSelected) cSelectedBindings.remove(binding.getTarget());
417                                 else cSelectedBindings[binding.getTarget()] = binding;
418                             }
419                         }
420 
421                         // Iterate over bindings
422                         foreach(binding; allBindings) {
423                             ImGuiTreeNodeFlags flags2 =
424                                 ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.OpenOnArrow |
425                                 ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen;
426 
427                             bool selected = cast(bool)(binding.getTarget() in cSelectedBindings);
428                             if (selected) flags2 |= ImGuiTreeNodeFlags.Selected;
429 
430                             // Style as inactive if not set at this keypoint
431                             if (!binding.isSet(cParamPoint))
432                                 igPushStyleColor(ImGuiCol.Text, inactiveColor);
433 
434 
435                             // Binding entry
436                             auto value = cast(ValueParameterBinding)binding;
437                             string label;
438                             if (value && binding.isSet(cParamPoint)) {
439                                 label = format("%s (%.02f)", binding.getName(), value.getValue(cParamPoint));
440                             } else {
441                                 label = binding.getName();
442                             }
443 
444                             // NOTE: This is a leaf node so it should NOT be popped.
445                             const(char)* bid = binding.getName().toStringz;
446                             igTreeNodeEx(bid, flags2, label.toStringz);
447                                 if (!binding.isSet(cParamPoint)) igPopStyleColor();
448 
449                                 // Binding selection logic
450                                 if (igIsItemClicked(ImGuiMouseButton.Right)) {
451                                     if (!selected) {
452                                         cSelectedBindings.clear();
453                                         cSelectedBindings[binding.getTarget()] = binding;
454                                     }
455                                     cCompatibleNodes = getCompatibleNodes();
456                                     igOpenPopup("###BindingPopup");
457                                 }
458                                 if (igIsItemClicked(ImGuiMouseButton.Left)) {
459                                     if (!io.KeyCtrl) {
460                                         cSelectedBindings.clear();
461                                         selected = false;
462                                     }
463                                     if (selected) cSelectedBindings.remove(binding.getTarget());
464                                     else cSelectedBindings[binding.getTarget()] = binding;
465                                 }
466                         }
467                         
468                         igTreePop();
469                     } else if (bindings is null) igPopStyleColor();
470                 }
471 
472                 igPopStyleVar();
473                 igPopStyleVar();
474             igEndChild();
475         }
476         incEndCategory();
477     }
478 
479     void pushColorScheme(vec3 color) {
480         float h, s, v;
481         igColorConvertRGBtoHSV(color.r, color.g, color.b, &h, &s, &v);
482 
483         float maxS = lerp(1, 0.60, v);
484 
485         vec3 c = color;
486         igColorConvertHSVtoRGB(
487             h, 
488             clamp(lerp(s, s-0.20, v), 0, maxS), 
489             clamp(v-0.15, 0.15, 0.90), 
490             &c.vector[0], &c.vector[1], &c.vector[2]
491         );
492         igPushStyleColor(ImGuiCol.FrameBg, ImVec4(c.r, c.g, c.b, 1));
493 
494 
495         maxS = lerp(1, 0.60, v);
496         igColorConvertHSVtoRGB(
497             h, 
498             lerp(
499                 clamp(s-0.25, 0, maxS),
500                 clamp(s+0.25, 0, maxS),
501                 s
502             ),
503             v <= 0.55 ?
504                 clamp(v+0.25, 0.45, 0.95) :
505                 clamp(v-(0.25*(1+v)), 0.30, 1),
506             &c.vector[0], &c.vector[1], &c.vector[2]
507         );
508         igPushStyleColor(ImGuiCol.TextDisabled, ImVec4(c.r, c.g, c.b, 1));
509     }
510 
511     void popColorScheme() {
512         igPopStyleColor(2);
513     }
514 
515     ptrdiff_t findParamIndex(ref Parameter[] paramArr, Parameter param) {
516         import std.algorithm.searching : countUntil;
517         ptrdiff_t idx = paramArr.countUntil(param);
518         return idx;
519     }
520     ParamDragDropData* dragDropData;
521 
522     bool removeParameter(Parameter param) {
523         ExParameterGroup parent = null;
524         ptrdiff_t idx = -1;
525 
526         mloop: foreach(i, iparam; incActivePuppet.parameters) {
527             if (iparam.uuid == param.uuid) {
528                 idx = i;
529                 break mloop;
530             }
531 
532             if (ExParameterGroup group = cast(ExParameterGroup)iparam) {
533                 foreach(x, ref xparam; group.children) {
534                     if (xparam.uuid == param.uuid) {
535                         idx = x;
536                         parent = group;
537                         break mloop;
538                     }
539                 }
540             }
541         }
542 
543         if (idx < 0) return false;
544 
545         if (parent) {
546             if (parent.children.length > 1) parent.children = parent.children.remove(idx);
547             else parent.children.length = 0;
548         } else {
549             if (incActivePuppet().parameters.length > 1) incActivePuppet().parameters = incActivePuppet().parameters.remove(idx);
550             else incActivePuppet().parameters.length = 0;
551         } 
552 
553         return true;
554     }
555 
556     void moveParameter(Parameter from, ExParameterGroup to = null, int index = 0) {
557         if (!removeParameter(from)) return;
558         import std.array : insertInPlace;
559 
560         // Map to bounds
561         if (index < 0) index = 0;
562         else if (to && index > to.children.length) index = cast(int)to.children.length-1;
563         else if (index > incActivePuppet().parameters.length) index = cast(int)incActivePuppet().parameters.length-1;
564 
565         // Insert
566         if (to) to.children.insertInPlace(index, from);
567         else incActivePuppet().parameters.insertInPlace(index, from);
568     }
569 
570     ExParameterGroup createParamGroup(int index = 0) {
571         import std.array : insertInPlace;
572 
573         if (index < 0) index = 0;
574         else if (index > incActivePuppet().parameters.length) index = cast(int)incActivePuppet().parameters.length-1;
575 
576         auto group = new ExParameterGroup(_("New Parameter Group"));
577         incActivePuppet().parameters.insertInPlace(index, group);
578         return group;
579     }
580 }
581 
582 struct ParamDragDropData {
583     Parameter param;
584 }
585 
586 /**
587     Generates a parameter view
588 */
589 void incParameterView(bool armedParam=false)(size_t idx, Parameter param, string* grabParam, bool canGroup, ref Parameter[] paramArr, vec3 groupColor = vec3.init) {
590     igPushID(cast(void*)param);
591     scope(exit) igPopID();
592 
593     
594     bool open;
595     if (!groupColor.isFinite) open = incBeginCategory(param.name.toStringz);
596     else open = incBeginCategory(param.name.toStringz, ImVec4(groupColor.r, groupColor.g, groupColor.b, 1));
597 
598     if(igBeginDragDropSource(ImGuiDragDropFlags.SourceAllowNullID)) {
599         if (!dragDropData) dragDropData = new ParamDragDropData;
600         
601         dragDropData.param = param;
602 
603         igSetDragDropPayload("_PARAMETER", cast(void*)&dragDropData, (&dragDropData).sizeof, ImGuiCond.Always);
604         incText(dragDropData.param.name);
605         igEndDragDropSource();
606     }
607 
608     if (canGroup) {
609         incBeginDragDropFake();
610             auto peek = igAcceptDragDropPayload("_PARAMETER", ImGuiDragDropFlags.AcceptPeekOnly | ImGuiDragDropFlags.SourceAllowNullID);
611             if(peek && peek.Data && (*cast(ParamDragDropData**)peek.Data).param != param) {
612                 if (igBeginDragDropTarget()) {
613                     auto payload = igAcceptDragDropPayload("_PARAMETER");
614                     
615                     if (payload !is null) {
616                         ParamDragDropData* payloadParam = *cast(ParamDragDropData**)payload.Data;
617 
618                         if (removeParameter(param)) {
619                             auto group = createParamGroup(cast(int)idx);
620                             group.children ~= param;
621                             moveParameter(payloadParam.param, group);
622                         }
623                     }
624                     igEndDragDropTarget();
625                 }
626             }
627         incEndDragDropFake();
628     }
629 
630     if (open) {
631         // Push color scheme
632         if (groupColor.isFinite) pushColorScheme(groupColor);
633 
634         float reqSpace = param.isVec2 ? 144 : 52;
635 
636         // Parameter Control
637         ImVec2 avail = incAvailableSpace();
638 
639         // We want to always show armed parameters but also make sure the child is begun.
640         bool childVisible = igBeginChild("###PARAM", ImVec2(avail.x-24, reqSpace));
641         if (childVisible || armedParam) {
642 
643             // Popup for rightclicking the controller
644             if (igBeginPopup("###ControlPopup")) {
645                 if (incArmedParameter() == param) {
646                     keypointActions(param, param.bindings);
647                 }
648                 igEndPopup();
649             }
650 
651             if (param.isVec2) incText("%.2f %.2f".format(param.value.x, param.value.y));
652             else incText("%.2f".format(param.value.x));
653 
654             if (incController("###CONTROLLER", param, ImVec2(avail.x-24, reqSpace-24), incArmedParameter() == param, *grabParam)) {
655                 if (incArmedParameter() == param) {
656                     incViewportNodeDeformNotifyParamValueChanged();
657                     paramPointChanged(param);
658                 }
659                 if (igIsMouseDown(ImGuiMouseButton.Left)) {
660                     if (*grabParam == null)
661                         *grabParam = param.name;
662                 } else {
663                     *grabParam = "";
664                 }
665             }
666             if (igIsItemClicked(ImGuiMouseButton.Right)) {
667                 if (incArmedParameter() == param) incViewportNodeDeformNotifyParamValueChanged();
668                 refreshBindingList(param);
669                 igOpenPopup("###ControlPopup");
670             }
671         }
672         igEndChild();
673 
674 
675         if (incEditMode == EditMode.ModelEdit) {
676             igSameLine(0, 0);
677 
678             // Parameter Setting Buttons
679             childVisible = igBeginChild("###SETTING", ImVec2(24, reqSpace), false);
680             if (childVisible || armedParam) {
681                 if (igBeginPopup("###EditParam")) {
682                     if (igMenuItem(__("Edit Properties"), "", false, true)) {
683                         incPushWindowList(new ParamPropWindow(param));
684                     }
685                     
686                     if (igMenuItem(__("Edit Axes Points"), "", false, true)) {
687                         incPushWindowList(new ParamAxesWindow(param));
688                     }
689                     
690                     if (igMenuItem(__("Split"), "", false, true)) {
691                         incPushWindowList(new ParamSplitWindow(idx, param));
692                     }
693 
694                     if (param.isVec2) {
695                         if (igMenuItem(__("Flip X"), "", false, true)) {
696                             auto action = new ParameterChangeBindingsAction("Flip X", param, null);
697                             param.reverseAxis(0);
698                             action.updateNewState();
699                             incActionPush(action);
700                         }
701                         if (igMenuItem(__("Flip Y"), "", false, true)) {
702                             auto action = new ParameterChangeBindingsAction("Flip Y", param, null);
703                             param.reverseAxis(1);
704                             action.updateNewState();
705                             incActionPush(action);
706                         }
707                     } else {
708                         if (igMenuItem(__("Flip"), "", false, true)) {
709                             auto action = new ParameterChangeBindingsAction("Flip", param, null);
710                             param.reverseAxis(0);
711                             action.updateNewState();
712                             incActionPush(action);
713                         }
714                     }
715                     if (igBeginMenu(__("Mirror"), true)) {
716                         if (igMenuItem(__("Horizontally"), "", false, true)) {
717                             mirrorAll(param, 0);
718                             incViewportNodeDeformNotifyParamValueChanged();
719                         }
720                         if (igMenuItem(__("Vertically"), "", false, true)) {
721                             mirrorAll(param, 1);
722                             incViewportNodeDeformNotifyParamValueChanged();
723                         }
724                         igEndMenu();
725                     }
726                     if (igBeginMenu(__("Mirrored Autofill"), true)) {
727                         if (igMenuItem("", "", false, true)) {
728                             mirroredAutofill(param, 0, 0, 0.4999);
729                             incViewportNodeDeformNotifyParamValueChanged();
730                         }
731                         if (igMenuItem("", "", false, true)) {
732                             mirroredAutofill(param, 0, 0.5001, 1);
733                             incViewportNodeDeformNotifyParamValueChanged();
734                         }
735                         if (param.isVec2) {
736                             if (igMenuItem("", "", false, true)) {
737                                 mirroredAutofill(param, 1, 0.5001, 1);
738                                 incViewportNodeDeformNotifyParamValueChanged();
739                             }
740                             if (igMenuItem("", "", false, true)) {
741                                 mirroredAutofill(param, 1, 0, 0.4999);
742                                 incViewportNodeDeformNotifyParamValueChanged();
743                             }
744                         }
745                         igEndMenu();
746                     }
747 
748                     igNewLine();
749                     igSeparator();
750 
751                     if (igMenuItem(__("Duplicate"), "", false, true)) {
752                         Parameter newParam = param.dup;
753                         incActivePuppet().parameters ~= newParam;
754                         incActionPush(new ParameterAddAction(newParam, &paramArr));
755                     }
756 
757                     if (igMenuItem(__("Delete"), "", false, true)) {
758                         if (incArmedParameter() == param) {
759                             incDisarmParameter();
760                         }
761                         incActivePuppet().removeParameter(param);
762                         incActionPush(new ParameterRemoveAction(param, &paramArr));
763                     }
764 
765                     igNewLine();
766                     igSeparator();
767 
768                     // Sets the default value of the param
769                     if (igMenuItem(__("Set Starting Position"), "", false, true)) {
770                         auto action = new ParameterValueChangeAction!vec2("axis points", param, &param.defaults);
771                         param.defaults = param.value;
772                         action.updateNewState();
773                         incActionPush(action);
774                     }
775                     igEndPopup();
776                 }
777                 
778                 if (igButton("", ImVec2(24, 24))) {
779                     igOpenPopup("###EditParam");
780                 }
781                 
782                 
783                 if (incButtonColored("", ImVec2(24, 24), incArmedParameter() == param ? ImVec4(1f, 0f, 0f, 1f) : *igGetStyleColorVec4(ImGuiCol.Text))) {
784                     if (incArmedParameter() == param) {
785                         incDisarmParameter();
786                     } else {
787                         param.value = param.getClosestKeypointValue();
788                         paramPointChanged(param);
789                         incArmParameter(idx, param);
790                     }
791                 }
792 
793                 // Arms the parameter for recording values.
794                 incTooltip(_("Arm Parameter"));
795             }
796             igEndChild();
797         }
798         if (incArmedParameter() == param) {
799             bindingList(param);
800         }
801         if (groupColor.isFinite) popColorScheme();
802     }
803     
804     incEndCategory();
805 }
806 
807 /**
808     The logger frame
809 */
810 class ParametersPanel : Panel {
811 private:
812     string filter;
813     string grabParam = "";
814 protected:
815     override
816     void onUpdate() {
817         if (incEditMode == EditMode.VertexEdit) {
818             incLabelOver(_("In vertex edit mode..."), ImVec2(0, 0), true);
819             return;
820         }
821 
822         auto parameters = incActivePuppet().parameters;
823 
824         if (igBeginPopup("###AddParameter")) {
825             if (igMenuItem(__("Add 1D Parameter (0..1)"), "", false, true)) {
826                 Parameter param = new Parameter(
827                     "Param #%d\0".format(parameters.length),
828                     false
829                 );
830                 incActivePuppet().parameters ~= param;
831                 incActionPush(new ParameterAddAction(param, &incActivePuppet().parameters));
832             }
833             if (igMenuItem(__("Add 1D Parameter (-1..1)"), "", false, true)) {
834                 Parameter param = new Parameter(
835                     "Param #%d\0".format(parameters.length),
836                     false
837                 );
838                 param.min.x = -1;
839                 param.max.x = 1;
840                 param.insertAxisPoint(0, 0.5);
841                 incActivePuppet().parameters ~= param;
842                 incActionPush(new ParameterAddAction(param, &incActivePuppet().parameters));
843             }
844             if (igMenuItem(__("Add 2D Parameter (0..1)"), "", false, true)) {
845                 Parameter param = new Parameter(
846                     "Param #%d\0".format(parameters.length),
847                     true
848                 );
849                 incActivePuppet().parameters ~= param;
850                 incActionPush(new ParameterAddAction(param, &incActivePuppet().parameters));
851             }
852             if (igMenuItem(__("Add 2D Parameter (-1..+1)"), "", false, true)) {
853                 Parameter param = new Parameter(
854                     "Param #%d\0".format(parameters.length),
855                     true
856                 );
857                 param.min = vec2(-1, -1);
858                 param.max = vec2(1, 1);
859                 param.insertAxisPoint(0, 0.5);
860                 param.insertAxisPoint(1, 0.5);
861                 incActivePuppet().parameters ~= param;
862                 incActionPush(new ParameterAddAction(param, &incActivePuppet().parameters));
863             }
864             if (igMenuItem(__("Add Mouth Shape"), "", false, true)) {
865                 Parameter param = new Parameter(
866                     "Mouth #%d\0".format(parameters.length),
867                     true
868                 );
869                 param.min = vec2(-1, 0);
870                 param.max = vec2(1, 1);
871                 param.insertAxisPoint(0, 0.25);
872                 param.insertAxisPoint(0, 0.5);
873                 param.insertAxisPoint(0, 0.6);
874                 param.insertAxisPoint(1, 0.3);
875                 param.insertAxisPoint(1, 0.5);
876                 param.insertAxisPoint(1, 0.6);
877                 incActivePuppet().parameters ~= param;
878                 incActionPush(new ParameterAddAction(param, &incActivePuppet().parameters));
879             }
880             igEndPopup();
881         }
882         if (igBeginChild("###FILTER", ImVec2(0, 32))) {
883             if (incInputText("Filter", filter)) {
884                 filter = filter.toLower;
885             }
886             incTooltip(_("Filter, search for specific parameters"));
887         }
888         igEndChild();
889 
890         if (igBeginChild("ParametersList", ImVec2(0, -36))) {
891             
892             // Always render the currently armed parameter on top
893             if (incArmedParameter()) {
894                 incParameterView!true(incArmedParameterIdx(), incArmedParameter(), &grabParam, false, parameters);
895             }
896 
897             // Render other parameters
898             foreach(i, ref param; parameters) {
899                 if (incArmedParameter() == param) continue;
900                 import std.algorithm.searching : canFind;
901                 ExParameterGroup group = cast(ExParameterGroup)param;
902                 bool found = filter.length == 0 || param.indexableName.canFind(filter);
903                 if (group) {
904                     foreach (ix, ref child; group.children) {
905                         if (incArmedParameter() == child) continue;
906                         if (child.indexableName.canFind(filter))
907                             found = true;
908                     }
909                 }
910                 if (found) {
911                     if (group) {
912                         igPushID(group.uuid);
913 
914                             bool open;
915                             if (group.color.isFinite) open = incBeginCategory(group.name.toStringz, ImVec4(group.color.r, group.color.g, group.color.b, 1));
916                             else open = incBeginCategory(group.name.toStringz);
917                             
918                             if (igIsItemClicked(ImGuiMouseButton.Right)) {
919                                 igOpenPopup("###CategorySettings");
920                             }
921 
922                             // Popup
923                             if (igBeginPopup("###CategorySettings")) {
924                                 if (igMenuItem(__("Rename"))) {
925                                     incPushWindow(new RenameWindow(group.name));
926                                 }
927 
928                                 if (igBeginMenu(__("Colors"))) {
929                                     auto flags = ImGuiColorEditFlags.NoLabel | ImGuiColorEditFlags.NoTooltip;
930                                     ImVec2 swatchSize = ImVec2(24, 24);
931 
932                                     // COLOR SWATCHES
933                                     if (igColorButton("NONE", ImVec4(0, 0, 0, 0), flags | ImGuiColorEditFlags.AlphaPreview, swatchSize)) group.color = vec3(float.nan, float.nan, float.nan);
934                                     igSameLine(0, 4);
935                                     if (igColorButton("RED", ImVec4(1, 0, 0, 1), flags, swatchSize)) group.color = vec3(0.25, 0.15, 0.15);
936                                     igSameLine(0, 4);
937                                     if (igColorButton("GREEN", ImVec4(0, 1, 0, 1), flags, swatchSize)) group.color = vec3(0.15, 0.25, 0.15);
938                                     igSameLine(0, 4);
939                                     if (igColorButton("BLUE", ImVec4(0, 0, 1, 1), flags, swatchSize)) group.color = vec3(0.15, 0.15, 0.25);
940                                     igSameLine(0, 4);
941                                     if (igColorButton("PURPLE", ImVec4(1, 0, 1, 1), flags, swatchSize)) group.color = vec3(0.25, 0.15, 0.25);
942                                     igSameLine(0, 4);
943                                     if (igColorButton("CYAN", ImVec4(0, 1, 1, 1), flags, swatchSize)) group.color = vec3(0.15, 0.25, 0.25);
944                                     igSameLine(0, 4);
945                                     if (igColorButton("YELLOW", ImVec4(1, 1, 0, 1), flags, swatchSize)) group.color = vec3(0.25, 0.25, 0.15);
946                                     igSameLine(0, 4);
947                                     if (igColorButton("WHITE", ImVec4(1, 1, 1, 1), flags, swatchSize)) group.color = vec3(0.25, 0.25, 0.25);
948                                     
949                                     igSpacing();
950 
951                                     // CUSTOM COLOR PICKER
952                                     // Allows user to select a custom color for parameter group.
953                                     igColorPicker3(__("Custom Color"), &group.color.vector, ImGuiColorEditFlags.InputRGB | ImGuiColorEditFlags.DisplayHSV);
954                                     igEndMenu();
955                                 }
956 
957                                 if (igMenuItem(__("Delete"))) {
958                                     if (i == 0) incActivePuppet().parameters = group.children ~ parameters[i+1..$];
959                                     else if (i+1 == parameters.length) incActivePuppet().parameters = parameters[0..$-1] ~ group.children;
960                                     else incActivePuppet().parameters = parameters[0..i] ~ group.children ~ parameters[i+1..$];
961                                     
962                                     // End early.
963                                     igEndPopup();
964                                     incEndCategory();
965                                     igPopID();
966                                     continue;
967                                 }
968                                 igEndPopup();
969                             }
970 
971                             // Allow drag/drop in to the category
972                             if (igBeginDragDropTarget()) {
973                                 auto payload = igAcceptDragDropPayload("_PARAMETER");
974                                 
975                                 if (payload !is null) {
976                                     ParamDragDropData* payloadParam = *cast(ParamDragDropData**)payload.Data;
977                                     moveParameter(payloadParam.param, group);
978                                 }
979                                 igEndDragDropTarget();
980                             }
981 
982                             // Render children if open
983                             if (open) {
984                                 foreach(ix, ref child; group.children) {
985 
986                                     // Skip armed param
987                                     if (incArmedParameter() == child) continue;
988                                     if (child.indexableName.canFind(filter)) {
989                                         // Otherwise render it
990                                         incParameterView(ix, child, &grabParam, false, group.children, group.color);
991                                     }
992                                 }
993                             }
994                             incEndCategory();
995                         igPopID();
996                     } else {
997                         incParameterView(i, param, &grabParam, true, incActivePuppet().parameters);
998                     }
999                 }
1000             }
1001         }
1002         igEndChild();
1003         
1004         // Allow drag/drop out of categories
1005         if (igBeginDragDropTarget()) {
1006             auto payload = igAcceptDragDropPayload("_PARAMETER");
1007             
1008             if (payload !is null) {
1009                 ParamDragDropData* payloadParam = *cast(ParamDragDropData**)payload.Data;
1010                 moveParameter(payloadParam.param, null);
1011             }
1012             igEndDragDropTarget();
1013         }
1014 
1015         // Right align add button
1016         ImVec2 avail = incAvailableSpace();
1017         incDummy(ImVec2(avail.x-32, 32));
1018         igSameLine(0, 0);
1019 
1020         // Add button
1021         if (igButton("", ImVec2(32, 32))) {
1022             igOpenPopup("###AddParameter");
1023         }
1024         incTooltip(_("Add Parameter"));
1025     }
1026 
1027 public:
1028     this() {
1029         super("Parameters", _("Parameters"), false);
1030     }
1031 }
1032 
1033 /**
1034     Generate logger frame
1035 */
1036 mixin incPanel!ParametersPanel;