Android: Move JNI keyboard/clipboard into backend, add display metrics (#3446)

Backend improvements (imgui_impl_android.h/.cpp):
- Moved JNI soft-keyboard and Unicode char polling from the example into
  the backend. The application no longer needs any JNI boilerplate.
- Added clipboard support via JNI to Android ClipboardManager. (#7259)
- Added display metrics (DPI, density, refresh rate, orientation) via JNI.
  Used by the example to set main_scale from actual device density instead
  of hardcoded 2.0f.
- BREAKING CHANGE: ImGui_ImplAndroid_Init() now takes additional
  asset_manager and native_activity parameters. See before/after in header.

Example (main.cpp):
- Removed ShowSoftKeyboardInput() and PollUnicodeChars() JNI helpers (~120 lines).
- Removed their forward declarations and per-frame calls.
- Updated Init() call to new signature.
- Use ImGui_ImplAndroid_GetDisplayMetrics() for main_scale.
- All other comments, EGL block, GetAssetData, APP_CMD cases unchanged.

MainActivity.kt: unchanged (same 3 JNI bridge methods, now called by backend).

docs/CHANGELOG.txt: Added entries for backend and example changes.
This commit is contained in:
Turtle Dove 2026-07-15 08:34:39 -05:00
parent 7038887c32
commit 372066e249
6 changed files with 420 additions and 202 deletions

View File

@ -1,17 +1,19 @@
// dear imgui: Platform Binding for Android native app
// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3)
// dear imgui: Platform Backend for Android native app
// This needs to be used along with a Renderer Backend (e.g. OpenGL3, Vulkan)
// Implemented features:
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy AKEYCODE_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen.
// [X] Platform: On-screen keyboard (soft input) — handled internally via JNI. No application code needed. (#3446)
// [X] Platform: Unicode character input — handled internally via JNI. No application code needed. (#3446)
// [X] Platform: Clipboard support (via JNI to Android ClipboardManager). (#7259)
// Missing features or Issues:
// [ ] Platform: Clipboard support.
// [ ] Platform: Gamepad support.
// [ ] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android.
// Important:
// - Consider using SDL or GLFW backend on Android, which will be more full-featured than this.
// - FIXME: On-screen keyboard currently needs to be enabled by the application (see examples/ and issue #3446)
// - FIXME: Unicode character inputs needs to be passed by Dear ImGui by the application (see examples/ and issue #3446)
// - This backend uses JNI internally to handle soft keyboard, Unicode character polling, and clipboard.
// The application does NOT need any JNI code — it just calls Init/NewFrame/Shutdown.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
@ -23,9 +25,11 @@
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2026-07-15: Inputs: clear mouse position on touch release (AMOTION_EVENT_ACTION_UP) to prevent items from staying in hovered state. (#6627, #9474)
// 2023-04-11: Inputs: calling new io.AddMouseSourceEvent() to discriminate Mouse from Touch events.
// 2022-09-26: Inputs: renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
// 2026-07-15: Android: Moved JNI soft-keyboard and Unicode char polling from the example into the backend. (#3446)
// Added clipboard support via JNI. (#7259)
// Added display metrics (DPI, density, refresh rate, orientation) via JNI.
// BREAKING CHANGE: ImGui_ImplAndroid_Init() now takes additional asset_manager and native_activity params.
// 2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).
// 2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
// 2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
// 2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
@ -35,15 +39,37 @@
#ifndef IMGUI_DISABLE
#include "imgui_impl_android.h"
#include <time.h>
#include <string.h>
#include <android/native_window.h>
#include <android/input.h>
#include <android/keycodes.h>
#include <android/log.h>
#include <jni.h>
// Android data
static double g_Time = 0.0;
static ANativeWindow* g_Window;
static char g_LogTag[] = "ImGuiExample";
static ANativeWindow* g_Window = nullptr;
static AAssetManager* g_AssetManager = nullptr;
static char g_LogTag[] = "ImGuiBackend";
// JNI state (for soft keyboard, Unicode polling, clipboard, display metrics)
static JavaVM* g_JavaVM = nullptr;
static jobject g_NativeActivity = nullptr; // Global ref
static bool g_HasJni = false;
// Clipboard state
static char* g_ClipboardText = nullptr;
// Display metrics
static ImGui_ImplAndroid_DisplayMetrics g_DisplayMetrics = {};
// Forward declarations of JNI helpers
static void ImGui_ImplAndroid_JniShowSoftKeyboard();
static void ImGui_ImplAndroid_JniHideSoftKeyboard();
static void ImGui_ImplAndroid_JniPollUnicodeChars();
static void ImGui_ImplAndroid_JniSetClipboardText(const char* text);
static const char* ImGui_ImplAndroid_JniGetClipboardText();
static ImGuiKey ImGui_ImplAndroid_KeyCodeToImGuiKey(int32_t key_code)
{
@ -232,8 +258,6 @@ int32_t ImGui_ImplAndroid_HandleInputEvent(const AInputEvent* input_event)
{
io.AddMousePosEvent(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index));
io.AddMouseButtonEvent(0, event_action == AMOTION_EVENT_ACTION_DOWN);
if (event_action == AMOTION_EVENT_ACTION_UP) // (#6627, #9474)
io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
}
break;
}
@ -265,48 +289,317 @@ int32_t ImGui_ImplAndroid_HandleInputEvent(const AInputEvent* input_event)
return 0;
}
bool ImGui_ImplAndroid_Init(ANativeWindow* window)
// --- JNI helpers ---
// These functions handle the soft keyboard, Unicode character polling, and clipboard
// entirely within the backend so the application code stays clean.
static JNIEnv* ImGui_ImplAndroid_GetEnv()
{
if (!g_JavaVM)
return nullptr;
JNIEnv* env = nullptr;
jint ret = g_JavaVM->GetEnv((void**)&env, JNI_VERSION_1_6);
if (ret == JNI_EDETACHED)
{
if (g_JavaVM->AttachCurrentThread(&env, nullptr) != JNI_OK)
return nullptr;
}
else if (ret != JNI_OK)
{
return nullptr;
}
return env;
}
static void ImGui_ImplAndroid_DetachEnv()
{
if (g_JavaVM)
g_JavaVM->DetachCurrentThread();
}
static void ImGui_ImplAndroid_JniShowSoftKeyboard()
{
if (!g_NativeActivity)
return;
JNIEnv* env = ImGui_ImplAndroid_GetEnv();
if (!env) return;
jclass cls = env->GetObjectClass(g_NativeActivity);
if (!cls) { ImGui_ImplAndroid_DetachEnv(); return; }
// Try the NativeActivity showSoftInput method via InputMethodManager
jmethodID method = env->GetMethodID(cls, "showSoftInput", "()V");
if (method)
env->CallVoidMethod(g_NativeActivity, method);
env->DeleteLocalRef(cls);
ImGui_ImplAndroid_DetachEnv();
}
static void ImGui_ImplAndroid_JniHideSoftKeyboard()
{
if (!g_NativeActivity)
return;
JNIEnv* env = ImGui_ImplAndroid_GetEnv();
if (!env) return;
jclass cls = env->GetObjectClass(g_NativeActivity);
if (!cls) { ImGui_ImplAndroid_DetachEnv(); return; }
jmethodID method = env->GetMethodID(cls, "hideSoftInput", "()V");
if (method)
env->CallVoidMethod(g_NativeActivity, method);
env->DeleteLocalRef(cls);
ImGui_ImplAndroid_DetachEnv();
}
static void ImGui_ImplAndroid_JniPollUnicodeChars()
{
if (!g_NativeActivity)
return;
JNIEnv* env = ImGui_ImplAndroid_GetEnv();
if (!env) return;
jclass cls = env->GetObjectClass(g_NativeActivity);
if (!cls) { ImGui_ImplAndroid_DetachEnv(); return; }
jmethodID method = env->GetMethodID(cls, "pollUnicodeChar", "()I");
if (!method) { env->DeleteLocalRef(cls); ImGui_ImplAndroid_DetachEnv(); return; }
ImGuiIO& io = ImGui::GetIO();
jint unicode_char;
while ((unicode_char = env->CallIntMethod(g_NativeActivity, method)) != 0)
io.AddInputCharacter(unicode_char);
env->DeleteLocalRef(cls);
ImGui_ImplAndroid_DetachEnv();
}
static void ImGui_ImplAndroid_JniSetClipboardText(const char* text)
{
if (!g_NativeActivity || !text)
return;
JNIEnv* env = ImGui_ImplAndroid_GetEnv();
if (!env) return;
// Get ClipboardManager and set text
jclass activity_cls = env->GetObjectClass(g_NativeActivity);
if (!activity_cls) { ImGui_ImplAndroid_DetachEnv(); return; }
jmethodID get_service = env->GetMethodID(activity_cls, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
if (!get_service) { env->DeleteLocalRef(activity_cls); ImGui_ImplAndroid_DetachEnv(); return; }
jstring service_name = env->NewStringUTF("clipboard");
jobject clipboard = env->CallObjectMethod(g_NativeActivity, get_service, service_name);
env->DeleteLocalRef(service_name);
if (clipboard)
{
jclass clipboard_cls = env->GetObjectClass(clipboard);
jmethodID set_text = env->GetMethodID(clipboard_cls, "setText", "(Ljava/lang/CharSequence;)V");
if (set_text)
{
jstring jtext = env->NewStringUTF(text);
env->CallVoidMethod(clipboard, set_text, jtext);
env->DeleteLocalRef(jtext);
}
env->DeleteLocalRef(clipboard_cls);
env->DeleteLocalRef(clipboard);
}
env->DeleteLocalRef(activity_cls);
ImGui_ImplAndroid_DetachEnv();
}
static const char* ImGui_ImplAndroid_JniGetClipboardText()
{
if (!g_NativeActivity)
return nullptr;
JNIEnv* env = ImGui_ImplAndroid_GetEnv();
if (!env) return nullptr;
jclass activity_cls = env->GetObjectClass(g_NativeActivity);
if (!activity_cls) { ImGui_ImplAndroid_DetachEnv(); return nullptr; }
jmethodID get_service = env->GetMethodID(activity_cls, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
if (!get_service) { env->DeleteLocalRef(activity_cls); ImGui_ImplAndroid_DetachEnv(); return nullptr; }
jstring service_name = env->NewStringUTF("clipboard");
jobject clipboard = env->CallObjectMethod(g_NativeActivity, get_service, service_name);
env->DeleteLocalRef(service_name);
if (g_ClipboardText) { IM_FREE(g_ClipboardText); g_ClipboardText = nullptr; }
if (clipboard)
{
jclass clipboard_cls = env->GetObjectClass(clipboard);
jmethodID get_text = env->GetMethodID(clipboard_cls, "getText", "()Ljava/lang/CharSequence;");
if (get_text)
{
jobject sequence = env->CallObjectMethod(clipboard, get_text);
if (sequence)
{
jstring text = (jstring)sequence;
const char* chars = env->GetStringUTFChars(text, nullptr);
if (chars)
{
size_t len = strlen(chars);
g_ClipboardText = (char*)IM_ALLOC(len + 1);
memcpy(g_ClipboardText, chars, len + 1);
env->ReleaseStringUTFChars(text, chars);
}
env->DeleteLocalRef(sequence);
}
}
env->DeleteLocalRef(clipboard_cls);
env->DeleteLocalRef(clipboard);
}
env->DeleteLocalRef(activity_cls);
ImGui_ImplAndroid_DetachEnv();
return g_ClipboardText;
}
void ImGui_ImplAndroid_ShowSoftKeyboard()
{
ImGui_ImplAndroid_JniShowSoftKeyboard();
}
void ImGui_ImplAndroid_HideSoftKeyboard()
{
ImGui_ImplAndroid_JniHideSoftKeyboard();
}
bool ImGui_ImplAndroid_Init(ANativeWindow* window, AAssetManager* asset_manager, jobject native_activity)
{
IMGUI_CHECKVERSION();
g_Window = window;
g_AssetManager = asset_manager;
g_Time = 0.0;
// Setup backend capabilities flags
ImGuiIO& io = ImGui::GetIO();
io.BackendPlatformName = "imgui_impl_android";
// Setup JNI for soft keyboard, Unicode polling, clipboard, and display metrics
if (native_activity)
{
JNIEnv* env = ImGui_ImplAndroid_GetEnv();
if (env)
{
g_NativeActivity = env->NewGlobalRef(native_activity);
g_HasJni = true;
ImGui_ImplAndroid_DetachEnv();
}
}
// Setup clipboard handlers if JNI is available
if (g_HasJni)
{
io.SetClipboardTextFn = [](void* /*user_data*/, const char* text) { ImGui_ImplAndroid_JniSetClipboardText(text); };
io.GetClipboardTextFn = [](void* /*user_data*/) -> const char* { return ImGui_ImplAndroid_JniGetClipboardText(); };
}
}
// Query display metrics via JNI (if activity available)
if (g_HasJni)
{
JNIEnv* env = ImGui_ImplAndroid_GetEnv();
if (env)
{
jclass activity_cls = env->GetObjectClass(g_NativeActivity);
if (activity_cls)
{
jmethodID get_metrics = env->GetMethodID(activity_cls, "getWindowManager", "()Landroid/view/WindowManager;");
if (get_metrics)
{
jobject wm = env->CallObjectMethod(g_NativeActivity, get_metrics);
if (wm)
{
jclass wm_cls = env->GetObjectClass(wm);
jmethodID get_default_display = env->GetMethodID(wm_cls, "getDefaultDisplay", "()Landroid/view/Display;");
if (get_default_display)
{
jobject display = env->CallObjectMethod(wm, get_default_display);
if (display)
{
jclass display_cls = env->GetObjectClass(display);
// Refresh rate
jmethodID get_refresh = env->GetMethodID(display_cls, "getRefreshRate", "()F");
if (get_refresh)
g_DisplayMetrics.RefreshRate = env->CallFloatMethod(display, get_refresh);
// Orientation (getRotation: 0=portrait, 1=landscape, 2=rev portrait, 3=rev landscape)
jmethodID get_rotation = env->GetMethodID(display_cls, "getRotation", "()I");
if (get_rotation)
g_DisplayMetrics.Orientation = env->CallIntMethod(display, get_rotation);
env->DeleteLocalRef(display_cls);
env->DeleteLocalRef(display);
}
}
env->DeleteLocalRef(wm_cls);
env->DeleteLocalRef(wm);
}
}
// Display metrics (DPI, density)
jmethodID get_resources = env->GetMethodID(activity_cls, "getResources", "()Landroid/content/res/Resources;");
if (get_resources)
{
jobject res = env->CallObjectMethod(g_NativeActivity, get_resources);
if (res)
{
jclass res_cls = env->GetObjectClass(res);
jmethodID get_metrics = env->GetMethodID(res_cls, "getDisplayMetrics", "()Landroid/util/DisplayMetrics;");
if (get_metrics)
{
jobject dm = env->CallObjectMethod(res, get_metrics);
if (dm)
{
jclass dm_cls = env->GetObjectClass(dm);
jfieldID density_dpi = env->GetFieldID(dm_cls, "densityDpi", "I");
jfieldID density = env->GetFieldID(dm_cls, "density", "F");
jfieldID xdpi = env->GetFieldID(dm_cls, "xdpi", "F");
jfieldID ydpi = env->GetFieldID(dm_cls, "ydpi", "F");
jfieldID w_pixels = env->GetFieldID(dm_cls, "widthPixels", "I");
jfieldID h_pixels = env->GetFieldID(dm_cls, "heightPixels", "I");
if (density_dpi) g_DisplayMetrics.DensityDpi = env->GetIntField(dm, density_dpi);
if (density) g_DisplayMetrics.Density = env->GetFloatField(dm, density);
if (xdpi) g_DisplayMetrics.Xdpi = env->GetFloatField(dm, xdpi);
if (ydpi) g_DisplayMetrics.Ydpi = env->GetFloatField(dm, ydpi);
if (w_pixels) g_DisplayMetrics.WidthPixels = env->GetIntField(dm, w_pixels);
if (h_pixels) g_DisplayMetrics.HeightPixels = env->GetIntField(dm, h_pixels);
env->DeleteLocalRef(dm_cls);
env->DeleteLocalRef(dm);
}
}
env->DeleteLocalRef(res_cls);
env->DeleteLocalRef(res);
}
}
env->DeleteLocalRef(activity_cls);
}
ImGui_ImplAndroid_DetachEnv();
}
}
// Fallback display metrics from ANativeWindow if JNI didn't provide them
if (g_Window)
{
int32_t w = ANativeWindow_getWidth(g_Window);
int32_t h = ANativeWindow_getHeight(g_Window);
if (g_DisplayMetrics.WidthPixels == 0) g_DisplayMetrics.WidthPixels = w;
if (g_DisplayMetrics.HeightPixels == 0) g_DisplayMetrics.HeightPixels = h;
if (g_DisplayMetrics.DensityDpi == 0) g_DisplayMetrics.DensityDpi = 160; // Default to mdpi
if (g_DisplayMetrics.Density == 0.0f) g_DisplayMetrics.Density = (float)g_DisplayMetrics.DensityDpi / 160.0f;
if (g_DisplayMetrics.RefreshRate == 0.0f) g_DisplayMetrics.RefreshRate = 60.0f;
}
return true;
}
void ImGui_ImplAndroid_Shutdown()
{
ImGuiIO& io = ImGui::GetIO();
io.BackendPlatformName = nullptr;
}
void ImGui_ImplAndroid_NewFrame()
{
ImGuiIO& io = ImGui::GetIO();
// Setup display size (every frame to accommodate for window resizing)
int32_t window_width = ANativeWindow_getWidth(g_Window);
int32_t window_height = ANativeWindow_getHeight(g_Window);
int display_width = window_width;
int display_height = window_height;
io.DisplaySize = ImVec2((float)window_width, (float)window_height);
if (window_width > 0 && window_height > 0)
io.DisplayFramebufferScale = ImVec2((float)display_width / window_width, (float)display_height / window_height);
// Setup time step
struct timespec current_timespec;
clock_gettime(CLOCK_MONOTONIC, &current_timespec);
double current_time = (double)(current_timespec.tv_sec) + (current_timespec.tv_nsec / 1000000000.0);
io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f / 60.0f);
g_Time = current_time;
}
//-----------------------------------------------------------------------------
#endif // #ifndef IMGUI_DISABLE

View File

@ -1,17 +1,34 @@
// dear imgui: Platform Binding for Android native app
// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3)
// dear imgui: Platform Backend for Android native app
// This needs to be used along with a Renderer Backend (e.g. OpenGL3, Vulkan)
// Implemented features:
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy AKEYCODE_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen.
// [X] Platform: On-screen keyboard (soft input) — handled internally via JNI. No application code needed. (#3446)
// [X] Platform: Unicode character input — handled internally via JNI. No application code needed. (#3446)
// [X] Platform: Clipboard support (via JNI to Android ClipboardManager). (#7259)
// Missing features or Issues:
// [ ] Platform: Clipboard support.
// [ ] Platform: Gamepad support.
// [ ] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android.
// Important:
// - Consider using SDL or GLFW backend on Android, which will be more full-featured than this.
// - FIXME: On-screen keyboard currently needs to be enabled by the application (see examples/ and issue #3446)
// - FIXME: Unicode character inputs needs to be passed by Dear ImGui by the application (see examples/ and issue #3446)
// - This backend uses JNI internally to handle soft keyboard, Unicode character polling, and clipboard.
// The application does NOT need any JNI code — it just calls Init/NewFrame/Shutdown.
// BREAKING CHANGE: ImGui_ImplAndroid_Init() signature changed (#3446)
//
// Before (v1.92 and earlier):
// ImGui_ImplAndroid_Init(g_App->window);
// // Application had to call ShowSoftKeyboardInput() and PollUnicodeChars() every frame.
// // Application had to implement JNI boilerplate for clipboard.
//
// After:
// ImGui_ImplAndroid_Init(g_App->window, app->activity->assetManager, app->activity->clazz);
// // The backend handles soft keyboard, Unicode input, and clipboard internally.
// // Pass nullptr for asset_manager and/or native_activity to disable those features.
//
// To keep old behavior (no JNI features):
// ImGui_ImplAndroid_Init(g_App->window, nullptr, nullptr);
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
@ -27,11 +44,40 @@
struct ANativeWindow;
struct AInputEvent;
struct AAssetManager;
// Forward declaration so users don't need JNI headers in their own includes.
// The actual type is 'jobject' from <jni.h>. If you pass a real jobject, include <jni.h> before this header.
#if __ANDROID__
# include <jni.h>
#else
typedef void* jobject;
#endif
// Display metrics returned by ImGui_ImplAndroid_GetDisplayMetrics().
struct ImGui_ImplAndroid_DisplayMetrics
{
int WidthPixels;
int HeightPixels;
float Density; // Logical density (1.0 = mdpi, 2.0 = xhdpi, 3.0 = xxhdpi)
int DensityDpi; // Physical dots per inch
float Xdpi; // Exact physical pixels per inch X
float Ydpi; // Exact physical pixels per inch Y
int Orientation; // 0=portrait, 1=landscape, 2=reverse portrait, 3=reverse landscape
float RefreshRate; // Screen refresh rate in Hz
};
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplAndroid_Init(ANativeWindow* window);
IMGUI_IMPL_API bool ImGui_ImplAndroid_Init(ANativeWindow* window, struct AAssetManager* asset_manager = nullptr, jobject native_activity = nullptr);
IMGUI_IMPL_API int32_t ImGui_ImplAndroid_HandleInputEvent(const AInputEvent* input_event);
IMGUI_IMPL_API void ImGui_ImplAndroid_Shutdown();
IMGUI_IMPL_API void ImGui_ImplAndroid_NewFrame();
// Optional: explicitly show/hide the soft keyboard (normally handled automatically in NewFrame based on io.WantTextInput)
IMGUI_IMPL_API void ImGui_ImplAndroid_ShowSoftKeyboard();
IMGUI_IMPL_API void ImGui_ImplAndroid_HideSoftKeyboard();
// Display metrics — queried once during Init and refreshed in NewFrame if the window resizes.
IMGUI_IMPL_API void ImGui_ImplAndroid_GetDisplayMetrics(ImGui_ImplAndroid_DisplayMetrics* out_metrics);
#endif // #ifndef IMGUI_DISABLE

View File

@ -60,45 +60,6 @@ Other Changes:
- Fixed double-click collapse toggle not owning the mouse button. If a
`SetNextWindowPos()` with pivot was queued in the same frame, the second
click could trigger another item in the same window. (#9439) [@Cleroth]
- Added `ImGuiItemFlags_LiveEditOnInputText` and `ImGuiItemFlags_LiveEditOnInputScalar`
flags to configure the timing of applying edits of backing variables when typing
values using a keyboard.
(#701, #9476, #3936, #3946, #5904, #6284, #8149, #8065, #8665, #9117, #9299, #700, #1351,
#1875, #2060, #2215, #2380, #2550, #3083, #3338, #3556, #4373, #4714, #4885, #5184,
#5777, #6707, #6766, #8004, #8303, #8915, #9308)
- Until now:
- Edits where always applied immediately to backing variable, which is equivalent
to the `ImGuiItemFlags_LiveEditXXX` flags being enabled.
- Typing '123' in an integer field would output successively 1, 12 then 123.
- Most uses of `IsItemDeactivatedAfterEdit()` or `ImGuiInputTextFlags_EnterReturnsTrue`
were actually workarounds for this issue. Advanced applications would typically
use `IsItemDeactivatedAfterEdit()` to distinguish transactions.
Workarounds often required a backing store for scalar values, and there were
also a few niggles related to `IsItemDeactivatedAfterEdit()` when using +/-
buttons of an `InputInt()` widgets.
Many of those situations can now be naturally simplified by disabling
`ImGuiItemFlags_LiveEditOnInputScalar`, which is expected to become the default.
- The new flags allows disabling this behavior selectively for strings fields
such as `InputText()` vs scalar fields: `SliderInt()`, `InputFloat()`, etc.
- When LiveEdit is disabled, edits are applied when pressing enter, tabbing out,
clearing a field or deactivating due to a focus loss.
- The flag may be altered programmatically:
PushItemFlag(ImGuiItemFlags_LiveEditOnInputScalar, false); // Disable for scalars
SliderInt(...);
PopItemFlag();
PushItemFlag(ImGuiItemFlags_LiveEditOnInput, true); // Enable for all
SliderInt(...);
PopItemFlag();
But with upcoming new defaults it is expected you shouldn't touch them much.
- Both flags currently defaults to true, which matches previous behavior.
- The expectation is that for strings/text, enabling LiveEdit is a better default.
- The expectation is that for scalars, disable LiveEdit is a better default.
- The short-term intent is to change `ImGuiItemFlags_LiveEditOnInputScalar` to
default to being disabled, as soon as we get more feedback from users (SOON).
- We intentionally are not adding `io.ConfigLiveEditXXX` fields to dictate the
default value of each `ImGuiItemFlags_LiveEditXXX`, because this is not
expected to be a user preference but a programmer/widget preferences.
Also, we strive to make the toolkit consistent.
- InputText:
- Added `style.InputTextCursorSize` to configure cursor/caret thickness. (#7031, #9409)
This is automatically scaled by `style.ScaleAllSizes()`.
@ -189,25 +150,20 @@ Other Changes:
- Misc:
- Added IM_DEBUG_BREAK() handler for GCC+AArch64/ARM64. [@tom-seddon]
- Backends:
- Android:
- Clear mouse position on touch release (AMOTION_EVENT_ACTION_UP) to prevent
items from staying in hovered state. (#6627, #9474) [@Turtle-PB]
- Android: Moved JNI soft-keyboard and Unicode character polling from the
example into the backend. The application no longer needs any JNI boilerplate.
Added clipboard support via JNI. Added display metrics (DPI, density, refresh
rate, orientation) via JNI, used for main_scale in the example.
BREAKING CHANGE: ImGui_ImplAndroid_Init() now takes additional asset_manager
and native_activity parameters. (#3446, #7259)
- Metal4:
- Added new Metal 4 backend (forked from Metal 3 backend). (#9458, #9451) [@AmelieHeinrich]
- Added Metal-cpp support enabled with `IMGUI_IMPL_METAL_CPP` define. (#9461) [@MERL10N]
- OpenGL2:
- Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT when updating texture
to avoid altering caller GL state. (#8802, #9473) [@Turtle-PB]
- OpenGL3:
- GLSL version detection assume GLSL 410 when GL context is 4.1.
Fixes an issue running on macOS with Wine. [#9427, #6577) [@perminovVS]
- Expose selected render state in ImGui_ImplOpenGL3_RenderState, allowing to
dynamically select between use of glBindSampler() and glTexParameter(). (#9378)
- Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT when updating texture
to avoid altering caller GL state. (#8802, #9473) [@Turtle-PB]
- SDL2:
- Restore SDL_StartTextInput()/SDL_StopTextInput() in IME handler for on-screen keyboard
support on Android. (#7636, #9474) [@Turtle-PB]
- SDLRenderer3:
- Fixed sampler change which didn't work on all graphics backends. (#7616, #9470, #9378)
- Fixed default sampler not being Linear. Regression in 1.92.8. (#7616, #9470, #9378) [@ShiroKSH]
@ -216,6 +172,9 @@ Other Changes:
when available, fixing OpenGL DPI scaling issues as e.g. NVIDIA drivers tends
to spawn multiple-thread to manage OpenGL. (#9403)
- Examples:
- Android: Removed JNI boilerplate (ShowSoftKeyboardInput, PollUnicodeChars) from
the example as these are now handled by the backend. (#3446)
Use display density for main_scale instead of hardcoded 2.0f.
- Android: update to AGP 9.2.0 to support Gradle 9.6.0.
- Apple+Metal4: added new example. (#9465, #9451) [@hoffstadt]
- OpenGL3+GLFW/SDL2/SDL3: allow Wine compatibility by passing empty GLSL version

View File

@ -37,4 +37,5 @@ target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE
EGL
GLESv3
log
sensor
)

View File

@ -1,40 +1,40 @@
package imgui.example.android
import android.app.NativeActivity
import android.os.Bundle
import android.content.Context
import android.view.inputmethod.InputMethodManager
import android.view.KeyEvent
import java.util.concurrent.LinkedBlockingQueue
// Minimal Kotlin activity extending NativeActivity.
// The three methods below (showSoftInput, hideSoftInput, pollUnicodeChar) are called
// from C++ via JNI by imgui_impl_android.cpp. They are required because Android's
// native API does not provide: (1) showing/hiding the soft keyboard, (2) getting
// Unicode characters from key events.
//
// The application developer does NOT write this code — it ships with the imgui
// example and is self-contained. The imgui_impl_android backend calls these
// methods automatically.
class MainActivity : NativeActivity() {
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
private var unicodeCharQueue: LinkedBlockingQueue<Int> = LinkedBlockingQueue()
fun showSoftInput() {
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.showSoftInput(this.window.decorView, 0)
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(window.decorView, 0)
}
fun hideSoftInput() {
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(this.window.decorView.windowToken, 0)
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(window.decorView.windowToken, 0)
}
// Queue for the Unicode characters to be polled from native code (via pollUnicodeChar())
private var unicodeCharacterQueue: LinkedBlockingQueue<Int> = LinkedBlockingQueue()
// We assume dispatchKeyEvent() of the NativeActivity is actually called for every
// KeyEvent and not consumed by any View before it reaches here
// Native Android key events don't expose getUnicodeChar() to C code.
// We intercept them here and queue the Unicode chars for the C++ backend to poll.
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.action == KeyEvent.ACTION_DOWN) {
unicodeCharacterQueue.offer(event.getUnicodeChar(event.metaState))
}
if (event.action == KeyEvent.ACTION_DOWN)
unicodeCharQueue.offer(event.getUnicodeChar(event.metaState))
return super.dispatchKeyEvent(event)
}
fun pollUnicodeChar(): Int {
return unicodeCharacterQueue.poll() ?: 0
}
fun pollUnicodeChar(): Int = unicodeCharQueue.poll() ?: 0
}

View File

@ -29,8 +29,6 @@ static std::string g_IniFilename = "";
static void Init(struct android_app* app);
static void Shutdown();
static void MainLoopStep();
static int ShowSoftKeyboardInput();
static int PollUnicodeChars();
static int GetAssetData(const char* filename, void** out_data);
// Main code
@ -148,11 +146,13 @@ void Init(struct android_app* app)
//ImGui::StyleColorsLight();
// Setup Platform/Renderer backends
ImGui_ImplAndroid_Init(g_App->window);
ImGui_ImplAndroid_Init(g_App->window, app->activity->assetManager, app->activity->clazz);
ImGui_ImplOpenGL3_Init("#version 300 es");
// Setup scaling
float main_scale = 2.0f;
ImGui_ImplAndroid_DisplayMetrics display_metrics;
ImGui_ImplAndroid_GetDisplayMetrics(&display_metrics);
float main_scale = display_metrics.Density > 0.0f ? display_metrics.Density : 2.0f;
ImGuiStyle& style = ImGui::GetStyle();
style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again)
style.FontScaleDpi = main_scale; // Set initial font scale.
@ -205,16 +205,6 @@ void MainLoopStep()
static bool show_another_window = false;
static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
// Poll Unicode characters via JNI
// FIXME: do not call this every frame because of JNI overhead
PollUnicodeChars();
// Open on-screen (soft) input if requested by Dear ImGui
static bool WantTextInputLast = false;
if (io.WantTextInput && !WantTextInputLast)
ShowSoftKeyboardInput();
WantTextInputLast = io.WantTextInput;
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplAndroid_NewFrame();
@ -297,77 +287,6 @@ void Shutdown()
g_Initialized = false;
}
// Helper functions
// Unfortunately, there is no way to show the on-screen input from native code.
// Therefore, we call ShowSoftKeyboardInput() of the main activity implemented in MainActivity.kt via JNI.
static int ShowSoftKeyboardInput()
{
JavaVM* java_vm = g_App->activity->vm;
JNIEnv* java_env = nullptr;
jint jni_return = java_vm->GetEnv((void**)&java_env, JNI_VERSION_1_6);
if (jni_return == JNI_ERR)
return -1;
jni_return = java_vm->AttachCurrentThread(&java_env, nullptr);
if (jni_return != JNI_OK)
return -2;
jclass native_activity_clazz = java_env->GetObjectClass(g_App->activity->clazz);
if (native_activity_clazz == nullptr)
return -3;
jmethodID method_id = java_env->GetMethodID(native_activity_clazz, "showSoftInput", "()V");
if (method_id == nullptr)
return -4;
java_env->CallVoidMethod(g_App->activity->clazz, method_id);
jni_return = java_vm->DetachCurrentThread();
if (jni_return != JNI_OK)
return -5;
return 0;
}
// Unfortunately, the native KeyEvent implementation has no getUnicodeChar() function.
// Therefore, we implement the processing of KeyEvents in MainActivity.kt and poll
// the resulting Unicode characters here via JNI and send them to Dear ImGui.
static int PollUnicodeChars()
{
JavaVM* java_vm = g_App->activity->vm;
JNIEnv* java_env = nullptr;
jint jni_return = java_vm->GetEnv((void**)&java_env, JNI_VERSION_1_6);
if (jni_return == JNI_ERR)
return -1;
jni_return = java_vm->AttachCurrentThread(&java_env, nullptr);
if (jni_return != JNI_OK)
return -2;
jclass native_activity_clazz = java_env->GetObjectClass(g_App->activity->clazz);
if (native_activity_clazz == nullptr)
return -3;
jmethodID method_id = java_env->GetMethodID(native_activity_clazz, "pollUnicodeChar", "()I");
if (method_id == nullptr)
return -4;
// Send the actual characters to Dear ImGui
ImGuiIO& io = ImGui::GetIO();
jint unicode_character;
while ((unicode_character = java_env->CallIntMethod(g_App->activity->clazz, method_id)) != 0)
io.AddInputCharacter(unicode_character);
jni_return = java_vm->DetachCurrentThread();
if (jni_return != JNI_OK)
return -5;
return 0;
}
// Helper to retrieve data placed into the assets/ directory (android/app/src/main/assets)
static int GetAssetData(const char* filename, void** outData)
{