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.viewport;
8 import creator.viewport;
9 import creator.widgets;
10 import creator.widgets.viewport;
11 import creator.core;
12 import creator.core.colorbleed;
13 import creator.panels;
14 import creator.actions;
15 import creator;
16 import inochi2d;
17 import inochi2d.core.dbg;
18 import bindbc.imgui;
19 import std.string;
20 import i18n;
21 
22 /**
23     A viewport
24 */
25 class ViewportPanel : Panel {
26 private:
27     ImVec2 lastSize;
28     bool actingInViewport;
29 
30 
31     ImVec2 priorWindowPadding;
32 
33 protected:
34     override
35     void onBeginUpdate() {
36         
37         ImGuiWindowClass wmclass;
38         wmclass.DockNodeFlagsOverrideSet = ImGuiDockNodeFlagsI.NoTabBar;
39         igSetNextWindowClass(&wmclass);
40         priorWindowPadding = igGetStyle().WindowPadding;
41         igPushStyleVar(ImGuiStyleVar.WindowPadding, ImVec2(0, 2));
42         igSetNextWindowDockID(incGetViewportDockSpace(), ImGuiCond.Always);
43         super.onBeginUpdate();
44     }
45 
46     override void onEndUpdate() {
47         super.onEndUpdate();
48         igPopStyleVar();
49     }
50 
51     override
52     void onUpdate() {
53 
54         auto io = igGetIO();
55         auto camera = inGetCamera();
56         auto drawList = igGetWindowDrawList();
57         auto window = igGetCurrentWindow();
58 
59         // Draw viewport itself
60         ImVec2 currSize;
61         igGetContentRegionAvail(&currSize);
62 
63         // We do not want the viewport to be NaN
64         // That will crash the app
65         if (currSize.x.isNaN || currSize.y.isNaN) {
66             currSize = ImVec2(0, 0);
67         }
68 
69         // Resize Inochi2D viewport according to frame
70         // Also viewport of 0 is too small, minimum 128.
71         currSize = ImVec2(clamp(currSize.x, 128, float.max), clamp(currSize.y, 128, float.max));
72         
73         foreach(btn; 0..cast(int)ImGuiMouseButton.COUNT) {
74             if (!incStartedDrag(btn)) {
75                 if (io.MouseDown[btn]) {
76                     if (igIsWindowHovered(ImGuiHoveredFlags.ChildWindows)) {
77                         incBeginDragInViewport(btn);
78                     }
79                     incBeginDrag(btn);
80                 }
81             }
82 
83             if (incStartedDrag(btn) && !io.MouseDown[btn]) {
84                 incEndDrag(btn);
85                 incEndDragInViewport(btn);
86             }
87         }
88 
89         if (igBeginChild("##ViewportView", ImVec2(0, -32))) {
90             igGetContentRegionAvail(&currSize);
91             currSize = ImVec2(
92                 clamp(currSize.x, 128, float.max), 
93                 clamp(currSize.y, 128, float.max)
94             );
95 
96             if (currSize != lastSize) {
97                 inSetViewport(cast(int)(currSize.x*incGetUIScale()), cast(int)(currSize.y*incGetUIScale()));
98             }
99 
100             incViewportPoll();
101 
102             // Ignore events within child windows *unless* drag started within
103             // viewport.
104             ImGuiHoveredFlags winFlags = ImGuiHoveredFlags.None;
105             if (actingInViewport) winFlags |= ImGuiHoveredFlags.ChildWindows | ImGuiHoveredFlags.AllowWhenBlockedByActiveItem;
106             if (igIsWindowHovered(winFlags)) {
107                 actingInViewport = igIsMouseDown(ImGuiMouseButton.Left) ||
108                     igIsMouseDown(ImGuiMouseButton.Middle) ||
109                     igIsMouseDown(ImGuiMouseButton.Right);
110                 incViewportUpdate();
111             } else if (incViewportAlwaysUpdate()) {
112                 incViewportUpdate(true);
113             }
114 
115             auto style = igGetStyle();
116             inSetClearColor(style.Colors[ImGuiCol.WindowBg].x, style.Colors[ImGuiCol.WindowBg].y, style.Colors[ImGuiCol.WindowBg].z, 1);
117             incViewportDraw();
118 
119             int width, height;
120             inGetViewport(width, height);
121 
122             // Render our viewport
123             igImage(
124                 cast(void*)inGetRenderImage(), 
125                 ImVec2(ceil(width/incGetUIScale()), ceil(height/incGetUIScale())), 
126                 ImVec2((0.5/width), 1-(0.5/height)), 
127                 ImVec2(1-(0.5/width), (0.5/height)), 
128                 ImVec4(1, 1, 1, 1), ImVec4(0, 0, 0, 0)
129             );
130 
131             // Popup right click menu
132             igPushStyleVar(ImGuiStyleVar.WindowPadding, priorWindowPadding);
133             if (incViewportHasMenu()) {
134                 static ImVec2 downPos;
135                 ImVec2 currPos;
136                 if (igIsItemHovered()) {
137                     if (igIsItemClicked(ImGuiMouseButton.Right)) {
138                         igGetMousePos(&downPos);
139                     }
140 
141                     if (!igIsPopupOpen("ViewportMenu") && igIsMouseReleased(ImGuiMouseButton.Right)) {
142                         igGetMousePos(&currPos);
143                         float dist = sqrt(((downPos.x-currPos.x)^^2)+((downPos.y-currPos.y)^^2));
144                         
145                         if (dist < 16) {
146                             incViewportMenuOpening();
147                             igOpenPopup("ViewportMenu");
148                         }
149                     }
150                 }
151 
152                 if (igBeginPopup("ViewportMenu")) {
153                     incViewportMenu();
154                     igEndPopup();
155                 }
156             }
157             igPopStyleVar();
158 
159             igPushStyleVar(ImGuiStyleVar.FrameBorderSize, 0);
160                 incBeginViewportToolArea("ToolArea", ImGuiDir.Left);
161                     igPushStyleVar_Vec2(ImGuiStyleVar.FramePadding, ImVec2(6, 6));
162                         incViewportDrawTools();
163                     igPopStyleVar();
164                 incEndViewportToolArea();
165 
166                 incBeginViewportToolArea("OptionsArea", ImGuiDir.Right);
167                     igPushStyleVar_Vec2(ImGuiStyleVar.FramePadding, ImVec2(6, 6));
168                         incViewportDrawOptions();
169                     igPopStyleVar();
170                 incEndViewportToolArea();
171 
172                 incBeginViewportToolArea("ConfirmArea", ImGuiDir.Left, ImGuiDir.Down, false);
173                     incViewportDrawConfirmBar();
174                 incEndViewportToolArea();
175             igPopStyleVar();
176 
177             lastSize = currSize;
178             igEndChild();
179         }
180 
181         // Draw line in a better way
182         ImDrawList_AddLine(drawList, 
183             ImVec2(
184                 window.InnerRect.Max.x-1,
185                 window.InnerRect.Max.y+currSize.y,
186             ),
187             ImVec2(
188                 window.InnerRect.Min.x+1,
189                 window.InnerRect.Max.y+currSize.y,
190             ), 
191             igColorConvertFloat4ToU32(*igGetStyleColorVec4(ImGuiCol.Separator)), 
192             2
193         );
194 
195 
196         // FILE DRAG & DROP
197         if (igBeginDragDropTarget()) {
198             const(ImGuiPayload)* payload = igAcceptDragDropPayload("__PARTS_DROP");
199             if (payload !is null) {
200                 string[] files = *cast(string[]*)payload.Data;
201                 import std.path : baseName, extension;
202                 import std.uni : toLower;
203                 mainLoop: foreach(file; files) {
204                     string fname = file.baseName;
205 
206                     switch(fname.extension.toLower) {
207                     case ".png", ".tga", ".jpeg", ".jpg":
208 
209                         try {
210                             auto tex = new ShallowTexture(file);
211                             incColorBleedPixels(tex);
212                             inTexPremultiply(tex.data);
213                             incAddChildWithHistory(
214                                 inCreateSimplePart(*tex, null, fname), 
215                                 incSelectedNode(), 
216                                 fname
217                             );
218                         } catch(Exception ex) {
219                             if (ex.msg[0..11] == "unsupported") {
220                                 incDialog(__("Error"), _("%s is not supported").format(fname));
221                             } else incDialog(__("Error"), ex.msg);
222                         }
223 
224                         // We've added new stuff, rescan nodes
225                         incActivePuppet().rescanNodes();
226                         break;
227 
228                     // Allow dragging PSD in to main window
229                     case ".psd":
230                         incImportPSD(file);
231                         break mainLoop;
232 
233                     default:
234                         incDialog(__("Error"), _("%s is not supported").format(fname)); 
235                         break;
236                     }
237                 }
238 
239                 // Finish the file drag
240                 incFinishFileDrag();
241             }
242 
243             igEndDragDropTarget();
244         }
245 
246         // BOTTOM VIEWPORT CONTROLS
247         igGetContentRegionAvail(&currSize);
248         if (igBeginChild("##ViewportControls", ImVec2(0, currSize.y), false, flags.NoScrollbar)) {
249             igSetCursorPosY(igGetCursorPosY()+4);
250             igPushItemWidth(72);
251                 igSpacing();
252                 igSameLine(0, 8);
253                 if (igSliderFloat(
254                     "##Zoom", 
255                     &incViewportZoom, 
256                     incVIEWPORT_ZOOM_MIN, 
257                     incVIEWPORT_ZOOM_MAX, 
258                     "%s%%\0".format(cast(int)(incViewportZoom*100)).ptr, 
259                     ImGuiSliderFlags.NoRoundToFormat)
260                 ) {
261                     camera.scale = vec2(incViewportZoom);
262                     incViewportTargetZoom = incViewportZoom;
263                 }
264                 if (incViewportTargetZoom != 1) {
265                     igSameLine(0, 8);
266                     if (igButton("", ImVec2(0, 0))) {
267                         incViewportTargetZoom = 1;
268                     }
269                 }
270 
271                 igSameLine(0, 8);
272                 igSeparatorEx(ImGuiSeparatorFlags.Vertical);
273 
274                 igSameLine(0, 8);
275                 incText("x = %.2f y = %.2f".format(incViewportTargetPosition.x, incViewportTargetPosition.y));
276                 if (incViewportTargetPosition != vec2(0)) {
277                     igSameLine(0, 8);
278                     if (igButton("##2", ImVec2(0, 0))) {
279                         incViewportTargetPosition = vec2(0, 0);
280                     }
281                 }
282 
283 
284             igPopItemWidth();
285         }
286         igEndChild();
287 
288         // Handle smooth move
289         incViewportZoom = dampen(incViewportZoom, incViewportTargetZoom, deltaTime);
290         camera.scale = vec2(incViewportZoom, incViewportZoom);
291         camera.position = vec2(dampen(camera.position, incViewportTargetPosition, deltaTime, 1.5));
292     }
293 
294 public:
295     this() {
296         super("Viewport", _("Viewport"), true);
297         this.alwaysVisible = true;
298     }
299 
300 }
301 
302 mixin incPanel!ViewportPanel;