Verify-step uncovered three categories of regressions where sx code
calls into the platform's C ABI through fn-pointer types or as a
registered callback. Every site now declares the right convention.
C-side calls INTO sx → callconv(.c) on the sx function:
- platform/android.sx: sx_android_render_thread_entry is the start
routine pthread_create invokes — pthread treats it as a C function.
Also annotate the pthread_create signature so the start-routine fn-
pointer field rejects mismatching sx fns at compile time.
sx code calling typed fn-pointers cast from C symbols → callconv(.c)
on the fn-pointer type:
- opengl.sx: 55 GL fn-ptr globals + load_gl's proc-loader param. GL
trampolines are macOS/iOS/Android system code.
- std/objc.sx: the two typed `objc_msgSend` casts.
- gpu/metal.sx: ~40 typed `objc_msgSend` casts across Metal command
encoder / device / pipeline construction.
The block invoke trampolines (objc_block.sx) call back INTO sx (the
closure trampoline). The typed fn-ptr there stays default-conv so
ctx prepends correctly. Compiler change: a callconv(.c) sx function
now binds `current_ctx_ref` to `&__sx_default_context` at entry (used
to be gated by `isExportedEntryName`). C-callable sx callbacks like
the block invokes don't get their own __sx_ctx param but their bodies
still need a real Context to forward to the closure they delegate to.
Tests: 152/152 example suite + chess green on all 3 platforms.
Screenshots at /tmp/sx-game-{macos,iossim,android}.png.
457 lines
19 KiB
Plaintext
457 lines
19 KiB
Plaintext
// Android backend driven by a `#jni_main` Activity (no native_app_glue).
|
|
//
|
|
// Lifecycle:
|
|
//
|
|
// 1. Java `SxApp.onCreate(b)` → native `sx_onCreate`: stash JNIEnv* +
|
|
// Activity globals, install the AAssetManager into the C file_utils,
|
|
// construct a `SurfaceView`, register `SxApp` as its
|
|
// SurfaceHolder.Callback, set as Activity content view.
|
|
// 2. Java `SxApp.surfaceCreated(holder)` → native `sx_surfaceCreated`:
|
|
// extract the ANativeWindow from the holder's Surface, then
|
|
// `pthread_create` the render thread on first delivery.
|
|
// 3. Render thread: brings up EGL on the ANativeWindow, then calls
|
|
// `sx_app_main()` — the user's entry-point, which sets up the
|
|
// AndroidPlatform / GPU / pipeline globals and ends in
|
|
// `run_frame_loop(closure(frame))`.
|
|
// 4. `run_frame_loop` drives the loop: drain touch events queue,
|
|
// invoke `frame_fn`, `eglSwapBuffers`, sleep ~1ms.
|
|
// 5. Java `onTouchEvent` → native `sx_onTouchEvent`: push the
|
|
// (action,x,y) tuple onto a mutex-guarded queue. `poll_events`
|
|
// drains the queue into the platform's standard `Event` shape.
|
|
//
|
|
// State model: every piece of mutable Android-backend state lives on
|
|
// `AndroidPlatform` (the EGL handles, the ANativeWindow, the render
|
|
// thread, the touch ring, the frame closure, the user main fn, the
|
|
// touch mutex). Module-level globals are out — they shadow consumer
|
|
// globals on `#import` and produced an integer/float-shadowing render
|
|
// regression on chess before we eliminated them.
|
|
//
|
|
// Vulkan-compatible: same ANativeWindow drives `vkCreate*SurfaceKHR`
|
|
// without changing the lifecycle.
|
|
|
|
#import "modules/std.sx";
|
|
#import "modules/compiler.sx";
|
|
#import "modules/ui/types.sx";
|
|
#import "modules/ui/events.sx";
|
|
#import "modules/platform/types.sx";
|
|
#import "modules/platform/api.sx";
|
|
|
|
// ── Foreign Java types ──────────────────────────────────────────────────
|
|
|
|
Bundle :: #foreign #jni_class("android/os/Bundle") { }
|
|
JContext :: #foreign #jni_class("android/content/Context") {
|
|
getAssets :: (self: *Self) -> *AssetManagerJ;
|
|
}
|
|
AssetManagerJ :: #foreign #jni_class("android/content/res/AssetManager") { }
|
|
|
|
Surface :: #foreign #jni_class("android/view/Surface") { }
|
|
SurfaceHolder :: #foreign #jni_class("android/view/SurfaceHolder") {
|
|
getSurface :: (self: *Self) -> *Surface;
|
|
addCallback :: (self: *Self, cb: *SurfaceHolderCallback);
|
|
}
|
|
SurfaceView :: #foreign #jni_class("android/view/SurfaceView") {
|
|
static new :: (ctx: *JContext) -> *Self;
|
|
getHolder :: (self: *Self) -> *SurfaceHolder;
|
|
}
|
|
SurfaceHolderCallback :: #foreign #jni_class("android/view/SurfaceHolder$Callback") { }
|
|
|
|
MotionEvent :: #foreign #jni_class("android/view/MotionEvent") {
|
|
getAction :: (self: *Self) -> s32;
|
|
getX :: (self: *Self) -> f32;
|
|
getY :: (self: *Self) -> f32;
|
|
}
|
|
|
|
JView :: #foreign #jni_class("android/view/View") { }
|
|
ActivityClass :: #foreign #jni_class("android/app/Activity") {
|
|
setContentView :: (self: *Self, v: *JView);
|
|
}
|
|
|
|
// ── Foreign C/NDK decls ─────────────────────────────────────────────────
|
|
|
|
// C side of file_utils — installs the AAssetManager so `read_file_bytes`
|
|
// can route through `AAssetManager_open` when running on Android.
|
|
sx_android_set_asset_manager :: (mgr: *void) #foreign;
|
|
|
|
__android_log_print :: (prio: s32, tag: *u8, fmt: *u8) -> s32 #foreign;
|
|
usleep :: (us: u32) -> s32 #foreign;
|
|
|
|
// libandroid
|
|
ANativeWindow_fromSurface :: (env: *void, surface: *void) -> *void #foreign;
|
|
ANativeWindow_release :: (window: *void) #foreign;
|
|
ANativeWindow_getWidth :: (window: *void) -> s32 #foreign;
|
|
ANativeWindow_getHeight :: (window: *void) -> s32 #foreign;
|
|
ANativeWindow_setBuffersGeometry :: (w: *void, width: s32, height: s32, fmt: s32) -> s32 #foreign;
|
|
|
|
AAssetManager_fromJava :: (env: *void, mgr: *void) -> *void #foreign;
|
|
|
|
// pthread (link libpthread is built into bionic).
|
|
pthread_create :: (thread: *u64, attr: *void, start: (*void) -> *void callconv(.c), arg: *void) -> s32 #foreign;
|
|
pthread_mutex_init :: (m: *void, attr: *void) -> s32 #foreign;
|
|
pthread_mutex_lock :: (m: *void) -> s32 #foreign;
|
|
pthread_mutex_unlock :: (m: *void) -> s32 #foreign;
|
|
|
|
// EGL. Constants from <EGL/egl.h>. We bring up an ES3 context with a
|
|
// 24-bit RGB framebuffer + 24-bit depth (same shape chess used under
|
|
// the legacy NDK path).
|
|
EGL_DEFAULT_DISPLAY :: 0;
|
|
EGL_NO_DISPLAY :*void: null;
|
|
EGL_NO_CONTEXT :*void: null;
|
|
EGL_NO_SURFACE :*void: null;
|
|
EGL_TRUE :u32: 1;
|
|
EGL_FALSE :u32: 0;
|
|
EGL_NONE :s32: 0x3038;
|
|
EGL_RED_SIZE :s32: 0x3024;
|
|
EGL_GREEN_SIZE :s32: 0x3023;
|
|
EGL_BLUE_SIZE :s32: 0x3022;
|
|
EGL_ALPHA_SIZE :s32: 0x3021;
|
|
EGL_DEPTH_SIZE :s32: 0x3025;
|
|
EGL_RENDERABLE_TYPE :s32: 0x3040;
|
|
EGL_SURFACE_TYPE :s32: 0x3033;
|
|
EGL_OPENGL_ES3_BIT :s32: 0x00000040;
|
|
EGL_WINDOW_BIT :s32: 0x0004;
|
|
EGL_NATIVE_VISUAL_ID :s32: 0x302E;
|
|
EGL_CONTEXT_CLIENT_VERSION :s32: 0x3098;
|
|
|
|
eglGetDisplay :: (id: u64) -> *void #foreign;
|
|
eglInitialize :: (d: *void, major: *s32, minor: *s32) -> u32 #foreign;
|
|
eglChooseConfig :: (d: *void, attrs: *s32, configs: **void, sz: s32, num: *s32) -> u32 #foreign;
|
|
eglGetConfigAttrib :: (d: *void, cfg: *void, attr: s32, value: *s32) -> u32 #foreign;
|
|
eglCreateContext :: (d: *void, cfg: *void, share: *void, attrs: *s32) -> *void #foreign;
|
|
eglCreateWindowSurface :: (d: *void, cfg: *void, window: *void, attrs: *s32) -> *void #foreign;
|
|
eglMakeCurrent :: (d: *void, draw: *void, read: *void, ctx: *void) -> u32 #foreign;
|
|
eglSwapBuffers :: (d: *void, surface: *void) -> u32 #foreign;
|
|
eglDestroyContext :: (d: *void, ctx: *void) -> u32 #foreign;
|
|
eglDestroySurface :: (d: *void, surface: *void) -> u32 #foreign;
|
|
eglTerminate :: (d: *void) -> u32 #foreign;
|
|
|
|
// ── Touch ring ──────────────────────────────────────────────────────────
|
|
|
|
TouchEvent :: struct {
|
|
action: s32;
|
|
x: f32;
|
|
y: f32;
|
|
}
|
|
|
|
// ── AndroidPlatform ─────────────────────────────────────────────────────
|
|
//
|
|
// Every per-instance piece of state — EGL handles, ANativeWindow, render
|
|
// thread, touch ring + mutex, frame closure, user main fn — lives here.
|
|
// No module-level globals: a previous shape had `g_viewport_w : s32` at
|
|
// module scope, which silently shadowed chess's own
|
|
// `g_viewport_w : f32` on `#import` and caused the renderer to receive
|
|
// a logical width cast to s32 instead of the physical pixel width.
|
|
//
|
|
// `logical_w` is the consumer's design width in points (e.g. chess sets
|
|
// 414 to match an iPhone 12 layout). `begin_frame` derives `dpi_scale`
|
|
// from `pixel_w / logical_w` so the renderer sees the design canvas
|
|
// regardless of physical density. Touch coords are divided by the same
|
|
// scale before delivery so layout-side hit-testing matches.
|
|
|
|
AndroidPlatform :: struct {
|
|
title: [:0]u8 = "";
|
|
width: s32 = 0;
|
|
height: s32 = 0;
|
|
|
|
// Set by consumer code BEFORE `init` if a fixed design width is
|
|
// wanted (chess uses 414). When 0, the platform reports
|
|
// viewport = pixel (1:1 logical/physical) — same as the legacy
|
|
// hardcoded `dpi_scale = 1.0` behaviour.
|
|
logical_w: f32 = 0.0;
|
|
|
|
// ANativeWindow + EGL state (set up by sx_android_attach_window +
|
|
// egl_init on the render thread).
|
|
app_window: *void = null;
|
|
egl_display: *void = null;
|
|
egl_context: *void = null;
|
|
egl_surface: *void = null;
|
|
egl_config: *void = null;
|
|
|
|
// Pixel-size from ANativeWindow_get{Width,Height}; derived dpi_scale
|
|
// (pixel_w / logical_w when logical_w > 0, else 1.0).
|
|
pixel_w: s32 = 0;
|
|
pixel_h: s32 = 0;
|
|
dpi_scale: f32 = 1.0;
|
|
|
|
// Render thread lifecycle. `user_main_fn` is the consumer's `main`
|
|
// entry, invoked once EGL is current. `should_stop` is checked in
|
|
// the frame loop; `stop()` flips it.
|
|
render_thread: u64 = 0;
|
|
render_thread_started: bool = false;
|
|
user_main_fn: () -> void = ---;
|
|
should_stop: bool = false;
|
|
frame_closure: ?Closure() = null;
|
|
|
|
events: List(Event) = .{};
|
|
|
|
// Touch ring: single-producer (Java UI thread via sx_android_push_touch)
|
|
// / single-consumer (render thread via sx_android_drain_touches).
|
|
// pthread_mutex_t is 40 bytes on bionic (NDK 26+); over-size to 64
|
|
// for safety. `touch_mutex_inited` tracks lazy init on first use.
|
|
touch_queue: [64]TouchEvent = ---;
|
|
touch_head: u32 = 0;
|
|
touch_tail: u32 = 0;
|
|
touch_mutex_storage: [64]u8 = ---;
|
|
touch_mutex_inited: bool = false;
|
|
}
|
|
|
|
// ── User-facing helpers for the consumer's `#jni_main` Activity ────────
|
|
//
|
|
// The consumer (chess, etc.) writes their own `SxApp :: #jni_main
|
|
// #jni_class("...")` declaration with `#implements SurfaceHolderCallback`
|
|
// and the standard lifecycle methods. This file provides the primitives
|
|
// those methods call:
|
|
//
|
|
// - `sx_android_forward_assets(env, activity)` from onCreate.
|
|
// - `sx_android_attach_window(plat, env, holder)` from surfaceCreated.
|
|
// - `sx_android_detach_window(plat)` from surfaceDestroyed.
|
|
// - `sx_android_set_viewport(plat, w, h)` from surfaceChanged.
|
|
// - `sx_android_start_render_thread(plat, main_fn)` once the surface
|
|
// is up.
|
|
// - `sx_android_push_touch(plat, action, x, y)` from onTouchEvent.
|
|
|
|
// Extract the AAssetManager from the Activity and install it into the
|
|
// C file_utils so `read_file_bytes` can route through `AAssetManager_open`.
|
|
// Call this from your Activity's `onCreate` (BEFORE any asset read).
|
|
sx_android_forward_assets :: (env: *void, activity: *JContext) {
|
|
#jni_env(env) {
|
|
assets := activity.getAssets();
|
|
aam := AAssetManager_fromJava(env, xx assets);
|
|
sx_android_set_asset_manager(aam);
|
|
}
|
|
}
|
|
|
|
// Extract the ANativeWindow from a SurfaceHolder and stash it on `plat`.
|
|
// Call this from your Activity's `surfaceCreated`. The window stays
|
|
// valid until `sx_android_detach_window` runs (typically in
|
|
// `surfaceDestroyed`).
|
|
sx_android_attach_window :: (plat: *AndroidPlatform, env: *void, holder: *SurfaceHolder) {
|
|
#jni_env(env) {
|
|
surface := holder.getSurface();
|
|
plat.app_window = ANativeWindow_fromSurface(env, xx surface);
|
|
}
|
|
}
|
|
|
|
sx_android_detach_window :: (plat: *AndroidPlatform) {
|
|
if plat.app_window != null {
|
|
ANativeWindow_release(plat.app_window);
|
|
plat.app_window = null;
|
|
}
|
|
}
|
|
|
|
sx_android_set_viewport :: (plat: *AndroidPlatform, w: s32, h: s32) {
|
|
plat.pixel_w = w;
|
|
plat.pixel_h = h;
|
|
sx_android_recompute_scale(plat);
|
|
}
|
|
|
|
// Start the render thread that brings up EGL on `plat.app_window` and
|
|
// calls `entry_fn` (typically the consumer's `main`). Safe to call once
|
|
// after `sx_android_attach_window` has set the window.
|
|
sx_android_start_render_thread :: (plat: *AndroidPlatform, entry_fn: () -> void) {
|
|
if plat.render_thread_started { return; }
|
|
plat.user_main_fn = entry_fn;
|
|
pthread_create(@plat.render_thread, null, sx_android_render_thread_entry, xx plat);
|
|
plat.render_thread_started = true;
|
|
}
|
|
|
|
sx_android_render_thread_entry :: (arg: *void) -> *void callconv(.c) {
|
|
plat : *AndroidPlatform = xx arg;
|
|
while plat.app_window == null and !plat.should_stop {
|
|
usleep(1000);
|
|
}
|
|
if plat.should_stop { return null; }
|
|
|
|
if !sx_android_egl_init(plat) {
|
|
__android_log_print(6, "sxapp".ptr, "EGL bootstrap failed\n".ptr);
|
|
return null;
|
|
}
|
|
|
|
if plat.user_main_fn != null {
|
|
fn := plat.user_main_fn;
|
|
fn();
|
|
}
|
|
null;
|
|
}
|
|
|
|
// Bring up EGL on `plat.app_window`. Sets the egl_* fields and makes
|
|
// the context current. Returns false on any failure — caller bails on
|
|
// the render thread.
|
|
sx_android_egl_init :: (plat: *AndroidPlatform) -> bool {
|
|
plat.egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
if plat.egl_display == EGL_NO_DISPLAY { return false; }
|
|
|
|
major : s32 = 0;
|
|
minor : s32 = 0;
|
|
if eglInitialize(plat.egl_display, @major, @minor) == EGL_FALSE { return false; }
|
|
|
|
cfg_attrs : [13]s32 = .{
|
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
|
|
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
|
EGL_RED_SIZE, 8,
|
|
EGL_GREEN_SIZE, 8,
|
|
EGL_BLUE_SIZE, 8,
|
|
EGL_DEPTH_SIZE, 24,
|
|
EGL_NONE,
|
|
};
|
|
num_cfg : s32 = 0;
|
|
if eglChooseConfig(plat.egl_display, @cfg_attrs[0], @plat.egl_config, 1, @num_cfg) == EGL_FALSE { return false; }
|
|
if num_cfg < 1 { return false; }
|
|
|
|
visual_id : s32 = 0;
|
|
eglGetConfigAttrib(plat.egl_display, plat.egl_config, EGL_NATIVE_VISUAL_ID, @visual_id);
|
|
ANativeWindow_setBuffersGeometry(plat.app_window, 0, 0, visual_id);
|
|
|
|
ctx_attrs : [3]s32 = .{ EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE };
|
|
plat.egl_context = eglCreateContext(plat.egl_display, plat.egl_config, EGL_NO_CONTEXT, @ctx_attrs[0]);
|
|
if plat.egl_context == EGL_NO_CONTEXT { return false; }
|
|
|
|
plat.egl_surface = eglCreateWindowSurface(plat.egl_display, plat.egl_config, plat.app_window, null);
|
|
if plat.egl_surface == EGL_NO_SURFACE { return false; }
|
|
|
|
if eglMakeCurrent(plat.egl_display, plat.egl_surface, plat.egl_surface, plat.egl_context) == EGL_FALSE { return false; }
|
|
|
|
plat.pixel_w = ANativeWindow_getWidth(plat.app_window);
|
|
plat.pixel_h = ANativeWindow_getHeight(plat.app_window);
|
|
sx_android_recompute_scale(plat);
|
|
true;
|
|
}
|
|
|
|
// Recompute `dpi_scale` from `pixel_w / logical_w`. Called on viewport
|
|
// changes and after EGL bringup populates pixel_w/h. Falls back to 1.0
|
|
// when consumer didn't set logical_w (1:1 logical/physical mode).
|
|
sx_android_recompute_scale :: (plat: *AndroidPlatform) {
|
|
if plat.logical_w > 0.0 and plat.pixel_w > 0 {
|
|
plat.dpi_scale = xx plat.pixel_w / plat.logical_w;
|
|
} else {
|
|
plat.dpi_scale = 1.0;
|
|
}
|
|
}
|
|
|
|
// ── Touch event queue ───────────────────────────────────────────────────
|
|
|
|
sx_android_push_touch :: (plat: *AndroidPlatform, action: s32, x: f32, y: f32) {
|
|
sx_android_ensure_touch_mutex(plat);
|
|
pthread_mutex_lock(xx @plat.touch_mutex_storage[0]);
|
|
next := (plat.touch_tail + 1) % 64;
|
|
if next != plat.touch_head { // drop on full
|
|
plat.touch_queue[plat.touch_tail] = TouchEvent.{ action = action, x = x, y = y };
|
|
plat.touch_tail = next;
|
|
}
|
|
pthread_mutex_unlock(xx @plat.touch_mutex_storage[0]);
|
|
}
|
|
|
|
sx_android_drain_touches :: (plat: *AndroidPlatform, out: *List(Event)) {
|
|
sx_android_ensure_touch_mutex(plat);
|
|
pthread_mutex_lock(xx @plat.touch_mutex_storage[0]);
|
|
inv : f32 = if plat.dpi_scale > 0.0 then 1.0 / plat.dpi_scale else 1.0;
|
|
while plat.touch_head != plat.touch_tail {
|
|
t := plat.touch_queue[plat.touch_head];
|
|
plat.touch_head = (plat.touch_head + 1) % 64;
|
|
// MotionEvent actions: 0=DOWN, 1=UP, 2=MOVE. Map onto chess's
|
|
// existing mouse Event variants — touch becomes a left-button
|
|
// mouse on the same screen coords. Coords come in as physical
|
|
// pixels; divide by dpi_scale so layout-side hit-testing
|
|
// matches its own logical-coord frames.
|
|
pos : Point = .{ x = t.x * inv, y = t.y * inv };
|
|
if t.action == 0 {
|
|
out.append(.mouse_down(.{ position = pos, button = .left }));
|
|
} else if t.action == 1 {
|
|
out.append(.mouse_up(.{ position = pos, button = .left }));
|
|
} else if t.action == 2 {
|
|
out.append(.mouse_moved(.{ position = pos, delta = .{ x = 0, y = 0 } }));
|
|
}
|
|
}
|
|
pthread_mutex_unlock(xx @plat.touch_mutex_storage[0]);
|
|
}
|
|
|
|
sx_android_ensure_touch_mutex :: (plat: *AndroidPlatform) {
|
|
if plat.touch_mutex_inited { return; }
|
|
pthread_mutex_init(xx @plat.touch_mutex_storage[0], null);
|
|
plat.touch_mutex_inited = true;
|
|
}
|
|
|
|
// ── Platform impl ───────────────────────────────────────────────────────
|
|
|
|
impl Platform for AndroidPlatform {
|
|
init :: (self: *AndroidPlatform, title: [:0]u8, w: s32, h: s32) -> bool {
|
|
self.title = title;
|
|
self.width = w;
|
|
self.height = h;
|
|
sx_android_recompute_scale(self);
|
|
true;
|
|
}
|
|
|
|
// NOTE: method order must match the `Platform` protocol declaration
|
|
// in modules/platform/api.sx. The vtable is built in impl source
|
|
// order; mismatched order silently routes calls to the wrong method
|
|
// (chess on Android lost touch entirely because poll_events sat in
|
|
// begin_frame's slot, etc.).
|
|
|
|
run_frame_loop :: (self: *AndroidPlatform, frame_fn: Closure()) {
|
|
self.frame_closure = frame_fn;
|
|
// `frame_fn` is expected to call `g_plat.end_frame()` which does
|
|
// the `eglSwapBuffers` — don't swap again here or the back buffer
|
|
// is presented twice per render, alternating with the previous
|
|
// frame's contents → visible flicker.
|
|
while !self.should_stop {
|
|
frame_fn();
|
|
usleep(1000);
|
|
}
|
|
}
|
|
|
|
poll_events :: (self: *AndroidPlatform) -> []Event {
|
|
self.events.len = 0;
|
|
sx_android_drain_touches(self, @self.events);
|
|
result : []Event = ---;
|
|
result.ptr = self.events.items;
|
|
result.len = self.events.len;
|
|
result;
|
|
}
|
|
|
|
begin_frame :: (self: *AndroidPlatform) -> FrameContext {
|
|
// viewport_* is the logical canvas the renderer + layout see —
|
|
// pixel_w/h divided by dpi_scale. With logical_w unset (1.0
|
|
// scale) viewport == pixel.
|
|
inv : f32 = if self.dpi_scale > 0.0 then 1.0 / self.dpi_scale else 1.0;
|
|
FrameContext.{
|
|
viewport_w = xx self.pixel_w * inv,
|
|
viewport_h = xx self.pixel_h * inv,
|
|
pixel_w = self.pixel_w,
|
|
pixel_h = self.pixel_h,
|
|
dpi_scale = self.dpi_scale,
|
|
delta_time = 0.016,
|
|
target_present_time = 0.0,
|
|
};
|
|
}
|
|
|
|
end_frame :: (self: *AndroidPlatform) {
|
|
if self.egl_display != null and self.egl_surface != null {
|
|
eglSwapBuffers(self.egl_display, self.egl_surface);
|
|
}
|
|
}
|
|
|
|
safe_insets :: (self: *AndroidPlatform) -> EdgeInsets {
|
|
EdgeInsets.{};
|
|
}
|
|
|
|
keyboard :: (self: *AndroidPlatform) -> KeyboardState {
|
|
KeyboardState.zero();
|
|
}
|
|
show_keyboard :: (self: *AndroidPlatform) { }
|
|
hide_keyboard :: (self: *AndroidPlatform) { }
|
|
|
|
stop :: (self: *AndroidPlatform) {
|
|
self.should_stop = true;
|
|
}
|
|
|
|
shutdown :: (self: *AndroidPlatform) {
|
|
if self.egl_display != null {
|
|
eglMakeCurrent(self.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
|
if self.egl_surface != null { eglDestroySurface(self.egl_display, self.egl_surface); self.egl_surface = null; }
|
|
if self.egl_context != null { eglDestroyContext(self.egl_display, self.egl_context); self.egl_context = null; }
|
|
eglTerminate(self.egl_display);
|
|
self.egl_display = null;
|
|
}
|
|
}
|
|
}
|