{
"$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"
}