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.widgets.mainmenu; 8 import creator.windows; 9 import creator.widgets; 10 import creator.panels; 11 import creator.core; 12 import creator.utils.link; 13 import creator; 14 import inochi2d; 15 import inochi2d.core.dbg; 16 import tinyfiledialogs; 17 import i18n; 18 19 import std.string; 20 21 private { 22 bool dbgShowStyleEditor; 23 bool dbgShowDebugger; 24 } 25 26 void incMainMenu() { 27 auto io = igGetIO(); 28 29 if(igBeginMainMenuBar()) { 30 ImVec2 avail; 31 igGetContentRegionAvail(&avail); 32 if (incGetUseNativeTitlebar()) { 33 igImage( 34 cast(void*)incGetLogo(), 35 ImVec2(avail.y*2, avail.y*2), 36 ImVec2(0, 0), ImVec2(1, 1), 37 ImVec4(1, 1, 1, 1), 38 ImVec4(0, 0, 0, 0) 39 ); 40 41 igSeparator(); 42 } 43 44 if (igBeginMenu(__("File"), true)) { 45 if(igMenuItem(__("New"), "Ctrl+N", false, true)) { 46 incNewProject(); 47 } 48 49 if (igBeginMenu(__("Open"), true)) { 50 igEndMenu(); 51 } 52 53 if(igMenuItem(__("Save"), "Ctrl+S", false, true)) { 54 } 55 56 if(igMenuItem(__("Save As..."), "Ctrl+Shift+S", false, true)) { 57 } 58 59 if (igBeginMenu(__("Import"), true)) { 60 incTooltip(_("Import photoshop document")); 61 if(igMenuItem_Bool(__("Photoshop Document"), "", false, true)) { 62 const TFD_Filter[] filters = [ 63 { ["*.psd"], "Photoshop Document (*.psd)" } 64 ]; 65 66 c_str filename = tinyfd_openFileDialog(__("Import..."), "", filters, false); 67 if (filename !is null) { 68 string file = cast(string)filename.fromStringz; 69 incImportPSD(file); 70 } 71 } 72 incTooltip(_("Import a standard Photoshop PSD file.")); 73 74 // This is only really useful for testing 75 // debug { 76 if (igMenuItem_Bool(__("Inochi2D Puppet"), "", false, true)) { 77 const TFD_Filter[] filters = [ 78 { ["*.inp"], "Inochi2D Puppet (*.inp)" } 79 ]; 80 81 c_str filename = tinyfd_openFileDialog(__("Import..."), "", filters, false); 82 if (filename !is null) { 83 string file = cast(string)filename.fromStringz; 84 incImportINP(file); 85 } 86 } 87 incTooltip(_("Import existing puppet file")); 88 // } 89 90 if (igMenuItem_Bool(__("Image Folder"))) { 91 c_str folder = tinyfd_selectFolderDialog(__("Select a Folder..."), null); 92 if (folder !is null) { 93 incImportFolder(cast(string)folder.fromStringz); 94 } 95 } 96 incTooltip(_("Supports PNGs, TGAs and JPEGs.")); 97 igEndMenu(); 98 } 99 if (igBeginMenu(__("Export"), true)) { 100 if(igMenuItem_Bool(__("Inochi Puppet"), "", false, true)) { 101 const TFD_Filter[] filters = [ 102 { ["*.inp"], "Inochi2D Puppet (*.inp)" } 103 ]; 104 105 import std.path : setExtension; 106 107 c_str filename = tinyfd_saveFileDialog(__("Export..."), "", filters); 108 if (filename !is null) { 109 string file = cast(string)filename.fromStringz; 110 111 // Remember to populate texture slots otherwise things will break real bad! 112 incActivePuppet().populateTextureSlots(); 113 114 // Write the puppet to file 115 inWriteINPPuppet(incActivePuppet(), file.setExtension(".inp")); 116 } 117 } 118 igEndMenu(); 119 } 120 121 if(igMenuItem_Bool(__("Quit"), "Alt+F4", false, true)) incExit(); 122 igEndMenu(); 123 } 124 125 if (igBeginMenu(__("Edit"), true)) { 126 if(igMenuItem_Bool(__("Undo"), "Ctrl+Z", false, incActionCanUndo())) incActionUndo(); 127 if(igMenuItem_Bool(__("Redo"), "Ctrl+Shift+Z", false, incActionCanRedo())) incActionRedo(); 128 129 igSeparator(); 130 if(igMenuItem_Bool(__("Cut"), "Ctrl+X", false, false)) {} 131 if(igMenuItem_Bool(__("Copy"), "Ctrl+C", false, false)) {} 132 if(igMenuItem_Bool(__("Paste"), "Ctrl+V", false, false)) {} 133 134 igSeparator(); 135 if(igMenuItem_Bool(__("Settings"), "", false, true)) { 136 if (!incIsSettingsOpen) incPushWindow(new SettingsWindow); 137 } 138 139 debug { 140 igSpacing(); 141 igSpacing(); 142 143 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("ImGui Debugging")); 144 145 igSeparator(); 146 if(igMenuItem_Bool(__("Style Editor"), "", false, true)) dbgShowStyleEditor = !dbgShowStyleEditor; 147 if(igMenuItem_Bool(__("ImGui Debugger"), "", false, true)) dbgShowDebugger = !dbgShowDebugger; 148 } 149 igEndMenu(); 150 } 151 152 if (igBeginMenu(__("View"), true)) { 153 if (igMenuItem(__("Reset Layout"), null, false, true)) { 154 incSetDefaultLayout(); 155 } 156 igSeparator(); 157 158 // Spacing 159 igSpacing(); 160 igSpacing(); 161 162 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Panels")); 163 igSeparator(); 164 165 foreach(panel; incPanels) { 166 167 // Skip panels that'll always be visible 168 if (panel.alwaysVisible) continue; 169 170 // Show menu item for panel 171 if(igMenuItem_Bool(panel.displayNameC, null, panel.visible, true)) { 172 panel.visible = !panel.visible; 173 incSettingsSet(panel.name~".visible", panel.visible); 174 } 175 } 176 177 // Spacing 178 igSpacing(); 179 igSpacing(); 180 181 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Extras")); 182 183 igSeparator(); 184 185 if (igMenuItem_Bool(__("Save Screenshot"), "", incShowStatsForNerds, true)) { 186 const TFD_Filter[] filters = [ 187 { ["*.png"], "PNG Image (*.png)" }, 188 { ["*.tga"], "TARGA Image (*.png)" } 189 ]; 190 191 import std.path : setExtension; 192 c_str filename = tinyfd_saveFileDialog(__("Export..."), "", filters); 193 if (filename !is null) { 194 string file = cast(string)filename.fromStringz; 195 196 // Dump viewport to RGBA byte array 197 int width, height; 198 inGetViewport(width, height); 199 Texture outTexture = new Texture(null, width, height); 200 201 // Texture data 202 inSetClearColor(0, 0, 0, 0); 203 inBeginScene(); 204 incActivePuppet().update(); 205 incActivePuppet().draw(); 206 inEndScene(); 207 ubyte[] textureData = new ubyte[inViewportDataLength()]; 208 inDumpViewport(textureData); 209 inTexUnPremuliply(textureData); 210 211 // Write to texture 212 outTexture.setData(textureData); 213 214 outTexture.save(file); 215 } 216 } 217 incTooltip(_("Saves screenshot as PNG of the editor framebuffer.")); 218 219 if (igMenuItem_Bool(__("Show Stats for Nerds"), "", incShowStatsForNerds, true)) { 220 incShowStatsForNerds = !incShowStatsForNerds; 221 incSettingsSet("NerdStats", incShowStatsForNerds); 222 } 223 224 igEndMenu(); 225 } 226 227 if (igBeginMenu(__("Tools"), true)) { 228 import creator.utils.repair : incAttemptRepairPuppet, incRegenerateNodeIDs; 229 230 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Puppet Texturing")); 231 igSeparator(); 232 if (igMenuItem(__("Rebleed textures..."), "", false)) { 233 incRebleedTextures(); 234 } 235 236 if (igMenuItem(__("Generate Mipmaps..."), "", false)) { 237 incRegenerateMipmaps(); 238 } 239 incTooltip(_("In some instances mipmaps may need to be re-generated.\nFor example PSD imports will need their mipmaps regenerated.")); 240 241 // Spacing 242 igSpacing(); 243 igSpacing(); 244 245 igTextColored(ImVec4(0.7, 0.5, 0.5, 1), __("Puppet Recovery")); 246 igSeparator(); 247 248 // FULL REPAIR 249 if (igMenuItem(__("Attempt full repair..."), "", false)) { 250 incAttemptRepairPuppet(incActivePuppet()); 251 } 252 incTooltip(_("Attempts all the recovery and repair methods below on the currently loaded model")); 253 254 // REGEN NODE IDs 255 if (igMenuItem(__("Regenerate Node IDs"), "", false)) { 256 import creator.utils.repair : incAttemptRepairPuppet; 257 incRegenerateNodeIDs(incActivePuppet().root); 258 } 259 incTooltip(_("Regenerates all the unique IDs for the model")); 260 261 // Premultiply textures 262 if (igMenuItem(__("Premultiply textures"), "", false)) { 263 import creator.utils.repair : incPremultTextures; 264 incPremultTextures(incActivePuppet()); 265 } 266 incTooltip(_("Premultiplies textures")); 267 268 // Spacing 269 igSpacing(); 270 igSpacing(); 271 igSeparator(); 272 if (igMenuItem(__("Verify INP File..."), "", false)) { 273 incAttemptRepairPuppet(incActivePuppet()); 274 } 275 incTooltip(_("Attempts to verify and repair INP files")); 276 277 igEndMenu(); 278 } 279 280 if (igBeginMenu(__("Help"), true)) { 281 282 if(igMenuItem_Bool(__("Tutorial"), "(TODO)", false, false)) { } 283 igSeparator(); 284 285 if(igMenuItem_Bool(__("Online Documentation"), "", false, true)) { 286 incOpenLink("https://github.com/Inochi2D/inochi-creator/wiki"); 287 } 288 289 if(igMenuItem_Bool(__("Inochi2D Documentation"), "", false, true)) { 290 incOpenLink("https://github.com/Inochi2D/inochi2d/wiki"); 291 } 292 igSeparator(); 293 294 if(igMenuItem_Bool(__("About"), "", false, true)) { 295 incPushWindow(new AboutWindow); 296 } 297 igEndMenu(); 298 } 299 300 // We need to pre-calculate the size of the right adjusted section 301 // This code is very ugly because imgui doesn't really exactly understand this 302 // stuff natively. 303 ImVec2 secondSectionLength = ImVec2(0, 0); 304 secondSectionLength.x += incMeasureString(_("Donate")).x+16; // Add 16 px padding 305 if (incShowStatsForNerds) { // Extra padding I guess 306 secondSectionLength.x += igGetStyle().ItemSpacing.x; 307 secondSectionLength.x += incMeasureString("1000ms").x; 308 } 309 incDummy(ImVec2(-secondSectionLength.x, 0)); 310 311 if (incShowStatsForNerds) { 312 string fpsText = "%.0fms\0".format(1000f/io.Framerate); 313 float textAreaDummyWidth = incMeasureString("1000ms").x-incMeasureString(fpsText).x; 314 incDummy(ImVec2(textAreaDummyWidth, 0)); 315 igText(fpsText.ptr); 316 } 317 318 // Donate button 319 // NOTE: Is this too obstructive in the UI? 320 if(igMenuItem(__("Donate"))) { 321 incOpenLink("https://www.patreon.com/clipsey"); 322 } 323 incTooltip(_("Support development via Patreon")); 324 325 igEndMainMenuBar(); 326 327 if (dbgShowStyleEditor) igShowStyleEditor(igGetStyle()); 328 if (dbgShowDebugger) igShowAboutWindow(&dbgShowDebugger); 329 } 330 }