2021-03-04 10:35:44 +01:00
// dear imgui: standalone example application for Android + OpenGL ES 3
2023-09-11 13:47:08 +02:00
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
2021-03-04 10:35:44 +01:00
# include "imgui.h"
# include "imgui_impl_android.h"
# include "imgui_impl_opengl3.h"
# include <android/log.h>
# include <android_native_app_glue.h>
# include <android/asset_manager.h>
# include <EGL/egl.h>
# include <GLES3/gl3.h>
2022-10-30 00:09:13 +02:00
# include <string>
2021-03-04 10:35:44 +01:00
2021-03-04 11:03:40 +01:00
// Data
2021-03-04 10:35:44 +01:00
static EGLDisplay g_EglDisplay = EGL_NO_DISPLAY ;
static EGLSurface g_EglSurface = EGL_NO_SURFACE ;
static EGLContext g_EglContext = EGL_NO_CONTEXT ;
2023-04-11 11:25:14 +02:00
static struct android_app * g_App = nullptr ;
2021-03-04 10:35:44 +01:00
static bool g_Initialized = false ;
2021-03-04 11:03:40 +01:00
static char g_LogTag [ ] = " ImGuiExample " ;
2022-10-30 00:09:13 +02:00
static std : : string g_IniFilename = " " ;
2021-03-04 10:35:44 +01:00
2021-03-04 11:03:40 +01:00
// Forward declarations of helper functions
2023-02-02 16:27:33 +01:00
static void Init ( struct android_app * app ) ;
static void Shutdown ( ) ;
static void MainLoopStep ( ) ;
2021-03-04 11:03:40 +01:00
static int ShowSoftKeyboardInput ( ) ;
static int PollUnicodeChars ( ) ;
static int GetAssetData ( const char * filename , void * * out_data ) ;
2021-03-04 10:35:44 +01:00
2023-02-02 16:27:33 +01:00
// Main code
static void handleAppCmd ( struct android_app * app , int32_t appCmd )
{
switch ( appCmd )
{
case APP_CMD_SAVE_STATE :
break ;
case APP_CMD_INIT_WINDOW :
Init ( app ) ;
break ;
case APP_CMD_TERM_WINDOW :
Shutdown ( ) ;
break ;
case APP_CMD_GAINED_FOCUS :
case APP_CMD_LOST_FOCUS :
break ;
}
}
static int32_t handleInputEvent ( struct android_app * app , AInputEvent * inputEvent )
{
return ImGui_ImplAndroid_HandleInputEvent ( inputEvent ) ;
}
void android_main ( struct android_app * app )
{
app - > onAppCmd = handleAppCmd ;
app - > onInputEvent = handleInputEvent ;
while ( true )
{
int out_events ;
struct android_poll_source * out_data ;
// Poll all events. If the app is not visible, this loop blocks until g_Initialized == true.
2024-11-04 14:24:54 +01:00
while ( ALooper_pollOnce ( g_Initialized ? 0 : - 1 , nullptr , & out_events , ( void * * ) & out_data ) > = 0 )
2023-02-02 16:27:33 +01:00
{
// Process one event
2023-04-11 11:25:14 +02:00
if ( out_data ! = nullptr )
2023-02-02 16:27:33 +01:00
out_data - > process ( app , out_data ) ;
// Exit the app by returning from within the infinite loop
if ( app - > destroyRequested ! = 0 )
{
// shutdown() should have been called already while processing the
// app command APP_CMD_TERM_WINDOW. But we play save here
if ( ! g_Initialized )
Shutdown ( ) ;
return ;
}
}
// Initiate a new frame
MainLoopStep ( ) ;
}
}
void Init ( struct android_app * app )
2021-03-04 10:35:44 +01:00
{
if ( g_Initialized )
return ;
g_App = app ;
ANativeWindow_acquire ( g_App - > window ) ;
// Initialize EGL
// This is mostly boilerplate code for EGL...
2021-03-04 11:03:40 +01:00
{
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 ( 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 " ) ;
// 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 ) ;
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 " ) ;
2023-04-11 11:25:14 +02:00
g_EglSurface = eglCreateWindowSurface ( g_EglDisplay , egl_config , g_App - > window , nullptr ) ;
2021-03-04 11:03:40 +01:00
eglMakeCurrent ( g_EglDisplay , g_EglSurface , g_EglSurface , g_EglContext ) ;
}
2021-03-04 10:35:44 +01:00
2021-03-04 11:03:40 +01:00
// Setup Dear ImGui context
2021-03-04 10:35:44 +01:00
IMGUI_CHECKVERSION ( ) ;
ImGui : : CreateContext ( ) ;
ImGuiIO & io = ImGui : : GetIO ( ) ;
2021-03-04 11:03:40 +01:00
2022-10-30 00:09:13 +02:00
// Redirect loading/saving of .ini file to our location.
// Make sure 'g_IniFilename' persists while we use Dear ImGui.
g_IniFilename = std : : string ( app - > activity - > internalDataPath ) + " /imgui.ini " ;
io . IniFilename = g_IniFilename . c_str ( ) ; ;
2021-03-04 11:03:40 +01:00
// Setup Dear ImGui style
2021-03-04 10:35:44 +01:00
ImGui : : StyleColorsDark ( ) ;
2022-07-06 20:58:20 +02:00
//ImGui::StyleColorsLight();
2021-03-04 11:03:40 +01:00
// Setup Platform/Renderer backends
2021-03-04 10:35:44 +01:00
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.
2023-04-11 11:25:14 +02:00
// - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
2021-03-04 10:35:44 +01:00
// - 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 \\ !
2021-03-04 11:03:40 +01:00
// - 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.
2021-03-04 10:35:44 +01:00
// We load the default font with increased size to improve readability on many devices with "high" DPI.
2021-03-04 11:03:40 +01:00
// FIXME: Put some effort into DPI awareness.
2024-05-16 08:58:45 +02:00
// Important: when calling AddFontFromMemoryTTF(), ownership of font_data is transferred by Dear ImGui by default (deleted is handled by Dear ImGui), unless we set FontDataOwnedByAtlas=false in ImFontConfig
2021-03-04 10:35:44 +01:00
ImFontConfig font_cfg ;
font_cfg . SizePixels = 22.0f ;
io . Fonts - > AddFontDefault ( & font_cfg ) ;
//void* font_data;
//int font_data_size;
//ImFont* font;
2022-09-19 17:45:05 +02:00
//font_data_size = GetAssetData("segoeui.ttf", &font_data);
2021-03-04 11:03:40 +01:00
//font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 16.0f);
2023-04-11 11:25:14 +02:00
//IM_ASSERT(font != nullptr);
2021-03-04 10:35:44 +01:00
//font_data_size = GetAssetData("DroidSans.ttf", &font_data);
2021-03-04 11:03:40 +01:00
//font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 16.0f);
2023-04-11 11:25:14 +02:00
//IM_ASSERT(font != nullptr);
2022-09-19 17:45:05 +02:00
//font_data_size = GetAssetData("Roboto-Medium.ttf", &font_data);
//font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 16.0f);
2023-04-11 11:25:14 +02:00
//IM_ASSERT(font != nullptr);
2022-09-19 17:45:05 +02:00
//font_data_size = GetAssetData("Cousine-Regular.ttf", &font_data);
//font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 15.0f);
2023-04-11 11:25:14 +02:00
//IM_ASSERT(font != nullptr);
2021-03-04 10:35:44 +01:00
//font_data_size = GetAssetData("ArialUni.ttf", &font_data);
2023-04-11 11:25:14 +02:00
//font = io.Fonts->AddFontFromMemoryTTF(font_data, font_data_size, 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese());
//IM_ASSERT(font != nullptr);
2021-03-04 10:35:44 +01:00
// Arbitrary scale-up
// FIXME: Put some effort into DPI awareness
ImGui : : GetStyle ( ) . ScaleAllSizes ( 3.0f ) ;
g_Initialized = true ;
}
2023-02-02 16:27:33 +01:00
void MainLoopStep ( )
2021-03-04 10:35:44 +01:00
{
2021-03-04 11:03:40 +01:00
ImGuiIO & io = ImGui : : GetIO ( ) ;
if ( g_EglDisplay = = EGL_NO_DISPLAY )
return ;
// Our state
2023-02-02 16:27:33 +01:00
// (we use static, which essentially makes the variable globals, as a convenience to keep the example code easy to follow)
2021-03-04 10:35:44 +01:00
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 ) ;
2021-03-04 11:03:40 +01:00
// Poll Unicode characters via JNI
// FIXME: do not call this every frame because of JNI overhead
PollUnicodeChars ( ) ;
2021-03-04 10:35:44 +01:00
2021-03-04 11:03:40 +01:00
// Open on-screen (soft) input if requested by Dear ImGui
static bool WantTextInputLast = false ;
if ( io . WantTextInput & & ! WantTextInputLast )
ShowSoftKeyboardInput ( ) ;
WantTextInputLast = io . WantTextInput ;
2021-03-04 10:35:44 +01:00
2021-03-04 11:03:40 +01:00
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame ( ) ;
ImGui_ImplAndroid_NewFrame ( ) ;
ImGui : : NewFrame ( ) ;
2021-03-04 10:35:44 +01:00
2021-03-04 11:03:40 +01:00
// 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 ) ;
2021-03-04 10:35:44 +01:00
2022-09-14 18:19:50 +02:00
// 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
2021-03-04 11:03:40 +01:00
{
static float f = 0.0f ;
static int counter = 0 ;
2021-03-04 10:35:44 +01:00
2021-03-04 11:03:40 +01:00
ImGui : : Begin ( " Hello, world! " ) ; // Create a window called "Hello, world!" and append into it.
2021-03-04 10:35:44 +01:00
2021-03-04 11:03:40 +01:00
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 ) ;
2021-03-04 10:35:44 +01:00
2021-03-04 11:03:40 +01:00
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
2021-03-04 10:35:44 +01:00
2021-03-04 11:03:40 +01:00
if ( ImGui : : Button ( " Button " ) ) // Buttons return true when clicked (most widgets return true when edited/activated)
counter + + ;
ImGui : : SameLine ( ) ;
ImGui : : Text ( " counter = %d " , counter ) ;
2021-03-04 10:35:44 +01:00
2023-03-10 18:35:03 +01:00
ImGui : : Text ( " Application average %.3f ms/frame (%.1f FPS) " , 1000.0f / io . Framerate , io . Framerate ) ;
2021-03-04 11:03:40 +01:00
ImGui : : End ( ) ;
}
2021-03-04 10:35:44 +01:00
2021-03-04 11:03:40 +01:00
// 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 ( ) ;
2021-03-04 10:35:44 +01:00
}
2021-03-04 11:03:40 +01:00
// 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 ) ;
2021-03-04 10:35:44 +01:00
}
2023-02-02 16:27:33 +01:00
void Shutdown ( )
2021-03-04 10:35:44 +01:00
{
if ( ! g_Initialized )
return ;
2021-03-04 11:03:40 +01:00
// Cleanup
2021-03-04 10:35:44 +01:00
ImGui_ImplOpenGL3_Shutdown ( ) ;
ImGui_ImplAndroid_Shutdown ( ) ;
ImGui : : DestroyContext ( ) ;
if ( g_EglDisplay ! = EGL_NO_DISPLAY )
{
eglMakeCurrent ( g_EglDisplay , EGL_NO_SURFACE , EGL_NO_SURFACE , EGL_NO_CONTEXT ) ;
if ( g_EglContext ! = EGL_NO_CONTEXT )
eglDestroyContext ( g_EglDisplay , g_EglContext ) ;
if ( g_EglSurface ! = EGL_NO_SURFACE )
eglDestroySurface ( g_EglDisplay , g_EglSurface ) ;
eglTerminate ( g_EglDisplay ) ;
}
g_EglDisplay = EGL_NO_DISPLAY ;
g_EglContext = EGL_NO_CONTEXT ;
g_EglSurface = EGL_NO_SURFACE ;
ANativeWindow_release ( g_App - > window ) ;
g_Initialized = false ;
}
2023-02-02 16:27:33 +01:00
// Helper functions
2021-03-04 11:03:40 +01:00
// 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 ;
2023-04-11 11:25:14 +02:00
JNIEnv * java_env = nullptr ;
2021-03-04 11:03:40 +01:00
jint jni_return = java_vm - > GetEnv ( ( void * * ) & java_env , JNI_VERSION_1_6 ) ;
if ( jni_return = = JNI_ERR )
return - 1 ;
2023-04-11 11:25:14 +02:00
jni_return = java_vm - > AttachCurrentThread ( & java_env , nullptr ) ;
2021-03-04 11:03:40 +01:00
if ( jni_return ! = JNI_OK )
return - 2 ;
jclass native_activity_clazz = java_env - > GetObjectClass ( g_App - > activity - > clazz ) ;
2023-04-11 11:25:14 +02:00
if ( native_activity_clazz = = nullptr )
2021-03-04 11:03:40 +01:00
return - 3 ;
jmethodID method_id = java_env - > GetMethodID ( native_activity_clazz , " showSoftInput " , " ()V " ) ;
2023-04-11 11:25:14 +02:00
if ( method_id = = nullptr )
2021-03-04 11:03:40 +01:00
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 ;
2023-04-11 11:25:14 +02:00
JNIEnv * java_env = nullptr ;
2021-03-04 11:03:40 +01:00
jint jni_return = java_vm - > GetEnv ( ( void * * ) & java_env , JNI_VERSION_1_6 ) ;
if ( jni_return = = JNI_ERR )
return - 1 ;
2023-04-11 11:25:14 +02:00
jni_return = java_vm - > AttachCurrentThread ( & java_env , nullptr ) ;
2021-03-04 11:03:40 +01:00
if ( jni_return ! = JNI_OK )
return - 2 ;
jclass native_activity_clazz = java_env - > GetObjectClass ( g_App - > activity - > clazz ) ;
2023-04-11 11:25:14 +02:00
if ( native_activity_clazz = = nullptr )
2021-03-04 11:03:40 +01:00
return - 3 ;
jmethodID method_id = java_env - > GetMethodID ( native_activity_clazz , " pollUnicodeChar " , " ()I " ) ;
2023-04-11 11:25:14 +02:00
if ( method_id = = nullptr )
2021-03-04 11:03:40 +01:00
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 ;
}