From 8dd692c29cdec030f61f9927b104d400fa536c4e Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 4 Mar 2021 11:03:40 +0100 Subject: [PATCH] Android: Amend backend and examples with minor consistency tweaks. (#3446) --- backends/imgui_impl_android.cpp | 61 ++- backends/imgui_impl_android.h | 6 +- backends/imgui_impl_glfw.cpp | 2 +- backends/imgui_impl_marmalade.cpp | 3 +- backends/imgui_impl_sdl.cpp | 2 +- backends/imgui_impl_wgpu.cpp | 2 +- backends/imgui_impl_win32.cpp | 2 +- docs/CHANGELOG.txt | 2 + .../example_android_opengl3/CMakeLists.txt | 2 +- .../android/app/src/main/AndroidManifest.xml | 4 +- examples/example_android_opengl3/main.cpp | 377 +++++++++--------- imgui.h | 2 +- 12 files changed, 236 insertions(+), 229 deletions(-) diff --git a/backends/imgui_impl_android.cpp b/backends/imgui_impl_android.cpp index 428f99e95..672a27e1d 100644 --- a/backends/imgui_impl_android.cpp +++ b/backends/imgui_impl_android.cpp @@ -3,9 +3,13 @@ // Implemented features: // [X] Platform: Keyboard arrays indexed using AKEYCODE_* codes, e.g. ImGui::IsKeyPressed(AKEYCODE_SPACE). +// Missing features: // [ ] Platform: Clipboard support. // [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [ ] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android. +// Important: +// - 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) // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. @@ -13,39 +17,35 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2021-03-02: Support for physical pointer device input (such as physical mouse) -// 2020-09-13: Support for Unicode characters -// 2020-08-31: On-screen and physical keyboard input (ASCII characters only) -// 2020-03-02: basic draft, touch input +// 2021-03-04: Initial version. #include "imgui.h" #include "imgui_impl_android.h" #include #include #include - -// Android #include #include #include #include +// Android data static double g_Time = 0.0; static ANativeWindow* g_Window; -static char g_LogTag[] = "ImguiExample"; +static char g_LogTag[] = "ImGuiExample"; static std::map> g_KeyEventQueues; // FIXME: Remove dependency on map and queue once we use upcoming input queue. -int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* inputEvent) +int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* input_event) { ImGuiIO& io = ImGui::GetIO(); - int32_t event_type = AInputEvent_getType(inputEvent); + int32_t event_type = AInputEvent_getType(input_event); switch (event_type) { case AINPUT_EVENT_TYPE_KEY: { - int32_t event_key_code = AKeyEvent_getKeyCode(inputEvent); - int32_t event_action = AKeyEvent_getAction(inputEvent); - int32_t event_meta_state = AKeyEvent_getMetaState(inputEvent); + int32_t event_key_code = AKeyEvent_getKeyCode(input_event); + int32_t event_action = AKeyEvent_getAction(input_event); + int32_t event_meta_state = AKeyEvent_getMetaState(input_event); io.KeyCtrl = ((event_meta_state & AMETA_CTRL_ON) != 0); io.KeyShift = ((event_meta_state & AMETA_SHIFT_ON) != 0); @@ -53,10 +53,9 @@ int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* inputEvent) switch (event_action) { - // FIXME: AKEY_EVENT_ACTION_DOWN and AKEY_EVENT_ACTION_UP occur at once - // as soon as a touch pointer goes up from a key. We use a simple key event queue - // and process one event per key per ImGui frame in ImGui_ImplAndroid_NewFrame(). - // ...or consider ImGui IO queue, if suitable: https://github.com/ocornut/imgui/issues/2787 + // FIXME: AKEY_EVENT_ACTION_DOWN and AKEY_EVENT_ACTION_UP occur at once as soon as a touch pointer + // goes up from a key. We use a simple key event queue/ and process one event per key per frame in + // ImGui_ImplAndroid_NewFrame()...or consider using IO queue, if suitable: https://github.com/ocornut/imgui/issues/2787 case AKEY_EVENT_ACTION_DOWN: case AKEY_EVENT_ACTION_UP: g_KeyEventQueues[event_key_code].push(event_action); @@ -68,7 +67,7 @@ int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* inputEvent) } case AINPUT_EVENT_TYPE_MOTION: { - int32_t event_action = AMotionEvent_getAction(inputEvent); + int32_t event_action = AMotionEvent_getAction(input_event); int32_t event_pointer_index = (event_action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; event_action &= AMOTION_EVENT_ACTION_MASK; switch (event_action) @@ -78,33 +77,29 @@ int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* inputEvent) // Physical mouse buttons (and probably other physical devices) also invoke the actions AMOTION_EVENT_ACTION_DOWN/_UP, // but we have to process them separately to identify the actual button pressed. This is done below via // AMOTION_EVENT_ACTION_BUTTON_PRESS/_RELEASE. Here, we only process "FINGER" input (and "UNKNOWN", as a fallback). - if((AMotionEvent_getToolType(inputEvent, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_FINGER) - || (AMotionEvent_getToolType(inputEvent, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_UNKNOWN)) + if((AMotionEvent_getToolType(input_event, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_FINGER) + || (AMotionEvent_getToolType(input_event, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_UNKNOWN)) { io.MouseDown[0] = (event_action == AMOTION_EVENT_ACTION_DOWN) ? true : false; - io.MousePos = ImVec2( - AMotionEvent_getX(inputEvent, event_pointer_index), - AMotionEvent_getY(inputEvent, event_pointer_index)); + io.MousePos = ImVec2(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index)); } break; case AMOTION_EVENT_ACTION_BUTTON_PRESS: case AMOTION_EVENT_ACTION_BUTTON_RELEASE: { - int32_t button_state = AMotionEvent_getButtonState(inputEvent); + int32_t button_state = AMotionEvent_getButtonState(input_event); io.MouseDown[0] = (button_state & AMOTION_EVENT_BUTTON_PRIMARY) ? true : false; io.MouseDown[1] = (button_state & AMOTION_EVENT_BUTTON_SECONDARY) ? true : false; io.MouseDown[2] = (button_state & AMOTION_EVENT_BUTTON_TERTIARY) ? true : false; } break; case AMOTION_EVENT_ACTION_HOVER_MOVE: // Hovering: Tool moves while NOT pressed (such as a physical mouse) - case AMOTION_EVENT_ACTION_MOVE: // Touch pointer moves while DOWN - io.MousePos = ImVec2( - AMotionEvent_getX(inputEvent, event_pointer_index), - AMotionEvent_getY(inputEvent, event_pointer_index)); + case AMOTION_EVENT_ACTION_MOVE: // Touch pointer moves while DOWN + io.MousePos = ImVec2(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index)); break; case AMOTION_EVENT_ACTION_SCROLL: - io.MouseWheel = AMotionEvent_getAxisValue(inputEvent, AMOTION_EVENT_AXIS_VSCROLL, event_pointer_index); - io.MouseWheelH = AMotionEvent_getAxisValue(inputEvent, AMOTION_EVENT_AXIS_HSCROLL, event_pointer_index); + io.MouseWheel = AMotionEvent_getAxisValue(input_event, AMOTION_EVENT_AXIS_VSCROLL, event_pointer_index); + io.MouseWheelH = AMotionEvent_getAxisValue(input_event, AMOTION_EVENT_AXIS_HSCROLL, event_pointer_index); break; default: break; @@ -123,11 +118,11 @@ bool ImGui_ImplAndroid_Init(ANativeWindow* window) g_Window = window; g_Time = 0.0; - // Setup back-end capabilities flags + // Setup backend capabilities flags ImGuiIO& io = ImGui::GetIO(); io.BackendPlatformName = "imgui_impl_android"; - // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. + // Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeysDown[] array. io.KeyMap[ImGuiKey_Tab] = AKEYCODE_TAB; io.KeyMap[ImGuiKey_LeftArrow] = AKEYCODE_DPAD_LEFT; // also covers physical keyboard arrow key io.KeyMap[ImGuiKey_RightArrow] = AKEYCODE_DPAD_RIGHT; // also covers physical keyboard arrow key @@ -161,10 +156,10 @@ void ImGui_ImplAndroid_Shutdown() void ImGui_ImplAndroid_NewFrame() { ImGuiIO& io = ImGui::GetIO(); - IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer back-end. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame()."); + IM_ASSERT(io.Fonts->IsBuilt() && "Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame()."); // Process queued key events - // FIXME: This is a workaround for multiple key event actions occuring at once (see above) and can be removed once we use upcoming input queue. + // FIXME: This is a workaround for multiple key event actions occurring at once (see above) and can be removed once we use upcoming input queue. for (auto& key_queue : g_KeyEventQueues) { if (key_queue.second.empty()) diff --git a/backends/imgui_impl_android.h b/backends/imgui_impl_android.h index 6ba3263fe..e2f175736 100644 --- a/backends/imgui_impl_android.h +++ b/backends/imgui_impl_android.h @@ -3,9 +3,13 @@ // Implemented features: // [X] Platform: Keyboard arrays indexed using AKEYCODE_* codes, e.g. ImGui::IsKeyPressed(AKEYCODE_SPACE). +// Missing features: // [ ] Platform: Clipboard support. // [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [ ] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android. +// Important: +// - 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) // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. @@ -16,7 +20,7 @@ struct ANativeWindow; struct AInputEvent; -IMGUI_IMPL_API int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* inputEvent); IMGUI_IMPL_API bool ImGui_ImplAndroid_Init(ANativeWindow* window); +IMGUI_IMPL_API int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* input_event); IMGUI_IMPL_API void ImGui_ImplAndroid_Shutdown(); IMGUI_IMPL_API void ImGui_ImplAndroid_NewFrame(); diff --git a/backends/imgui_impl_glfw.cpp b/backends/imgui_impl_glfw.cpp index fbc167d65..cd0f51dbc 100644 --- a/backends/imgui_impl_glfw.cpp +++ b/backends/imgui_impl_glfw.cpp @@ -149,7 +149,7 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) io.BackendPlatformName = "imgui_impl_glfw"; - // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. + // Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeysDown[] array. io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; diff --git a/backends/imgui_impl_marmalade.cpp b/backends/imgui_impl_marmalade.cpp index c4f83a804..c0c06e1e6 100644 --- a/backends/imgui_impl_marmalade.cpp +++ b/backends/imgui_impl_marmalade.cpp @@ -220,7 +220,8 @@ bool ImGui_Marmalade_Init(bool install_callbacks) ImGuiIO& io = ImGui::GetIO(); io.BackendPlatformName = io.BackendRendererName = "imgui_impl_marmalade"; - io.KeyMap[ImGuiKey_Tab] = s3eKeyTab; // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. + // Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeysDown[] array. + io.KeyMap[ImGuiKey_Tab] = s3eKeyTab io.KeyMap[ImGuiKey_LeftArrow] = s3eKeyLeft; io.KeyMap[ImGuiKey_RightArrow] = s3eKeyRight; io.KeyMap[ImGuiKey_UpArrow] = s3eKeyUp; diff --git a/backends/imgui_impl_sdl.cpp b/backends/imgui_impl_sdl.cpp index bf273ac61..7ab70c57f 100644 --- a/backends/imgui_impl_sdl.cpp +++ b/backends/imgui_impl_sdl.cpp @@ -138,7 +138,7 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window) io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) io.BackendPlatformName = "imgui_impl_sdl"; - // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. + // Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeysDown[] array. io.KeyMap[ImGuiKey_Tab] = SDL_SCANCODE_TAB; io.KeyMap[ImGuiKey_LeftArrow] = SDL_SCANCODE_LEFT; io.KeyMap[ImGuiKey_RightArrow] = SDL_SCANCODE_RIGHT; diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index 1eb290a3c..348818780 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -731,7 +731,7 @@ void ImGui_ImplWGPU_InvalidateDeviceObjects() bool ImGui_ImplWGPU_Init(WGPUDevice device, int num_frames_in_flight, WGPUTextureFormat rt_format) { - // Setup back-end capabilities flags + // Setup backend capabilities flags ImGuiIO& io = ImGui::GetIO(); io.BackendRendererName = "imgui_impl_webgpu"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. diff --git a/backends/imgui_impl_win32.cpp b/backends/imgui_impl_win32.cpp index df5f50296..04e462ce4 100644 --- a/backends/imgui_impl_win32.cpp +++ b/backends/imgui_impl_win32.cpp @@ -91,7 +91,7 @@ bool ImGui_ImplWin32_Init(void* hwnd) io.BackendPlatformName = "imgui_impl_win32"; io.ImeWindowHandle = hwnd; - // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array that we will update during the application lifetime. + // Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeysDown[] array that we will update during the application lifetime. io.KeyMap[ImGuiKey_Tab] = VK_TAB; io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 27c68342a..93dcf21c4 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -58,6 +58,7 @@ Other Changes: - ImDrawList: AddCircle, AddCircleFilled(): Tweaked default segment count calculation to honor MaxError with more accuracy. Made default segment count always even for better looking result. (#3808) [@thedmd] - ImDrawList: AddCircle, AddCircleFilled(): New default for style. +- Backends: Android: Added native Android backend. (#3446) [@duddel] - Backends: Win32: Added ImGui_ImplWin32_EnableAlphaCompositing() to facilitate experimenting with alpha compositing and transparent windows. (#2766, #3447 etc.). - Backends: OpenGL, Vulkan, DX9, DX10, DX11, DX12, Metal, WebGPU, Allegro: Rework blending equation to @@ -66,6 +67,7 @@ Other Changes: (#2693, #2764, #2766, #2873, #3447, #3813, #3816) [@ocornut, @thedmd, @ShawnM427, @Ubpa, @aiekick] - Backends: DX9: Fix to support IMGUI_USE_BGRA_PACKED_COLOR. (#3844) [@Xiliusha] - Backends: DX9: Fix to support colored glyphs, using newly introduced 'TexPixelsUseColors' info. (#3844) +- Examples: Android: Added Android + GL ES2 example. (#3446) [@duddel] - Examples: Reworked setup of clear color to be compatible with transparent values. - CI: Use a dedicated "scheduled" workflow to trigger scheduled builds. Forks may disable this workflow if scheduled builds builds are not required. [@rokups] diff --git a/examples/example_android_opengl3/CMakeLists.txt b/examples/example_android_opengl3/CMakeLists.txt index 0010b4bdb..63531f4dc 100644 --- a/examples/example_android_opengl3/CMakeLists.txt +++ b/examples/example_android_opengl3/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.6) -project(ImguiExample) +project(ImGuiExample) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/examples/example_android_opengl3/android/app/src/main/AndroidManifest.xml b/examples/example_android_opengl3/android/app/src/main/AndroidManifest.xml index ea838e6d7..c4009e529 100644 --- a/examples/example_android_opengl3/android/app/src/main/AndroidManifest.xml +++ b/examples/example_android_opengl3/android/app/src/main/AndroidManifest.xml @@ -3,7 +3,7 @@ package="imgui.example.android"> @@ -13,7 +13,7 @@ android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:configChanges="orientation|keyboardHidden|screenSize"> + android:value="ImGuiExample" /> diff --git a/examples/example_android_opengl3/main.cpp b/examples/example_android_opengl3/main.cpp index 9d38531b9..5ab690348 100644 --- a/examples/example_android_opengl3/main.cpp +++ b/examples/example_android_opengl3/main.cpp @@ -10,98 +10,18 @@ #include #include +// Data static EGLDisplay g_EglDisplay = EGL_NO_DISPLAY; static EGLSurface g_EglSurface = EGL_NO_SURFACE; static EGLContext g_EglContext = EGL_NO_CONTEXT; static struct android_app* g_App = NULL; static bool g_Initialized = false; -static char g_LogTag[] = "ImguiExample"; +static char g_LogTag[] = "ImGuiExample"; -// Unfortunately, there is no way to show the on-screen input from native code. -// Therefore, we call showSoftInput() of the main activity implemented in MainActivity.kt via JNI. -static int showSoftInput() -{ - JavaVM* java_vm = g_App->activity->vm; - JNIEnv* java_env = NULL; - - 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, NULL); - if (jni_return != JNI_OK) - return -2; - - jclass native_activity_clazz = java_env->GetObjectClass(g_App->activity->clazz); - if (native_activity_clazz == NULL) - return -3; - - jmethodID method_id = java_env->GetMethodID(native_activity_clazz, "showSoftInput", "()V"); - if (method_id == NULL) - 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 = NULL; - - 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, NULL); - if (jni_return != JNI_OK) - return -2; - - jclass native_activity_clazz = java_env->GetObjectClass(g_App->activity->clazz); - if (native_activity_clazz == NULL) - return -3; - - jmethodID method_id = java_env->GetMethodID(native_activity_clazz, "pollUnicodeChar", "()I"); - if (method_id == NULL) - 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; -} - -static int GetAssetData(const char* filename, void** outData) -{ - int num_bytes = 0; - AAsset* asset_descriptor = AAssetManager_open(g_App->activity->assetManager, filename, AASSET_MODE_BUFFER); - if(asset_descriptor) - { - num_bytes = AAsset_getLength(asset_descriptor); - *outData = IM_ALLOC(num_bytes); - int64_t num_bytes_read = AAsset_read(asset_descriptor, *outData, num_bytes); - AAsset_close(asset_descriptor); - IM_ASSERT(num_bytes_read == num_bytes); - } - return num_bytes; -} +// Forward declarations of helper functions +static int ShowSoftKeyboardInput(); +static int PollUnicodeChars(); +static int GetAssetData(const char* filename, void** out_data); void init(struct android_app* app) { @@ -113,65 +33,66 @@ void init(struct android_app* app) // Initialize EGL // This is mostly boilerplate code for EGL... - g_EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + { + g_EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (g_EglDisplay == EGL_NO_DISPLAY) + __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", "eglGetDisplay(EGL_DEFAULT_DISPLAY) returned EGL_NO_DISPLAY"); - if (g_EglDisplay == EGL_NO_DISPLAY) - __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", "eglGetDisplay(EGL_DEFAULT_DISPLAY) returned EGL_NO_DISPLAY"); + if (eglInitialize(g_EglDisplay, 0, 0) != EGL_TRUE) + __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", "eglInitialize() returned with an error"); - if (eglInitialize(g_EglDisplay, 0, 0) != EGL_TRUE) - __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", "eglInitialize(..) returned with an error"); + const EGLint egl_attributes[] = { EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_DEPTH_SIZE, 24, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE }; + EGLint num_configs = 0; + if (eglChooseConfig(g_EglDisplay, egl_attributes, nullptr, 0, &num_configs) != EGL_TRUE) + __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", "eglChooseConfig() returned with an error"); + if (num_configs == 0) + __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", "eglChooseConfig() returned 0 matching config"); - const EGLint egl_attributes[] = { - EGL_BLUE_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_RED_SIZE, 8, - EGL_DEPTH_SIZE, 24, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_NONE}; + // Get the first matching config + EGLConfig egl_config; + eglChooseConfig(g_EglDisplay, egl_attributes, &egl_config, 1, &num_configs); + EGLint egl_format; + eglGetConfigAttrib(g_EglDisplay, egl_config, EGL_NATIVE_VISUAL_ID, &egl_format); + ANativeWindow_setBuffersGeometry(g_App->window, 0, 0, egl_format); - EGLint num_configs = 0; - if (eglChooseConfig(g_EglDisplay, egl_attributes, nullptr, 0, &num_configs) != EGL_TRUE) - __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", "eglChooseConfig(..) returned with an error"); + const EGLint egl_context_attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; + g_EglContext = eglCreateContext(g_EglDisplay, egl_config, EGL_NO_CONTEXT, egl_context_attributes); - if (num_configs == 0) - __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", "eglChooseConfig(..) returned 0 matching configs"); + if (g_EglContext == EGL_NO_CONTEXT) + __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", "eglCreateContext() returned EGL_NO_CONTEXT"); - // Get the (first) matching config - EGLConfig egl_config; - eglChooseConfig(g_EglDisplay, egl_attributes, &egl_config, 1, &num_configs); - EGLint egl_format; - eglGetConfigAttrib(g_EglDisplay, egl_config, EGL_NATIVE_VISUAL_ID, &egl_format); - ANativeWindow_setBuffersGeometry(g_App->window, 0, 0, egl_format); + g_EglSurface = eglCreateWindowSurface(g_EglDisplay, egl_config, g_App->window, NULL); + eglMakeCurrent(g_EglDisplay, g_EglSurface, g_EglSurface, g_EglContext); + } - const EGLint egl_context_attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; - g_EglContext = eglCreateContext(g_EglDisplay, egl_config, EGL_NO_CONTEXT, egl_context_attributes); - - if (g_EglContext == EGL_NO_CONTEXT) - __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", "eglCreateContext(..) returned EGL_NO_CONTEXT"); - - g_EglSurface = eglCreateWindowSurface(g_EglDisplay, egl_config, g_App->window, NULL); - eglMakeCurrent(g_EglDisplay, g_EglSurface, g_EglSurface, g_EglContext); - - // Dear Imgui + // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); + + // Disable loading/saving of .ini file from disk. + // FIXME: Consider using LoadIniSettingsFromMemory() / SaveIniSettingsToMemory() to save in appropriate location for Android. io.IniFilename = NULL; + + // Setup Dear ImGui style ImGui::StyleColorsDark(); + //ImGui::StyleColorsClassic(); + + // Setup Platform/Renderer backends ImGui_ImplAndroid_Init(g_App->window); ImGui_ImplOpenGL3_Init("#version 300 es"); // Load Fonts // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. - // - add_font_from_assets_ttf() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! - // - The TTF files have to be placed into the assets/ directory (android/app/src/main/assets). + // - Android: The TTF files have to be placed into the assets/ directory (android/app/src/main/assets), we use our GetAssetData() helper to retrieve them. // We load the default font with increased size to improve readability on many devices with "high" DPI. - // FIXME: Put some effort into DPI awareness + // FIXME: Put some effort into DPI awareness. + // Important: when calling AddFontFromMemoryTTF(), ownership of font_data is transfered by Dear ImGui by default (deleted is handled by Dear ImGui), unless we set FontDataOwnedByAtlas=false in ImFontConfig ImFontConfig font_cfg; font_cfg.SizePixels = 22.0f; io.Fonts->AddFontDefault(&font_cfg); @@ -179,19 +100,19 @@ void init(struct android_app* app) //int font_data_size; //ImFont* font; //font_data_size = GetAssetData("Roboto-Medium.ttf", &font_data); - //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 16.0f); // Ownership of font_data is transfered to ImGui. Deletion is handled by ImGui. + //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 16.0f); //IM_ASSERT(font != NULL); //font_data_size = GetAssetData("Cousine-Regular.ttf", &font_data); - //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 15.0f); // Ownership of font_data is transfered to ImGui. Deletion is handled by ImGui. + //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 15.0f); //IM_ASSERT(font != NULL); //font_data_size = GetAssetData("DroidSans.ttf", &font_data); - //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 16.0f); // Ownership of font_data is transfered to ImGui. Deletion is handled by ImGui. + //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 16.0f); //IM_ASSERT(font != NULL); //font_data_size = GetAssetData("ProggyTiny.ttf", &font_data); - //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 10.0f); // Ownership of font_data is transfered to ImGui. Deletion is handled by ImGui. + //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 10.0f); //IM_ASSERT(font != NULL); //font_data_size = GetAssetData("ArialUni.ttf", &font_data); - //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese()); // Ownership of font_data is transfered to ImGui. Deletion is handled by ImGui. + //font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese()); //IM_ASSERT(font != NULL); // Arbitrary scale-up @@ -203,75 +124,74 @@ void init(struct android_app* app) void tick() { - // Our state (Dear Imgui) + ImGuiIO& io = ImGui::GetIO(); + if (g_EglDisplay == EGL_NO_DISPLAY) + return; + + // Our state static bool show_demo_window = true; static bool show_another_window = false; static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - if (g_EglDisplay != EGL_NO_DISPLAY) + // 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(); + ImGui::NewFrame(); + + // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + // 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window. { - ImGuiIO& io = ImGui::GetIO(); + static float f = 0.0f; + static int counter = 0; - // Poll Unicode characters via JNI - // FIXME: do not call this every frame because of JNI overhead - pollUnicodeChars(); + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. - // Open on-screen (soft) input if demanded by Dear ImGui - static bool WantTextInputLast = false; - if (io.WantTextInput && !WantTextInputLast) - showSoftInput(); - WantTextInputLast = io.WantTextInput; + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Another Window", &show_another_window); - // Start the Dear ImGui frame - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplAndroid_NewFrame(); - ImGui::NewFrame(); + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color - // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). - if (show_demo_window) - ImGui::ShowDemoWindow(&show_demo_window); + if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); - // 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window. - { - static float f = 0.0f; - static int counter = 0; - - ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. - - ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) - ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state - ImGui::Checkbox("Another Window", &show_another_window); - - ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f - ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color - - if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) - counter++; - ImGui::SameLine(); - ImGui::Text("counter = %d", counter); - - ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); - ImGui::End(); - } - - // 3. Show another simple window. - if (show_another_window) - { - ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) - ImGui::Text("Hello from another window!"); - if (ImGui::Button("Close Me")) - show_another_window = false; - ImGui::End(); - } - - // Rendering - ImGui::Render(); - glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); - glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - eglSwapBuffers(g_EglDisplay, g_EglSurface); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); + ImGui::End(); } + + // 3. Show another simple window. + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } + + // Rendering + ImGui::Render(); + glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + eglSwapBuffers(g_EglDisplay, g_EglSurface); } void shutdown() @@ -279,7 +199,7 @@ void shutdown() if (!g_Initialized) return; - // Cleanup (Dear Imgui) + // Cleanup ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplAndroid_Shutdown(); ImGui::DestroyContext(); @@ -362,3 +282,88 @@ void android_main(struct android_app* app) tick(); } } + +// 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 = NULL; + + 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, NULL); + if (jni_return != JNI_OK) + return -2; + + jclass native_activity_clazz = java_env->GetObjectClass(g_App->activity->clazz); + if (native_activity_clazz == NULL) + return -3; + + jmethodID method_id = java_env->GetMethodID(native_activity_clazz, "showSoftInput", "()V"); + if (method_id == NULL) + 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 = NULL; + + 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, NULL); + if (jni_return != JNI_OK) + return -2; + + jclass native_activity_clazz = java_env->GetObjectClass(g_App->activity->clazz); + if (native_activity_clazz == NULL) + return -3; + + jmethodID method_id = java_env->GetMethodID(native_activity_clazz, "pollUnicodeChar", "()I"); + if (method_id == NULL) + 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) +{ + int num_bytes = 0; + AAsset* asset_descriptor = AAssetManager_open(g_App->activity->assetManager, filename, AASSET_MODE_BUFFER); + if (asset_descriptor) + { + num_bytes = AAsset_getLength(asset_descriptor); + *outData = IM_ALLOC(num_bytes); + int64_t num_bytes_read = AAsset_read(asset_descriptor, *outData, num_bytes); + AAsset_close(asset_descriptor); + IM_ASSERT(num_bytes_read == num_bytes); + } + return num_bytes; +} diff --git a/imgui.h b/imgui.h index 079902093..5507c19fe 100644 --- a/imgui.h +++ b/imgui.h @@ -2767,7 +2767,7 @@ enum ImGuiViewportFlags_ struct ImGuiViewport { ImGuiViewportFlags Flags; // See ImGuiViewportFlags_ - ImVec2 Pos; // Main Area: Position of the viewport (Dear Imgui coordinates are the same as OS desktop/native coordinates) + ImVec2 Pos; // Main Area: Position of the viewport (Dear ImGui coordinates are the same as OS desktop/native coordinates) ImVec2 Size; // Main Area: Size of the viewport. ImVec2 WorkPos; // Work Area: Position of the viewport minus task bars, menus bars, status bars (>= Pos) ImVec2 WorkSize; // Work Area: Size of the viewport minus task bars, menu bars, status bars (<= Size)