package imgui.app; import imgui.ImGui; import imgui.flag.ImGuiConfigFlags; import imgui.gl3.ImGuiImplGl3; import imgui.glfw.ImGuiImplGlfw; import org.lwjgl.glfw.Callbacks; import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFWErrorCallback; import org.lwjgl.glfw.GLFWVidMode; import org.lwjgl.glfw.GLFWWindowSizeCallback; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL32; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; import java.nio.IntBuffer; import java.util.Objects; /** * Low-level abstraction, which creates application window and starts the main loop. * It's recommended to use {@link Application}, but this class could be extended directly as well. * When extended, life-cycle methods should be called manually. */ public abstract class Window { private final ImGuiImplGlfw imGuiGlfw = new ImGuiImplGlfw(); private final ImGuiImplGl3 imGuiGl3 = new ImGuiImplGl3(); private String glslVersion = null; /** * Pointer to the native GLFW window. */ protected long handle; /** * Background color of the window. */ protected final Color colorBg = new Color(.5f, .5f, .5f, 1); /** * Method to initialize application. * * @param config configuration object with basic window information */ protected void init(final Configuration config) { initWindow(config); initImGui(config); imGuiGlfw.init(handle, true); imGuiGl3.init(glslVersion); } /** * Method to dispose all used application resources and destroy its window. */ protected void dispose() { imGuiGl3.dispose(); imGuiGlfw.dispose(); disposeImGui(); disposeWindow(); } /** * Method to create and initialize GLFW window. * * @param config configuration object with basic window information */ protected void initWindow(final Configuration config) { GLFWErrorCallback.createPrint(System.err).set(); if (!GLFW.glfwInit()) { throw new IllegalStateException("Unable to initialize GLFW"); } decideGlGlslVersions(); GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE); handle = GLFW.glfwCreateWindow(config.getWidth(), config.getHeight(), config.getTitle(), MemoryUtil.NULL, MemoryUtil.NULL); if (handle == MemoryUtil.NULL) { throw new RuntimeException("Failed to create the GLFW window"); } try (MemoryStack stack = MemoryStack.stackPush()) { final IntBuffer pWidth = stack.mallocInt(1); // int* final IntBuffer pHeight = stack.mallocInt(1); // int* GLFW.glfwGetWindowSize(handle, pWidth, pHeight); final GLFWVidMode vidmode = Objects.requireNonNull(GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor())); GLFW.glfwSetWindowPos(handle, (vidmode.width() - pWidth.get(0)) / 2, (vidmode.height() - pHeight.get(0)) / 2); } GLFW.glfwMakeContextCurrent(handle); GL.createCapabilities(); GLFW.glfwSwapInterval(GLFW.GLFW_TRUE); if (config.isFullScreen()) { GLFW.glfwMaximizeWindow(handle); } else { GLFW.glfwShowWindow(handle); } clearBuffer(); renderBuffer(); GLFW.glfwSetWindowSizeCallback(handle, new GLFWWindowSizeCallback() { @Override public void invoke(final long window, final int width, final int height) { runFrame(); } }); } private void decideGlGlslVersions() { final boolean isMac = System.getProperty("os.name").toLowerCase().contains("mac"); if (isMac) { glslVersion = "#version 150"; GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3); GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 2); GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE); // 3.2+ only GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GLFW.GLFW_TRUE); // Required on Mac } else { glslVersion = "#version 130"; GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3); GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 0); } } /** * Method to initialize Dear ImGui context. Could be overridden to do custom Dear ImGui setup before application start. * * @param config configuration object with basic window information */ protected void initImGui(final Configuration config) { ImGui.createContext(); } /** * Method called every frame, before calling {@link #process()} method. */ protected void preProcess() { } /** * Method called every frame, after calling {@link #process()} method. */ protected void postProcess() { } /** * Main application loop. */ protected void run() { while (!GLFW.glfwWindowShouldClose(handle)) { runFrame(); } } /** * Method used to run the next frame. */ protected void runFrame() { startFrame(); preProcess(); process(); postProcess(); endFrame(); } /** * Method to be overridden by user to provide main application logic. */ public abstract void process(); /** * Method used to clear the OpenGL buffer. */ private void clearBuffer() { GL32.glClearColor(colorBg.getRed(), colorBg.getGreen(), colorBg.getBlue(), colorBg.getAlpha()); GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT); } /** * Method called at the beginning of the main cycle. * It clears OpenGL buffer and starts an ImGui frame. */ protected void startFrame() { clearBuffer(); imGuiGlfw.newFrame(); ImGui.newFrame(); } /** * Method called in the end of the main cycle. * It renders ImGui and swaps GLFW buffers to show an updated frame. */ protected void endFrame() { ImGui.render(); imGuiGl3.renderDrawData(ImGui.getDrawData()); if (ImGui.getIO().hasConfigFlags(ImGuiConfigFlags.ViewportsEnable)) { final long backupWindowPtr = GLFW.glfwGetCurrentContext(); ImGui.updatePlatformWindows(); ImGui.renderPlatformWindowsDefault(); GLFW.glfwMakeContextCurrent(backupWindowPtr); } renderBuffer(); } /** * Method to render the OpenGL buffer and poll window events. */ private void renderBuffer() { GLFW.glfwSwapBuffers(handle); GLFW.glfwPollEvents(); } /** * Method to destroy Dear ImGui context. */ protected void disposeImGui() { ImGui.destroyContext(); } /** * Method to destroy GLFW window. */ protected void disposeWindow() { Callbacks.glfwFreeCallbacks(handle); GLFW.glfwDestroyWindow(handle); GLFW.glfwTerminate(); Objects.requireNonNull(GLFW.glfwSetErrorCallback(null)).free(); } /** * @return pointer to the native GLFW window */ public final long getHandle() { return handle; } /** * @return {@link Color} instance, which represents background color for the window */ public final Color getColorBg() { return colorBg; } }