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.core;
8 import creator.core.font;
9 import creator.panels;
10 import creator.windows;
11 import creator.utils.link;
12 import creator;
13 
14 import std.exception;
15 
16 import bindbc.sdl;
17 import bindbc.opengl;
18 import inochi2d;
19 import tinyfiledialogs;
20 import std.string;
21 import std.stdio;
22 import std.conv;
23 import std.range : repeat;
24 
25 public import bindbc.imgui;
26 public import bindbc.imgui.ogl;
27 public import creator.core.settings;
28 public import creator.core.actionstack;
29 public import creator.core.taskstack;
30 public import creator.core.path;
31 public import creator.core.font;
32 import i18n;
33 
34 private {
35     SDL_GLContext gl_context;
36     SDL_Window* window;
37     ImGuiIO* io;
38     bool done = false;
39     ImGuiID viewportDock;
40 
41     version (InBranding) Texture inLogo;
42 
43     ImFont* mainFont;
44     ImFont* iconFont;
45     ImFont* biggerFont;
46 
47     bool isDarkMode = true;
48     string[] files;
49 }
50 
51 bool incShowStatsForNerds;
52 
53 
54 /**
55     Finalizes everything by freeing imgui resources, etc.
56 */
57 void incFinalize() {
58 
59     // This is important to prevent thread leakage
60     import creator.viewport.test : incViewportTestWithdraw;
61     incViewportTestWithdraw();
62 
63     // Save settings
64     igSaveIniSettingsToDisk(igGetIO().IniFilename);
65 
66     // Cleanup
67     ImGuiOpenGLBackend.shutdown();
68     ImGui_ImplSDL2_Shutdown();
69     igDestroyContext(null);
70 
71     SDL_GL_DeleteContext(gl_context);
72     SDL_DestroyWindow(window);
73     SDL_Quit();
74 }
75 
76 /**
77     Gets dockspace of the viewport
78 */
79 ImGuiID incGetViewportDockSpace() {
80     return viewportDock;
81 }
82 
83 /**
84     Initialize styling
85 */
86 void incInitStyling() {
87     auto style = igGetStyle();
88     //style.WindowBorderSize = 0;
89     style.ChildBorderSize = 1;
90     style.PopupBorderSize = 1;
91     style.FrameBorderSize = 1;
92     style.TabBorderSize = 1;
93 
94     style.WindowRounding = 4;
95     style.ChildRounding = 0;
96     style.FrameRounding = 3;
97     style.PopupRounding = 6;
98     style.ScrollbarRounding = 18;
99     style.GrabRounding = 3;
100     style.LogSliderDeadzone = 6;
101     style.TabRounding = 6;
102 
103     style.IndentSpacing = 10;
104     style.ItemSpacing.y = 3;
105     style.FramePadding.y = 4;
106 
107     style.GrabMinSize = 13;
108     style.ScrollbarSize = 14;
109     style.ChildBorderSize = 1;
110 }
111 
112 
113 /**
114     Opens Window
115 */
116 void incOpenWindow() {
117     auto sdlSupport = loadSDL();
118     enforce(sdlSupport != SDLSupport.noLibrary, "SDL2 library not found!");
119     enforce(sdlSupport != SDLSupport.badLibrary, "Bad SDL2 library found!");
120     
121     auto imSupport = loadImGui();
122     enforce(imSupport != ImGuiSupport.noLibrary, "cimgui library not found!");
123     
124     // HACK: For some reason this check fails on some macOS and Linux installations
125     version(Windows) enforce(imSupport != ImGuiSupport.badLibrary, "Bad cimgui library found!");
126     SDL_Init(SDL_INIT_EVERYTHING);
127 
128     
129     SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GLcontextFlag.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
130     SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE);
131     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
132     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
133 
134     debug SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GLcontextFlag.SDL_GL_CONTEXT_DEBUG_FLAG | SDL_GLcontextFlag.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
135     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
136     SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
137     SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
138     SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
139     SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
140     SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
141     SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
142     SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
143 
144     SDL_WindowFlags flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
145 
146     if (incSettingsGet!bool("WinMax", false)) {
147         flags |= SDL_WINDOW_MAXIMIZED;
148     }
149 
150     // Don't make KDE freak out when Inochi Creator opens
151     if (!incSettingsGet!bool("DisableCompositor")) SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
152 
153     version(InGallium) {
154         import std.process : environment;
155         if (incSettingsGet!bool("SoftwareRenderer")) {
156 
157             // For Mesa builds, use llvmpipe gallium driver
158             environment["GALLIUM_DRIVER"] = "llvmpipe";
159         } else {
160 
161             // For Mesa builds, use zink gallium driver
162             environment["GALLIUM_DRIVER"] = "zink";
163         }
164     }
165 
166 
167     version(InBranding) {
168         debug string WIN_TITLE = "Inochi Creator "~_("(Debug Mode)");
169         else string WIN_TITLE = "Inochi Creator "~INC_VERSION;
170     } else string WIN_TITLE = "Inochi Creator "~_("(Unsupported)");
171     window = SDL_CreateWindow(
172         WIN_TITLE.toStringz, 
173         SDL_WINDOWPOS_UNDEFINED,
174         SDL_WINDOWPOS_UNDEFINED,
175         cast(uint)incSettingsGet!int("WinW", 1280), 
176         cast(uint)incSettingsGet!int("WinH", 800), 
177         flags
178     );
179     
180     GLSupport support;
181 
182     // Gallium Support
183     version(InGallium) {
184 
185         bool incInitGalliumCtx() {
186             if (gl_context !is null) SDL_GL_DeleteContext(gl_context);
187             gl_context = SDL_GL_CreateContext(window);
188             SDL_GL_SetSwapInterval(1);
189             support = loadOpenGL();
190             return support != GLSupport.noLibrary && support != GLSupport.noContext;
191         }
192         
193         if (!incInitGalliumCtx() && !incSettingsGet!bool("SoftwareRenderer")) {
194             debug writeln("Attempting Gallium software rendering...");
195 
196             environment["GALLIUM_DRIVER"] = "llvmpipe";
197             if (!incInitGalliumCtx()) {
198                 incSettingsSet("SoftwareRenderer", true);
199                 throw new Exception("Could not create Gallium Zink nor llvmpipe GL 3.2 instance!");
200             }
201         }
202 
203     } else {
204 
205         gl_context = SDL_GL_CreateContext(window);
206         SDL_GL_SetSwapInterval(1);
207 
208         // Load GL 3
209         support = loadOpenGL();
210         switch(support) {
211             case GLSupport.noLibrary:
212                 throw new Exception("OpenGL library could not be loaded!");
213 
214             case GLSupport.noContext:
215                 throw new Exception("No valid OpenGL 4.2 context was found!");
216 
217             default: break;
218         }
219     }
220 
221 
222     import std.string : fromStringz;
223     debug {
224         writefln("GLInfo:\n\t%s\n\t%s\n\t%s\n\t%s\n\tgls=%s",
225             glGetString(GL_VERSION).fromStringz,
226             glGetString(GL_VENDOR).fromStringz,
227             glGetString(GL_RENDERER).fromStringz,
228             glGetString(GL_SHADING_LANGUAGE_VERSION).fromStringz,
229             support
230         );
231 
232         glEnable(GL_DEBUG_OUTPUT);
233         version(linux) {
234             glDebugMessageCallback(&incDebugCallback, null);
235         }
236     }
237 
238     // Setup Inochi2D
239     inInit(() { return igGetTime(); });
240 
241     incCreateContext();
242 
243     version (InBranding) {
244         // Load image resources
245         inLogo = new Texture(ShallowTexture(cast(ubyte[])import("logo.png")));
246     }
247 
248     // Load Settings
249     incShowStatsForNerds = incSettingsCanGet("NerdStats") ? incSettingsGet!bool("NerdStats") : false;
250 
251     import creator.widgets.titlebar : incSetUseNativeTitlebar, incGetUseNativeTitlebar, incCanUseAppTitlebar;
252     incCanUseAppTitlebar = SDL_SetWindowHitTest(incGetWindowPtr(), null, null) != -1;
253     incSetUseNativeTitlebar(incSettingsGet("UseNativeTitleBar", false));
254     
255 }
256 
257 void incCreateContext() {
258 
259     // Setup IMGUI
260     igCreateContext(null);
261     io = igGetIO();
262     
263     // Setup font handling
264     incInitFonts();
265 
266     import std.file : exists;
267     if (!exists(incGetAppImguiConfigFile())) {
268         // TODO: Setup a base config
269     }
270 
271 
272     // Copy string out of GC memory to make sure it doesn't get yeeted before imgui exits.
273     import core.stdc.stdlib : malloc;
274     import core.stdc.string : memcpy;
275     io.IniFilename = cast(char*)malloc(incGetAppImguiConfigFile().length+1);
276     memcpy(cast(void*)io.IniFilename, toStringz(incGetAppImguiConfigFile), incGetAppImguiConfigFile().length+1);
277     igLoadIniSettingsFromDisk(io.IniFilename);
278 
279     incSetDarkMode(incSettingsGet!bool("DarkMode", true));
280 
281     io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;           // Enable Docking
282     io.ConfigFlags |= ImGuiConfigFlags.ViewportsEnable;         // Enable Viewports (causes freezes)
283     //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Navigation
284     io.ConfigWindowsResizeFromEdges = true;                     // Enable Edge resizing
285     ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
286     ImGuiOpenGLBackend.init(null);
287 
288     incInitStyling();
289 }
290 
291 void incSetDarkMode(bool darkMode) {
292     auto style = igGetStyle();
293 
294     if (darkMode) {
295         style.Colors[ImGuiCol.Text]                   = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
296         style.Colors[ImGuiCol.TextDisabled]           = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
297         style.Colors[ImGuiCol.WindowBg]               = ImVec4(0.17f, 0.17f, 0.17f, 1.00f);
298         style.Colors[ImGuiCol.ChildBg]                = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
299         style.Colors[ImGuiCol.PopupBg]                = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
300         style.Colors[ImGuiCol.Border]                 = ImVec4(0.00f, 0.00f, 0.00f, 0.16f);
301         style.Colors[ImGuiCol.BorderShadow]           = ImVec4(0.00f, 0.00f, 0.00f, 0.16f);
302         style.Colors[ImGuiCol.FrameBg]                = ImVec4(0.12f, 0.12f, 0.12f, 1.00f);
303         style.Colors[ImGuiCol.FrameBgHovered]         = ImVec4(0.15f, 0.15f, 0.15f, 0.40f);
304         style.Colors[ImGuiCol.FrameBgActive]          = ImVec4(0.22f, 0.22f, 0.22f, 0.67f);
305         style.Colors[ImGuiCol.TitleBg]                = ImVec4(0.04f, 0.04f, 0.04f, 1.00f);
306         style.Colors[ImGuiCol.TitleBgActive]          = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
307         style.Colors[ImGuiCol.TitleBgCollapsed]       = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
308         style.Colors[ImGuiCol.MenuBarBg]              = ImVec4(0.05f, 0.05f, 0.05f, 1.00f);
309         style.Colors[ImGuiCol.ScrollbarBg]            = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
310         style.Colors[ImGuiCol.ScrollbarGrab]          = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
311         style.Colors[ImGuiCol.ScrollbarGrabHovered]   = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
312         style.Colors[ImGuiCol.ScrollbarGrabActive]    = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
313         style.Colors[ImGuiCol.CheckMark]              = ImVec4(0.76f, 0.76f, 0.76f, 1.00f);
314         style.Colors[ImGuiCol.SliderGrab]             = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
315         style.Colors[ImGuiCol.SliderGrabActive]       = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
316         style.Colors[ImGuiCol.Button]                 = ImVec4(0.39f, 0.39f, 0.39f, 0.40f);
317         style.Colors[ImGuiCol.ButtonHovered]          = ImVec4(0.44f, 0.44f, 0.44f, 1.00f);
318         style.Colors[ImGuiCol.ButtonActive]           = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
319         style.Colors[ImGuiCol.Header]                 = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
320         style.Colors[ImGuiCol.HeaderHovered]          = ImVec4(0.28f, 0.28f, 0.28f, 0.80f);
321         style.Colors[ImGuiCol.HeaderActive]           = ImVec4(0.44f, 0.44f, 0.44f, 1.00f);
322         style.Colors[ImGuiCol.Separator]              = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
323         style.Colors[ImGuiCol.SeparatorHovered]       = ImVec4(0.29f, 0.29f, 0.29f, 0.78f);
324         style.Colors[ImGuiCol.SeparatorActive]        = ImVec4(0.47f, 0.47f, 0.47f, 1.00f);
325         style.Colors[ImGuiCol.ResizeGrip]             = ImVec4(0.35f, 0.35f, 0.35f, 0.00f);
326         style.Colors[ImGuiCol.ResizeGripHovered]      = ImVec4(0.40f, 0.40f, 0.40f, 0.00f);
327         style.Colors[ImGuiCol.ResizeGripActive]       = ImVec4(0.55f, 0.55f, 0.56f, 0.00f);
328         style.Colors[ImGuiCol.Tab]                    = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
329         style.Colors[ImGuiCol.TabHovered]             = ImVec4(0.34f, 0.34f, 0.34f, 0.80f);
330         style.Colors[ImGuiCol.TabActive]              = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
331         style.Colors[ImGuiCol.TabUnfocused]           = ImVec4(0.14f, 0.14f, 0.14f, 0.97f);
332         style.Colors[ImGuiCol.TabUnfocusedActive]     = ImVec4(0.17f, 0.17f, 0.17f, 1.00f);
333         style.Colors[ImGuiCol.DockingPreview]         = ImVec4(0.62f, 0.68f, 0.75f, 0.70f);
334         style.Colors[ImGuiCol.DockingEmptyBg]         = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
335         style.Colors[ImGuiCol.PlotLines]              = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
336         style.Colors[ImGuiCol.PlotLinesHovered]       = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
337         style.Colors[ImGuiCol.PlotHistogram]          = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
338         style.Colors[ImGuiCol.PlotHistogramHovered]   = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
339         style.Colors[ImGuiCol.TableHeaderBg]          = ImVec4(0.19f, 0.19f, 0.20f, 1.00f);
340         style.Colors[ImGuiCol.TableBorderStrong]      = ImVec4(0.31f, 0.31f, 0.35f, 1.00f);
341         style.Colors[ImGuiCol.TableBorderLight]       = ImVec4(0.23f, 0.23f, 0.25f, 1.00f);
342         style.Colors[ImGuiCol.TableRowBg]             = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
343         style.Colors[ImGuiCol.TableRowBgAlt]          = ImVec4(1.00f, 1.00f, 1.00f, 0.06f);
344         style.Colors[ImGuiCol.TextSelectedBg]         = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
345         style.Colors[ImGuiCol.DragDropTarget]         = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
346         style.Colors[ImGuiCol.NavHighlight]           = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
347         style.Colors[ImGuiCol.NavWindowingHighlight]  = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
348         style.Colors[ImGuiCol.NavWindowingDimBg]      = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
349         style.Colors[ImGuiCol.ModalWindowDimBg]       = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
350 
351         style.FrameBorderSize = 1;
352         style.TabBorderSize = 1;
353     } else {
354         igStyleColorsLight(null);
355         style.Colors[ImGuiCol.Border] = ImVec4(0.8, 0.8, 0.8, 0.5);
356         style.Colors[ImGuiCol.BorderShadow] = ImVec4(0, 0, 0, 0.05);
357 
358         style.FrameBorderSize = 1;
359     } 
360 
361     // Set Dark mode setting
362     incSettingsSet("DarkMode", darkMode);
363     isDarkMode = darkMode;
364 }
365 
366 bool incGetDarkMode() {
367     return isDarkMode;
368 }
369 
370 /**
371     Gets whether a frame should be processed
372 */
373 bool incShouldProcess() {
374     return (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) == 0;
375 }
376 
377 /**
378     Gets SDL Window Pointer
379 */
380 SDL_Window* incGetWindowPtr() {
381     return window;
382 }
383 
384 void incFinishFileDrag() {
385     files.length = 0;
386 }
387 
388 void incBeginLoopNoEv() {
389     // Start the Dear ImGui frame
390     ImGuiOpenGLBackend.new_frame();
391     ImGui_ImplSDL2_NewFrame();
392     igNewFrame();
393 
394     if (files.length > 0) {
395         if (igBeginDragDropSource(ImGuiDragDropFlags.SourceExtern)) {
396             igSetDragDropPayload("__PARTS_DROP", &files, files.sizeof);
397             igBeginTooltip();
398             foreach(file; files) {
399                 igText(file.toStringz);
400             }
401             igEndTooltip();
402             igEndDragDropSource();
403         }
404     }
405 
406     // Add docking space
407     viewportDock = igDockSpaceOverViewport(null, cast(ImGuiDockNodeFlags)0, null);
408     if (!incSettingsCanGet("firstrun_complete")) {
409         incSetDefaultLayout();
410         incSettingsSet("firstrun_complete", true);
411     }
412 }
413 
414 void incSetDefaultLayout() {
415     import creator.panels;
416     
417     igDockBuilderRemoveNodeChildNodes(viewportDock);
418     ImGuiID 
419         dockMainID, dockIDNodes, dockIDInspector, dockIDHistory, dockIDParams,
420         dockIDToolSettings, dockIDLoggerAndTextureSlots;
421 
422     dockMainID = viewportDock;
423     dockIDNodes = igDockBuilderSplitNode(dockMainID, ImGuiDir.Left, 0.10f, null, &dockMainID);
424     dockIDInspector = igDockBuilderSplitNode(dockIDNodes, ImGuiDir.Down, 0.60f, null, &dockIDNodes);
425     dockIDToolSettings = igDockBuilderSplitNode(dockMainID, ImGuiDir.Right, 0.10f, null, &dockMainID);
426     dockIDHistory = igDockBuilderSplitNode(dockIDToolSettings, ImGuiDir.Down, 0.50f, null, &dockIDToolSettings);
427     dockIDParams = igDockBuilderSplitNode(dockMainID, ImGuiDir.Left, 0.15f, null, &dockMainID);
428     dockIDLoggerAndTextureSlots = igDockBuilderSplitNode(dockMainID, ImGuiDir.Down, 0.15f, null, &dockMainID);
429 
430     igDockBuilderDockWindow("###Nodes", dockIDNodes);
431     igDockBuilderDockWindow("###Inspector", dockIDInspector);
432     igDockBuilderDockWindow("###Tool Settings", dockIDToolSettings);
433     igDockBuilderDockWindow("###History", dockIDHistory);
434     igDockBuilderDockWindow("###Tracking", dockIDHistory);
435     igDockBuilderDockWindow("###Parameters", dockIDParams);
436     igDockBuilderDockWindow("###Texture Slots", dockIDLoggerAndTextureSlots);
437     igDockBuilderDockWindow("###Logger", dockIDLoggerAndTextureSlots);
438 
439     igDockBuilderFinish(viewportDock);
440 }
441 
442 /**
443     Begins the Inochi Creator rendering loop
444 */
445 void incBeginLoop() {
446     SDL_Event event;
447 
448     while(SDL_PollEvent(&event)) {
449         switch(event.type) {
450             case SDL_QUIT:
451                 incExit();
452                 break;
453 
454             case SDL_DROPFILE:
455                 files ~= cast(string)event.drop.file.fromStringz;
456                 SDL_RaiseWindow(window);
457                 break;
458             
459             default: 
460                 ImGui_ImplSDL2_ProcessEvent(&event);
461                 if (
462                     event.type == SDL_WINDOWEVENT && 
463                     event.window.event == SDL_WINDOWEVENT_CLOSE && 
464                     event.window.windowID == SDL_GetWindowID(window)
465                 ) incExit();
466                 break;
467         }
468     }
469 
470     incTaskUpdate();
471 
472     // Begin loop post-event
473     incBeginLoopNoEv();
474 }
475 
476 /**
477     Ends the Inochi Creator rendering loop
478 */
479 void incEndLoop() {
480 
481     // Rendering
482     igRender();
483     glViewport(0, 0, cast(int)io.DisplaySize.x, cast(int)io.DisplaySize.y);
484     glClearColor(0.5, 0.5, 0.5, 1);
485     glClear(GL_COLOR_BUFFER_BIT);
486     ImGuiOpenGLBackend.render_draw_data(igGetDrawData());
487 
488     if (io.ConfigFlags & ImGuiConfigFlags.ViewportsEnable) {
489         SDL_Window* currentWindow = SDL_GL_GetCurrentWindow();
490         SDL_GLContext currentCtx = SDL_GL_GetCurrentContext();
491         igUpdatePlatformWindows();
492         igRenderPlatformWindowsDefault();
493         SDL_GL_MakeCurrent(currentWindow, currentCtx);
494     }
495 
496     SDL_GL_SwapWindow(window);
497 }
498 
499 /**
500     Prints ImGui debug info
501 */
502 void incDebugImGuiState(string msg, int indent = 0) {
503     debug(imgui) {
504         static int currentIndent = 0;
505 
506         string flag = "  ";
507         if (indent > 0) {
508             currentIndent += indent;
509             flag = ">>";
510         } else if (indent < 0) {
511             flag = "<<";
512         }
513 
514         //auto g = igGetCurrentContext();
515         auto win = igGetCurrentWindow();
516         writefln(
517             "%s%s%s [%s]", ' '.repeat(currentIndent * 2), flag, msg,
518             to!string(win.Name)
519         );
520 
521         if (indent < 0) {
522             currentIndent += indent;
523             if (currentIndent < 0) {
524                 debug writeln("ERROR: dedented too far!");
525                 currentIndent = 0;
526             }
527         }
528     }
529 }
530 
531 /**
532     Gets whether Inochi Creator has requested the app to close
533 */
534 bool incIsCloseRequested() {
535     return done;
536 }
537 
538 /**
539     Exit Inochi Creator
540 */
541 void incExit() {
542     done = true;
543 
544     int w, h;
545     SDL_WindowFlags flags;
546     flags = SDL_GetWindowFlags(window);
547     SDL_GetWindowSize(window, &w, &h);
548     incSettingsSet("WinW", w);
549     incSettingsSet("WinH", h);
550     incSettingsSet!bool("WinMax", (flags & SDL_WINDOW_MAXIMIZED) > 0);
551 }
552 
553 /**
554     Main font
555 */
556 ImFont* incMainFont() {
557     return mainFont;
558 }
559 
560 /**
561     Bigger sized font
562 */
563 ImFont* incBiggerFont() {
564     return biggerFont;
565 }
566 
567 /**
568     Bigger sized font
569 */
570 ImFont* incIconFont() {
571     return iconFont;
572 }
573 
574 
575 version (InBranding) {
576     /**
577         Gets the Inochi2D Logo
578     */
579     GLuint incGetLogo() {
580         return inLogo.getTextureId;
581     }
582 }
583 
584 void incHandleShortcuts() {
585     auto io = igGetIO();
586     
587     if (io.KeyCtrl && io.KeyShift && igIsKeyPressed(igGetKeyIndex(ImGuiKey.Z), true)) {
588         incActionRedo();
589     } else if (io.KeyCtrl && igIsKeyPressed(igGetKeyIndex(ImGuiKey.Z), true)) {
590         incActionUndo();
591     }
592 }
593 
594 
595 debug {
596     extern(C)
597     void incDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const(char)* message, void* userParam) nothrow {
598         import core.stdc.stdio : fprintf, stderr;
599         if (type == 0x8251) return;
600 
601         // HACK: I have no clue what causes this error
602         // but everything seems to work nontheless
603         // I'll just quietly ignore it.
604         if (type == 0x824c) return; 
605 
606         fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
607            ( type == GL_DEBUG_TYPE_ERROR ? cast(char*)"** GL ERROR **" : cast(char*)"" ),
608             type, severity, message );
609 
610     }
611 }