How Dear ImGui's Input System Works: Keyboard, Mouse, and Gamepad Handling

Dear ImGui receives all user input through the ImGuiIO structure, where backends queue events via Add* functions and ImGui::NewFrame() processes them into state that widgets query each frame.

The ocornut/imgui repository implements a minimal, platform-agnostic input system that unifies keyboard, mouse, and gamepad handling through a single event queue. This design allows the core library to remain OS-independent while backend implementations translate platform-specific windowing events into Dear ImGui's standard input format.

The ImGuiIO Structure: Central Input Hub

All input configuration and state lives in the ImGuiIO structure defined in imgui.h around line 2425. This structure serves as the bridge between your application and Dear ImGui's internal systems.

Key input-related members include:

  • MousePos, MouseDelta, MouseDown[5], MouseWheel – Mouse state updated each frame
  • KeysDown[] and KeysData[] – Keyboard key states indexed by ImGuiKey values
  • NavInputs[] – Gamepad and navigation controller analog values
  • KeyMap[] – Array mapping ImGuiKey_* identifiers to native key codes
  • WantCaptureMouse / WantCaptureKeyboard – Flags indicating whether Dear ImGui consumed the input

The backend populates these fields indirectly by calling event addition functions, while the UI queries them directly through public API functions like ImGui::IsKeyPressed() and ImGui::IsMouseClicked().

Input Event Architecture

Dear ImGui uses a queued event system rather than immediate state modification. This ensures thread safety and consistent input handling across different backends.

The Event Queue Flow

Input flows through three distinct stages:

  1. Platform Event Receipt – The OS delivers raw input (Windows messages, GLFW callbacks, SDL events)
  2. Backend Translation – Backend code calls ImGuiIO Add functions* to queue standardized events
  3. Frame Processing – ImGui::NewFrame() (implemented in imgui.cpp around line 4808) consumes the queue and updates the live state

The main Add functions* declared in imgui.h around line 2555 include:

  • AddMousePosEvent(float x, float y)
  • AddMouseButtonEvent(int button, bool down)
  • AddMouseWheelEvent(float wheel_x, float wheel_y)
  • AddKeyEvent(ImGuiKey key, bool down)
  • AddKeyAnalogEvent(ImGuiKey key, bool down, float v) – Used for gamepad triggers and sticks
  • AddInputCharacter(unsigned int c) – For text input

These implementations reside in imgui.cpp around line 4626, where they push events into an internal ring buffer.

Keyboard Input Handling

Keyboard handling relies on the ImGuiKey enum defined around line 3300 in imgui.h, which provides logical identifiers for every key (e.g., ImGuiKey_A, ImGuiKey_LeftArrow, ImGuiKey_LeftCtrl).

Key Mapping Configuration

Backends must populate the io.KeyMap[] array to translate native key codes into Dear ImGui's logical keys. For example, in imgui_impl_glfw.cpp:

ImGuiIO& io = ImGui::GetIO();
io.KeyMap[ImGuiKey_A] = GLFW_KEY_A;
io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT;

Key State Updates

When a key event occurs, the backend calls io.AddKeyEvent():

// In imgui_impl_glfw.cpp key callback
io.AddKeyEvent(ImGuiKey_Escape, (action == GLFW_PRESS));

After NewFrame() processes the queue, you can query key states:

if (ImGui::IsKeyDown(ImGuiKey_Escape))
    // Handle escape key held
if (ImGui::IsKeyPressed(ImGuiKey_Enter))
    // Handle enter key pressed (with repeat)

The KeysData[] array in imgui_internal.h (around line 2220) stores per-key timing information used by the repeat logic, controlled by io.KeyRepeatDelay and io.KeyRepeatRate.

Mouse Input Handling

Mouse state accumulates through discrete events that NewFrame() reconciles into continuous state.

Position and Delta

The AddMousePosEvent(float x, float y) function updates the internal queue. After processing, io.MousePos contains the latest coordinates, while io.MouseDelta contains the frame-to-frame movement delta.

Button States

Mouse buttons use zero-based indexing: 0 (left), 1 (right), 2 (middle), 3 (extra1), 4 (extra2). Backends call:

io.AddMouseButtonEvent(0, true);   // Left button pressed
io.AddMouseButtonEvent(0, false);  // Left button released

The resulting state resides in io.MouseDown[], where io.MouseDown[0] indicates whether the left button is currently held.

Scroll and Cursor

Wheel events feed into io.MouseWheel (vertical) and io.MouseWheelH (horizontal) via AddMouseWheelEvent. The io.MouseDrawCursor flag allows Dear ImGui to render its own cursor, while io.WantCaptureMouse tells the host application whether to hide the system cursor or pass events to underlying game logic.

Gamepad Input Handling

Gamepad support integrates with the navigation system through analog-aware key events and dedicated navigation inputs.

Gamepad Keys and Analog Input

Gamepad buttons and axes use the ImGuiKey_Gamepad_* enum values (defined around line 3325 in imgui.h), such as ImGuiKey_Gamepad_DpadLeft or ImGuiKey_Gamepad_FaceLeft.

For analog triggers and sticks, backends use AddKeyAnalogEvent:

// From imgui_impl_sdl2.cpp
io.AddKeyAnalogEvent(ImGuiKey_Gamepad_L2, (gamepad_axis_l2 > 0), gamepad_axis_l2);
io.AddKeyAnalogEvent(ImGuiKey_Gamepad_LStickLeft, (gamepad_axis_left_x < -0.1f), -gamepad_axis_left_x);

The io.NavInputs[] array (indexed by ImGuiNavInput_* enum values defined around line 3340) stores processed analog values for navigation-concerned inputs like ImGuiNavInput_DpadLeft or ImGuiNavInput_LStickLeft. The navigation system consumes these during NewFrame() to determine focus movement.

Enable gamepad navigation by setting:

io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;

Backend Integration Examples

Dear ImGui provides reference implementations in the backends/ directory demonstrating platform-specific integration.

GLFW Backend

imgui_impl_glfw.cpp (around line 150) demonstrates GLFW callback translation:

void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
{
    ImGuiIO& io = ImGui::GetIO();
    if (action == GLFW_PRESS)
        io.AddMouseButtonEvent(button, true);
    else if (action == GLFW_RELEASE)
        io.AddMouseButtonEvent(button, false);
}

SDL2 Backend

imgui_impl_sdl2.cpp (around line 380) handles SDL events including gamepad connections:

case SDL_MOUSEMOTION:
    io.AddMousePosEvent((float)event.motion.x, (float)event.motion.y);
    break;
case SDL_KEYDOWN:
    io.AddKeyEvent(ImGui_ImplSDL2_KeycodeToImGuiKey(event.key.keysym.sym), true);
    break;

Win32 Backend

imgui_impl_win32.cpp (around line 210) processes Windows messages (WM_KEYDOWN, WM_MOUSEMOVE) and manages IME (Input Method Editor) state for international text input.

Manual Input Implementation

If implementing a custom backend or bypassing existing ones, you can populate the input queue directly:

ImGuiIO& io = ImGui::GetIO();

// Poll your OS or input library
while (PollNativeEvents())
{
    // Mouse position
    if (event.type == MOUSE_MOVE)
        io.AddMousePosEvent(event.x, event.y);
    
    // Mouse buttons
    if (event.type == MOUSE_BUTTON)
        io.AddMouseButtonEvent(event.button, event.pressed);
    
    // Keyboard
    if (event.type == KEY_DOWN)
        io.AddKeyEvent(TranslateToImGuiKey(event.key), true);
    if (event.type == KEY_UP)
        io.AddKeyEvent(TranslateToImGuiKey(event.key), false);
    
    // Text characters
    if (event.type == TEXT_INPUT)
        io.AddInputCharacter(event.unicode);
}

ImGui::NewFrame();  // Processes all queued events
// ... Draw UI widgets ...
ImGui::Render();

Summary

  • Dear ImGui's input system centers on the ImGuiIO structure, which acts as a broker between platform-specific backends and the UI framework.
  • Event queuing via AddMousePosEvent, AddKeyEvent, and AddKeyAnalogEvent ensures thread-safe input collection before NewFrame() processing.
  • Backend responsibility involves translating OS windowing events (GLFW, SDL2, Win32) into Dear ImGui's logical key and button identifiers.
  • Unified handling treats keyboard, mouse, and gamepad input uniformly through the same ImGuiKey enum and navigation system.
  • Query API functions like IsKeyPressed() and IsMouseClicked() read the processed state after NewFrame() updates the internal structures.

Frequently Asked Questions

How do I check if Dear ImGui wants to capture mouse input?

Check the io.WantCaptureMouse boolean after calling ImGui::NewFrame(). If this value is true, Dear ImGui is interacting with a widget (hovering, clicking, or dragging), and your underlying application should not process the mouse input. Similarly, io.WantCaptureKeyboard indicates when text input or navigation is active.

Can I use Dear ImGui with a gamepad only, without a mouse?

Yes. Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad before NewFrame(). Your backend must populate gamepad events using AddKeyEvent and AddKeyAnalogEvent with ImGuiKey_Gamepad_* values. The navigation system will automatically map the D-pad and left stick to move focus between widgets, and face buttons to activate them.

What is the difference between AddKeyEvent and the old KeysDown array?

Modern Dear ImGui versions use AddKeyEvent() to queue changes, which NewFrame() processes into the internal KeysData[] structure (defined in imgui_internal.h). While io.KeysDown[] still exists for legacy compatibility, the event-based API provides better support for key repeat handling, analog values, and thread safety. Backends migrating from direct array manipulation should switch to the Add* functions.

How do I handle high-DPI mouse coordinates in Dear ImGui?

Always pass physical pixel coordinates (not logical display coordinates) to AddMousePosEvent. If your OS or windowing library scales coordinates for high-DPI displays, divide by the scale factor before calling Dear ImGui functions, or set io.DisplayFramebufferScale appropriately so that MousePos values match your actual render target resolution.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →