diff --git a/.gitignore b/.gitignore index 4fb9383..b585021 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,9 @@ t2-output /linux /build /.history -/xcode \ No newline at end of file +/xcode +.gradle +.idea +.cxx +build +kk diff --git a/README.md b/README.md index 36992e3..b30e74c 100644 --- a/README.md +++ b/README.md @@ -233,8 +233,8 @@ unsigned mfb_get_window_width(struct mfb_window *window); unsigned mfb_get_window_height(struct mfb_window *window); int mfb_get_mouse_x(struct mfb_window *window); // Last mouse pos X int mfb_get_mouse_y(struct mfb_window *window); // Last mouse pos Y +const uint8_t * mfb_get_mouse_button_buffer(struct mfb_window *window); // One byte for every button. Press (1), Release 0. (up to 8 buttons) -// Not working on Linux (X11 nor Wayland) void mfb_get_monitor_scale(struct mfb_window *window, float *scale_x, float *scale_y) // [Deprecated] Use mfb_get_monitor_scale instead void mfb_get_monitor_dpi(struct mfb_window *window, float *dpi_x, float *dpi_y) @@ -288,7 +288,7 @@ For now, no multitouch is available. mfb_update_state state = mfb_update(g_window, g_buffer); if (state != STATE_OK) { free(g_buffer); - g_buffer = 0x0; + g_buffer = 0x0; g_width = 0; g_height = 0; } @@ -303,6 +303,53 @@ cd build cmake -G Xcode -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 .. ``` +### Android (beta) + +Take a look at the example in tests/android. You need **Android Studio** to build and run it. + +**Functions:** + +Some of the MiniFB functions don't make sense on mobile. +The available functions for Android are: + +```c +struct mfb_window * mfb_open(const char *title, unsigned width, unsigned height); +struct mfb_window * mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags); // flags ignored + +mfb_update_state mfb_update(struct mfb_window *window, void *buffer); + +void mfb_close(struct mfb_window *window); + +void mfb_set_user_data(struct mfb_window *window, void *user_data); +void * mfb_get_user_data(struct mfb_window *window); + +bool mfb_set_viewport(struct mfb_window *window, unsigned offset_x, unsigned offset_y, unsigned width, unsigned height); + +void mfb_set_active_callback(struct mfb_window *window, mfb_active_func callback); +void mfb_set_mouse_button_callback(struct mfb_window *window, mfb_mouse_button_func callback); +void mfb_set_mouse_move_callback(struct mfb_window *window, mfb_mouse_move_func callback); +void mfb_set_resize_callback(struct mfb_window *window, mfb_resize_func callback); + +bool mfb_is_window_active(struct mfb_window *window); +unsigned mfb_get_window_width(struct mfb_window *window); +unsigned mfb_get_window_height(struct mfb_window *window); +int mfb_get_mouse_x(struct mfb_window *window); // Last mouse pos X +int mfb_get_mouse_y(struct mfb_window *window); // Last mouse pos Y +const uint8_t * mfb_get_mouse_button_buffer(struct mfb_window *window); // One byte for every button. Press (1), Release 0. (up to 8 buttons) +``` + +Timers are also available. + +```c +struct mfb_timer * mfb_timer_create(void); +void mfb_timer_destroy(struct mfb_timer *tmr); +void mfb_timer_reset(struct mfb_timer *tmr); +double mfb_timer_now(struct mfb_timer *tmr); +double mfb_timer_delta(struct mfb_timer *tmr); +double mfb_timer_get_frequency(void); +double mfb_timer_get_resolution(void); +``` + ### Windows If you use **CMake** the Visual Studio project will be generated (2015, 2017 and 2019 have been tested). diff --git a/include/MiniFB_ios.h b/include/MiniFB_ios.h index 0fa40ab..5975c5d 100644 --- a/include/MiniFB_ios.h +++ b/include/MiniFB_ios.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "MiniFB_enums.h" void user_implemented_init(struct mfb_window *window); diff --git a/src/MiniFB_common.c b/src/MiniFB_common.c index c442832..f6c87e4 100755 --- a/src/MiniFB_common.c +++ b/src/MiniFB_common.c @@ -1,6 +1,6 @@ -#include "MiniFB.h" +#include #include "WindowData.h" -#include +#include "MiniFB_internal.h" //------------------------------------- short int g_keycodes[512] = { 0 }; diff --git a/src/MiniFB_timer.c b/src/MiniFB_timer.c index 2dd755a..6a1ca5b 100644 --- a/src/MiniFB_timer.c +++ b/src/MiniFB_timer.c @@ -1,4 +1,4 @@ -#include "MiniFB.h" +#include #include "MiniFB_internal.h" #include diff --git a/src/android/AndroidMiniFB.c b/src/android/AndroidMiniFB.c new file mode 100644 index 0000000..f8c0523 --- /dev/null +++ b/src/android/AndroidMiniFB.c @@ -0,0 +1,473 @@ +#include +#include +#include +//-- +#include +#include +#include +//-- +#include +#include +#include "WindowData_Android.h" + +#define LOG_TAG "MiniFB" +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) + +#define kCall(func, ...) if(window_data && window_data->func) window_data->func((struct mfb_window *) window_data, __VA_ARGS__); + +#define kUnused(var) (void) var; + +struct android_app *gApplication; + +//------------------------------------- +extern void +stretch_image(uint32_t *srcImage, uint32_t srcX, uint32_t srcY, uint32_t srcWidth, uint32_t srcHeight, uint32_t srcPitch, + uint32_t *dstImage, uint32_t dstX, uint32_t dstY, uint32_t dstWidth, uint32_t dstHeight, uint32_t dstPitch); + +//------------------------------------- +extern int +main(int argc, char *argv[]); + +//------------------------------------- +static void +draw(SWindowData *window_data, ANativeWindow_Buffer *window_buffer) { + if(window_data == 0x0 || window_data->draw_buffer == 0x0 || window_buffer == 0x0) + return; + + if((window_data->buffer_width == window_buffer->width) && (window_data->buffer_height == window_buffer->height)) { + if(window_data->buffer_stride == window_buffer->stride*4) { + memcpy(window_buffer->bits, window_data->draw_buffer, window_data->buffer_width * window_data->buffer_height * 4); + } + else { + uint8_t *src = window_data->draw_buffer; + uint32_t *dst = window_buffer->bits; + for(uint32_t y=0; ywindow_height; ++y) { + memcpy(dst, src, window_data->buffer_width * 4); + src += window_data->buffer_stride; + dst += window_buffer->stride; + } + } + } + else { + uint32_t *src = window_data->draw_buffer; + uint32_t *dst = window_buffer->bits; + stretch_image( + src, 0, 0, window_data->buffer_width, window_data->buffer_height, window_data->buffer_width, + dst, 0, 0, window_buffer->width, window_buffer->height, window_buffer->stride + ); + } +} + +//------------------------------------- +static int32_t +handle_input(struct android_app* app, AInputEvent* event) { + SWindowData *window_data = (SWindowData *) app->userData; + if (window_data->close) { + //destroy_window_data(window_data); + return 0; + } + + SWindowData_Android *window_data_android = (SWindowData_Android *) window_data->specific; + + int t = AInputEvent_getType(event); + int s = AInputEvent_getSource(event); + LOGV("Event: type= %d, source=%d", t, s); + if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { + //if (AInputEvent_getSource(event) == AINPUT_SOURCE_TOUCHSCREEN) { + int action = AMotionEvent_getAction(event); + int type = action & AMOTION_EVENT_ACTION_MASK; + switch(type) { + case AMOTION_EVENT_ACTION_POINTER_DOWN: + case AMOTION_EVENT_ACTION_POINTER_UP: + { + int idx = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + int id = AMotionEvent_getPointerId(event, idx); + int x = AMotionEvent_getX(event, idx); + int y = AMotionEvent_getY(event, idx); + window_data->mouse_pos_x = x | (id << 28); + window_data->mouse_pos_y = y | (id << 28); + window_data->mouse_button_status[id & 0x07] = (action == AMOTION_EVENT_ACTION_POINTER_DOWN); + kCall(mouse_btn_func, id, 0, action == AMOTION_EVENT_ACTION_POINTER_DOWN); + } + break; + + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_UP: + { + int count = AMotionEvent_getPointerCount(event); + for(int i=0; i < count; ++i) { + int id = AMotionEvent_getPointerId(event, i); + int x = AMotionEvent_getX(event, i); + int y = AMotionEvent_getY(event, i); + window_data->mouse_pos_x = x | (id << 28); + window_data->mouse_pos_y = y | (id << 28); + window_data->mouse_button_status[id & 0x07] = (action == AMOTION_EVENT_ACTION_POINTER_DOWN); + kCall(mouse_btn_func, id, 0, action == AMOTION_EVENT_ACTION_DOWN); + } + } + break; + + case AMOTION_EVENT_ACTION_MOVE: + { + int count = AMotionEvent_getPointerCount(event); + for(int i=0; i < count; ++i){ + int id = AMotionEvent_getPointerId(event, i); + int x = AMotionEvent_getX(event, i); + int y = AMotionEvent_getY(event, i); + window_data->mouse_pos_x = x | (id << 28); + window_data->mouse_pos_y = y | (id << 28); + window_data->mouse_button_status[id & 0x07] = true; + kCall(mouse_move_func, window_data->mouse_pos_x, window_data->mouse_pos_y); + } + } + break; + + default: + LOGV("Touch: event: action=%x, source=%x, type=%d", action, s, t); + break; + } + + if(window_data != 0x0) { + window_data->is_active = true; + } + return 1; + } + else + if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) { + LOGV("Key event: action=%d keyCode=%d metaState=0x%x", + AKeyEvent_getAction(event), + AKeyEvent_getKeyCode(event), + AKeyEvent_getMetaState(event)); + } + + return 0; +} + +//------------------------------------- +static void +handle_cmd(struct android_app* app, int32_t cmd) { + static int32_t format = WINDOW_FORMAT_RGBX_8888; + static int sCurrentState = -1; + + sCurrentState = cmd; + + SWindowData *window_data; + SWindowData_Android *window_data_android; + + window_data = (SWindowData *) app->userData; + if(window_data != 0x0) { + window_data_android = (SWindowData_Android *) window_data->specific; + } + + LOGV("cmd: %d", cmd); + // Init: 10, 11, 0, 1, 3, 5, 4, 6 + // START, RESUME, INPUT_CHANGED, INIT_WINDOW, WINDOW_RESIZED, CONTENT_RECT_CHANGED, WINDOW_REDRAW_NEEDED, GAINED_FOCUS + // Pause: 13, 7, 2, 14, 12 + // PAUSE, LOST_FOCUS, TERM_WINDOW, STOP, SAVE_STATE + // Resume: 10, 11, 1, 3, 4, 6 + // START, RESUME, INIT_WINDOW, WINDOW_RESIZED, WINDOW_REDRAW_NEEDED, GAINED_FOCUS + // Close: 0, 15 + // INPUT_CHANGED, DESTROY + // Lower the shutter: 7, 0 + // LOST_FOCUS, INPUT_CHANGED + // Raising the shutter: 6, 1 + // GAINED_FOCUS, INIT_WINDOW + // Rotate: 13, 2, 14, 12, 0, 15, 10, 11, 0, 1, 3, 5, 4, 6, 4 + // PAUSE, TERM_WINDOW, STOP, SAVE_STATE, (similar to Pause but LOST_FOCUS) + // INPUT_CHANGED, DESTROY, (like Close) + // START, RESUME, INPUT_CHANGED, INIT_WINDOW, WINDOW_RESIZED, CONTENT_RECT_CHANGED, WINDOW_REDRAW_NEEDED, GAINED_FOCUS (like Init) + switch (cmd) { + // The app's activity has been started. + case APP_CMD_START: + break; + + // The app's activity has been resumed. + case APP_CMD_RESUME: + break; + + // The AInputQueue has changed. + // Upon processing this command, android_app->inputQueue will be updated to the new queue (or NULL). + case APP_CMD_INPUT_CHANGED: + break; + + // A new ANativeWindow is ready for use. + // Upon receiving this command, android_app->window will contain the new window surface. + case APP_CMD_INIT_WINDOW: + if (app->window != NULL) { + format = ANativeWindow_getFormat(app->window); + ANativeWindow_setBuffersGeometry(app->window, + ANativeWindow_getWidth(app->window), + ANativeWindow_getHeight(app->window), + format + ); + //engine_draw_frame(window_data_android); + } + break; + + // The current ANativeWindow has been resized. Please redraw with its new size. + case APP_CMD_WINDOW_RESIZED: + break; + + // The content area of the window has changed, such as from the soft input window being shown or hidden. + // You can find the new content rect in android_app::contentRect. + case APP_CMD_CONTENT_RECT_CHANGED: + if(window_data_android != 0x0) { + // This does not work + //int32_t width = window_data_android->app->contentRect.right - window_data_android->app->contentRect.left; + //int32_t height = window_data_android->app->contentRect.bottom - window_data_android->app->contentRect.top; + // TODO: Check the DPI? + if(window_data != 0x0) { + window_data->window_width = ANativeWindow_getWidth(app->window); + window_data->window_height = ANativeWindow_getHeight(app->window); + kCall(resize_func, window_data->window_width, window_data->window_height); + } + } + break; + + // The system needs that the current ANativeWindow be redrawn. + // You should redraw the window before handing this to android_app_exec_cmd() in order to avoid transient drawing glitches. + case APP_CMD_WINDOW_REDRAW_NEEDED: + break; + + // The app's activity window has gained input focus. + case APP_CMD_GAINED_FOCUS: + if(window_data != 0x0) { + window_data->is_active = true; + } + kCall(active_func, true); + break; + + // The app's activity has been paused. + case APP_CMD_PAUSE: + break; + + // The app's activity window has lost input focus. + case APP_CMD_LOST_FOCUS: + if(window_data != 0x0) { + window_data->is_active = true; + //engine_draw_frame(window_data_android); + } + kCall(active_func, false); + break; + + // The existing ANativeWindow needs to be terminated. + // Upon receiving this command, android_app->window still contains the existing window; + // after calling android_app_exec_cmd it will be set to NULL. + case APP_CMD_TERM_WINDOW: + if(window_data != 0x0) { + window_data->is_active = false; + } + ANativeWindow_setBuffersGeometry(app->window, + ANativeWindow_getWidth(app->window), + ANativeWindow_getHeight(app->window), + format + ); + break; + + // The app's activity has been stopped. + case APP_CMD_STOP: + break; + + // The app should generate a new saved state for itself, to restore from later if needed. + // If you have saved state, allocate it with malloc and place it in android_app.savedState with + // the size in android_app.savedStateSize. + // The will be freed for you later. + case APP_CMD_SAVE_STATE: + break; + + // The app's activity is being destroyed, and waiting for the app thread to clean up and exit before proceeding. + case APP_CMD_DESTROY: + if(window_data != 0x0) { + window_data->close = true; + } + break; + + // The system is running low on memory. Try to reduce your memory use. + case APP_CMD_LOW_MEMORY: + break; + + // The current device configuration has changed. + case APP_CMD_CONFIG_CHANGED: + break; + } +} + +//------------------------------------- +void +android_main(struct android_app* app) { + app->onAppCmd = handle_cmd; + app->onInputEvent = handle_input; + gApplication = app; + + // Read all pending events. + int ident; + int events; + struct android_poll_source* source; + while(app->window == 0x0) { + while ((ident = ALooper_pollAll(0, NULL, &events, (void **) &source)) >= 0) { + // Process this event. + if (source != NULL) { + source->process(app, source); + } + + // Check if we are exiting. + if (app->destroyRequested != 0) { + LOGD("Engine thread destroy requested!"); + return; + } + } + } + + char cwd[1024]; + getcwd(cwd, sizeof(cwd)); + char *argv[] = { + cwd, + (char *) app + }; + main(2, argv); +} + +//------------------------------------- +struct mfb_window * +mfb_open_ex(const char *title, unsigned width, unsigned height, unsigned flags) { + kUnused(title); + kUnused(flags); + + SWindowData *window_data = malloc(sizeof(SWindowData)); + if (window_data == 0x0) { + return 0x0; + } + memset(window_data, 0, sizeof(SWindowData)); + + SWindowData_Android *window_data_android = malloc(sizeof(SWindowData_Android)); + if(window_data_android == 0x0) { + free(window_data); + return 0x0; + } + memset(window_data_android, 0, sizeof(SWindowData_Android)); + window_data->specific = window_data_android; + + window_data->is_active = true; + window_data_android->app = gApplication; + window_data_android->timer = mfb_timer_create(); + + window_data->buffer_width = width; + window_data->buffer_height = height; + window_data->buffer_stride = width * 4; + + gApplication->userData = window_data; + if(gApplication->window != 0x0) { + window_data->window_width = ANativeWindow_getWidth(gApplication->window); + window_data->window_height = ANativeWindow_getHeight(gApplication->window); + } + + window_data->is_initialized = true; + return (struct mfb_window *) window_data; +} + +//------------------------------------- +mfb_update_state +mfb_update_ex(struct mfb_window *window, void *buffer, unsigned width, unsigned height) { + if (window == 0x0) { + return STATE_INVALID_WINDOW; + } + + SWindowData *window_data = (SWindowData *) window; + if (window_data->close) { + //destroy_window_data(window_data); + return STATE_EXIT; + } + + if (buffer == 0x0) { + return STATE_INVALID_BUFFER; + } + + window_data->draw_buffer = buffer; + window_data->buffer_width = width; + window_data->buffer_stride = width * 4; + window_data->buffer_height = height; + + SWindowData_Android *window_data_android = (SWindowData_Android *) window_data->specific; + + ANativeWindow_Buffer native_buffer; + if (ANativeWindow_lock(window_data_android->app->window, &native_buffer, NULL) < 0) { + LOGE("Unable to lock window buffer"); + return STATE_INTERNAL_ERROR; + } + + draw(window_data, &native_buffer); + + ANativeWindow_unlockAndPost(window_data_android->app->window); + + return STATE_OK; +} + +//------------------------------------- +extern double g_time_for_frame; + +bool +mfb_wait_sync(struct mfb_window *window) { + if (window == 0x0) { + return false; + } + + SWindowData *window_data = (SWindowData *) window; + if (window_data->close) { + //destroy_window_data(window_data); + return false; + } + + SWindowData_Android *window_data_android = (SWindowData_Android *) window_data->specific; + + // Read all pending events. + int ident; + int events; + struct android_poll_source *source; + double current; + + while(1) { + // If not animating, we will block forever waiting for events. + // If animating, we loop until all events are read, then continue + // to draw the next frame of animation. + while ((ident = ALooper_pollAll(window_data->is_active ? 0 : -1, NULL, &events, (void **) &source)) >= 0) { + // Process this event. + if (source != NULL) { + source->process(window_data_android->app, source); + } + + // Check if we are exiting. + if (window_data_android->app->destroyRequested != 0) { + LOGD("Engine thread destroy requested!"); + window_data->is_active = false; + window_data->close = true; + return false; + } + } + + current = mfb_timer_now(window_data_android->timer); + if (current >= g_time_for_frame) { + break; + } + } + mfb_timer_reset(window_data_android->timer); + + return true; +} + +//------------------------------------- +void +mfb_get_monitor_scale(struct mfb_window *window, float *scale_x, float *scale_y) { + kUnused(window); + + if(scale_x != 0x0) { + *scale_x = 1.0f; + } + if(scale_y != 0x0) { + *scale_y = 1.0f; + } +} diff --git a/src/android/WindowData_Android.h b/src/android/WindowData_Android.h new file mode 100644 index 0000000..a5ec31c --- /dev/null +++ b/src/android/WindowData_Android.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +//------------------------------------- +typedef struct { + struct android_app *app; + struct mfb_timer *timer; +} SWindowData_Android; diff --git a/src/x11/X11MiniFB.c b/src/x11/X11MiniFB.c index 446eb5e..3f910e5 100644 --- a/src/x11/X11MiniFB.c +++ b/src/x11/X11MiniFB.c @@ -438,7 +438,7 @@ extern bool g_use_hardware_sync; bool mfb_wait_sync(struct mfb_window *window) { if (window == 0x0) { - return STATE_INVALID_WINDOW; + return false; } SWindowData *window_data = (SWindowData *) window; diff --git a/tests/android/.idea/.gitignore b/tests/android/.idea/.gitignore new file mode 100644 index 0000000..eaf91e2 --- /dev/null +++ b/tests/android/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/tests/android/app/build.gradle b/tests/android/app/build.gradle new file mode 100644 index 0000000..db30a5d --- /dev/null +++ b/tests/android/app/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 30 + ndkVersion '22.1.7171670' + + defaultConfig { + applicationId 'com.example.noise' + minSdkVersion 14 + targetSdkVersion 29 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + externalNativeBuild { + cmake { + version '3.18.1' + path 'src/main/cpp/CMakeLists.txt' + } + } +} diff --git a/tests/android/app/src/main/AndroidManifest.xml b/tests/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2074c74 --- /dev/null +++ b/tests/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/tests/android/app/src/main/cpp/CMakeLists.txt b/tests/android/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..f3b43d1 --- /dev/null +++ b/tests/android/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,68 @@ +cmake_minimum_required(VERSION 3.18.1) +project(noise) + +# Set our flags +#-------------------------------------- +#add_compile_options("$<$:-g>") +#add_compile_options("$,-O0,-O2>") +#if(APPLE) +# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic -Wno-switch -Wno-unused-function -Wno-implicit-fallthrough") +#else() +# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic -Wno-switch -Wno-unused-function -Wno-implicit-fallthrough -Wno-cast-function-type") +#endif() +#set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}") +#set(CMAKE_OBJC_FLAGS "${CMAKE_C_FLAGS}") +#set(CMAKE_OBJCXX_FLAGS "${CMAKE_CXX_FLAGS}") + +# build native_app_glue as a static lib +add_library(native_app_glue STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c +) + +add_library(minifb STATIC + ${CMAKE_CURRENT_LIST_DIR}/../../../../../../include/MiniFB.h + ${CMAKE_CURRENT_LIST_DIR}/../../../../../../include/MiniFB_cpp.h + ${CMAKE_CURRENT_LIST_DIR}/../../../../../../include/MiniFB_enums.h + + ${CMAKE_CURRENT_LIST_DIR}/../../../../../../src/MiniFB_common.c + ${CMAKE_CURRENT_LIST_DIR}/../../../../../../src/MiniFB_cpp.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../../../../../src/MiniFB_internal.c + ${CMAKE_CURRENT_LIST_DIR}/../../../../../../src/MiniFB_internal.h + ${CMAKE_CURRENT_LIST_DIR}/../../../../../../src/MiniFB_timer.c + ${CMAKE_CURRENT_LIST_DIR}/../../../../../../src/MiniFB_linux.c + ${CMAKE_CURRENT_LIST_DIR}/../../../../../../src/WindowData.h + + ${CMAKE_CURRENT_LIST_DIR}/../../../../../../src/android/AndroidMiniFB.c + ${CMAKE_CURRENT_LIST_DIR}/../../../../../../src/android/WindowData_Android.h +) + +target_include_directories(minifb PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue + ${CMAKE_CURRENT_LIST_DIR}/../../../../../../include + ${CMAKE_CURRENT_LIST_DIR}/../../../../../../src +) + +# now build app's shared lib +add_library(noise SHARED + noise.c +) + +target_include_directories(noise PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue + ${CMAKE_CURRENT_LIST_DIR}/../../../../../../include +) + +# Export ANativeActivity_onCreate(), +# Refer to: https://github.com/android-ndk/ndk/issues/381. +set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate" +) + +# add lib dependencies +target_link_libraries(noise + android + minifb + native_app_glue + log + m +) diff --git a/tests/android/app/src/main/cpp/noise.c b/tests/android/app/src/main/cpp/noise.c new file mode 100644 index 0000000..ef5d99e --- /dev/null +++ b/tests/android/app/src/main/cpp/noise.c @@ -0,0 +1,149 @@ +#include +#include +#include +#include +#include + +#define LOG_TAG "Noise" +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) + +#define kUnused(var) (void) var; + +#define kTouchIdMask 0xf0000000 +#define kTouchPosMask 0x0fffffff +#define kTouchIdShift 28 + +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) + +typedef struct { + bool enabled; + int x, y; +} Pos; + +static uint32_t g_width = 200; +static uint32_t g_height = 100; +static uint32_t *g_buffer = 0x0; +static Pos g_positions[16] = {}; + +void +active(struct mfb_window *window, bool isActive) { + LOGI("active: %d", isActive); +} + +void +resize(struct mfb_window *window, int width, int height) { + LOGI("resize: %d, %d", width, height); + g_width = (width >> 1); + g_height = (height >> 1); + g_buffer = realloc(g_buffer, g_width * g_height * 4); +} + +void +keyboard(struct mfb_window *window, mfb_key key, mfb_key_mod mod, bool isPressed) { + LOGI("keyboard:"); +} + +void +char_input(struct mfb_window *window, unsigned int charCode) { + LOGI("char_input:"); +} + +void +mouse_btn(struct mfb_window *window, mfb_mouse_button button, mfb_key_mod mod, bool isPressed) { + int x = (mfb_get_mouse_x(window) & kTouchPosMask) >> 1; + int y = (mfb_get_mouse_y(window) & kTouchPosMask) >> 1; + g_positions[button].enabled = isPressed; + g_positions[button].x = x; + g_positions[button].y = y; + LOGI("mouse_btn: button: id %d=%d, x=%d, y=%d", (int)button, (int) isPressed, x, y); +} + +void +mouse_move(struct mfb_window *window, int x, int y) { + int id = (x & kTouchIdMask) >> kTouchIdShift; + x = (x & kTouchPosMask) >> 1; + y = (y & kTouchPosMask) >> 1; + g_positions[id].enabled = true; + g_positions[id].x = x; + g_positions[id].y = y; + LOGI("mouse_move: %d, %d [%d]", x, y, id); +} + +void +mouse_scroll(struct mfb_window *window, mfb_key_mod mod, float deltaX, float deltaY) { + LOGI("mouse_scroll:"); +} + +// I'm not sure that we can use this function name but it works +//------------------------------------- +int +main(int argc, char *argv[]) { + kUnused(argc); + kUnused(argv); + uint32_t i, noise, carry, seed = 0xbeef; + + struct mfb_window *window = mfb_open("Ignored", g_width, g_height); + if(window == 0x0) + return 0; + + mfb_set_active_callback(window, active); + mfb_set_resize_callback(window, resize); + mfb_set_keyboard_callback(window, keyboard); // not working + mfb_set_char_input_callback(window, char_input); // not working + mfb_set_mouse_button_callback(window, mouse_btn); + mfb_set_mouse_move_callback(window, mouse_move); + mfb_set_mouse_scroll_callback(window, mouse_scroll); // not working + + g_buffer = (uint32_t *) malloc(g_width * g_height * 4); + + mfb_update_state state; + do { + bool isActive = mfb_is_window_active(window); + unsigned width = mfb_get_window_width(window); + unsigned height = mfb_get_window_height(window); + int mouseX = mfb_get_mouse_x(window); + int mouseY = mfb_get_mouse_y(window); + float scrollX = mfb_get_mouse_scroll_x(window); // not working + float scrollY = mfb_get_mouse_scroll_y(window); // not working + const uint8_t *buttons = mfb_get_mouse_button_buffer(window); + const uint8_t *keys = mfb_get_key_buffer(window); // not working + + for (i = 0; i < g_width * g_height; ++i) { + noise = seed; + noise >>= 3; + noise ^= seed; + carry = noise & 1; + noise >>= 1; + seed >>= 1; + seed |= (carry << 30); + noise &= 0xFF; + g_buffer[i] = MFB_RGB(noise, noise, noise); + } + + for (int p = 0; p < 16; ++p) { + if (g_positions[p].enabled) { + int minX = MAX(g_positions[p].x - 16, 0); + int maxX = MIN(g_positions[p].x + 16, g_width); + int minY = MAX(g_positions[p].y - 16, 0); + int maxY = MIN(g_positions[p].y + 16, g_height); + for(int y=minY; y + + Noise + diff --git a/tests/android/build.gradle b/tests/android/build.gradle new file mode 100644 index 0000000..3706753 --- /dev/null +++ b/tests/android/build.gradle @@ -0,0 +1,17 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.2.2' + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} diff --git a/tests/android/gradle/wrapper/gradle-wrapper.jar b/tests/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f3d88b1 Binary files /dev/null and b/tests/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/tests/android/gradle/wrapper/gradle-wrapper.properties b/tests/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1f3fdbc --- /dev/null +++ b/tests/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/tests/android/gradlew b/tests/android/gradlew new file mode 100644 index 0000000..2fe81a7 --- /dev/null +++ b/tests/android/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/tests/android/gradlew.bat b/tests/android/gradlew.bat new file mode 100644 index 0000000..24467a1 --- /dev/null +++ b/tests/android/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/tests/android/settings.gradle b/tests/android/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/tests/android/settings.gradle @@ -0,0 +1 @@ +include ':app'