android: dpi_scale, scissor, JNI safe-insets, touch input
Four Android UX wins landing together; all verified end-to-end on a
Pixel 7 Pro (board fills width, info-panel text renders, status bar
inset honored, tap-to-select + tap-to-move plays 1. e4).
- AndroidPlatform.init reads density via AConfiguration_getDensity
(app->config at offset 32) and sets dpi_scale = density / 160. The
hardcoded 1.0 had been making every logical unit equal one physical
pixel; ChessBoardView's 520-default size_that_fits fallback then
rendered at ~half the framebuffer width on the device, and glyphs
rasterized at literal 11-13 physical pixels were essentially invisible
on a 2340-tall display.
- gles3.sx set_scissor un-stubbed; with dpi_scale right the renderer
feeds in valid pixel bounds and the Y-flip math lands inside the
framebuffer.
- New library/vendors/sx_android_jni/sx_android_jni.c walks
activity -> window -> decorView -> rootWindowInsets via JNI and
publishes the system-bar insets. safe_insets() lazy-queries the
first call after EGL is up (decor view isn't attached at bootstrap).
- sx_android_install_input_handler sets app->onInputEvent; sx-side
sx_android_input_event translates AMotionEvent DOWN/MOVE/UP/CANCEL
into existing mouse_down/mouse_moved/mouse_up Events so the chess
board's tap-to-select + ScrollView drag path Just Works. Coordinates
divided by dpi_scale so layout-side hit tests match. poll_events
drains its slice after returning (mirrors the SDL pattern).
- src/imports.zig now routes #import c { #source / #include } paths
through the same chain as #import (importing dir -> CWD -> stdlib
search paths). Lets library-owned C helpers like the JNI bridge
live in sx/library/vendors/ without forcing consumers to vendor a
copy. Existing CWD-relative consumer layouts (chess's vendors/...)
still resolve first, so no regression.
86/86 regression tests pass.
This commit is contained in:
94
library/vendors/sx_android_jni/sx_android_jni.c
vendored
Normal file
94
library/vendors/sx_android_jni/sx_android_jni.c
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
// JNI helpers used by modules/platform/android.sx. Kept in the library
|
||||
// so consumers don't need to vendor an identically-named copy. The sx
|
||||
// compiler resolves `#source "vendors/..."` against the stdlib search
|
||||
// paths in addition to the consumer's project root.
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/native_activity.h>
|
||||
#include <android/input.h>
|
||||
#include <jni.h>
|
||||
|
||||
// Mirror of struct android_app (NDK 29 / arm64) up to the fields we touch.
|
||||
// Avoids depending on the glue header in the sx library compile path.
|
||||
struct sx_android_app_min {
|
||||
void* userData;
|
||||
void (*onAppCmd)(struct sx_android_app_min* app, int cmd);
|
||||
int (*onInputEvent)(struct sx_android_app_min* app, AInputEvent* event);
|
||||
// ...rest of struct ignored; we only assign onInputEvent.
|
||||
};
|
||||
|
||||
// Install an sx-side handler as `app->onInputEvent`. native_app_glue's
|
||||
// process_input loop calls this for every AInputEvent it pulls off the
|
||||
// input queue. Returning 1 marks the event as consumed.
|
||||
void sx_android_install_input_handler(void* app,
|
||||
int (*handler)(void* app, void* event)) {
|
||||
if (app == 0) return;
|
||||
struct sx_android_app_min* a = (struct sx_android_app_min*)app;
|
||||
a->onInputEvent = (int (*)(struct sx_android_app_min*, AInputEvent*))handler;
|
||||
}
|
||||
|
||||
// Query system-bar insets (status bar, nav bar) via JNI:
|
||||
// activity → getWindow() → getDecorView()
|
||||
// → getRootWindowInsets()
|
||||
// → getSystemWindowInset[Top|Left|Bottom|Right]()
|
||||
// Out params receive physical-pixel insets; the sx caller divides by
|
||||
// dpi_scale to convert to logical units. Falls back to zeros if the
|
||||
// view isn't attached yet (e.g. called before the first frame).
|
||||
void sx_android_query_safe_insets(void* activity_ptr,
|
||||
int* out_top, int* out_left,
|
||||
int* out_bottom, int* out_right) {
|
||||
*out_top = 0; *out_left = 0; *out_bottom = 0; *out_right = 0;
|
||||
if (activity_ptr == 0) return;
|
||||
|
||||
ANativeActivity* act = (ANativeActivity*)activity_ptr;
|
||||
JavaVM* vm = act->vm;
|
||||
if (vm == 0) return;
|
||||
|
||||
JNIEnv* env = 0;
|
||||
int already_attached = 1;
|
||||
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
|
||||
already_attached = 0;
|
||||
if ((*vm)->AttachCurrentThread(vm, &env, 0) != 0) return;
|
||||
}
|
||||
|
||||
jobject activity_obj = act->clazz;
|
||||
jclass activity_cls = (*env)->GetObjectClass(env, activity_obj);
|
||||
|
||||
jmethodID m_getWindow = (*env)->GetMethodID(env, activity_cls,
|
||||
"getWindow", "()Landroid/view/Window;");
|
||||
if (m_getWindow == 0) goto done;
|
||||
jobject window = (*env)->CallObjectMethod(env, activity_obj, m_getWindow);
|
||||
if (window == 0) goto done;
|
||||
|
||||
jclass window_cls = (*env)->GetObjectClass(env, window);
|
||||
jmethodID m_getDecorView = (*env)->GetMethodID(env, window_cls,
|
||||
"getDecorView", "()Landroid/view/View;");
|
||||
if (m_getDecorView == 0) goto done;
|
||||
jobject decor = (*env)->CallObjectMethod(env, window, m_getDecorView);
|
||||
if (decor == 0) goto done;
|
||||
|
||||
jclass view_cls = (*env)->GetObjectClass(env, decor);
|
||||
jmethodID m_getRootInsets = (*env)->GetMethodID(env, view_cls,
|
||||
"getRootWindowInsets", "()Landroid/view/WindowInsets;");
|
||||
if (m_getRootInsets == 0) goto done;
|
||||
jobject insets = (*env)->CallObjectMethod(env, decor, m_getRootInsets);
|
||||
if (insets == 0) goto done;
|
||||
|
||||
jclass insets_cls = (*env)->GetObjectClass(env, insets);
|
||||
jmethodID m_top = (*env)->GetMethodID(env, insets_cls,
|
||||
"getSystemWindowInsetTop", "()I");
|
||||
jmethodID m_left = (*env)->GetMethodID(env, insets_cls,
|
||||
"getSystemWindowInsetLeft", "()I");
|
||||
jmethodID m_bottom = (*env)->GetMethodID(env, insets_cls,
|
||||
"getSystemWindowInsetBottom", "()I");
|
||||
jmethodID m_right = (*env)->GetMethodID(env, insets_cls,
|
||||
"getSystemWindowInsetRight", "()I");
|
||||
if (m_top) *out_top = (*env)->CallIntMethod(env, insets, m_top);
|
||||
if (m_left) *out_left = (*env)->CallIntMethod(env, insets, m_left);
|
||||
if (m_bottom) *out_bottom = (*env)->CallIntMethod(env, insets, m_bottom);
|
||||
if (m_right) *out_right = (*env)->CallIntMethod(env, insets, m_right);
|
||||
|
||||
done:
|
||||
if (!already_attached) (*vm)->DetachCurrentThread(vm);
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user