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 }