External Publication
Visit Post

Use ImGui in version 3.10.0

jMonkeyEngine Hub June 13, 2026
Source

Vlog 03: Integrating Dear ImGui (1.92.0+) with latest jME3: Initial Findings, Traps, and Architecture Choices General Discussion

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 …

Reference content

Added the controller section

package com.jmeimgui;

import com.jme3.system.JmeContext;
import com.jme3.system.JmeSystem;
import com.jme3.system.Platform;
import com.jme3.system.lwjgl.LwjglWindow;
import com.jme3.input.InputManager;
import com.jme3.input.RawInputListener;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.JoyAxisEvent;
import imgui.ImDrawData;
import imgui.ImGui;
import imgui.ImGuiIO;
import imgui.flag.ImGuiConfigFlags;
import imgui.flag.ImGuiKey;
import imgui.gl3.ImGuiImplGl3;
import imgui.type.ImInt;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import static org.lwjgl.opengl.GL11.GL_LINEAR;
import static org.lwjgl.opengl.GL11.GL_RGBA;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_BINDING_2D;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL11.GL_VERSION;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL11.glGenTextures;
import static org.lwjgl.opengl.GL11.glGetInteger;
import static org.lwjgl.opengl.GL11.glGetString;
import static org.lwjgl.opengl.GL11.glTexImage2D;
import static org.lwjgl.opengl.GL11.glTexParameteri;
import static org.lwjgl.opengl.GL11C.GL_TEXTURE_MIN_FILTER;

/**
 * JME3与ImGui集成类 - 已适配 JME 3.10.0-alpha5 及 LWJGL 3.4.1 SDL3 后端
 * @author Icyboxs, Gemini
 */
public class JmeImGui {

    private final ImGuiImplGl3 imGuiGl3 = new ImGuiImplGl3();
    private long windowHandle;
    private boolean initialized = false;

    // 【关键修改点 1】:新增成员变量,用来就地缓存初始化时喂进来的上下文句柄
    private JmeContext cachedContext;

    private InputManager inputManager;
    private final List<KeyEventEntry> keyEventQueue = new ArrayList<>();
    private final List<MouseButtonEntry> mouseButtonQueue = new ArrayList<>();
    private float mousePosX = -1f;
    private float mousePosY = -1f;
    private float mouseWheelH = 0f;
    private float mouseWheelV = 0f;
    private boolean ctrlDown = false;
    private boolean shiftDown = false;
    private boolean altDown = false;
    private boolean superDown = false;
    private boolean windowFocused = true;

    private static class KeyEventEntry {
        final int keyCode;
        final char keyChar;
        final boolean pressed;
        final boolean repeating;
        KeyEventEntry(int keyCode, char keyChar, boolean pressed, boolean repeating) {
            this.keyCode = keyCode;
            this.keyChar = keyChar;
            this.pressed = pressed;
            this.repeating = repeating;
        }
    }

    private static class MouseButtonEntry {
        final int button;
        final boolean pressed;
        MouseButtonEntry(int button, boolean pressed) {
            this.button = button;
            this.pressed = pressed;
        }
    }

    /**
     * 初始化ImGui - 适配1.92.0+ 与 SDL 窗口上下文(无InputManager,兼容旧代码)
     */
    public void init(JmeContext context, Runnable beforeInit) {
        init(context, null, beforeInit);
    }

    public void init(JmeContext context) {
        init(context, null, null);
    }

    public void init(JmeContext context, InputManager inputManager) {
        init(context, inputManager, null);
    }

    /**
     * 初始化ImGui - 完整版,接收InputManager以转发输入事件
     */
    public void init(JmeContext context, InputManager inputManager, Runnable beforeInit) {
        this.cachedContext = context;
        this.windowHandle = ((LwjglWindow) context).getWindowHandle();
        this.inputManager = inputManager;

        ImGui.createContext();

        ImGuiIO io = ImGui.getIO();
        io.setIniFilename(null);

        io.addConfigFlags(ImGuiConfigFlags.DockingEnable);

        if (beforeInit != null) {
            beforeInit.run();
        }

        try {
            io.setBackendPlatformName("imgui_impl_sdl3");
            io.setBackendRendererName("imgui_impl_opengl3");
        } catch (NoSuchMethodError e) {
        }

        if (inputManager != null) {
            setupInputListener();
            System.out.println("[JmeImGui] 输入监听器已注册,ImGui可正常接收键鼠事件");
        } else {
            System.out.println("[JmeImGui] 警告:未提供InputManager,ImGui将无法响应键鼠输入");
        }

        imGuiGl3.init(decideGlslVersion());
        imGuiGl3.newFrame();

        initialized = true;
    }

    public void startFrame() {
        if (!initialized) return;

        imgui.ImGuiIO io = ImGui.getIO();

        try {
            if (cachedContext != null && cachedContext.getSettings() != null) {
                com.jme3.system.AppSettings settings = cachedContext.getSettings();
                io.setDisplaySize((float) settings.getWidth(), (float) settings.getHeight());

                if (cachedContext.getTimer() != null) {
                    float tpf = cachedContext.getTimer().getTimePerFrame();
                    io.setDeltaTime(tpf > 0 ? tpf : 1.0f / 60.0f);
                } else {
                    io.setDeltaTime(1.0f / 60.0f);
                }
            } else {
                io.setDisplaySize(1920f, 1080f);
                io.setDeltaTime(1.0f / 60.0f);
            }
        } catch (Exception e) {
            io.setDisplaySize(1920f, 1080f);
            io.setDeltaTime(1.0f / 60.0f);
        }

        // 转发 JME3 输入事件到 ImGuiIO
        processInput();

        ImGui.newFrame();
    }

    /**
     * 结束帧并渲染
     */
    public void endFrame() {
        if (!initialized || ImGui.getCurrentContext() == null) {
            return;
        }

        try {
            ImGui.render(); //
            final ImDrawData drawData = ImGui.getDrawData(); //
            if (!isValidDrawData(drawData)) { //
                return;
            }

            // 渲染主视口
            imGuiGl3.renderDrawData(drawData); //

        } catch (Exception e) {
            System.err.println("ImGui渲染错误: " + e.getClass().getSimpleName());
            e.printStackTrace();
        }
    }

    /**
     * 验证绘制数据有效性
     */
    private boolean isValidDrawData(ImDrawData drawData) {
        if (drawData == null) { //
            return false;
        }

        if (!drawData.getValid()) { //
            return false;
        }

        try {
            float width = drawData.getDisplaySizeX(); //
            float height = drawData.getDisplaySizeY(); //
            return width > 0 && height > 0; //
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 刷新字体纹理
     */
    public void refreshFontTexture() {
        if (!initialized) return;

        ImInt fontW = new ImInt(); //
        ImInt fontH = new ImInt(); //
        ImGuiIO imGuiIO = ImGui.getIO(); //
        ByteBuffer fontData = imGuiIO.getFonts().getTexDataAsRGBA32(fontW, fontH); //

        int originalTexture = glGetInteger(GL_TEXTURE_BINDING_2D); //
        int fontTexture = glGenTextures(); //

        glBindTexture(GL_TEXTURE_2D, fontTexture); //
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fontW.get(), fontH.get(), 0, GL_RGBA, GL_UNSIGNED_BYTE, fontData); //

        imGuiIO.getFonts().setTexID(fontTexture); //
        glBindTexture(GL_TEXTURE_2D, originalTexture); //
    }

    /**
     * 释放资源
     */
    public void dispose() {
        if (!initialized) return;

        try {
            ImGui.destroyContext(); //
            initialized = false;
            System.out.println("ImGui资源已释放");
        } catch (Exception e) {
            System.err.println("ImGui资源释放错误: " + e.getMessage());
        }
    }

    private void setupInputListener() {
        if (inputManager == null) return;
        inputManager.addRawInputListener(new RawInputListener() {
            @Override public void beginInput() {}
            @Override public void endInput() {}

            @Override
            public void onKeyEvent(KeyInputEvent evt) {
                keyEventQueue.add(new KeyEventEntry(
                    evt.getKeyCode(), evt.getKeyChar(),
                    evt.isPressed(), evt.isRepeating()
                ));
            }

            @Override
            public void onMouseButtonEvent(MouseButtonEvent evt) {
                mousePosX = (float) evt.getX();
                mousePosY = (float) evt.getY();
                int btn = evt.getButtonIndex();
                if (btn < 3) {
                    mouseButtonQueue.add(new MouseButtonEntry(btn, evt.isPressed()));
                }
            }

            @Override
            public void onMouseMotionEvent(MouseMotionEvent evt) {
                mousePosX = (float) evt.getX();
                mousePosY = (float) evt.getY();
                if (evt.getDeltaWheel() != 0) {
                    mouseWheelV += evt.getDeltaWheel() / 120f;
                }
            }

            @Override public void onTouchEvent(TouchEvent evt) {}
            @Override public void onJoyButtonEvent(JoyButtonEvent evt) {}
            @Override public void onJoyAxisEvent(JoyAxisEvent evt) {}
        });
    }

    /**
     * 将JME3 RawInputListener捕获的事件批量转发到ImGuiIO
     * 必须在每帧 ImGui.newFrame() 前调用
     */
    private void processInput() {
        ImGuiIO io = ImGui.getIO();

        io.addFocusEvent(windowFocused);

        // 鼠标位置 (JME3 Y=0在底部 → ImGui Y=0在顶部,需翻转)
        if (mousePosX >= 0 && mousePosY >= 0) {
            io.addMousePosEvent(mousePosX, io.getDisplaySizeY() - mousePosY);
        }

        // 滚轮
        if (mouseWheelH != 0f || mouseWheelV != 0f) {
            io.addMouseWheelEvent(mouseWheelH, mouseWheelV);
            mouseWheelH = 0f;
            mouseWheelV = 0f;
        }

        // 鼠标按键
        for (int i = 0; i < mouseButtonQueue.size(); i++) {
            MouseButtonEntry e = mouseButtonQueue.get(i);
            io.addMouseButtonEvent(e.button, e.pressed);
        }
        mouseButtonQueue.clear();

        // 键盘事件
        for (int i = 0; i < keyEventQueue.size(); i++) {
            KeyEventEntry e = keyEventQueue.get(i);
            int imKey = mapJmeKeyToImGuiKey(e.keyCode);
            if (imKey >= 0) {
                io.addKeyEvent(imKey, e.pressed);

                // 跟踪修饰键状态
                if (e.keyCode == 42 || e.keyCode == 54) {
                    shiftDown = e.pressed;
                } else if (e.keyCode == 29 || e.keyCode == 157) {
                    ctrlDown = e.pressed;
                } else if (e.keyCode == 56 || e.keyCode == 184) {
                    altDown = e.pressed;
                } else if (e.keyCode == 219 || e.keyCode == 220) {
                    superDown = e.pressed;
                }
            }

            // 文本输入
            if (e.pressed && e.keyChar != 0 && e.keyChar != '\0') {
                io.addInputCharacter(e.keyChar);
            }
        }
        keyEventQueue.clear();

        // 同步修饰键状态
        io.setKeyCtrl(ctrlDown);
        io.setKeyShift(shiftDown);
        io.setKeyAlt(altDown);
        io.setKeySuper(superDown);
    }

    /**
     * JME3 KeyInput 键码 → ImGuiKey 键码映射
     */
    @SuppressWarnings("MagicNumber")
    private static int mapJmeKeyToImGuiKey(int jmeKey) {
        switch (jmeKey) {
            // 字母键
            case 30: return ImGuiKey.A;    case 48: return ImGuiKey.B;
            case 46: return ImGuiKey.C;    case 32: return ImGuiKey.D;
            case 18: return ImGuiKey.E;    case 33: return ImGuiKey.F;
            case 34: return ImGuiKey.G;    case 35: return ImGuiKey.H;
            case 23: return ImGuiKey.I;    case 36: return ImGuiKey.J;
            case 37: return ImGuiKey.K;    case 38: return ImGuiKey.L;
            case 50: return ImGuiKey.M;    case 49: return ImGuiKey.N;
            case 24: return ImGuiKey.O;    case 25: return ImGuiKey.P;
            case 16: return ImGuiKey.Q;    case 19: return ImGuiKey.R;
            case 31: return ImGuiKey.S;    case 20: return ImGuiKey.T;
            case 22: return ImGuiKey.U;    case 47: return ImGuiKey.V;
            case 17: return ImGuiKey.W;    case 45: return ImGuiKey.X;
            case 21: return ImGuiKey.Y;    case 44: return ImGuiKey.Z;

            // 数字键
            case 11: return ImGuiKey._0;   case 2:  return ImGuiKey._1;
            case 3:  return ImGuiKey._2;   case 4:  return ImGuiKey._3;
            case 5:  return ImGuiKey._4;   case 6:  return ImGuiKey._5;
            case 7:  return ImGuiKey._6;   case 8:  return ImGuiKey._7;
            case 9:  return ImGuiKey._8;   case 10: return ImGuiKey._9;

            // 功能键
            case 59: return ImGuiKey.F1;   case 60: return ImGuiKey.F2;
            case 61: return ImGuiKey.F3;   case 62: return ImGuiKey.F4;
            case 63: return ImGuiKey.F5;   case 64: return ImGuiKey.F6;
            case 65: return ImGuiKey.F7;   case 66: return ImGuiKey.F8;
            case 67: return ImGuiKey.F9;   case 68: return ImGuiKey.F10;
            case 87: return ImGuiKey.F11;  case 88: return ImGuiKey.F12;

            // 方向键
            case 203: return ImGuiKey.LeftArrow;
            case 205: return ImGuiKey.RightArrow;
            case 200: return ImGuiKey.UpArrow;
            case 208: return ImGuiKey.DownArrow;

            // 特殊键
            case 15:  return ImGuiKey.Tab;
            case 28:  return ImGuiKey.Enter;
            case 1:   return ImGuiKey.Escape;
            case 14:  return ImGuiKey.Backspace;
            case 211: return ImGuiKey.Delete;
            case 210: return ImGuiKey.Insert;
            case 199: return ImGuiKey.Home;
            case 207: return ImGuiKey.End;
            case 201: return ImGuiKey.PageUp;
            case 209: return ImGuiKey.PageDown;
            case 57:  return ImGuiKey.Space;
            case 183: return ImGuiKey.PrintScreen;
            case 197: return ImGuiKey.Pause;
            case 58:  return ImGuiKey.CapsLock;
            case 69:  return ImGuiKey.NumLock;
            case 70:  return ImGuiKey.ScrollLock;

            // 修饰键
            case 42:  return ImGuiKey.LeftShift;
            case 54:  return ImGuiKey.RightShift;
            case 29:  return ImGuiKey.LeftCtrl;
            case 157: return ImGuiKey.RightCtrl;
            case 56:  return ImGuiKey.LeftAlt;
            case 184: return ImGuiKey.RightAlt;
            case 219: return ImGuiKey.LeftSuper;
            case 220: return ImGuiKey.RightSuper;
            case 221: return ImGuiKey.Menu;

            // 符号键
            case 12:  return ImGuiKey.Minus;
            case 13:  return ImGuiKey.Equal;
            case 26:  return ImGuiKey.LeftBracket;
            case 27:  return ImGuiKey.RightBracket;
            case 43:  return ImGuiKey.Backslash;
            case 39:  return ImGuiKey.Semicolon;
            case 40:  return ImGuiKey.Apostrophe;
            case 51:  return ImGuiKey.Comma;
            case 52:  return ImGuiKey.Period;
            case 53:  return ImGuiKey.Slash;
            case 41:  return ImGuiKey.GraveAccent;

            // 小键盘
            case 156: return ImGuiKey.KeypadEnter;
            case 82:  return ImGuiKey.Keypad0;
            case 79:  return ImGuiKey.Keypad1;
            case 80:  return ImGuiKey.Keypad2;
            case 81:  return ImGuiKey.Keypad3;
            case 75:  return ImGuiKey.Keypad4;
            case 76:  return ImGuiKey.Keypad5;
            case 77:  return ImGuiKey.Keypad6;
            case 71:  return ImGuiKey.Keypad7;
            case 72:  return ImGuiKey.Keypad8;
            case 73:  return ImGuiKey.Keypad9;
            case 181: return ImGuiKey.KeypadDivide;
            case 55:  return ImGuiKey.KeypadMultiply;
            case 74:  return ImGuiKey.KeypadSubtract;
            case 78:  return ImGuiKey.KeypadAdd;
            case 83:  return ImGuiKey.KeypadDecimal;

            default:  return -1;
        }
    }

    /**
     * 判断GLSL版本
     */
    protected String decideGlslVersion() {
        Platform.Os os = JmeSystem.getPlatform().getOs(); //
        if (os == Platform.Os.Windows) { //
            return "#version 150"; //
        } else if (isOpenGLVersionSupported(3, 3)) { //
            return "#version 330"; //
        } else {
            return "#version 130"; //
        }
    }

    /**
     * 检查是否支持特定的OpenGL版本
     */
    public boolean isOpenGLVersionSupported(int major, int minor) {
        try {
            String version = getOpenGLVersion(); //
            if (version == null || version.equals("Unknown")) { //
                return false;
            }

            String[] parts = version.split("\\s+")[0].split("\\."); //
            if (parts.length >= 2) { //
                int actualMajor = Integer.parseInt(parts[0]); //
                int actualMinor = Integer.parseInt(parts[1]); //

                if (actualMajor > major) { //
                    return true;
                }
                if (actualMajor == major && actualMinor >= minor) { //
                    return true;
                }
            }
            return false; //
        } catch (Exception e) {
            return false;
        }
    }

    public String getOpenGLVersion() {
        try {
            String version = glGetString(GL_VERSION);
            return version != null ? version : "Unknown";
        } catch (Exception e) {
            return "Error: " + e.getMessage();
        }
    }

    public boolean isInitialized() {
        return initialized;
    }
}

Recently, 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

capdevon provided a comprehensive solution in the post.

Discussion in the ATmosphere

Loading comments...