Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Native Android backend + example #3446

Merged
merged 1 commit into from
Mar 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -458,10 +458,20 @@ jobs:
popd
make -C examples/example_emscripten_wgpu

Android:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2

- name: Build example_android_opengl3
run: |
cd examples/example_android_opengl3/android
gradle assembleDebug

Discord-CI:
runs-on: ubuntu-18.04
if: always()
needs: [Windows, Linux, MacOS, iOS, Emscripten]
needs: [Windows, Linux, MacOS, iOS, Emscripten, Android]
steps:
- uses: dearimgui/github_discord_notifier@latest
with:
Expand Down
192 changes: 192 additions & 0 deletions backends/imgui_impl_android.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// dear imgui: Platform Binding for Android native app
// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3)

// Implemented features:
// [X] Platform: Keyboard arrays indexed using AKEYCODE_* codes, e.g. ImGui::IsKeyPressed(AKEYCODE_SPACE).
// [ ] 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.

// 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.
// https://github.com/ocornut/imgui

// 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

#include "imgui.h"
#include "imgui_impl_android.h"
#include <time.h>
#include <map>
#include <queue>

// Android
#include <android/native_window.h>
#include <android/input.h>
#include <android/keycodes.h>
#include <android/log.h>

static double g_Time = 0.0;
static ANativeWindow* g_Window;
static char g_LogTag[] = "ImguiExample";
static std::map<int32_t, std::queue<int32_t>> g_KeyEventQueues; // FIXME: Remove dependency on map and queue once we use upcoming input queue.

int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* inputEvent)
{
ImGuiIO& io = ImGui::GetIO();
int32_t event_type = AInputEvent_getType(inputEvent);
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);

io.KeyCtrl = ((event_meta_state & AMETA_CTRL_ON) != 0);
io.KeyShift = ((event_meta_state & AMETA_SHIFT_ON) != 0);
io.KeyAlt = ((event_meta_state & AMETA_ALT_ON) != 0);

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
case AKEY_EVENT_ACTION_DOWN:
case AKEY_EVENT_ACTION_UP:
g_KeyEventQueues[event_key_code].push(event_action);
break;
default:
break;
}
break;
}
case AINPUT_EVENT_TYPE_MOTION:
{
int32_t event_action = AMotionEvent_getAction(inputEvent);
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)
{
case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_UP:
// 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))
{
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));
}
break;
case AMOTION_EVENT_ACTION_BUTTON_PRESS:
case AMOTION_EVENT_ACTION_BUTTON_RELEASE:
{
int32_t button_state = AMotionEvent_getButtonState(inputEvent);
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));
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);
break;
default:
break;
}
}
return 1;
default:
break;
}

return 0;
}

bool ImGui_ImplAndroid_Init(ANativeWindow* window)
{
g_Window = window;
g_Time = 0.0;

// Setup back-end 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.
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
io.KeyMap[ImGuiKey_UpArrow] = AKEYCODE_DPAD_UP; // also covers physical keyboard arrow key
io.KeyMap[ImGuiKey_DownArrow] = AKEYCODE_DPAD_DOWN; // also covers physical keyboard arrow key
io.KeyMap[ImGuiKey_PageUp] = AKEYCODE_PAGE_UP;
io.KeyMap[ImGuiKey_PageDown] = AKEYCODE_PAGE_DOWN;
io.KeyMap[ImGuiKey_Home] = AKEYCODE_MOVE_HOME;
io.KeyMap[ImGuiKey_End] = AKEYCODE_MOVE_END;
io.KeyMap[ImGuiKey_Insert] = AKEYCODE_INSERT;
io.KeyMap[ImGuiKey_Delete] = AKEYCODE_FORWARD_DEL;
io.KeyMap[ImGuiKey_Backspace] = AKEYCODE_DEL;
io.KeyMap[ImGuiKey_Space] = AKEYCODE_SPACE;
io.KeyMap[ImGuiKey_Enter] = AKEYCODE_ENTER;
io.KeyMap[ImGuiKey_Escape] = AKEYCODE_ESCAPE;
io.KeyMap[ImGuiKey_KeyPadEnter] = AKEYCODE_NUMPAD_ENTER;
io.KeyMap[ImGuiKey_A] = AKEYCODE_A;
io.KeyMap[ImGuiKey_C] = AKEYCODE_C;
io.KeyMap[ImGuiKey_V] = AKEYCODE_V;
io.KeyMap[ImGuiKey_X] = AKEYCODE_X;
io.KeyMap[ImGuiKey_Y] = AKEYCODE_Y;
io.KeyMap[ImGuiKey_Z] = AKEYCODE_Z;

return true;
}

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().");

// 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.
for (auto& key_queue : g_KeyEventQueues)
{
if (key_queue.second.empty())
continue;
io.KeysDown[key_queue.first] = (key_queue.second.front() == AKEY_EVENT_ACTION_DOWN);
key_queue.second.pop();
}

// 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;
}
22 changes: 22 additions & 0 deletions backends/imgui_impl_android.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// dear imgui: Platform Binding for Android native app
// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3)

// Implemented features:
// [X] Platform: Keyboard arrays indexed using AKEYCODE_* codes, e.g. ImGui::IsKeyPressed(AKEYCODE_SPACE).
// [ ] 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.

// 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.
// https://github.com/ocornut/imgui

#pragma once

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 void ImGui_ImplAndroid_Shutdown();
IMGUI_IMPL_API void ImGui_ImplAndroid_NewFrame();
1 change: 1 addition & 0 deletions docs/BACKENDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ In the [backends/](https://github.com/ocornut/imgui/blob/master/backends) folder

List of Platforms Backends:

imgui_impl_android.cpp ; Android native app API
imgui_impl_glfw.cpp ; GLFW (Windows, macOS, Linux, etc.) http://www.glfw.org/
imgui_impl_osx.mm ; macOS native API (not as feature complete as glfw/sdl backends)
imgui_impl_sdl.cpp ; SDL2 (Windows, macOS, Linux, iOS, Android) https://www.libsdl.org
Expand Down
4 changes: 4 additions & 0 deletions docs/EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ Changelog, so if you want to update them later it will be easier to catch up wit
Allegro 5 example. <BR>
= main.cpp + imgui_impl_allegro5.cpp

[example_android_opengl3/](https://github.com/ocornut/imgui/blob/master/examples/example_android_opengl3/) <BR>
Android + OpenGL3 (ES) example. <BR>
= main.cpp + imgui_impl_android.cpp + imgui_impl_opengl3.cpp

[example_apple_metal/](https://github.com/ocornut/imgui/blob/master/examples/example_metal/) <BR>
OSX & iOS + Metal example. <BR>
= main.m + imgui_impl_osx.mm + imgui_impl_metal.mm <BR>
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ Integrating Dear ImGui within your custom engine is a matter of 1) wiring mouse/

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

[Third-party backends/bindings](https://github.com/ocornut/imgui/wiki/Bindings) wiki page:
Expand Down
40 changes: 40 additions & 0 deletions examples/example_android_opengl3/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
cmake_minimum_required(VERSION 3.6)

project(ImguiExample)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_library(${CMAKE_PROJECT_NAME} SHARED
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../imgui.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../imgui_demo.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../imgui_draw.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../imgui_tables.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../imgui_widgets.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../backends/imgui_impl_android.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../backends/imgui_impl_opengl3.cpp
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c
)

set(CMAKE_SHARED_LINKER_FLAGS
"${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate"
)

target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE
IMGUI_IMPL_OPENGL_ES3
)

target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../..
${CMAKE_CURRENT_SOURCE_DIR}/../../backends
${ANDROID_NDK}/sources/android/native_app_glue
)

target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE
android
EGL
GLESv3
log
)
12 changes: 12 additions & 0 deletions examples/example_android_opengl3/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.cxx
.externalNativeBuild
build/
*.iml

.idea
.gradle
local.properties

# Android Studio puts a Gradle wrapper here, that we don't want:
gradle/
gradlew*
34 changes: 34 additions & 0 deletions examples/example_android_opengl3/android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 29
buildToolsVersion "30.0.3"
ndkVersion "21.4.7075529"
defaultConfig {
applicationId "imgui.example.android"
minSdkVersion 23
targetSdkVersion 29
versionCode 1
versionName "1.0"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
}
}

externalNativeBuild {
cmake {
path "../../CMakeLists.txt"
}
}
}
repositories {
mavenCentral()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="imgui.example.android">

<application
android:label="ImguiExample"
android:allowBackup="false"
android:fullBackupContent="false"
android:hasCode="true">

<activity
android:name="imgui.example.android.MainActivity"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:configChanges="orientation|keyboardHidden|screenSize">
<meta-data android:name="android.app.lib_name"
android:value="ImguiExample" />

<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Loading