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;