Skip to content

Commit fb85c03

Browse files
authored
Add Android backend and example (ocornut#3446)
1 parent d8c88bd commit fb85c03

File tree

14 files changed

+770
-2
lines changed

14 files changed

+770
-2
lines changed

.github/workflows/build.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,10 +458,20 @@ jobs:
458458
popd
459459
make -C examples/example_emscripten_wgpu
460460
461+
Android:
462+
runs-on: ubuntu-18.04
463+
steps:
464+
- uses: actions/checkout@v2
465+
466+
- name: Build example_android_opengl3
467+
run: |
468+
cd examples/example_android_opengl3/android
469+
gradle assembleDebug
470+
461471
Discord-CI:
462472
runs-on: ubuntu-18.04
463473
if: always()
464-
needs: [Windows, Linux, MacOS, iOS, Emscripten]
474+
needs: [Windows, Linux, MacOS, iOS, Emscripten, Android]
465475
steps:
466476
- uses: dearimgui/github_discord_notifier@latest
467477
with:

backends/imgui_impl_android.cpp

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
// dear imgui: Platform Binding for Android native app
2+
// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3)
3+
4+
// Implemented features:
5+
// [X] Platform: Keyboard arrays indexed using AKEYCODE_* codes, e.g. ImGui::IsKeyPressed(AKEYCODE_SPACE).
6+
// [ ] Platform: Clipboard support.
7+
// [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
8+
// [ ] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android.
9+
10+
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
11+
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
12+
// https://github.com/ocornut/imgui
13+
14+
// CHANGELOG
15+
// (minor and older changes stripped away, please see git history for details)
16+
// 2021-03-02: Support for physical pointer device input (such as physical mouse)
17+
// 2020-09-13: Support for Unicode characters
18+
// 2020-08-31: On-screen and physical keyboard input (ASCII characters only)
19+
// 2020-03-02: basic draft, touch input
20+
21+
#include "imgui.h"
22+
#include "imgui_impl_android.h"
23+
#include <time.h>
24+
#include <map>
25+
#include <queue>
26+
27+
// Android
28+
#include <android/native_window.h>
29+
#include <android/input.h>
30+
#include <android/keycodes.h>
31+
#include <android/log.h>
32+
33+
static double g_Time = 0.0;
34+
static ANativeWindow* g_Window;
35+
static char g_LogTag[] = "ImguiExample";
36+
static std::map<int32_t, std::queue<int32_t>> g_KeyEventQueues; // FIXME: Remove dependency on map and queue once we use upcoming input queue.
37+
38+
int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* inputEvent)
39+
{
40+
ImGuiIO& io = ImGui::GetIO();
41+
int32_t event_type = AInputEvent_getType(inputEvent);
42+
switch (event_type)
43+
{
44+
case AINPUT_EVENT_TYPE_KEY:
45+
{
46+
int32_t event_key_code = AKeyEvent_getKeyCode(inputEvent);
47+
int32_t event_action = AKeyEvent_getAction(inputEvent);
48+
int32_t event_meta_state = AKeyEvent_getMetaState(inputEvent);
49+
50+
io.KeyCtrl = ((event_meta_state & AMETA_CTRL_ON) != 0);
51+
io.KeyShift = ((event_meta_state & AMETA_SHIFT_ON) != 0);
52+
io.KeyAlt = ((event_meta_state & AMETA_ALT_ON) != 0);
53+
54+
switch (event_action)
55+
{
56+
// FIXME: AKEY_EVENT_ACTION_DOWN and AKEY_EVENT_ACTION_UP occur at once
57+
// as soon as a touch pointer goes up from a key. We use a simple key event queue
58+
// and process one event per key per ImGui frame in ImGui_ImplAndroid_NewFrame().
59+
// ...or consider ImGui IO queue, if suitable: https://github.com/ocornut/imgui/issues/2787
60+
case AKEY_EVENT_ACTION_DOWN:
61+
case AKEY_EVENT_ACTION_UP:
62+
g_KeyEventQueues[event_key_code].push(event_action);
63+
break;
64+
default:
65+
break;
66+
}
67+
break;
68+
}
69+
case AINPUT_EVENT_TYPE_MOTION:
70+
{
71+
int32_t event_action = AMotionEvent_getAction(inputEvent);
72+
int32_t event_pointer_index = (event_action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
73+
event_action &= AMOTION_EVENT_ACTION_MASK;
74+
switch (event_action)
75+
{
76+
case AMOTION_EVENT_ACTION_DOWN:
77+
case AMOTION_EVENT_ACTION_UP:
78+
// Physical mouse buttons (and probably other physical devices) also invoke the actions AMOTION_EVENT_ACTION_DOWN/_UP,
79+
// but we have to process them separately to identify the actual button pressed. This is done below via
80+
// AMOTION_EVENT_ACTION_BUTTON_PRESS/_RELEASE. Here, we only process "FINGER" input (and "UNKNOWN", as a fallback).
81+
if((AMotionEvent_getToolType(inputEvent, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_FINGER)
82+
|| (AMotionEvent_getToolType(inputEvent, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_UNKNOWN))
83+
{
84+
io.MouseDown[0] = (event_action == AMOTION_EVENT_ACTION_DOWN) ? true : false;
85+
io.MousePos = ImVec2(
86+
AMotionEvent_getX(inputEvent, event_pointer_index),
87+
AMotionEvent_getY(inputEvent, event_pointer_index));
88+
}
89+
break;
90+
case AMOTION_EVENT_ACTION_BUTTON_PRESS:
91+
case AMOTION_EVENT_ACTION_BUTTON_RELEASE:
92+
{
93+
int32_t button_state = AMotionEvent_getButtonState(inputEvent);
94+
io.MouseDown[0] = (button_state & AMOTION_EVENT_BUTTON_PRIMARY) ? true : false;
95+
io.MouseDown[1] = (button_state & AMOTION_EVENT_BUTTON_SECONDARY) ? true : false;
96+
io.MouseDown[2] = (button_state & AMOTION_EVENT_BUTTON_TERTIARY) ? true : false;
97+
}
98+
break;
99+
case AMOTION_EVENT_ACTION_HOVER_MOVE: // Hovering: Tool moves while NOT pressed (such as a physical mouse)
100+
case AMOTION_EVENT_ACTION_MOVE: // Touch pointer moves while DOWN
101+
io.MousePos = ImVec2(
102+
AMotionEvent_getX(inputEvent, event_pointer_index),
103+
AMotionEvent_getY(inputEvent, event_pointer_index));
104+
break;
105+
case AMOTION_EVENT_ACTION_SCROLL:
106+
io.MouseWheel = AMotionEvent_getAxisValue(inputEvent, AMOTION_EVENT_AXIS_VSCROLL, event_pointer_index);
107+
io.MouseWheelH = AMotionEvent_getAxisValue(inputEvent, AMOTION_EVENT_AXIS_HSCROLL, event_pointer_index);
108+
break;
109+
default:
110+
break;
111+
}
112+
}
113+
return 1;
114+
default:
115+
break;
116+
}
117+
118+
return 0;
119+
}
120+
121+
bool ImGui_ImplAndroid_Init(ANativeWindow* window)
122+
{
123+
g_Window = window;
124+
g_Time = 0.0;
125+
126+
// Setup back-end capabilities flags
127+
ImGuiIO& io = ImGui::GetIO();
128+
io.BackendPlatformName = "imgui_impl_android";
129+
130+
// Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
131+
io.KeyMap[ImGuiKey_Tab] = AKEYCODE_TAB;
132+
io.KeyMap[ImGuiKey_LeftArrow] = AKEYCODE_DPAD_LEFT; // also covers physical keyboard arrow key
133+
io.KeyMap[ImGuiKey_RightArrow] = AKEYCODE_DPAD_RIGHT; // also covers physical keyboard arrow key
134+
io.KeyMap[ImGuiKey_UpArrow] = AKEYCODE_DPAD_UP; // also covers physical keyboard arrow key
135+
io.KeyMap[ImGuiKey_DownArrow] = AKEYCODE_DPAD_DOWN; // also covers physical keyboard arrow key
136+
io.KeyMap[ImGuiKey_PageUp] = AKEYCODE_PAGE_UP;
137+
io.KeyMap[ImGuiKey_PageDown] = AKEYCODE_PAGE_DOWN;
138+
io.KeyMap[ImGuiKey_Home] = AKEYCODE_MOVE_HOME;
139+
io.KeyMap[ImGuiKey_End] = AKEYCODE_MOVE_END;
140+
io.KeyMap[ImGuiKey_Insert] = AKEYCODE_INSERT;
141+
io.KeyMap[ImGuiKey_Delete] = AKEYCODE_FORWARD_DEL;
142+
io.KeyMap[ImGuiKey_Backspace] = AKEYCODE_DEL;
143+
io.KeyMap[ImGuiKey_Space] = AKEYCODE_SPACE;
144+
io.KeyMap[ImGuiKey_Enter] = AKEYCODE_ENTER;
145+
io.KeyMap[ImGuiKey_Escape] = AKEYCODE_ESCAPE;
146+
io.KeyMap[ImGuiKey_KeyPadEnter] = AKEYCODE_NUMPAD_ENTER;
147+
io.KeyMap[ImGuiKey_A] = AKEYCODE_A;
148+
io.KeyMap[ImGuiKey_C] = AKEYCODE_C;
149+
io.KeyMap[ImGuiKey_V] = AKEYCODE_V;
150+
io.KeyMap[ImGuiKey_X] = AKEYCODE_X;
151+
io.KeyMap[ImGuiKey_Y] = AKEYCODE_Y;
152+
io.KeyMap[ImGuiKey_Z] = AKEYCODE_Z;
153+
154+
return true;
155+
}
156+
157+
void ImGui_ImplAndroid_Shutdown()
158+
{
159+
}
160+
161+
void ImGui_ImplAndroid_NewFrame()
162+
{
163+
ImGuiIO& io = ImGui::GetIO();
164+
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().");
165+
166+
// Process queued key events
167+
// 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.
168+
for (auto& key_queue : g_KeyEventQueues)
169+
{
170+
if (key_queue.second.empty())
171+
continue;
172+
io.KeysDown[key_queue.first] = (key_queue.second.front() == AKEY_EVENT_ACTION_DOWN);
173+
key_queue.second.pop();
174+
}
175+
176+
// Setup display size (every frame to accommodate for window resizing)
177+
int32_t window_width = ANativeWindow_getWidth(g_Window);
178+
int32_t window_height = ANativeWindow_getHeight(g_Window);
179+
int display_width = window_width;
180+
int display_height = window_height;
181+
182+
io.DisplaySize = ImVec2((float)window_width, (float)window_height);
183+
if (window_width > 0 && window_height > 0)
184+
io.DisplayFramebufferScale = ImVec2((float)display_width / window_width, (float)display_height / window_height);
185+
186+
// Setup time step
187+
struct timespec current_timespec;
188+
clock_gettime(CLOCK_MONOTONIC, &current_timespec);
189+
double current_time = (double)(current_timespec.tv_sec) + (current_timespec.tv_nsec / 1000000000.0);
190+
io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f / 60.0f);
191+
g_Time = current_time;
192+
}

backends/imgui_impl_android.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// dear imgui: Platform Binding for Android native app
2+
// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3)
3+
4+
// Implemented features:
5+
// [X] Platform: Keyboard arrays indexed using AKEYCODE_* codes, e.g. ImGui::IsKeyPressed(AKEYCODE_SPACE).
6+
// [ ] Platform: Clipboard support.
7+
// [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
8+
// [ ] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android.
9+
10+
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
11+
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
12+
// https://github.com/ocornut/imgui
13+
14+
#pragma once
15+
16+
struct ANativeWindow;
17+
struct AInputEvent;
18+
19+
IMGUI_IMPL_API int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* inputEvent);
20+
IMGUI_IMPL_API bool ImGui_ImplAndroid_Init(ANativeWindow* window);
21+
IMGUI_IMPL_API void ImGui_ImplAndroid_Shutdown();
22+
IMGUI_IMPL_API void ImGui_ImplAndroid_NewFrame();

docs/BACKENDS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ In the [backends/](https://github.com/ocornut/imgui/blob/master/backends) folder
5757

5858
List of Platforms Backends:
5959

60+
imgui_impl_android.cpp ; Android native app API
6061
imgui_impl_glfw.cpp ; GLFW (Windows, macOS, Linux, etc.) http://www.glfw.org/
6162
imgui_impl_osx.mm ; macOS native API (not as feature complete as glfw/sdl backends)
6263
imgui_impl_sdl.cpp ; SDL2 (Windows, macOS, Linux, iOS, Android) https://www.libsdl.org

docs/EXAMPLES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ Changelog, so if you want to update them later it will be easier to catch up wit
7979
Allegro 5 example. <BR>
8080
= main.cpp + imgui_impl_allegro5.cpp
8181

82+
[example_android_opengl3/](https://github.com/ocornut/imgui/blob/master/examples/example_android_opengl3/) <BR>
83+
Android + OpenGL3 (ES) example. <BR>
84+
= main.cpp + imgui_impl_android.cpp + imgui_impl_opengl3.cpp
85+
8286
[example_apple_metal/](https://github.com/ocornut/imgui/blob/master/examples/example_metal/) <BR>
8387
OSX & iOS + Metal example. <BR>
8488
= main.m + imgui_impl_osx.mm + imgui_impl_metal.mm <BR>

docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Integrating Dear ImGui within your custom engine is a matter of 1) wiring mouse/
117117

118118
Officially maintained backends/bindings (in repository):
119119
- Renderers: DirectX9, DirectX10, DirectX11, DirectX12, Metal, OpenGL/ES/ES2, Vulkan, WebGPU.
120-
- Platforms: GLFW, SDL2, Win32, Glut, OSX.
120+
- Platforms: GLFW, SDL2, Win32, Glut, OSX, Android.
121121
- Frameworks: Emscripten, Allegro5, Marmalade.
122122

123123
[Third-party backends/bindings](https://github.com/ocornut/imgui/wiki/Bindings) wiki page:
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
cmake_minimum_required(VERSION 3.6)
2+
3+
project(ImguiExample)
4+
5+
set(CMAKE_CXX_STANDARD 11)
6+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
7+
set(CMAKE_CXX_EXTENSIONS OFF)
8+
9+
add_library(${CMAKE_PROJECT_NAME} SHARED
10+
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
11+
${CMAKE_CURRENT_SOURCE_DIR}/../../imgui.cpp
12+
${CMAKE_CURRENT_SOURCE_DIR}/../../imgui_demo.cpp
13+
${CMAKE_CURRENT_SOURCE_DIR}/../../imgui_draw.cpp
14+
${CMAKE_CURRENT_SOURCE_DIR}/../../imgui_tables.cpp
15+
${CMAKE_CURRENT_SOURCE_DIR}/../../imgui_widgets.cpp
16+
${CMAKE_CURRENT_SOURCE_DIR}/../../backends/imgui_impl_android.cpp
17+
${CMAKE_CURRENT_SOURCE_DIR}/../../backends/imgui_impl_opengl3.cpp
18+
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c
19+
)
20+
21+
set(CMAKE_SHARED_LINKER_FLAGS
22+
"${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate"
23+
)
24+
25+
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE
26+
IMGUI_IMPL_OPENGL_ES3
27+
)
28+
29+
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
30+
${CMAKE_CURRENT_SOURCE_DIR}/../..
31+
${CMAKE_CURRENT_SOURCE_DIR}/../../backends
32+
${ANDROID_NDK}/sources/android/native_app_glue
33+
)
34+
35+
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE
36+
android
37+
EGL
38+
GLESv3
39+
log
40+
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.cxx
2+
.externalNativeBuild
3+
build/
4+
*.iml
5+
6+
.idea
7+
.gradle
8+
local.properties
9+
10+
# Android Studio puts a Gradle wrapper here, that we don't want:
11+
gradle/
12+
gradlew*
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
apply plugin: 'com.android.application'
2+
apply plugin: 'kotlin-android'
3+
4+
android {
5+
compileSdkVersion 29
6+
buildToolsVersion "30.0.3"
7+
ndkVersion "21.4.7075529"
8+
defaultConfig {
9+
applicationId "imgui.example.android"
10+
minSdkVersion 23
11+
targetSdkVersion 29
12+
versionCode 1
13+
versionName "1.0"
14+
}
15+
16+
buildTypes {
17+
release {
18+
minifyEnabled false
19+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
20+
}
21+
}
22+
23+
externalNativeBuild {
24+
cmake {
25+
path "../../CMakeLists.txt"
26+
}
27+
}
28+
}
29+
repositories {
30+
mavenCentral()
31+
}
32+
dependencies {
33+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
34+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="imgui.example.android">
4+
5+
<application
6+
android:label="ImguiExample"
7+
android:allowBackup="false"
8+
android:fullBackupContent="false"
9+
android:hasCode="true">
10+
11+
<activity
12+
android:name="imgui.example.android.MainActivity"
13+
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
14+
android:configChanges="orientation|keyboardHidden|screenSize">
15+
<meta-data android:name="android.app.lib_name"
16+
android:value="ImguiExample" />
17+
18+
<intent-filter>
19+
<action android:name="android.intent.action.MAIN" />
20+
<category android:name="android.intent.category.LAUNCHER" />
21+
</intent-filter>
22+
</activity>
23+
</application>
24+
</manifest>

0 commit comments

Comments
 (0)