{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreiea6s7nfh3dfbhfzrjncwqhqrizpplav6ovjxlq5366gk7fyt7uum",
    "uri": "at://did:plc:dxjzgxe7cvirxkwfjr2tjspt/app.bsky.feed.post/3mo5f5f7qwfj2"
  },
  "path": "/t/use-imgui-in-version-3-10-0/49605#post_7",
  "publishedAt": "2026-06-13T02:53:10.000Z",
  "site": "https://hub.jmonkeyengine.org",
  "tags": [
    "Vlog 03: Integrating Dear ImGui (1.92.0+) with latest jME3: Initial Findings, Traps, and Architecture Choices",
    "General Discussion",
    "capdevon",
    "@author",
    "@Override",
    "@SuppressWarnings"
  ],
  "textContent": "Vlog 03: Integrating Dear ImGui (1.92.0+) with latest jME3: Initial Findings, Traps, and Architecture Choices General Discussion\n\n> Hi everyone, I am currently running some integration tests between the latest versions of the imgui-java wrapper (specifically targeting Dear ImGui 1.92.0+) and jMonkeyEngine 3.10.0+. My goal is to build a robust, desktop-targeted UI integration layer. During my initial research and implementation phases, I encountered a few architectural shifts in both ecosystems that drastically change how we used to integrate ImGui in the past. I wanted to share my current findings with the community as a …\n\nReference content\n\nAdded the controller section\n\n\n    package com.jmeimgui;\n\n    import com.jme3.system.JmeContext;\n    import com.jme3.system.JmeSystem;\n    import com.jme3.system.Platform;\n    import com.jme3.system.lwjgl.LwjglWindow;\n    import com.jme3.input.InputManager;\n    import com.jme3.input.RawInputListener;\n    import com.jme3.input.event.KeyInputEvent;\n    import com.jme3.input.event.MouseButtonEvent;\n    import com.jme3.input.event.MouseMotionEvent;\n    import com.jme3.input.event.TouchEvent;\n    import com.jme3.input.event.JoyButtonEvent;\n    import com.jme3.input.event.JoyAxisEvent;\n    import imgui.ImDrawData;\n    import imgui.ImGui;\n    import imgui.ImGuiIO;\n    import imgui.flag.ImGuiConfigFlags;\n    import imgui.flag.ImGuiKey;\n    import imgui.gl3.ImGuiImplGl3;\n    import imgui.type.ImInt;\n    import java.nio.ByteBuffer;\n    import java.util.ArrayList;\n    import java.util.List;\n    import static org.lwjgl.opengl.GL11.GL_LINEAR;\n    import static org.lwjgl.opengl.GL11.GL_RGBA;\n    import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;\n    import static org.lwjgl.opengl.GL11.GL_TEXTURE_BINDING_2D;\n    import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER;\n    import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;\n    import static org.lwjgl.opengl.GL11.GL_VERSION;\n    import static org.lwjgl.opengl.GL11.glBindTexture;\n    import static org.lwjgl.opengl.GL11.glGenTextures;\n    import static org.lwjgl.opengl.GL11.glGetInteger;\n    import static org.lwjgl.opengl.GL11.glGetString;\n    import static org.lwjgl.opengl.GL11.glTexImage2D;\n    import static org.lwjgl.opengl.GL11.glTexParameteri;\n    import static org.lwjgl.opengl.GL11C.GL_TEXTURE_MIN_FILTER;\n\n    /**\n     * JME3与ImGui集成类 - 已适配 JME 3.10.0-alpha5 及 LWJGL 3.4.1 SDL3 后端\n     * @author Icyboxs, Gemini\n     */\n    public class JmeImGui {\n\n        private final ImGuiImplGl3 imGuiGl3 = new ImGuiImplGl3();\n        private long windowHandle;\n        private boolean initialized = false;\n\n        // 【关键修改点 1】:新增成员变量,用来就地缓存初始化时喂进来的上下文句柄\n        private JmeContext cachedContext;\n\n        private InputManager inputManager;\n        private final List<KeyEventEntry> keyEventQueue = new ArrayList<>();\n        private final List<MouseButtonEntry> mouseButtonQueue = new ArrayList<>();\n        private float mousePosX = -1f;\n        private float mousePosY = -1f;\n        private float mouseWheelH = 0f;\n        private float mouseWheelV = 0f;\n        private boolean ctrlDown = false;\n        private boolean shiftDown = false;\n        private boolean altDown = false;\n        private boolean superDown = false;\n        private boolean windowFocused = true;\n\n        private static class KeyEventEntry {\n            final int keyCode;\n            final char keyChar;\n            final boolean pressed;\n            final boolean repeating;\n            KeyEventEntry(int keyCode, char keyChar, boolean pressed, boolean repeating) {\n                this.keyCode = keyCode;\n                this.keyChar = keyChar;\n                this.pressed = pressed;\n                this.repeating = repeating;\n            }\n        }\n\n        private static class MouseButtonEntry {\n            final int button;\n            final boolean pressed;\n            MouseButtonEntry(int button, boolean pressed) {\n                this.button = button;\n                this.pressed = pressed;\n            }\n        }\n\n        /**\n         * 初始化ImGui - 适配1.92.0+ 与 SDL 窗口上下文(无InputManager,兼容旧代码)\n         */\n        public void init(JmeContext context, Runnable beforeInit) {\n            init(context, null, beforeInit);\n        }\n\n        public void init(JmeContext context) {\n            init(context, null, null);\n        }\n\n        public void init(JmeContext context, InputManager inputManager) {\n            init(context, inputManager, null);\n        }\n\n        /**\n         * 初始化ImGui - 完整版,接收InputManager以转发输入事件\n         */\n        public void init(JmeContext context, InputManager inputManager, Runnable beforeInit) {\n            this.cachedContext = context;\n            this.windowHandle = ((LwjglWindow) context).getWindowHandle();\n            this.inputManager = inputManager;\n\n            ImGui.createContext();\n\n            ImGuiIO io = ImGui.getIO();\n            io.setIniFilename(null);\n\n            io.addConfigFlags(ImGuiConfigFlags.DockingEnable);\n\n            if (beforeInit != null) {\n                beforeInit.run();\n            }\n\n            try {\n                io.setBackendPlatformName(\"imgui_impl_sdl3\");\n                io.setBackendRendererName(\"imgui_impl_opengl3\");\n            } catch (NoSuchMethodError e) {\n            }\n\n            if (inputManager != null) {\n                setupInputListener();\n                System.out.println(\"[JmeImGui] 输入监听器已注册,ImGui可正常接收键鼠事件\");\n            } else {\n                System.out.println(\"[JmeImGui] 警告:未提供InputManager,ImGui将无法响应键鼠输入\");\n            }\n\n            imGuiGl3.init(decideGlslVersion());\n            imGuiGl3.newFrame();\n\n            initialized = true;\n        }\n\n        public void startFrame() {\n            if (!initialized) return;\n\n            imgui.ImGuiIO io = ImGui.getIO();\n\n            try {\n                if (cachedContext != null && cachedContext.getSettings() != null) {\n                    com.jme3.system.AppSettings settings = cachedContext.getSettings();\n                    io.setDisplaySize((float) settings.getWidth(), (float) settings.getHeight());\n\n                    if (cachedContext.getTimer() != null) {\n                        float tpf = cachedContext.getTimer().getTimePerFrame();\n                        io.setDeltaTime(tpf > 0 ? tpf : 1.0f / 60.0f);\n                    } else {\n                        io.setDeltaTime(1.0f / 60.0f);\n                    }\n                } else {\n                    io.setDisplaySize(1920f, 1080f);\n                    io.setDeltaTime(1.0f / 60.0f);\n                }\n            } catch (Exception e) {\n                io.setDisplaySize(1920f, 1080f);\n                io.setDeltaTime(1.0f / 60.0f);\n            }\n\n            // 转发 JME3 输入事件到 ImGuiIO\n            processInput();\n\n            ImGui.newFrame();\n        }\n\n        /**\n         * 结束帧并渲染\n         */\n        public void endFrame() {\n            if (!initialized || ImGui.getCurrentContext() == null) {\n                return;\n            }\n\n            try {\n                ImGui.render(); //\n                final ImDrawData drawData = ImGui.getDrawData(); //\n                if (!isValidDrawData(drawData)) { //\n                    return;\n                }\n\n                // 渲染主视口\n                imGuiGl3.renderDrawData(drawData); //\n\n            } catch (Exception e) {\n                System.err.println(\"ImGui渲染错误: \" + e.getClass().getSimpleName());\n                e.printStackTrace();\n            }\n        }\n\n        /**\n         * 验证绘制数据有效性\n         */\n        private boolean isValidDrawData(ImDrawData drawData) {\n            if (drawData == null) { //\n                return false;\n            }\n\n            if (!drawData.getValid()) { //\n                return false;\n            }\n\n            try {\n                float width = drawData.getDisplaySizeX(); //\n                float height = drawData.getDisplaySizeY(); //\n                return width > 0 && height > 0; //\n            } catch (Exception e) {\n                return false;\n            }\n        }\n\n        /**\n         * 刷新字体纹理\n         */\n        public void refreshFontTexture() {\n            if (!initialized) return;\n\n            ImInt fontW = new ImInt(); //\n            ImInt fontH = new ImInt(); //\n            ImGuiIO imGuiIO = ImGui.getIO(); //\n            ByteBuffer fontData = imGuiIO.getFonts().getTexDataAsRGBA32(fontW, fontH); //\n\n            int originalTexture = glGetInteger(GL_TEXTURE_BINDING_2D); //\n            int fontTexture = glGenTextures(); //\n\n            glBindTexture(GL_TEXTURE_2D, fontTexture); //\n            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //\n            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //\n            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fontW.get(), fontH.get(), 0, GL_RGBA, GL_UNSIGNED_BYTE, fontData); //\n\n            imGuiIO.getFonts().setTexID(fontTexture); //\n            glBindTexture(GL_TEXTURE_2D, originalTexture); //\n        }\n\n        /**\n         * 释放资源\n         */\n        public void dispose() {\n            if (!initialized) return;\n\n            try {\n                ImGui.destroyContext(); //\n                initialized = false;\n                System.out.println(\"ImGui资源已释放\");\n            } catch (Exception e) {\n                System.err.println(\"ImGui资源释放错误: \" + e.getMessage());\n            }\n        }\n\n        private void setupInputListener() {\n            if (inputManager == null) return;\n            inputManager.addRawInputListener(new RawInputListener() {\n                @Override public void beginInput() {}\n                @Override public void endInput() {}\n\n                @Override\n                public void onKeyEvent(KeyInputEvent evt) {\n                    keyEventQueue.add(new KeyEventEntry(\n                        evt.getKeyCode(), evt.getKeyChar(),\n                        evt.isPressed(), evt.isRepeating()\n                    ));\n                }\n\n                @Override\n                public void onMouseButtonEvent(MouseButtonEvent evt) {\n                    mousePosX = (float) evt.getX();\n                    mousePosY = (float) evt.getY();\n                    int btn = evt.getButtonIndex();\n                    if (btn < 3) {\n                        mouseButtonQueue.add(new MouseButtonEntry(btn, evt.isPressed()));\n                    }\n                }\n\n                @Override\n                public void onMouseMotionEvent(MouseMotionEvent evt) {\n                    mousePosX = (float) evt.getX();\n                    mousePosY = (float) evt.getY();\n                    if (evt.getDeltaWheel() != 0) {\n                        mouseWheelV += evt.getDeltaWheel() / 120f;\n                    }\n                }\n\n                @Override public void onTouchEvent(TouchEvent evt) {}\n                @Override public void onJoyButtonEvent(JoyButtonEvent evt) {}\n                @Override public void onJoyAxisEvent(JoyAxisEvent evt) {}\n            });\n        }\n\n        /**\n         * 将JME3 RawInputListener捕获的事件批量转发到ImGuiIO\n         * 必须在每帧 ImGui.newFrame() 前调用\n         */\n        private void processInput() {\n            ImGuiIO io = ImGui.getIO();\n\n            io.addFocusEvent(windowFocused);\n\n            // 鼠标位置 (JME3 Y=0在底部 → ImGui Y=0在顶部,需翻转)\n            if (mousePosX >= 0 && mousePosY >= 0) {\n                io.addMousePosEvent(mousePosX, io.getDisplaySizeY() - mousePosY);\n            }\n\n            // 滚轮\n            if (mouseWheelH != 0f || mouseWheelV != 0f) {\n                io.addMouseWheelEvent(mouseWheelH, mouseWheelV);\n                mouseWheelH = 0f;\n                mouseWheelV = 0f;\n            }\n\n            // 鼠标按键\n            for (int i = 0; i < mouseButtonQueue.size(); i++) {\n                MouseButtonEntry e = mouseButtonQueue.get(i);\n                io.addMouseButtonEvent(e.button, e.pressed);\n            }\n            mouseButtonQueue.clear();\n\n            // 键盘事件\n            for (int i = 0; i < keyEventQueue.size(); i++) {\n                KeyEventEntry e = keyEventQueue.get(i);\n                int imKey = mapJmeKeyToImGuiKey(e.keyCode);\n                if (imKey >= 0) {\n                    io.addKeyEvent(imKey, e.pressed);\n\n                    // 跟踪修饰键状态\n                    if (e.keyCode == 42 || e.keyCode == 54) {\n                        shiftDown = e.pressed;\n                    } else if (e.keyCode == 29 || e.keyCode == 157) {\n                        ctrlDown = e.pressed;\n                    } else if (e.keyCode == 56 || e.keyCode == 184) {\n                        altDown = e.pressed;\n                    } else if (e.keyCode == 219 || e.keyCode == 220) {\n                        superDown = e.pressed;\n                    }\n                }\n\n                // 文本输入\n                if (e.pressed && e.keyChar != 0 && e.keyChar != '\\0') {\n                    io.addInputCharacter(e.keyChar);\n                }\n            }\n            keyEventQueue.clear();\n\n            // 同步修饰键状态\n            io.setKeyCtrl(ctrlDown);\n            io.setKeyShift(shiftDown);\n            io.setKeyAlt(altDown);\n            io.setKeySuper(superDown);\n        }\n\n        /**\n         * JME3 KeyInput 键码 → ImGuiKey 键码映射\n         */\n        @SuppressWarnings(\"MagicNumber\")\n        private static int mapJmeKeyToImGuiKey(int jmeKey) {\n            switch (jmeKey) {\n                // 字母键\n                case 30: return ImGuiKey.A;    case 48: return ImGuiKey.B;\n                case 46: return ImGuiKey.C;    case 32: return ImGuiKey.D;\n                case 18: return ImGuiKey.E;    case 33: return ImGuiKey.F;\n                case 34: return ImGuiKey.G;    case 35: return ImGuiKey.H;\n                case 23: return ImGuiKey.I;    case 36: return ImGuiKey.J;\n                case 37: return ImGuiKey.K;    case 38: return ImGuiKey.L;\n                case 50: return ImGuiKey.M;    case 49: return ImGuiKey.N;\n                case 24: return ImGuiKey.O;    case 25: return ImGuiKey.P;\n                case 16: return ImGuiKey.Q;    case 19: return ImGuiKey.R;\n                case 31: return ImGuiKey.S;    case 20: return ImGuiKey.T;\n                case 22: return ImGuiKey.U;    case 47: return ImGuiKey.V;\n                case 17: return ImGuiKey.W;    case 45: return ImGuiKey.X;\n                case 21: return ImGuiKey.Y;    case 44: return ImGuiKey.Z;\n\n                // 数字键\n                case 11: return ImGuiKey._0;   case 2:  return ImGuiKey._1;\n                case 3:  return ImGuiKey._2;   case 4:  return ImGuiKey._3;\n                case 5:  return ImGuiKey._4;   case 6:  return ImGuiKey._5;\n                case 7:  return ImGuiKey._6;   case 8:  return ImGuiKey._7;\n                case 9:  return ImGuiKey._8;   case 10: return ImGuiKey._9;\n\n                // 功能键\n                case 59: return ImGuiKey.F1;   case 60: return ImGuiKey.F2;\n                case 61: return ImGuiKey.F3;   case 62: return ImGuiKey.F4;\n                case 63: return ImGuiKey.F5;   case 64: return ImGuiKey.F6;\n                case 65: return ImGuiKey.F7;   case 66: return ImGuiKey.F8;\n                case 67: return ImGuiKey.F9;   case 68: return ImGuiKey.F10;\n                case 87: return ImGuiKey.F11;  case 88: return ImGuiKey.F12;\n\n                // 方向键\n                case 203: return ImGuiKey.LeftArrow;\n                case 205: return ImGuiKey.RightArrow;\n                case 200: return ImGuiKey.UpArrow;\n                case 208: return ImGuiKey.DownArrow;\n\n                // 特殊键\n                case 15:  return ImGuiKey.Tab;\n                case 28:  return ImGuiKey.Enter;\n                case 1:   return ImGuiKey.Escape;\n                case 14:  return ImGuiKey.Backspace;\n                case 211: return ImGuiKey.Delete;\n                case 210: return ImGuiKey.Insert;\n                case 199: return ImGuiKey.Home;\n                case 207: return ImGuiKey.End;\n                case 201: return ImGuiKey.PageUp;\n                case 209: return ImGuiKey.PageDown;\n                case 57:  return ImGuiKey.Space;\n                case 183: return ImGuiKey.PrintScreen;\n                case 197: return ImGuiKey.Pause;\n                case 58:  return ImGuiKey.CapsLock;\n                case 69:  return ImGuiKey.NumLock;\n                case 70:  return ImGuiKey.ScrollLock;\n\n                // 修饰键\n                case 42:  return ImGuiKey.LeftShift;\n                case 54:  return ImGuiKey.RightShift;\n                case 29:  return ImGuiKey.LeftCtrl;\n                case 157: return ImGuiKey.RightCtrl;\n                case 56:  return ImGuiKey.LeftAlt;\n                case 184: return ImGuiKey.RightAlt;\n                case 219: return ImGuiKey.LeftSuper;\n                case 220: return ImGuiKey.RightSuper;\n                case 221: return ImGuiKey.Menu;\n\n                // 符号键\n                case 12:  return ImGuiKey.Minus;\n                case 13:  return ImGuiKey.Equal;\n                case 26:  return ImGuiKey.LeftBracket;\n                case 27:  return ImGuiKey.RightBracket;\n                case 43:  return ImGuiKey.Backslash;\n                case 39:  return ImGuiKey.Semicolon;\n                case 40:  return ImGuiKey.Apostrophe;\n                case 51:  return ImGuiKey.Comma;\n                case 52:  return ImGuiKey.Period;\n                case 53:  return ImGuiKey.Slash;\n                case 41:  return ImGuiKey.GraveAccent;\n\n                // 小键盘\n                case 156: return ImGuiKey.KeypadEnter;\n                case 82:  return ImGuiKey.Keypad0;\n                case 79:  return ImGuiKey.Keypad1;\n                case 80:  return ImGuiKey.Keypad2;\n                case 81:  return ImGuiKey.Keypad3;\n                case 75:  return ImGuiKey.Keypad4;\n                case 76:  return ImGuiKey.Keypad5;\n                case 77:  return ImGuiKey.Keypad6;\n                case 71:  return ImGuiKey.Keypad7;\n                case 72:  return ImGuiKey.Keypad8;\n                case 73:  return ImGuiKey.Keypad9;\n                case 181: return ImGuiKey.KeypadDivide;\n                case 55:  return ImGuiKey.KeypadMultiply;\n                case 74:  return ImGuiKey.KeypadSubtract;\n                case 78:  return ImGuiKey.KeypadAdd;\n                case 83:  return ImGuiKey.KeypadDecimal;\n\n                default:  return -1;\n            }\n        }\n\n        /**\n         * 判断GLSL版本\n         */\n        protected String decideGlslVersion() {\n            Platform.Os os = JmeSystem.getPlatform().getOs(); //\n            if (os == Platform.Os.Windows) { //\n                return \"#version 150\"; //\n            } else if (isOpenGLVersionSupported(3, 3)) { //\n                return \"#version 330\"; //\n            } else {\n                return \"#version 130\"; //\n            }\n        }\n\n        /**\n         * 检查是否支持特定的OpenGL版本\n         */\n        public boolean isOpenGLVersionSupported(int major, int minor) {\n            try {\n                String version = getOpenGLVersion(); //\n                if (version == null || version.equals(\"Unknown\")) { //\n                    return false;\n                }\n\n                String[] parts = version.split(\"\\\\s+\")[0].split(\"\\\\.\"); //\n                if (parts.length >= 2) { //\n                    int actualMajor = Integer.parseInt(parts[0]); //\n                    int actualMinor = Integer.parseInt(parts[1]); //\n\n                    if (actualMajor > major) { //\n                        return true;\n                    }\n                    if (actualMajor == major && actualMinor >= minor) { //\n                        return true;\n                    }\n                }\n                return false; //\n            } catch (Exception e) {\n                return false;\n            }\n        }\n\n        public String getOpenGLVersion() {\n            try {\n                String version = glGetString(GL_VERSION);\n                return version != null ? version : \"Unknown\";\n            } catch (Exception e) {\n                return \"Error: \" + e.getMessage();\n            }\n        }\n\n        public boolean isInitialized() {\n            return initialized;\n        }\n    }\n\n\nRecently, I was playing with Vulkan and ignored the test for ImGui. When I saw the content in the post, I realized that I seemed to be missing the controller part\n\ncapdevon provided a comprehensive solution in the post.",
  "title": "Use ImGui in version 3.10.0"
}