diff --git a/imgui-lwjgl3/src/main/java/imgui/glfw/ImGuiImplGlfw.java b/imgui-lwjgl3/src/main/java/imgui/glfw/ImGuiImplGlfw.java new file mode 100644 index 0000000..b8859f9 --- /dev/null +++ b/imgui-lwjgl3/src/main/java/imgui/glfw/ImGuiImplGlfw.java @@ -0,0 +1,414 @@ +package imgui.glfw; + +import static org.lwjgl.glfw.GLFW.GLFW_ARROW_CURSOR; +import static org.lwjgl.glfw.GLFW.GLFW_CURSOR; +import static org.lwjgl.glfw.GLFW.GLFW_CURSOR_DISABLED; +import static org.lwjgl.glfw.GLFW.GLFW_CURSOR_HIDDEN; +import static org.lwjgl.glfw.GLFW.GLFW_CURSOR_NORMAL; +import static org.lwjgl.glfw.GLFW.GLFW_FOCUSED; +import static org.lwjgl.glfw.GLFW.GLFW_HAND_CURSOR; +import static org.lwjgl.glfw.GLFW.GLFW_HRESIZE_CURSOR; +import static org.lwjgl.glfw.GLFW.GLFW_IBEAM_CURSOR; +import static org.lwjgl.glfw.GLFW.GLFW_JOYSTICK_1; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_A; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_BACKSPACE; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_C; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_DELETE; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_DOWN; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_END; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_ENTER; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_ESCAPE; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_HOME; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_INSERT; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_KP_ENTER; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_LEFT; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_LEFT_ALT; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_LEFT_CONTROL; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_LEFT_SHIFT; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_LEFT_SUPER; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_PAGE_DOWN; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_PAGE_UP; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_RIGHT; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_RIGHT_ALT; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_RIGHT_CONTROL; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_RIGHT_SHIFT; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_RIGHT_SUPER; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_SPACE; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_TAB; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_UP; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_V; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_X; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_Y; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_Z; +import static org.lwjgl.glfw.GLFW.GLFW_PRESS; +import static org.lwjgl.glfw.GLFW.GLFW_RELEASE; +import static org.lwjgl.glfw.GLFW.GLFW_VRESIZE_CURSOR; +import static org.lwjgl.glfw.GLFW.glfwCreateStandardCursor; +import static org.lwjgl.glfw.GLFW.glfwDestroyCursor; +import static org.lwjgl.glfw.GLFW.glfwGetClipboardString; +import static org.lwjgl.glfw.GLFW.glfwGetCursorPos; +import static org.lwjgl.glfw.GLFW.glfwGetFramebufferSize; +import static org.lwjgl.glfw.GLFW.glfwGetInputMode; +import static org.lwjgl.glfw.GLFW.glfwGetJoystickAxes; +import static org.lwjgl.glfw.GLFW.glfwGetJoystickButtons; +import static org.lwjgl.glfw.GLFW.glfwGetMouseButton; +import static org.lwjgl.glfw.GLFW.glfwGetTime; +import static org.lwjgl.glfw.GLFW.glfwGetWindowAttrib; +import static org.lwjgl.glfw.GLFW.glfwGetWindowSize; +import static org.lwjgl.glfw.GLFW.glfwSetCharCallback; +import static org.lwjgl.glfw.GLFW.glfwSetClipboardString; +import static org.lwjgl.glfw.GLFW.glfwSetCursor; +import static org.lwjgl.glfw.GLFW.glfwSetCursorPos; +import static org.lwjgl.glfw.GLFW.glfwSetErrorCallback; +import static org.lwjgl.glfw.GLFW.glfwSetInputMode; +import static org.lwjgl.glfw.GLFW.glfwSetKeyCallback; +import static org.lwjgl.glfw.GLFW.glfwSetMouseButtonCallback; +import static org.lwjgl.glfw.GLFW.glfwSetScrollCallback; + +import imgui.ImGui; +import imgui.ImGuiIO; +import imgui.ImVec2; +import imgui.callback.ImStrConsumer; +import imgui.callback.ImStrSupplier; +import imgui.flag.ImGuiBackendFlags; +import imgui.flag.ImGuiConfigFlags; +import imgui.flag.ImGuiKey; +import imgui.flag.ImGuiMouseButton; +import imgui.flag.ImGuiMouseCursor; +import imgui.flag.ImGuiNavInput; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import org.lwjgl.glfw.GLFWCharCallback; +import org.lwjgl.glfw.GLFWErrorCallback; +import org.lwjgl.glfw.GLFWKeyCallback; +import org.lwjgl.glfw.GLFWMouseButtonCallback; +import org.lwjgl.glfw.GLFWScrollCallback; + +/** + * This class is a straightforward port of the + * imgui_impl_glfw.cpp. + *
+ * It supports clipboard, gamepad, mouse and keyboard in the same way the original Dear ImGui code does. You can copy-paste this class in your codebase and + * modify the rendering routine in the way you'd like. + *
+ */ +public class ImGuiImplGlfw { + + // Id of the current GLFW window + private long windowId; + + // For application window properties + private final int[] winWidth = new int[1]; + private final int[] winHeight = new int[1]; + private final int[] fbWidth = new int[1]; + private final int[] fbHeight = new int[1]; + + // Mouse cursors provided by GLFW + private final long[] mouseCursors = new long[ImGuiMouseCursor.COUNT]; + + // For mouse tracking + private final boolean[] mouseJustPressed = new boolean[ImGuiMouseButton.COUNT]; + private final double[] cursorPosX = new double[1]; + private final double[] cursorPosY = new double[1]; + + // GLFW callbacks + private GLFWMouseButtonCallback previousMouseButtonCallback = null; + private GLFWScrollCallback previousScrollCallback = null; + private GLFWKeyCallback previousKeyCallback = null; + private GLFWCharCallback previousCharCallback = null; + + private boolean callbacksInstalled; + private double time = 0.0; + + /** + * Method to set the {@link GLFWMouseButtonCallback}. + */ + public void mouseButtonCallback(final long windowId, final int button, final int action, final int mods) { + if (previousMouseButtonCallback != null) { + previousMouseButtonCallback.invoke(windowId, button, action, mods); + } + + if (action == GLFW_PRESS && button >= 0 && button < mouseJustPressed.length) { + mouseJustPressed[button] = true; + } + } + + /** + * Method to set the {@link GLFWScrollCallback}. + */ + public void scrollCallback(final long windowId, final double xOffset, final double yOffset) { + if (previousScrollCallback != null) { + previousScrollCallback.invoke(windowId, xOffset, yOffset); + } + + final ImGuiIO io = ImGui.getIO(); + io.setMouseWheelH((float) xOffset); + io.setMouseWheel((float) yOffset); + } + + /** + * Method to set the {@link GLFWKeyCallback}. + */ + public void keyCallback(final long windowId, final int key, final int scancode, final int action, final int mods) { + if (previousKeyCallback != null) { + previousKeyCallback.invoke(windowId, key, scancode, action, mods); + } + + final ImGuiIO io = ImGui.getIO(); + if (action == GLFW_PRESS) { + io.setKeysDown(key, true); + } + if (action == GLFW_RELEASE) { + io.setKeysDown(key, false); + } + + io.setKeyCtrl(io.getKeysDown(GLFW_KEY_LEFT_CONTROL) || io.getKeysDown(GLFW_KEY_RIGHT_CONTROL)); + io.setKeyShift(io.getKeysDown(GLFW_KEY_LEFT_SHIFT) || io.getKeysDown(GLFW_KEY_RIGHT_SHIFT)); + io.setKeyAlt(io.getKeysDown(GLFW_KEY_LEFT_ALT) || io.getKeysDown(GLFW_KEY_RIGHT_ALT)); + io.setKeySuper(io.getKeysDown(GLFW_KEY_LEFT_SUPER) || io.getKeysDown(GLFW_KEY_RIGHT_SUPER)); + } + + /** + * Method to set the {@link GLFWCharCallback}. + */ + public void charCallback(final long windowId, final int c) { + if (previousCharCallback != null) { + previousCharCallback.invoke(windowId, c); + } + + final ImGuiIO io = ImGui.getIO(); + io.addInputCharacter(c); + } + + /** + * Method to do an initialization of the {@link ImGuiImplGlfw} state. It SHOULD be called before calling the {@link ImGuiImplGlfw#newFrame()} method. + *
+ * Method takes two arguments, which should be a valid GLFW window pointer and a boolean indicating whether or not to install callbacks. + */ + public boolean init(final long windowId, final boolean installCallbacks) { + this.windowId = windowId; + time = 0.0; + + final ImGuiIO io = ImGui.getIO(); + + io.setBackendFlags(ImGuiBackendFlags.HasMouseCursors | ImGuiBackendFlags.HasSetMousePos); + io.setBackendPlatformName("imgui_java_impl_glfw"); + + // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. + final int[] keyMap = new int[ImGuiKey.COUNT]; + keyMap[ImGuiKey.Tab] = GLFW_KEY_TAB; + keyMap[ImGuiKey.LeftArrow] = GLFW_KEY_LEFT; + keyMap[ImGuiKey.RightArrow] = GLFW_KEY_RIGHT; + keyMap[ImGuiKey.UpArrow] = GLFW_KEY_UP; + keyMap[ImGuiKey.DownArrow] = GLFW_KEY_DOWN; + keyMap[ImGuiKey.PageUp] = GLFW_KEY_PAGE_UP; + keyMap[ImGuiKey.PageDown] = GLFW_KEY_PAGE_DOWN; + keyMap[ImGuiKey.Home] = GLFW_KEY_HOME; + keyMap[ImGuiKey.End] = GLFW_KEY_END; + keyMap[ImGuiKey.Insert] = GLFW_KEY_INSERT; + keyMap[ImGuiKey.Delete] = GLFW_KEY_DELETE; + keyMap[ImGuiKey.Backspace] = GLFW_KEY_BACKSPACE; + keyMap[ImGuiKey.Space] = GLFW_KEY_SPACE; + keyMap[ImGuiKey.Enter] = GLFW_KEY_ENTER; + keyMap[ImGuiKey.Escape] = GLFW_KEY_ESCAPE; + keyMap[ImGuiKey.KeyPadEnter] = GLFW_KEY_KP_ENTER; + keyMap[ImGuiKey.A] = GLFW_KEY_A; + keyMap[ImGuiKey.C] = GLFW_KEY_C; + keyMap[ImGuiKey.V] = GLFW_KEY_V; + keyMap[ImGuiKey.X] = GLFW_KEY_X; + keyMap[ImGuiKey.Y] = GLFW_KEY_Y; + keyMap[ImGuiKey.Z] = GLFW_KEY_Z; + + io.setKeyMap(keyMap); + + io.setGetClipboardTextFn(new ImStrSupplier() { + @Override + public String get() { + final String clipboardString = glfwGetClipboardString(windowId); + return clipboardString != null ? clipboardString : ""; + } + }); + + io.setSetClipboardTextFn(new ImStrConsumer() { + @Override + public void accept(final String str) { + glfwSetClipboardString(windowId, str); + } + }); + + // Mouse cursors mapping. Disable errors whilst setting due to X11. + final GLFWErrorCallback prevErrorCallback = glfwSetErrorCallback(null); + mouseCursors[ImGuiMouseCursor.Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + mouseCursors[ImGuiMouseCursor.TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); + mouseCursors[ImGuiMouseCursor.ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + mouseCursors[ImGuiMouseCursor.ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); + mouseCursors[ImGuiMouseCursor.ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); + mouseCursors[ImGuiMouseCursor.ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + mouseCursors[ImGuiMouseCursor.ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + mouseCursors[ImGuiMouseCursor.Hand] = glfwCreateStandardCursor(GLFW_HAND_CURSOR); + mouseCursors[ImGuiMouseCursor.NotAllowed] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + glfwSetErrorCallback(prevErrorCallback); + + if (installCallbacks) { + callbacksInstalled = true; + previousMouseButtonCallback = glfwSetMouseButtonCallback(windowId, this::mouseButtonCallback); + previousScrollCallback = glfwSetScrollCallback(windowId, this::scrollCallback); + previousKeyCallback = glfwSetKeyCallback(windowId, this::keyCallback); + previousCharCallback = glfwSetCharCallback(windowId, this::charCallback); + } + + return true; + } + + /** + * Method to restore {@link org.lwjgl.glfw.GLFW} to it's state prior to calling method {@link ImGuiImplGlfw#init(long, boolean)}. + */ + public void dispose() { + if (callbacksInstalled) { + glfwSetMouseButtonCallback(windowId, previousMouseButtonCallback); + glfwSetScrollCallback(windowId, previousScrollCallback); + glfwSetKeyCallback(windowId, previousKeyCallback); + glfwSetCharCallback(windowId, previousCharCallback); + callbacksInstalled = false; + } + + for (int i = 0; i < ImGuiMouseCursor.COUNT; i++) { + glfwDestroyCursor(mouseCursors[i]); + } + } + + private void updateMousePosAndButtons(final float scaleX, final float scaleY) { + final ImGuiIO io = ImGui.getIO(); + for (int i = 0; i < ImGuiMouseButton.COUNT; i++) { + io.setMouseDown(i, mouseJustPressed[i] || glfwGetMouseButton(windowId, i) != 0); + mouseJustPressed[i] = false; + } + + final ImVec2 mousePosBackup = new ImVec2(); + io.getMousePos(mousePosBackup); + io.setMousePos(-Float.MAX_VALUE, -Float.MAX_VALUE); + + final boolean focused = glfwGetWindowAttrib(windowId, GLFW_FOCUSED) != 0; + if (focused) { + if (io.getWantSetMousePos()) { + glfwSetCursorPos(windowId, mousePosBackup.x, mousePosBackup.y); + } else { + glfwGetCursorPos(windowId, cursorPosX, cursorPosY); + io.setMousePos((float) cursorPosX[0] * scaleX, (float) cursorPosY[0] * scaleY); + } + } + } + + private void updateMouseCursor() { + final ImGuiIO io = ImGui.getIO(); + final boolean noCursorChange = (io.getConfigFlags() & ImGuiConfigFlags.NoMouseCursorChange) + == ImGuiConfigFlags.NoMouseCursorChange; + final boolean cursorDisabled = glfwGetInputMode(windowId, GLFW_CURSOR) == GLFW_CURSOR_DISABLED; + if (noCursorChange || cursorDisabled) { + return; + } + + final int cursor = ImGui.getMouseCursor(); + if (cursor == ImGuiMouseCursor.None || io.getMouseDrawCursor()) { + glfwSetInputMode(windowId, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + } else { + glfwSetCursor(windowId, mouseCursors[cursor] != 0 ? mouseCursors[cursor] + : mouseCursors[ImGuiMouseCursor.Arrow]); + glfwSetInputMode(windowId, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + } + + private void updateGamepads() { + final ImGuiIO io = ImGui.getIO(); + + final float[] navInputs = new float[ImGuiNavInput.COUNT]; + io.setNavInputs(navInputs); + + if ((io.getConfigFlags() & ImGuiConfigFlags.NavEnableGamepad) == 0) { + return; + } + + final ByteBuffer buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1); + final int buttonsCount = buttons.limit(); + + final FloatBuffer axis = glfwGetJoystickAxes(GLFW_JOYSTICK_1); + final int axisCount = axis.limit(); + + mapButton(ImGuiNavInput.Activate, 0, buttons, buttonsCount, io); // Cross / A + mapButton(ImGuiNavInput.Cancel, 1, buttons, buttonsCount, io); // Circle / B + mapButton(ImGuiNavInput.Menu, 2, buttons, buttonsCount, io); // Square / X + mapButton(ImGuiNavInput.Input, 3, buttons, buttonsCount, io); // Triangle / Y + mapButton(ImGuiNavInput.DpadLeft, 13, buttons, buttonsCount, io); // D-Pad Left + mapButton(ImGuiNavInput.DpadRight, 11, buttons, buttonsCount, io); // D-Pad Right + mapButton(ImGuiNavInput.DpadUp, 10, buttons, buttonsCount, io); // D-Pad Up + mapButton(ImGuiNavInput.DpadDown, 12, buttons, buttonsCount, io); // D-Pad Down + mapButton(ImGuiNavInput.FocusPrev, 4, buttons, buttonsCount, io); // L1 / LB + mapButton(ImGuiNavInput.FocusNext, 5, buttons, buttonsCount, io); // R1 / RB + mapButton(ImGuiNavInput.TweakSlow, 4, buttons, buttonsCount, io); // L1 / LB + mapButton(ImGuiNavInput.TweakFast, 5, buttons, buttonsCount, io); // R1 / RB + mapAnalog(ImGuiNavInput.LStickLeft, 0, -0.3f, -0.9f, axis, axisCount, io); + mapAnalog(ImGuiNavInput.LStickRight, 0, +0.3f, +0.9f, axis, axisCount, io); + mapAnalog(ImGuiNavInput.LStickUp, 1, +0.3f, +0.9f, axis, axisCount, io); + mapAnalog(ImGuiNavInput.LStickDown, 1, -0.3f, -0.9f, axis, axisCount, io); + + if (axisCount > 0 && buttonsCount > 0) { + io.setBackendFlags(io.getBackendFlags() | ImGuiBackendFlags.HasGamepad); + } else { + io.setBackendFlags(io.getBackendFlags() & ~ImGuiBackendFlags.HasGamepad); + } + } + + private void mapButton(final int navNo, final int buttonNo, final ByteBuffer buttons, final int buttonsCount, final ImGuiIO io) { + if (buttonsCount > buttonNo && buttons.get(buttonNo) == GLFW_PRESS) { + io.setNavInputs(navNo, 1.0f); + } + } + + private void mapAnalog( + final int navNo, + final int axisNo, + final float v0, + final float v1, + final FloatBuffer axis, + final int axisCount, + final ImGuiIO io + ) { + float v = axisCount > axisNo ? axis.get(axisNo) : v0; + v = (v - v0) / (v1 - v0); + if (v > 1.0f) { + v = 1.0f; + } + if (io.getNavInputs(navNo) < v) { + io.setNavInputs(navNo, v); + } + } + + /** + * Updates {@link ImGuiIO} and {@link org.lwjgl.glfw.GLFW} state. + */ + public void newFrame() { + final ImGuiIO io = ImGui.getIO(); + if (!io.getFonts().isBuilt()) { + throw new IllegalStateException( + "Font atlas not built! It is generally built by the renderer back-end. Missing call to renderer init() method? e.g. ImGuiImplGl3.init()."); + } + + glfwGetWindowSize(windowId, winWidth, winHeight); + glfwGetFramebufferSize(windowId, fbWidth, fbHeight); + + final float scaleX = (float) fbWidth[0] / winWidth[0]; + final float scaleY = (float) fbHeight[0] / winHeight[0]; + + io.setDisplaySize((float) winWidth[0], (float) winHeight[0]); + if (winWidth[0] > 0 && winHeight[0] > 0) { + io.setDisplayFramebufferScale(scaleX, scaleY); + } + + final double currentTime = glfwGetTime(); + io.setDeltaTime(time > 0.0 ? (float) (currentTime - time) : 1.0f / 60.0f); + + updateMousePosAndButtons(scaleX, scaleY); + updateMouseCursor(); + updateGamepads(); + } +} diff --git a/imgui-lwjgl3/src/test/java/ImGuiGlfwExample.java b/imgui-lwjgl3/src/test/java/ImGuiGlfwExample.java index 5e9c2b8..cffd80a 100644 --- a/imgui-lwjgl3/src/test/java/ImGuiGlfwExample.java +++ b/imgui-lwjgl3/src/test/java/ImGuiGlfwExample.java @@ -1,35 +1,56 @@ +import static org.lwjgl.glfw.GLFW.GLFW_CONTEXT_VERSION_MAJOR; +import static org.lwjgl.glfw.GLFW.GLFW_CONTEXT_VERSION_MINOR; +import static org.lwjgl.glfw.GLFW.GLFW_FALSE; +import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_CORE_PROFILE; +import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_FORWARD_COMPAT; +import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_PROFILE; +import static org.lwjgl.glfw.GLFW.GLFW_TRUE; +import static org.lwjgl.glfw.GLFW.GLFW_VISIBLE; +import static org.lwjgl.glfw.GLFW.glfwCreateWindow; +import static org.lwjgl.glfw.GLFW.glfwDefaultWindowHints; +import static org.lwjgl.glfw.GLFW.glfwGetPrimaryMonitor; +import static org.lwjgl.glfw.GLFW.glfwGetVideoMode; +import static org.lwjgl.glfw.GLFW.glfwGetWindowSize; +import static org.lwjgl.glfw.GLFW.glfwInit; +import static org.lwjgl.glfw.GLFW.glfwMakeContextCurrent; +import static org.lwjgl.glfw.GLFW.glfwPollEvents; +import static org.lwjgl.glfw.GLFW.glfwSetWindowPos; +import static org.lwjgl.glfw.GLFW.glfwShowWindow; +import static org.lwjgl.glfw.GLFW.glfwSwapBuffers; +import static org.lwjgl.glfw.GLFW.glfwSwapInterval; +import static org.lwjgl.glfw.GLFW.glfwWindowHint; +import static org.lwjgl.glfw.GLFW.glfwWindowShouldClose; +import static org.lwjgl.opengl.GL32.GL_COLOR_BUFFER_BIT; +import static org.lwjgl.opengl.GL32.GL_TRUE; +import static org.lwjgl.opengl.GL32.glClear; +import static org.lwjgl.opengl.GL32.glClearColor; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.system.MemoryUtil.NULL; + import imgui.ImFontAtlas; import imgui.ImFontConfig; import imgui.ImGui; import imgui.ImGuiFreeType; import imgui.ImGuiIO; -import imgui.callback.ImStrConsumer; -import imgui.callback.ImStrSupplier; import imgui.flag.ImGuiBackendFlags; import imgui.flag.ImGuiConfigFlags; -import imgui.flag.ImGuiKey; import imgui.flag.ImGuiMouseCursor; import imgui.gl3.ImGuiImplGl3; -import org.lwjgl.glfw.GLFWErrorCallback; -import org.lwjgl.glfw.GLFWVidMode; -import org.lwjgl.opengl.GL; -import org.lwjgl.system.MemoryStack; - +import imgui.glfw.ImGuiImplGlfw; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.nio.IntBuffer; import java.util.Objects; - -import static org.lwjgl.glfw.Callbacks.glfwFreeCallbacks; -import static org.lwjgl.glfw.GLFW.*; -import static org.lwjgl.opengl.GL32.*; -import static org.lwjgl.system.MemoryStack.stackPush; -import static org.lwjgl.system.MemoryUtil.NULL; +import org.lwjgl.glfw.GLFWErrorCallback; +import org.lwjgl.glfw.GLFWVidMode; +import org.lwjgl.opengl.GL; +import org.lwjgl.system.MemoryStack; @SuppressWarnings("MagicNumber") public final class ImGuiGlfwExample { + private long windowPtr; // pointer to the current GLFW window // For application window properties @@ -45,6 +66,9 @@ public final class ImGuiGlfwExample { // Mouse cursors provided by GLFW private final long[] mouseCursors = new long[ImGuiMouseCursor.COUNT]; + // LWJGJ3 window backend + private final ImGuiImplGlfw imGuiGlfw = new ImGuiImplGlfw(); + // LWJGL3 renderer (SHOULD be initialized) private final ImGuiImplGl3 imGuiGl3 = new ImGuiImplGl3(); private String glslVersion = null; // We can initialize our renderer with different versions of the GLSL @@ -53,17 +77,34 @@ public final class ImGuiGlfwExample { private final ExampleUi exampleUi = new ExampleUi(); public void run() throws Exception { - initGlfw(); - initImGui(); + // IMPORTANT!! + // This line is critical for Dear ImGui to work. + ImGui.createContext(); + + // Method initializes GLFW backend. + // This method SHOULD be called after you've setup GLFW. + // ImGui context should be created as well. + setupGlfw(); + imGuiGlfw.init(windowPtr, true); + + // Method initializes LWJGL3 renderer. + // This method SHOULD be called after you've initialized your ImGui configuration (fonts and so on). + // ImGui context should be created as well. + setupImGui(); + imGuiGl3.init(glslVersion); + exampleUi.init(); loop(); - destroyImGui(); - destroyGlfw(); + + // You should clean up after yourself in reverse order. + imGuiGl3.dispose(); + imGuiGlfw.dispose(); + ImGui.destroyContext(); } // Initialize GLFW + create an OpenGL context. // All code is mostly a copy-paste from the official LWJGL3 "Get Started": https://www.lwjgl.org/guide - private void initGlfw() { + private void setupGlfw() { // Setup an error callback. The default implementation // will print the error message in System.err. GLFWErrorCallback.createPrint(System.err).set(); @@ -129,11 +170,7 @@ public final class ImGuiGlfwExample { } // Initialize Dear ImGui. - private void initImGui() { - // IMPORTANT!! - // This line is critical for Dear ImGui to work. - ImGui.createContext(); - + private void setupImGui() { // ------------------------------------------------------------ // Initialize ImGuiIO config final ImGuiIO io = ImGui.getIO(); @@ -143,103 +180,6 @@ public final class ImGuiGlfwExample { io.setBackendFlags(ImGuiBackendFlags.HasMouseCursors); // Mouse cursors to display while resizing windows etc. io.setBackendPlatformName("imgui_java_impl_glfw"); - // ------------------------------------------------------------ - // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. - final int[] keyMap = new int[ImGuiKey.COUNT]; - keyMap[ImGuiKey.Tab] = GLFW_KEY_TAB; - keyMap[ImGuiKey.LeftArrow] = GLFW_KEY_LEFT; - keyMap[ImGuiKey.RightArrow] = GLFW_KEY_RIGHT; - keyMap[ImGuiKey.UpArrow] = GLFW_KEY_UP; - keyMap[ImGuiKey.DownArrow] = GLFW_KEY_DOWN; - keyMap[ImGuiKey.PageUp] = GLFW_KEY_PAGE_UP; - keyMap[ImGuiKey.PageDown] = GLFW_KEY_PAGE_DOWN; - keyMap[ImGuiKey.Home] = GLFW_KEY_HOME; - keyMap[ImGuiKey.End] = GLFW_KEY_END; - keyMap[ImGuiKey.Insert] = GLFW_KEY_INSERT; - keyMap[ImGuiKey.Delete] = GLFW_KEY_DELETE; - keyMap[ImGuiKey.Backspace] = GLFW_KEY_BACKSPACE; - keyMap[ImGuiKey.Space] = GLFW_KEY_SPACE; - keyMap[ImGuiKey.Enter] = GLFW_KEY_ENTER; - keyMap[ImGuiKey.Escape] = GLFW_KEY_ESCAPE; - keyMap[ImGuiKey.KeyPadEnter] = GLFW_KEY_KP_ENTER; - keyMap[ImGuiKey.A] = GLFW_KEY_A; - keyMap[ImGuiKey.C] = GLFW_KEY_C; - keyMap[ImGuiKey.V] = GLFW_KEY_V; - keyMap[ImGuiKey.X] = GLFW_KEY_X; - keyMap[ImGuiKey.Y] = GLFW_KEY_Y; - keyMap[ImGuiKey.Z] = GLFW_KEY_Z; - io.setKeyMap(keyMap); - - // ------------------------------------------------------------ - // Mouse cursors mapping - mouseCursors[ImGuiMouseCursor.Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); - mouseCursors[ImGuiMouseCursor.TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); - mouseCursors[ImGuiMouseCursor.ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); - mouseCursors[ImGuiMouseCursor.ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); - mouseCursors[ImGuiMouseCursor.ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); - mouseCursors[ImGuiMouseCursor.ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); - mouseCursors[ImGuiMouseCursor.ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); - mouseCursors[ImGuiMouseCursor.Hand] = glfwCreateStandardCursor(GLFW_HAND_CURSOR); - mouseCursors[ImGuiMouseCursor.NotAllowed] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); - - // ------------------------------------------------------------ - // GLFW callbacks to handle user input - - glfwSetKeyCallback(windowPtr, (w, key, scancode, action, mods) -> { - if (action == GLFW_PRESS) { - io.setKeysDown(key, true); - } else if (action == GLFW_RELEASE) { - io.setKeysDown(key, false); - } - - io.setKeyCtrl(io.getKeysDown(GLFW_KEY_LEFT_CONTROL) || io.getKeysDown(GLFW_KEY_RIGHT_CONTROL)); - io.setKeyShift(io.getKeysDown(GLFW_KEY_LEFT_SHIFT) || io.getKeysDown(GLFW_KEY_RIGHT_SHIFT)); - io.setKeyAlt(io.getKeysDown(GLFW_KEY_LEFT_ALT) || io.getKeysDown(GLFW_KEY_RIGHT_ALT)); - io.setKeySuper(io.getKeysDown(GLFW_KEY_LEFT_SUPER) || io.getKeysDown(GLFW_KEY_RIGHT_SUPER)); - }); - - glfwSetCharCallback(windowPtr, (w, c) -> { - if (c != GLFW_KEY_DELETE) { - io.addInputCharacter(c); - } - }); - - glfwSetMouseButtonCallback(windowPtr, (w, button, action, mods) -> { - io.setMouseDown(0, button == GLFW_MOUSE_BUTTON_1 && action != GLFW_RELEASE); - io.setMouseDown(1, button == GLFW_MOUSE_BUTTON_2 && action != GLFW_RELEASE); - io.setMouseDown(2, button == GLFW_MOUSE_BUTTON_3 && action != GLFW_RELEASE); - io.setMouseDown(3, button == GLFW_MOUSE_BUTTON_4 && action != GLFW_RELEASE); - io.setMouseDown(4, button == GLFW_MOUSE_BUTTON_5 && action != GLFW_RELEASE); - - if (!io.getWantCaptureMouse() && io.getMouseDown(1)) { - ImGui.setWindowFocus(null); - } - }); - - glfwSetScrollCallback(windowPtr, (w, xOffset, yOffset) -> { - io.setMouseWheelH(io.getMouseWheelH() + (float) xOffset); - io.setMouseWheel(io.getMouseWheel() + (float) yOffset); - }); - - io.setSetClipboardTextFn(new ImStrConsumer() { - @Override - public void accept(final String s) { - glfwSetClipboardString(windowPtr, s); - } - }); - - io.setGetClipboardTextFn(new ImStrSupplier() { - @Override - public String get() { - final String clipboardString = glfwGetClipboardString(windowPtr); - if (clipboardString != null) { - return clipboardString; - } else { - return ""; - } - } - }); - // ------------------------------------------------------------ // Fonts configuration // Read: https://raw.githubusercontent.com/ocornut/imgui/master/docs/FONTS.txt @@ -278,28 +218,18 @@ public final class ImGuiGlfwExample { // ------------------------------------------------------------ // Use freetype instead of stb_truetype to build a fonts texture ImGuiFreeType.buildFontAtlas(fontAtlas, ImGuiFreeType.RasterizerFlags.LightHinting); - - // Method initializes LWJGL3 renderer. - // This method SHOULD be called after you've initialized your ImGui configuration (fonts and so on). - // ImGui context should be created as well. - imGuiGl3.init(glslVersion); } // Main application loop private void loop() { - double time = 0; // to track our frame delta value - // Run the rendering loop until the user has attempted to close the window while (!glfwWindowShouldClose(windowPtr)) { - // Count frame delta value - final double currentTime = glfwGetTime(); - final double deltaTime = (time > 0) ? (currentTime - time) : 1f / 60f; - time = currentTime; - - startFrame((float) deltaTime); + startFrame(); // Any Dear ImGui code SHOULD go between ImGui.newFrame()/ImGui.render() methods + imGuiGlfw.newFrame(); ImGui.newFrame(); + exampleUi.render(); ImGui.render(); @@ -307,30 +237,10 @@ public final class ImGuiGlfwExample { } } - private void startFrame(final float deltaTime) { + private void startFrame() { // Set the clear color and clear the window glClearColor(exampleUi.backgroundColor[0], exampleUi.backgroundColor[1], exampleUi.backgroundColor[2], 0.0f); glClear(GL_COLOR_BUFFER_BIT); - - // Get window properties and mouse position - glfwGetWindowSize(windowPtr, winWidth, winHeight); - glfwGetFramebufferSize(windowPtr, fbWidth, fbHeight); - glfwGetCursorPos(windowPtr, mousePosX, mousePosY); - - final float scaleX = (float) fbWidth[0] / winWidth[0]; - final float scaleY = (float) fbHeight[0] / winHeight[0]; - - // We SHOULD call those methods to update Dear ImGui state for the current frame - final ImGuiIO io = ImGui.getIO(); - io.setDisplaySize(fbWidth[0], fbHeight[0]); - io.setDisplayFramebufferScale(scaleX, scaleY); - io.setMousePos((float) mousePosX[0] * scaleX, (float) mousePosY[0] * scaleY); - io.setDeltaTime(deltaTime); - - // Update the mouse cursor - final int imguiCursor = ImGui.getMouseCursor(); - glfwSetCursor(windowPtr, mouseCursors[imguiCursor]); - glfwSetInputMode(windowPtr, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } private void endFrame() { @@ -342,26 +252,9 @@ public final class ImGuiGlfwExample { glfwPollEvents(); } - // If you want to clean a room after yourself - do it by yourself - private void destroyImGui() { - imGuiGl3.dispose(); - ImGui.destroyContext(); - } - - private void destroyGlfw() { - for (long mouseCursor : mouseCursors) { - glfwDestroyCursor(mouseCursor); - } - - glfwFreeCallbacks(windowPtr); - glfwDestroyWindow(windowPtr); - glfwTerminate(); - Objects.requireNonNull(glfwSetErrorCallback(null)).free(); - } - private byte[] loadFromResources(final String fileName) { try (InputStream is = Objects.requireNonNull(getClass().getClassLoader().getResourceAsStream(fileName)); - ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { final byte[] data = new byte[16384];