initial commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
*.apk
|
||||
*.apk.*
|
||||
*keystore
|
||||
android/lib/lib/arm64-v8a/libmain.so
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.illusionman1212.dogfood2"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
|
||||
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="37" />
|
||||
<uses-feature android:glEsVersion="0x00030002" android:required="true" />
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:label="Dogfooding"
|
||||
android:hasCode="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:debuggable="false">
|
||||
|
||||
<activity android:name="android.app.NativeActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
|
||||
|
||||
<meta-data android:name="android.app.lib_name"
|
||||
android:value="main" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="260dp"
|
||||
android:height="260dp"
|
||||
android:viewportWidth="260"
|
||||
android:viewportHeight="260">
|
||||
<path
|
||||
android:pathData="m260.84,130.84c0,71.8 -58.2,130 -130,130 -71.8,0 -130,-58.2 -130,-130 0,-71.8 58.2,-130 130,-130 71.8,0 130,58.2 130,130z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:centerX="29.32"
|
||||
android:centerY="29.32"
|
||||
android:gradientRadius="203.03"
|
||||
android:type="radial">
|
||||
<item android:offset="0" android:color="#FF189CFF"/>
|
||||
<item android:offset="0.58" android:color="#FF138EEB"/>
|
||||
<item android:offset="0.8" android:color="#FF0971BF"/>
|
||||
<item android:offset="1" android:color="#FF005493"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="260dp"
|
||||
android:height="260dp"
|
||||
android:viewportWidth="260"
|
||||
android:viewportHeight="260">
|
||||
<path
|
||||
android:pathData="m101.51,171.2c-1.92,-1.32 -3.44,-2.6 -3.44,-2.6L147.46,83.06c0,0 2.03,0.75 3.71,1.56 2.12,1.01 3.87,2 3.87,2 23.94,13.82 32.16,44.48 18.33,68.42 -13.82,23.94 -44.48,32.16 -68.42,18.33 0,0 -1.68,-0.96 -3.45,-2.18zM150.87,93.85 L109.13,166.15c19.95,11.52 45.5,4.67 57.02,-15.28 11.52,-19.95 4.67,-45.5 -15.28,-57.02zM88.9,158.64c-1.68,-2.43 -2.28,-3.6 -2.28,-3.6 -8.63,-14.99 -9.26,-34.05 0,-50.09 9.26,-16.04 26.08,-25.02 43.38,-25.04 0,0 2.33,-0.01 4.28,0.18 2.93,0.28 4.88,0.66 4.88,0.66L91.92,162.56c0,0 -1.12,-1.16 -3.03,-3.92zM125,88.57c-12.59,1.53 -24.33,8.75 -31.15,20.56 -6.82,11.82 -7.2,25.59 -2.23,37.26z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Create a debug signing key to sign our application.
|
||||
if [ ! -f .keystore ]; then
|
||||
keytool -genkey -dname "CN=Android Debug, O=Android, C=US" -keystore .keystore -alias androiddebugkey -storepass android -keypass android -keyalg RSA -validity 30000
|
||||
if [ $? -ne 0 ]; then exit 1; fi
|
||||
fi
|
||||
|
||||
# Build the shared lib
|
||||
if [[ $1 == "release" ]]; then
|
||||
odin build . -target:linux_arm64 -subtarget:android -build-mode:shared -o:speed -define:RELEASE_BUILD=true
|
||||
else
|
||||
odin build . -target:linux_arm64 -subtarget:android -build-mode:shared -debug
|
||||
fi
|
||||
|
||||
name=$(basename "$PWD")
|
||||
|
||||
# Create the proper lib directories and move the shared lib to them
|
||||
# Rename the shared lib to match what we specified in the AndroidManifest.xml file
|
||||
# for the android.app.lib_name metadata value
|
||||
mkdir -p android/lib/lib/arm64-v8a
|
||||
mv $name.so android/lib/lib/arm64-v8a/libmain.so || exit 1
|
||||
|
||||
# Bundle the directory into an apk
|
||||
odin bundle android android -android-keystore:.keystore -android-keystore-password:"android"
|
||||
|
||||
cleanup() {
|
||||
rm test.apk-build
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
// Array marshalling between Odin slices and Java arrays.
|
||||
//
|
||||
// GENERATED by jnigen. Do not edit.
|
||||
//
|
||||
// Every to_* returns a LOCAL ref the caller must free; every from_* allocates
|
||||
// with the given allocator and the caller owns the result.
|
||||
package jni
|
||||
|
||||
import "shared:android"
|
||||
|
||||
to_bool_array :: proc(e: Env, vals: []bool) -> android.jbooleanArray {
|
||||
arr := e^^.NewBooleanArray(e, android.jsize(len(vals)))
|
||||
if arr == nil || len(vals) == 0 {
|
||||
return arr
|
||||
}
|
||||
// Identical layout, so the slice can be handed over without a copy.
|
||||
e^^.SetBooleanArrayRegion(e, arr, 0, android.jsize(len(vals)), cast([^]android.jboolean)raw_data(vals))
|
||||
return arr
|
||||
}
|
||||
|
||||
from_bool_array :: proc(e: Env, arr: android.jbooleanArray, allocator := context.allocator) -> []bool {
|
||||
if arr == nil {
|
||||
return nil
|
||||
}
|
||||
n := int(e^^.GetArrayLength(e, android.jarray(arr)))
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]bool, n, allocator)
|
||||
e^^.GetBooleanArrayRegion(e, arr, 0, android.jsize(n), cast([^]android.jboolean)raw_data(out))
|
||||
return out
|
||||
}
|
||||
|
||||
to_i8_array :: proc(e: Env, vals: []i8) -> android.jbyteArray {
|
||||
arr := e^^.NewByteArray(e, android.jsize(len(vals)))
|
||||
if arr == nil || len(vals) == 0 {
|
||||
return arr
|
||||
}
|
||||
// Identical layout, so the slice can be handed over without a copy.
|
||||
e^^.SetByteArrayRegion(e, arr, 0, android.jsize(len(vals)), cast([^]android.jbyte)raw_data(vals))
|
||||
return arr
|
||||
}
|
||||
|
||||
from_i8_array :: proc(e: Env, arr: android.jbyteArray, allocator := context.allocator) -> []i8 {
|
||||
if arr == nil {
|
||||
return nil
|
||||
}
|
||||
n := int(e^^.GetArrayLength(e, android.jarray(arr)))
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]i8, n, allocator)
|
||||
e^^.GetByteArrayRegion(e, arr, 0, android.jsize(n), cast([^]android.jbyte)raw_data(out))
|
||||
return out
|
||||
}
|
||||
|
||||
to_u16_array :: proc(e: Env, vals: []u16) -> android.jcharArray {
|
||||
arr := e^^.NewCharArray(e, android.jsize(len(vals)))
|
||||
if arr == nil || len(vals) == 0 {
|
||||
return arr
|
||||
}
|
||||
// Identical layout, so the slice can be handed over without a copy.
|
||||
e^^.SetCharArrayRegion(e, arr, 0, android.jsize(len(vals)), cast([^]android.jchar)raw_data(vals))
|
||||
return arr
|
||||
}
|
||||
|
||||
from_u16_array :: proc(e: Env, arr: android.jcharArray, allocator := context.allocator) -> []u16 {
|
||||
if arr == nil {
|
||||
return nil
|
||||
}
|
||||
n := int(e^^.GetArrayLength(e, android.jarray(arr)))
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]u16, n, allocator)
|
||||
e^^.GetCharArrayRegion(e, arr, 0, android.jsize(n), cast([^]android.jchar)raw_data(out))
|
||||
return out
|
||||
}
|
||||
|
||||
to_i16_array :: proc(e: Env, vals: []i16) -> android.jshortArray {
|
||||
arr := e^^.NewShortArray(e, android.jsize(len(vals)))
|
||||
if arr == nil || len(vals) == 0 {
|
||||
return arr
|
||||
}
|
||||
// Identical layout, so the slice can be handed over without a copy.
|
||||
e^^.SetShortArrayRegion(e, arr, 0, android.jsize(len(vals)), cast([^]android.jshort)raw_data(vals))
|
||||
return arr
|
||||
}
|
||||
|
||||
from_i16_array :: proc(e: Env, arr: android.jshortArray, allocator := context.allocator) -> []i16 {
|
||||
if arr == nil {
|
||||
return nil
|
||||
}
|
||||
n := int(e^^.GetArrayLength(e, android.jarray(arr)))
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]i16, n, allocator)
|
||||
e^^.GetShortArrayRegion(e, arr, 0, android.jsize(n), cast([^]android.jshort)raw_data(out))
|
||||
return out
|
||||
}
|
||||
|
||||
to_i32_array :: proc(e: Env, vals: []i32) -> android.jintArray {
|
||||
arr := e^^.NewIntArray(e, android.jsize(len(vals)))
|
||||
if arr == nil || len(vals) == 0 {
|
||||
return arr
|
||||
}
|
||||
// Identical layout, so the slice can be handed over without a copy.
|
||||
e^^.SetIntArrayRegion(e, arr, 0, android.jsize(len(vals)), cast([^]android.jint)raw_data(vals))
|
||||
return arr
|
||||
}
|
||||
|
||||
from_i32_array :: proc(e: Env, arr: android.jintArray, allocator := context.allocator) -> []i32 {
|
||||
if arr == nil {
|
||||
return nil
|
||||
}
|
||||
n := int(e^^.GetArrayLength(e, android.jarray(arr)))
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]i32, n, allocator)
|
||||
e^^.GetIntArrayRegion(e, arr, 0, android.jsize(n), cast([^]android.jint)raw_data(out))
|
||||
return out
|
||||
}
|
||||
|
||||
to_i64_array :: proc(e: Env, vals: []i64) -> android.jlongArray {
|
||||
arr := e^^.NewLongArray(e, android.jsize(len(vals)))
|
||||
if arr == nil || len(vals) == 0 {
|
||||
return arr
|
||||
}
|
||||
// Identical layout, so the slice can be handed over without a copy.
|
||||
e^^.SetLongArrayRegion(e, arr, 0, android.jsize(len(vals)), cast([^]android.jlong)raw_data(vals))
|
||||
return arr
|
||||
}
|
||||
|
||||
from_i64_array :: proc(e: Env, arr: android.jlongArray, allocator := context.allocator) -> []i64 {
|
||||
if arr == nil {
|
||||
return nil
|
||||
}
|
||||
n := int(e^^.GetArrayLength(e, android.jarray(arr)))
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]i64, n, allocator)
|
||||
e^^.GetLongArrayRegion(e, arr, 0, android.jsize(n), cast([^]android.jlong)raw_data(out))
|
||||
return out
|
||||
}
|
||||
|
||||
to_f32_array :: proc(e: Env, vals: []f32) -> android.jfloatArray {
|
||||
arr := e^^.NewFloatArray(e, android.jsize(len(vals)))
|
||||
if arr == nil || len(vals) == 0 {
|
||||
return arr
|
||||
}
|
||||
// Identical layout, so the slice can be handed over without a copy.
|
||||
e^^.SetFloatArrayRegion(e, arr, 0, android.jsize(len(vals)), cast([^]android.jfloat)raw_data(vals))
|
||||
return arr
|
||||
}
|
||||
|
||||
from_f32_array :: proc(e: Env, arr: android.jfloatArray, allocator := context.allocator) -> []f32 {
|
||||
if arr == nil {
|
||||
return nil
|
||||
}
|
||||
n := int(e^^.GetArrayLength(e, android.jarray(arr)))
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]f32, n, allocator)
|
||||
e^^.GetFloatArrayRegion(e, arr, 0, android.jsize(n), cast([^]android.jfloat)raw_data(out))
|
||||
return out
|
||||
}
|
||||
|
||||
to_f64_array :: proc(e: Env, vals: []f64) -> android.jdoubleArray {
|
||||
arr := e^^.NewDoubleArray(e, android.jsize(len(vals)))
|
||||
if arr == nil || len(vals) == 0 {
|
||||
return arr
|
||||
}
|
||||
// Identical layout, so the slice can be handed over without a copy.
|
||||
e^^.SetDoubleArrayRegion(e, arr, 0, android.jsize(len(vals)), cast([^]android.jdouble)raw_data(vals))
|
||||
return arr
|
||||
}
|
||||
|
||||
from_f64_array :: proc(e: Env, arr: android.jdoubleArray, allocator := context.allocator) -> []f64 {
|
||||
if arr == nil {
|
||||
return nil
|
||||
}
|
||||
n := int(e^^.GetArrayLength(e, android.jarray(arr)))
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]f64, n, allocator)
|
||||
e^^.GetDoubleArrayRegion(e, arr, 0, android.jsize(n), cast([^]android.jdouble)raw_data(out))
|
||||
return out
|
||||
}
|
||||
|
||||
// Object arrays. `elem_class` is the array's component type.
|
||||
to_object_array :: proc(e: Env, vals: []android.jobject, elem_class: android.jclass) -> android.jobjectArray {
|
||||
arr := e^^.NewObjectArray(e, android.jsize(len(vals)), elem_class, nil)
|
||||
if arr == nil {
|
||||
return nil
|
||||
}
|
||||
for v, i in vals {
|
||||
e^^.SetObjectArrayElement(e, arr, android.jsize(i), v)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
from_object_array :: proc(e: Env, arr: android.jobjectArray, allocator := context.allocator) -> []android.jobject {
|
||||
if arr == nil {
|
||||
return nil
|
||||
}
|
||||
n := int(e^^.GetArrayLength(e, android.jarray(arr)))
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]android.jobject, n, allocator)
|
||||
for i in 0 ..< n {
|
||||
out[i] = e^^.GetObjectArrayElement(e, arr, android.jsize(i))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// String arrays need per-element conversion in both directions.
|
||||
to_string_array :: proc(e: Env, vals: []string) -> android.jobjectArray {
|
||||
cls := class_ref(e, "java/lang/String")
|
||||
if cls == nil {
|
||||
return nil
|
||||
}
|
||||
arr := e^^.NewObjectArray(e, android.jsize(len(vals)), cls, nil)
|
||||
if arr == nil {
|
||||
return nil
|
||||
}
|
||||
for v, i in vals {
|
||||
s := to_jstring(e, v)
|
||||
e^^.SetObjectArrayElement(e, arr, android.jsize(i), android.jobject(s))
|
||||
free_local(e, android.jobject(s))
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
from_string_array :: proc(e: Env, arr: android.jobjectArray, allocator := context.allocator) -> []string {
|
||||
if arr == nil {
|
||||
return nil
|
||||
}
|
||||
n := int(e^^.GetArrayLength(e, android.jarray(arr)))
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]string, n, allocator)
|
||||
for i in 0 ..< n {
|
||||
el := e^^.GetObjectArrayElement(e, arr, android.jsize(i))
|
||||
out[i] = from_jstring(e, android.jstring(el), allocator)
|
||||
free_local(e, el)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
// Runtime support for generated JNI bindings: VM/env acquisition, exception
|
||||
// handling, cached class lookup, and string marshalling.
|
||||
//
|
||||
package jni
|
||||
|
||||
import "core:strings"
|
||||
import "shared:android"
|
||||
|
||||
Env :: ^android.JNIEnv
|
||||
|
||||
@(private)
|
||||
vm: ^android.JavaVM
|
||||
|
||||
// Records the process JavaVM. Call once from android_main before any binding.
|
||||
init :: proc(app: ^android.android_app) -> bool {
|
||||
if app == nil || app.activity == nil {
|
||||
return false
|
||||
}
|
||||
vm = app.activity.vm
|
||||
return vm != nil
|
||||
}
|
||||
|
||||
// The Activity instance, usable anywhere a Context is expected.
|
||||
activity :: proc(app: ^android.android_app) -> android.jobject {
|
||||
if app == nil || app.activity == nil {
|
||||
return nil
|
||||
}
|
||||
return app.activity.clazz
|
||||
}
|
||||
|
||||
// The engine's loop runs on a glue-spawned thread, not the one holding
|
||||
// activity.env, so the env has to come from the VM and the thread attached on
|
||||
// first use. The thread lives as long as the app, so it is never detached.
|
||||
@(thread_local)
|
||||
tls_env: Env
|
||||
|
||||
env :: proc() -> Env {
|
||||
if tls_env != nil {
|
||||
return tls_env
|
||||
}
|
||||
if vm == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
e: Env
|
||||
if vm^^.GetEnv(vm, cast(^rawptr)&e, android.JNI_VERSION_1_6) == android.JNI_EDETACHED {
|
||||
if vm^^.AttachCurrentThread(vm, &e, nil) != android.JNI_OK {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
tls_env = e
|
||||
return e
|
||||
}
|
||||
|
||||
//
|
||||
// Exceptions
|
||||
//
|
||||
|
||||
// Reports whether a Java exception is pending, printing and clearing it if so.
|
||||
// Left pending, an exception makes every later JNI call on this thread fail.
|
||||
failed :: proc(e: Env) -> bool {
|
||||
if e == nil {
|
||||
return true
|
||||
}
|
||||
if e^^.ExceptionCheck(e) == 0 {
|
||||
return false
|
||||
}
|
||||
e^^.ExceptionDescribe(e)
|
||||
e^^.ExceptionClear(e)
|
||||
return true
|
||||
}
|
||||
|
||||
//
|
||||
// Lookup
|
||||
//
|
||||
|
||||
// Resolves a class and pins it with a global ref. Generated code caches the
|
||||
// result in a static, so a class is only ever looked up once. A local ref would
|
||||
// go stale the moment the native frame returns.
|
||||
class_ref :: proc(e: Env, name: cstring) -> android.jclass {
|
||||
local := e^^.FindClass(e, name)
|
||||
if local == nil || failed(e) {
|
||||
return nil
|
||||
}
|
||||
global := e^^.NewGlobalRef(e, android.jobject(local))
|
||||
e^^.DeleteLocalRef(e, android.jobject(local))
|
||||
return android.jclass(global)
|
||||
}
|
||||
|
||||
method_id :: proc(e: Env, cls: android.jclass, name, sig: cstring) -> android.jmethodID {
|
||||
id := e^^.GetMethodID(e, cls, name, sig)
|
||||
if failed(e) {
|
||||
return nil
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
static_method_id :: proc(e: Env, cls: android.jclass, name, sig: cstring) -> android.jmethodID {
|
||||
id := e^^.GetStaticMethodID(e, cls, name, sig)
|
||||
if failed(e) {
|
||||
return nil
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
field_id :: proc(e: Env, cls: android.jclass, name, sig: cstring) -> android.jfieldID {
|
||||
id := e^^.GetFieldID(e, cls, name, sig)
|
||||
if failed(e) {
|
||||
return nil
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
static_field_id :: proc(e: Env, cls: android.jclass, name, sig: cstring) -> android.jfieldID {
|
||||
id := e^^.GetStaticFieldID(e, cls, name, sig)
|
||||
if failed(e) {
|
||||
return nil
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
free_local :: proc(e: Env, obj: android.jobject) {
|
||||
if obj != nil {
|
||||
e^^.DeleteLocalRef(e, obj)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Strings
|
||||
//
|
||||
|
||||
to_jstring :: proc(e: Env, s: string) -> android.jstring {
|
||||
c := strings.clone_to_cstring(s, context.temp_allocator)
|
||||
return e^^.NewStringUTF(e, c)
|
||||
}
|
||||
|
||||
from_jstring :: proc(e: Env, s: android.jstring, allocator := context.allocator) -> string {
|
||||
if s == nil {
|
||||
return ""
|
||||
}
|
||||
utf := e^^.GetStringUTFChars(e, s, nil)
|
||||
if utf == nil {
|
||||
return ""
|
||||
}
|
||||
out := strings.clone(string(utf), allocator)
|
||||
e^^.ReleaseStringUTFChars(e, s, utf)
|
||||
return out
|
||||
}
|
||||
|
||||
//
|
||||
// Local reference frames
|
||||
//
|
||||
// Every object returned by a binding is a LOCAL ref, and locals are only
|
||||
// reclaimed when control returns to Java -- which never happens on a native
|
||||
// thread that owns its own loop. A sequence of object-returning calls made
|
||||
// per-frame will therefore exhaust the local ref table (512 entries) and abort
|
||||
// the process. Scope such a sequence in a frame:
|
||||
//
|
||||
// if !jni.push_frame(env) { return false }
|
||||
// defer jni.pop_frame(env)
|
||||
//
|
||||
// Anything still needed after pop_frame must be a primitive, a copied string,
|
||||
// or passed to pop_frame as `keep` to be promoted into the outer frame.
|
||||
//
|
||||
|
||||
push_frame :: proc(e: Env, capacity: i32 = 16) -> bool {
|
||||
if e == nil {
|
||||
return false
|
||||
}
|
||||
return e^^.PushLocalFrame(e, android.jint(capacity)) == android.JNI_OK
|
||||
}
|
||||
|
||||
pop_frame :: proc(e: Env, keep: android.jobject = nil) -> android.jobject {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e^^.PopLocalFrame(e, keep)
|
||||
}
|
||||
@@ -0,0 +1,901 @@
|
||||
// GENERATED by jnigen from the JNI spec. Do not edit.
|
||||
//
|
||||
// Every binding returns `ok` -- false means the class, method or field
|
||||
// failed to resolve, or the call raised a Java exception (already printed
|
||||
// to logcat and cleared). Class and method IDs resolve once and are cached.
|
||||
package main
|
||||
|
||||
import "shared:android"
|
||||
import "jni"
|
||||
|
||||
//
|
||||
// Object types
|
||||
//
|
||||
|
||||
// android/content/Context
|
||||
Context :: distinct android.jobject
|
||||
|
||||
// android/widget/Toast
|
||||
Toast :: distinct android.jobject
|
||||
|
||||
// java/lang/StringBuilder
|
||||
StringBuilder :: distinct android.jobject
|
||||
|
||||
// android/os/Looper
|
||||
Looper :: distinct android.jobject
|
||||
|
||||
// android/app/Activity
|
||||
Activity :: distinct android.jobject
|
||||
|
||||
// android/view/Window
|
||||
Window :: distinct android.jobject
|
||||
|
||||
// android/view/View
|
||||
View :: distinct android.jobject
|
||||
|
||||
// android/view/WindowInsets
|
||||
WindowInsets :: distinct android.jobject
|
||||
|
||||
// android/graphics/Insets
|
||||
Insets :: distinct android.jobject
|
||||
|
||||
// java/lang/System.currentTimeMillis()J
|
||||
system_current_time_millis :: proc() -> (result: i64, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "java/lang/System")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.static_method_id(env, cls, "currentTimeMillis", "()J")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.CallStaticLongMethodA(env, cls, mid, nil)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = i64(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/widget/Toast.makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
|
||||
toast_make_text :: proc(ctx: Context, text: string, duration: i32) -> (result: Toast, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/widget/Toast")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.static_method_id(env, cls, "makeText", "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
args := [3]android.jvalue{}
|
||||
args[0].l = android.jobject(ctx)
|
||||
_a1 := jni.to_jstring(env, text)
|
||||
defer jni.free_local(env, android.jobject(_a1))
|
||||
args[1].l = android.jobject(_a1)
|
||||
args[2].i = android.jint(duration)
|
||||
|
||||
_ret := env^^.CallStaticObjectMethodA(env, cls, mid, raw_data(args[:]))
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = Toast(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/os/Looper.prepare()V
|
||||
looper_prepare :: proc() -> (ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/os/Looper")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.static_method_id(env, cls, "prepare", "()V")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
env^^.CallStaticVoidMethodA(env, cls, mid, nil)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/os/Looper.myLooper()Landroid/os/Looper;
|
||||
looper_my_looper :: proc() -> (result: Looper, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/os/Looper")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.static_method_id(env, cls, "myLooper", "()Landroid/os/Looper;")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.CallStaticObjectMethodA(env, cls, mid, nil)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = Looper(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/widget/Toast.show()V
|
||||
toast_show :: proc(self: Toast) -> (ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/widget/Toast")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.method_id(env, cls, "show", "()V")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
env^^.CallVoidMethodA(env, android.jobject(self), mid, nil)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||
sb_append :: proc(self: StringBuilder, s: string) -> (result: StringBuilder, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "java/lang/StringBuilder")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.method_id(env, cls, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
args := [1]android.jvalue{}
|
||||
_a0 := jni.to_jstring(env, s)
|
||||
defer jni.free_local(env, android.jobject(_a0))
|
||||
args[0].l = android.jobject(_a0)
|
||||
|
||||
_ret := env^^.CallObjectMethodA(env, android.jobject(self), mid, raw_data(args[:]))
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = StringBuilder(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// java/lang/StringBuilder.toString()Ljava/lang/String;
|
||||
sb_to_string :: proc(self: StringBuilder, allocator := context.allocator) -> (result: string, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "java/lang/StringBuilder")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.method_id(env, cls, "toString", "()Ljava/lang/String;")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.CallObjectMethodA(env, android.jobject(self), mid, nil)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
defer jni.free_local(env, _ret)
|
||||
result = jni.from_jstring(env, android.jstring(_ret), allocator)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// new java/lang/StringBuilder()V
|
||||
sb_new :: proc() -> (result: StringBuilder, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "java/lang/StringBuilder")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.method_id(env, cls, "<init>", "()V")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.NewObjectA(env, cls, mid, nil)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = StringBuilder(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/os/Build$VERSION.SDK_INT : I (get)
|
||||
build_sdk_int :: proc() -> (result: i32, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/os/Build$VERSION")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) fid: android.jfieldID
|
||||
if fid == nil {
|
||||
fid = jni.static_field_id(env, cls, "SDK_INT", "I")
|
||||
if fid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.GetStaticIntField(env, cls, fid)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = i32(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/os/Build.MODEL : Ljava/lang/String; (get)
|
||||
build_model :: proc(allocator := context.allocator) -> (result: string, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/os/Build")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) fid: android.jfieldID
|
||||
if fid == nil {
|
||||
fid = jni.static_field_id(env, cls, "MODEL", "Ljava/lang/String;")
|
||||
if fid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.GetStaticObjectField(env, cls, fid)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
defer jni.free_local(env, _ret)
|
||||
result = jni.from_jstring(env, android.jstring(_ret), allocator)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// java/util/Arrays.sort([I)V
|
||||
arrays_sort :: proc(vals: []i32) -> (ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "java/util/Arrays")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.static_method_id(env, cls, "sort", "([I)V")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
args := [1]android.jvalue{}
|
||||
_a0 := jni.to_i32_array(env, vals)
|
||||
defer jni.free_local(env, android.jobject(_a0))
|
||||
args[0].l = android.jobject(_a0)
|
||||
|
||||
env^^.CallStaticVoidMethodA(env, cls, mid, raw_data(args[:]))
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/os/Build.SUPPORTED_ABIS : [Ljava/lang/String; (get)
|
||||
build_supported_abis :: proc(allocator := context.allocator) -> (result: []string, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/os/Build")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) fid: android.jfieldID
|
||||
if fid == nil {
|
||||
fid = jni.static_field_id(env, cls, "SUPPORTED_ABIS", "[Ljava/lang/String;")
|
||||
if fid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.GetStaticObjectField(env, cls, fid)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
defer jni.free_local(env, _ret)
|
||||
result = jni.from_string_array(env, android.jobjectArray(_ret), allocator)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/app/Activity.getWindow()Landroid/view/Window;
|
||||
activity_get_window :: proc(self: Activity) -> (result: Window, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/app/Activity")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.method_id(env, cls, "getWindow", "()Landroid/view/Window;")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.CallObjectMethodA(env, android.jobject(self), mid, nil)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = Window(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/view/Window.getDecorView()Landroid/view/View;
|
||||
window_get_decor_view :: proc(self: Window) -> (result: View, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/view/Window")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.method_id(env, cls, "getDecorView", "()Landroid/view/View;")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.CallObjectMethodA(env, android.jobject(self), mid, nil)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = View(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/view/View.getRootWindowInsets()Landroid/view/WindowInsets;
|
||||
view_get_root_window_insets :: proc(self: View) -> (result: WindowInsets, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/view/View")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.method_id(env, cls, "getRootWindowInsets", "()Landroid/view/WindowInsets;")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.CallObjectMethodA(env, android.jobject(self), mid, nil)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = WindowInsets(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/view/WindowInsets$Type.systemBars()I
|
||||
insets_type_system_bars :: proc() -> (result: i32, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/view/WindowInsets$Type")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.static_method_id(env, cls, "systemBars", "()I")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.CallStaticIntMethodA(env, cls, mid, nil)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = i32(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/view/WindowInsets$Type.displayCutout()I
|
||||
insets_type_display_cutout :: proc() -> (result: i32, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/view/WindowInsets$Type")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.static_method_id(env, cls, "displayCutout", "()I")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.CallStaticIntMethodA(env, cls, mid, nil)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = i32(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/view/WindowInsets.getInsetsIgnoringVisibility(I)Landroid/graphics/Insets;
|
||||
window_insets_get_ignoring_visibility :: proc(self: WindowInsets, type_mask: i32) -> (result: Insets, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/view/WindowInsets")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.method_id(env, cls, "getInsetsIgnoringVisibility", "(I)Landroid/graphics/Insets;")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
args := [1]android.jvalue{}
|
||||
args[0].i = android.jint(type_mask)
|
||||
|
||||
_ret := env^^.CallObjectMethodA(env, android.jobject(self), mid, raw_data(args[:]))
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = Insets(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/view/WindowInsets.getInsets(I)Landroid/graphics/Insets;
|
||||
window_insets_get :: proc(self: WindowInsets, type_mask: i32) -> (result: Insets, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/view/WindowInsets")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.method_id(env, cls, "getInsets", "(I)Landroid/graphics/Insets;")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
args := [1]android.jvalue{}
|
||||
args[0].i = android.jint(type_mask)
|
||||
|
||||
_ret := env^^.CallObjectMethodA(env, android.jobject(self), mid, raw_data(args[:]))
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = Insets(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/graphics/Insets.left : I (get)
|
||||
insets_left :: proc(self: Insets) -> (result: i32, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/graphics/Insets")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) fid: android.jfieldID
|
||||
if fid == nil {
|
||||
fid = jni.field_id(env, cls, "left", "I")
|
||||
if fid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.GetIntField(env, android.jobject(self), fid)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = i32(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/graphics/Insets.top : I (get)
|
||||
insets_top :: proc(self: Insets) -> (result: i32, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/graphics/Insets")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) fid: android.jfieldID
|
||||
if fid == nil {
|
||||
fid = jni.field_id(env, cls, "top", "I")
|
||||
if fid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.GetIntField(env, android.jobject(self), fid)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = i32(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/graphics/Insets.right : I (get)
|
||||
insets_right :: proc(self: Insets) -> (result: i32, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/graphics/Insets")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) fid: android.jfieldID
|
||||
if fid == nil {
|
||||
fid = jni.field_id(env, cls, "right", "I")
|
||||
if fid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.GetIntField(env, android.jobject(self), fid)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = i32(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/graphics/Insets.bottom : I (get)
|
||||
insets_bottom :: proc(self: Insets) -> (result: i32, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/graphics/Insets")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) fid: android.jfieldID
|
||||
if fid == nil {
|
||||
fid = jni.field_id(env, cls, "bottom", "I")
|
||||
if fid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.GetIntField(env, android.jobject(self), fid)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = i32(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/view/WindowInsets.getSystemWindowInsetLeft()I
|
||||
window_insets_sys_left :: proc(self: WindowInsets) -> (result: i32, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/view/WindowInsets")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.method_id(env, cls, "getSystemWindowInsetLeft", "()I")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.CallIntMethodA(env, android.jobject(self), mid, nil)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = i32(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/view/WindowInsets.getSystemWindowInsetTop()I
|
||||
window_insets_sys_top :: proc(self: WindowInsets) -> (result: i32, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/view/WindowInsets")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.method_id(env, cls, "getSystemWindowInsetTop", "()I")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.CallIntMethodA(env, android.jobject(self), mid, nil)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = i32(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/view/WindowInsets.getSystemWindowInsetRight()I
|
||||
window_insets_sys_right :: proc(self: WindowInsets) -> (result: i32, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/view/WindowInsets")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.method_id(env, cls, "getSystemWindowInsetRight", "()I")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.CallIntMethodA(env, android.jobject(self), mid, nil)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = i32(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// android/view/WindowInsets.getSystemWindowInsetBottom()I
|
||||
window_insets_sys_bottom :: proc(self: WindowInsets) -> (result: i32, ok: bool) {
|
||||
env := jni.env()
|
||||
if env == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@(static) cls: android.jclass
|
||||
if cls == nil {
|
||||
cls = jni.class_ref(env, "android/view/WindowInsets")
|
||||
if cls == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@(static) mid: android.jmethodID
|
||||
if mid == nil {
|
||||
mid = jni.method_id(env, cls, "getSystemWindowInsetBottom", "()I")
|
||||
if mid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ret := env^^.CallIntMethodA(env, android.jobject(self), mid, nil)
|
||||
if jni.failed(env) {
|
||||
return
|
||||
}
|
||||
result = i32(_ret)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
#+build ignore
|
||||
// JNI binding spec. Never compiled -- jnigen parses this file and emits
|
||||
// jni_generated.odin. Regenerate with:
|
||||
//
|
||||
// odin run jnigen -- jni_spec.odin jni_generated.odin main
|
||||
//
|
||||
// Attributes:
|
||||
// java_class="a/b/C" required; the class, or the type's Java class
|
||||
// java_name="foo" Java-side name (defaults to the Odin proc name)
|
||||
// java_static static method or field
|
||||
// java_ctor constructor; must return a registered object type
|
||||
// java_field field access; 1 result = getter, 0 results = setter
|
||||
// java_sig="..." override the computed signature
|
||||
package main
|
||||
|
||||
import "shared:android"
|
||||
|
||||
//
|
||||
// Object types. Each becomes `Name :: distinct android.jobject` and lets
|
||||
// signatures resolve the Odin type to its Java class.
|
||||
//
|
||||
|
||||
@(java_class = "android/content/Context")
|
||||
Context :: distinct android.jobject
|
||||
|
||||
@(java_class = "android/widget/Toast")
|
||||
Toast :: distinct android.jobject
|
||||
|
||||
@(java_class = "java/lang/StringBuilder")
|
||||
StringBuilder :: distinct android.jobject
|
||||
|
||||
@(java_class = "android/os/Looper")
|
||||
Looper :: distinct android.jobject
|
||||
|
||||
@(java_class = "android/app/Activity")
|
||||
Activity :: distinct android.jobject
|
||||
|
||||
@(java_class = "android/view/Window")
|
||||
Window :: distinct android.jobject
|
||||
|
||||
@(java_class = "android/view/View")
|
||||
View :: distinct android.jobject
|
||||
|
||||
@(java_class = "android/view/WindowInsets")
|
||||
WindowInsets :: distinct android.jobject
|
||||
|
||||
@(java_class = "android/graphics/Insets")
|
||||
Insets :: distinct android.jobject
|
||||
|
||||
//
|
||||
// Static methods
|
||||
//
|
||||
|
||||
@(java_class = "java/lang/System", java_static, java_name = "currentTimeMillis")
|
||||
system_current_time_millis :: proc() -> i64
|
||||
|
||||
// makeText takes a CharSequence, not a String, so the computed signature would
|
||||
// not match. java_sig pins the real one while `string` stays in the Odin API.
|
||||
@(
|
||||
java_class = "android/widget/Toast",
|
||||
java_static,
|
||||
java_name = "makeText",
|
||||
java_sig = "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;",
|
||||
)
|
||||
toast_make_text :: proc(ctx: Context, text: string, duration: i32) -> Toast
|
||||
|
||||
// Toast's constructor reads Looper.myLooper() and throws if the thread has
|
||||
// none. The engine loop runs on a glue-spawned thread that only ever got a
|
||||
// native ALooper, so the Java-side looper has to be prepared once by hand.
|
||||
@(java_class = "android/os/Looper", java_static, java_name = "prepare")
|
||||
looper_prepare :: proc()
|
||||
|
||||
@(java_class = "android/os/Looper", java_static, java_name = "myLooper")
|
||||
looper_my_looper :: proc() -> Looper
|
||||
|
||||
//
|
||||
// Instance methods -- the first parameter is the receiver.
|
||||
//
|
||||
|
||||
@(java_class = "android/widget/Toast", java_name = "show")
|
||||
toast_show :: proc(self: Toast)
|
||||
|
||||
@(java_class = "java/lang/StringBuilder", java_name = "append", java_sig = "(Ljava/lang/String;)Ljava/lang/StringBuilder;")
|
||||
sb_append :: proc(self: StringBuilder, s: string) -> StringBuilder
|
||||
|
||||
@(java_class = "java/lang/StringBuilder", java_name = "toString")
|
||||
sb_to_string :: proc(self: StringBuilder) -> string
|
||||
|
||||
//
|
||||
// Constructor
|
||||
//
|
||||
|
||||
@(java_class = "java/lang/StringBuilder", java_ctor)
|
||||
sb_new :: proc() -> StringBuilder
|
||||
|
||||
//
|
||||
// Fields
|
||||
//
|
||||
|
||||
@(java_class = "android/os/Build$VERSION", java_static, java_field, java_name = "SDK_INT")
|
||||
build_sdk_int :: proc() -> i32
|
||||
|
||||
@(java_class = "android/os/Build", java_static, java_field, java_name = "MODEL")
|
||||
build_model :: proc() -> string
|
||||
|
||||
//
|
||||
// Arrays
|
||||
//
|
||||
|
||||
@(java_class = "java/util/Arrays", java_static, java_name = "sort", java_sig = "([I)V")
|
||||
arrays_sort :: proc(vals: []i32)
|
||||
|
||||
@(java_class = "android/os/Build", java_static, java_field, java_name = "SUPPORTED_ABIS")
|
||||
build_supported_abis :: proc() -> []string
|
||||
|
||||
//
|
||||
// Window insets: Activity -> Window -> decor View -> WindowInsets -> Insets.
|
||||
//
|
||||
// getRootWindowInsets returns null until the decor view is attached, so the
|
||||
// first frames after launch will come back nil -- retry rather than treating
|
||||
// it as an error.
|
||||
//
|
||||
|
||||
@(java_class = "android/app/Activity", java_name = "getWindow")
|
||||
activity_get_window :: proc(self: Activity) -> Window
|
||||
|
||||
@(java_class = "android/view/Window", java_name = "getDecorView")
|
||||
window_get_decor_view :: proc(self: Window) -> View
|
||||
|
||||
@(java_class = "android/view/View", java_name = "getRootWindowInsets")
|
||||
view_get_root_window_insets :: proc(self: View) -> WindowInsets
|
||||
|
||||
// WindowInsets.Type is a nested class, hence the $ separator.
|
||||
@(java_class = "android/view/WindowInsets$Type", java_static, java_name = "systemBars")
|
||||
insets_type_system_bars :: proc() -> i32
|
||||
|
||||
// A fullscreen theme hides the system bars, so systemBars() alone reports 0.
|
||||
// The cutout is what still needs avoiding -- the two are OR'd into one mask.
|
||||
@(java_class = "android/view/WindowInsets$Type", java_static, java_name = "displayCutout")
|
||||
insets_type_display_cutout :: proc() -> i32
|
||||
|
||||
// Reports bar sizes even while hidden, which is what a fullscreen app wants
|
||||
// when it draws its own chrome where the bars would otherwise sit.
|
||||
@(java_class = "android/view/WindowInsets", java_name = "getInsetsIgnoringVisibility")
|
||||
window_insets_get_ignoring_visibility :: proc(self: WindowInsets, type_mask: i32) -> Insets
|
||||
|
||||
// API 30+. The computed signature (I)Landroid/graphics/Insets; is already right.
|
||||
@(java_class = "android/view/WindowInsets", java_name = "getInsets")
|
||||
window_insets_get :: proc(self: WindowInsets, type_mask: i32) -> Insets
|
||||
|
||||
// Insets exposes its edges as public final int fields.
|
||||
@(java_class = "android/graphics/Insets", java_field, java_name = "left")
|
||||
insets_left :: proc(self: Insets) -> i32
|
||||
|
||||
@(java_class = "android/graphics/Insets", java_field, java_name = "top")
|
||||
insets_top :: proc(self: Insets) -> i32
|
||||
|
||||
@(java_class = "android/graphics/Insets", java_field, java_name = "right")
|
||||
insets_right :: proc(self: Insets) -> i32
|
||||
|
||||
@(java_class = "android/graphics/Insets", java_field, java_name = "bottom")
|
||||
insets_bottom :: proc(self: Insets) -> i32
|
||||
|
||||
// Deprecated in API 30 but present since 20 -- the fallback below minSdk 30.
|
||||
@(java_class = "android/view/WindowInsets", java_name = "getSystemWindowInsetLeft")
|
||||
window_insets_sys_left :: proc(self: WindowInsets) -> i32
|
||||
|
||||
@(java_class = "android/view/WindowInsets", java_name = "getSystemWindowInsetTop")
|
||||
window_insets_sys_top :: proc(self: WindowInsets) -> i32
|
||||
|
||||
@(java_class = "android/view/WindowInsets", java_name = "getSystemWindowInsetRight")
|
||||
window_insets_sys_right :: proc(self: WindowInsets) -> i32
|
||||
|
||||
@(java_class = "android/view/WindowInsets", java_name = "getSystemWindowInsetBottom")
|
||||
window_insets_sys_bottom :: proc(self: WindowInsets) -> i32
|
||||
@@ -0,0 +1,296 @@
|
||||
package jnigen
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
Emitter :: struct {
|
||||
b: strings.Builder,
|
||||
}
|
||||
|
||||
w :: proc(e: ^Emitter, format: string, args: ..any) {
|
||||
fmt.sbprintf(&e.b, format, ..args)
|
||||
strings.write_byte(&e.b, '\n')
|
||||
}
|
||||
|
||||
emit_file :: proc(g: ^Gen, package_name: string) -> string {
|
||||
e: Emitter
|
||||
e.b = strings.builder_make()
|
||||
|
||||
w(&e, "// GENERATED by jnigen from the JNI spec. Do not edit.")
|
||||
w(&e, "//")
|
||||
w(&e, "// Every binding returns `ok` -- false means the class, method or field")
|
||||
w(&e, "// failed to resolve, or the call raised a Java exception (already printed")
|
||||
w(&e, "// to logcat and cleared). Class and method IDs resolve once and are cached.")
|
||||
w(&e, "package %s", package_name)
|
||||
w(&e, "")
|
||||
w(&e, "import \"shared:android\"")
|
||||
w(&e, "import \"jni\"")
|
||||
w(&e, "")
|
||||
|
||||
if len(g.class_decls) > 0 {
|
||||
w(&e, "//")
|
||||
w(&e, "// Object types")
|
||||
w(&e, "//")
|
||||
w(&e, "")
|
||||
for c in g.class_decls {
|
||||
w(&e, "// %s", c.java_class)
|
||||
w(&e, "%s :: distinct android.jobject", c.odin_name)
|
||||
w(&e, "")
|
||||
}
|
||||
}
|
||||
|
||||
for b in g.bindings {
|
||||
emit_binding(&e, b)
|
||||
}
|
||||
|
||||
return strings.to_string(e.b)
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
emit_signature :: proc(e: ^Emitter, b: Binding) {
|
||||
sb := strings.builder_make(context.temp_allocator)
|
||||
|
||||
if b.has_recv {
|
||||
fmt.sbprintf(&sb, "%s: %s", b.recv.name, b.recv.type.odin)
|
||||
}
|
||||
for p in b.params {
|
||||
if strings.builder_len(sb) > 0 {
|
||||
strings.write_string(&sb, ", ")
|
||||
}
|
||||
fmt.sbprintf(&sb, "%s: %s", p.name, p.type.odin)
|
||||
}
|
||||
if allocates(b.result) {
|
||||
if strings.builder_len(sb) > 0 {
|
||||
strings.write_string(&sb, ", ")
|
||||
}
|
||||
strings.write_string(&sb, "allocator := context.allocator")
|
||||
}
|
||||
|
||||
if b.result.of == .Void {
|
||||
w(e, "%s :: proc(%s) -> (ok: bool) {{", b.odin_name, strings.to_string(sb))
|
||||
} else {
|
||||
w(e, "%s :: proc(%s) -> (result: %s, ok: bool) {{", b.odin_name, strings.to_string(sb), b.result.odin)
|
||||
}
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
emit_binding :: proc(e: ^Emitter, b: Binding) {
|
||||
switch b.kind {
|
||||
case .Method:
|
||||
w(e, "// %s.%s%s", b.java_class, b.java_name, b.sig)
|
||||
case .Ctor:
|
||||
w(e, "// new %s%s", b.java_class, b.sig)
|
||||
case .Field_Get:
|
||||
w(e, "// %s.%s : %s (get)", b.java_class, b.java_name, b.sig)
|
||||
case .Field_Set:
|
||||
w(e, "// %s.%s : %s (set)", b.java_class, b.java_name, b.sig)
|
||||
}
|
||||
|
||||
emit_signature(e, b)
|
||||
w(e, " env := jni.env()")
|
||||
w(e, " if env == nil {{")
|
||||
w(e, " return")
|
||||
w(e, " }}")
|
||||
w(e, "")
|
||||
|
||||
// Resolved once; a global ref keeps the class alive past this frame.
|
||||
w(e, " @(static) cls: android.jclass")
|
||||
w(e, " if cls == nil {{")
|
||||
w(e, " cls = jni.class_ref(env, \"%s\")", b.java_class)
|
||||
w(e, " if cls == nil {{")
|
||||
w(e, " return")
|
||||
w(e, " }}")
|
||||
w(e, " }}")
|
||||
|
||||
is_field := b.kind == .Field_Get || b.kind == .Field_Set
|
||||
if is_field {
|
||||
lookup := b.is_static ? "static_field_id" : "field_id"
|
||||
w(e, " @(static) fid: android.jfieldID")
|
||||
w(e, " if fid == nil {{")
|
||||
w(e, " fid = jni.%s(env, cls, \"%s\", \"%s\")", lookup, b.java_name, b.sig)
|
||||
w(e, " if fid == nil {{")
|
||||
w(e, " return")
|
||||
w(e, " }}")
|
||||
w(e, " }}")
|
||||
} else {
|
||||
lookup := (b.is_static && b.kind != .Ctor) ? "static_method_id" : "method_id"
|
||||
w(e, " @(static) mid: android.jmethodID")
|
||||
w(e, " if mid == nil {{")
|
||||
w(e, " mid = jni.%s(env, cls, \"%s\", \"%s\")", lookup, b.java_name, b.sig)
|
||||
w(e, " if mid == nil {{")
|
||||
w(e, " return")
|
||||
w(e, " }}")
|
||||
w(e, " }}")
|
||||
}
|
||||
w(e, "")
|
||||
|
||||
switch b.kind {
|
||||
case .Method, .Ctor:
|
||||
emit_call(e, b)
|
||||
case .Field_Get:
|
||||
emit_field_get(e, b)
|
||||
case .Field_Set:
|
||||
emit_field_set(e, b)
|
||||
}
|
||||
|
||||
w(e, "}}")
|
||||
w(e, "")
|
||||
}
|
||||
|
||||
// Marshals one argument into `args[idx]`, emitting any temporary the value needs.
|
||||
@(private = "file")
|
||||
emit_marshal :: proc(e: ^Emitter, p: Param, idx: int) {
|
||||
t := p.type
|
||||
switch t.of {
|
||||
case .Prim:
|
||||
w(e, " args[%d].%s = %s(%s)", idx, t.jval, t.jcast, p.name)
|
||||
case .String:
|
||||
w(e, " _a%d := jni.to_jstring(env, %s)", idx, p.name)
|
||||
w(e, " defer jni.free_local(env, android.jobject(_a%d))", idx)
|
||||
w(e, " args[%d].l = android.jobject(_a%d)", idx, idx)
|
||||
case .Array:
|
||||
conv := t.elem == "string" ? "to_string_array" : fmt.tprintf("to_%s_array", t.elem)
|
||||
w(e, " _a%d := jni.%s(env, %s)", idx, conv, p.name)
|
||||
w(e, " defer jni.free_local(env, android.jobject(_a%d))", idx)
|
||||
w(e, " args[%d].l = android.jobject(_a%d)", idx, idx)
|
||||
case .Object:
|
||||
w(e, " args[%d].l = android.jobject(%s)", idx, p.name)
|
||||
case .Void:
|
||||
}
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
emit_call :: proc(e: ^Emitter, b: Binding) {
|
||||
args_expr := "nil"
|
||||
if len(b.params) > 0 {
|
||||
w(e, " args := [%d]android.jvalue{{}}", len(b.params))
|
||||
for p, i in b.params {
|
||||
emit_marshal(e, p, i)
|
||||
}
|
||||
w(e, "")
|
||||
args_expr = "raw_data(args[:])"
|
||||
}
|
||||
|
||||
kind := title(b.result.call)
|
||||
switch {
|
||||
case b.kind == .Ctor:
|
||||
w(e, " _ret := env^^.NewObjectA(env, cls, mid, %s)", args_expr)
|
||||
case b.result.of == .Void && b.is_static:
|
||||
// A void call has no value to bind.
|
||||
w(e, " env^^.CallStaticVoidMethodA(env, cls, mid, %s)", args_expr)
|
||||
case b.result.of == .Void:
|
||||
w(e, " env^^.CallVoidMethodA(env, android.jobject(%s), mid, %s)", b.recv.name, args_expr)
|
||||
case b.is_static:
|
||||
w(e, " _ret := env^^.CallStatic%sMethodA(env, cls, mid, %s)", kind, args_expr)
|
||||
case:
|
||||
w(e, " _ret := env^^.Call%sMethodA(env, android.jobject(%s), mid, %s)", kind, b.recv.name, args_expr)
|
||||
}
|
||||
|
||||
w(e, " if jni.failed(env) {{")
|
||||
w(e, " return")
|
||||
w(e, " }}")
|
||||
emit_result(e, b, "_ret")
|
||||
w(e, " ok = true")
|
||||
w(e, " return")
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
emit_result :: proc(e: ^Emitter, b: Binding, ret: string) {
|
||||
t := b.result
|
||||
switch t.of {
|
||||
case .Void:
|
||||
case .Prim:
|
||||
w(e, " result = %s(%s)", t.odin, ret)
|
||||
case .String:
|
||||
w(e, " defer jni.free_local(env, %s)", ret)
|
||||
w(e, " result = jni.from_jstring(env, android.jstring(%s), allocator)", ret)
|
||||
case .Array:
|
||||
conv := t.elem == "string" ? "from_string_array" : fmt.tprintf("from_%s_array", t.elem)
|
||||
jarr := t.elem == "string" ? "android.jobjectArray" : fmt.tprintf("android.j%sArray", java_arr_name(t.elem))
|
||||
w(e, " defer jni.free_local(env, %s)", ret)
|
||||
w(e, " result = jni.%s(env, %s(%s), allocator)", conv, jarr, ret)
|
||||
case .Object:
|
||||
w(e, " result = %s(%s)", t.odin, ret)
|
||||
}
|
||||
}
|
||||
|
||||
// Odin element type -> the jni.odin array type infix (jintArray, jlongArray...).
|
||||
java_arr_name :: proc(elem: string) -> string {
|
||||
for p in prims {
|
||||
if p.odin == elem {
|
||||
return p.call
|
||||
}
|
||||
}
|
||||
return "object"
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
emit_field_get :: proc(e: ^Emitter, b: Binding) {
|
||||
t := b.result
|
||||
getter := field_accessor(t, b.is_static, true)
|
||||
target := b.is_static ? "cls" : fmt.tprintf("android.jobject(%s)", b.recv.name)
|
||||
w(e, " _ret := env^^.%s(env, %s, fid)", getter, target)
|
||||
w(e, " if jni.failed(env) {{")
|
||||
w(e, " return")
|
||||
w(e, " }}")
|
||||
emit_result(e, b, "_ret")
|
||||
w(e, " ok = true")
|
||||
w(e, " return")
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
emit_field_set :: proc(e: ^Emitter, b: Binding) {
|
||||
p := b.params[0]
|
||||
setter := field_accessor(p.type, b.is_static, false)
|
||||
target := b.is_static ? "cls" : fmt.tprintf("android.jobject(%s)", b.recv.name)
|
||||
|
||||
value := ""
|
||||
switch p.type.of {
|
||||
case .Prim:
|
||||
value = fmt.tprintf("%s(%s)", p.type.jcast, p.name)
|
||||
case .String:
|
||||
w(e, " _v := jni.to_jstring(env, %s)", p.name)
|
||||
w(e, " defer jni.free_local(env, android.jobject(_v))")
|
||||
value = "android.jobject(_v)"
|
||||
case .Array:
|
||||
conv := p.type.elem == "string" ? "to_string_array" : fmt.tprintf("to_%s_array", p.type.elem)
|
||||
w(e, " _v := jni.%s(env, %s)", conv, p.name)
|
||||
w(e, " defer jni.free_local(env, android.jobject(_v))")
|
||||
value = "android.jobject(_v)"
|
||||
case .Object:
|
||||
value = fmt.tprintf("android.jobject(%s)", p.name)
|
||||
case .Void:
|
||||
}
|
||||
|
||||
w(e, " env^^.%s(env, %s, fid, %s)", setter, target, value)
|
||||
w(e, " if jni.failed(env) {{")
|
||||
w(e, " return")
|
||||
w(e, " }}")
|
||||
w(e, " ok = true")
|
||||
w(e, " return")
|
||||
}
|
||||
|
||||
// GetStaticIntField / SetObjectField / ...
|
||||
field_accessor :: proc(t: Type_Ref, is_static: bool, is_get: bool) -> string {
|
||||
kind := "Object"
|
||||
if t.of == .Prim {
|
||||
for p in prims {
|
||||
if p.odin == t.odin {
|
||||
kind = title(p.call)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
verb := is_get ? "Get" : "Set"
|
||||
if is_static {
|
||||
return fmt.tprintf("%sStatic%sField", verb, kind)
|
||||
}
|
||||
return fmt.tprintf("%s%sField", verb, kind)
|
||||
}
|
||||
|
||||
title :: proc(s: string) -> string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
return fmt.tprintf("%c%s", s[0] - 32, s[1:])
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// jnigen -- generates JNI binding boilerplate from an annotated Odin spec.
|
||||
//
|
||||
// odin run jnigen -- [spec.odin] [out.odin] [package]
|
||||
//
|
||||
// The spec is never compiled (it carries `#+build ignore`), so it needs no
|
||||
// -custom-attribute flags and cannot collide with the generated procs.
|
||||
package jnigen
|
||||
|
||||
import "core:fmt"
|
||||
import "core:odin/ast"
|
||||
import "core:odin/parser"
|
||||
import "core:os"
|
||||
|
||||
main :: proc() {
|
||||
spec_path := "jni_spec.odin"
|
||||
out_path := "jni_generated.odin"
|
||||
package_name := "main"
|
||||
|
||||
args := os.args[1:]
|
||||
if len(args) > 0 {
|
||||
spec_path = args[0]
|
||||
}
|
||||
if len(args) > 1 {
|
||||
out_path = args[1]
|
||||
}
|
||||
if len(args) > 2 {
|
||||
package_name = args[2]
|
||||
}
|
||||
|
||||
src, read_err := os.read_entire_file(spec_path, context.allocator)
|
||||
if read_err != nil {
|
||||
fmt.eprintfln("jnigen: cannot read %s: %v", spec_path, read_err)
|
||||
os.exit(1)
|
||||
}
|
||||
|
||||
p := parser.default_parser()
|
||||
f := ast.File{
|
||||
src = string(src),
|
||||
fullpath = spec_path,
|
||||
}
|
||||
if !parser.parse_file(&p, &f) {
|
||||
fmt.eprintfln("jnigen: failed to parse %s", spec_path)
|
||||
os.exit(1)
|
||||
}
|
||||
|
||||
g: Gen
|
||||
g.classes = make(map[string]string)
|
||||
|
||||
collect_classes(&g, &f)
|
||||
collect_bindings(&g, &f)
|
||||
|
||||
if len(g.errors) > 0 {
|
||||
for e in g.errors {
|
||||
fmt.eprintfln("jnigen: %s", e)
|
||||
}
|
||||
os.exit(1)
|
||||
}
|
||||
|
||||
out := emit_file(&g, package_name)
|
||||
if werr := os.write_entire_file(out_path, transmute([]byte)out); werr != nil {
|
||||
fmt.eprintfln("jnigen: cannot write %s: %v", out_path, werr)
|
||||
os.exit(1)
|
||||
}
|
||||
|
||||
fmt.printfln(
|
||||
"jnigen: %s -> %s (%d classes, %d bindings)",
|
||||
spec_path,
|
||||
out_path,
|
||||
len(g.class_decls),
|
||||
len(g.bindings),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package jnigen
|
||||
|
||||
import "core:fmt"
|
||||
import "core:odin/ast"
|
||||
import "core:strings"
|
||||
|
||||
Kind :: enum {
|
||||
Method,
|
||||
Ctor,
|
||||
Field_Get,
|
||||
Field_Set,
|
||||
}
|
||||
|
||||
Class_Of :: enum {
|
||||
Void,
|
||||
Prim,
|
||||
String,
|
||||
Object,
|
||||
Array,
|
||||
}
|
||||
|
||||
Type_Ref :: struct {
|
||||
odin: string, // Odin type as written in the spec
|
||||
sig: string, // JNI signature fragment
|
||||
of: Class_Of,
|
||||
call: string, // "int" / "object" / "void" -- picks the jni.call_* wrapper
|
||||
jval: string, // jvalue union field
|
||||
jcast: string, // android.jint etc, for widening into the union
|
||||
elem: string, // element type, arrays only
|
||||
}
|
||||
|
||||
Param :: struct {
|
||||
name: string,
|
||||
type: Type_Ref,
|
||||
}
|
||||
|
||||
Binding :: struct {
|
||||
odin_name: string,
|
||||
java_class: string,
|
||||
java_name: string,
|
||||
kind: Kind,
|
||||
is_static: bool,
|
||||
recv: Param,
|
||||
has_recv: bool,
|
||||
params: []Param,
|
||||
result: Type_Ref,
|
||||
sig: string,
|
||||
line: int,
|
||||
}
|
||||
|
||||
Class_Bind :: struct {
|
||||
odin_name: string,
|
||||
java_class: string,
|
||||
}
|
||||
|
||||
Gen :: struct {
|
||||
classes: map[string]string, // Odin type name -> java/class/path
|
||||
class_decls: [dynamic]Class_Bind,
|
||||
bindings: [dynamic]Binding,
|
||||
errors: [dynamic]string,
|
||||
}
|
||||
|
||||
errorf :: proc(g: ^Gen, line: int, format: string, args: ..any) {
|
||||
msg := fmt.tprintf(format, ..args)
|
||||
append(&g.errors, fmt.aprintf("spec:%d: %s", line, msg))
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
attr_map :: proc(f: ^ast.File, attrs: [dynamic]^ast.Attribute) -> map[string]string {
|
||||
out := make(map[string]string)
|
||||
for attr in attrs {
|
||||
for elem in attr.elems {
|
||||
#partial switch e in elem.derived {
|
||||
case ^ast.Field_Value:
|
||||
key := src_of(f, e.field)
|
||||
val := strings.trim(src_of(f, e.value), "\"")
|
||||
out[key] = val
|
||||
case ^ast.Ident:
|
||||
out[e.name] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
src_of :: proc(f: ^ast.File, e: ^ast.Node) -> string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.trim_space(f.src[e.pos.offset:e.end.offset])
|
||||
}
|
||||
|
||||
// First pass: every `@(java_class=...) Name :: distinct jobject` becomes a
|
||||
// registered object type, so later signatures can resolve `Toast` to
|
||||
// `Landroid/widget/Toast;`.
|
||||
collect_classes :: proc(g: ^Gen, f: ^ast.File) {
|
||||
for decl in f.decls {
|
||||
vd, ok := decl.derived.(^ast.Value_Decl)
|
||||
if !ok || len(vd.attributes) == 0 || len(vd.values) == 0 {
|
||||
continue
|
||||
}
|
||||
// A proc type/lit is a method binding, not a class registration.
|
||||
#partial switch _ in vd.values[0].derived {
|
||||
case ^ast.Proc_Type:
|
||||
continue
|
||||
case ^ast.Proc_Lit:
|
||||
continue
|
||||
}
|
||||
|
||||
attrs := attr_map(f, vd.attributes)
|
||||
defer delete(attrs)
|
||||
cls, has := attrs["java_class"]
|
||||
if !has || len(vd.names) == 0 {
|
||||
continue
|
||||
}
|
||||
name := src_of(f, vd.names[0])
|
||||
g.classes[strings.clone(name)] = strings.clone(cls)
|
||||
append(&g.class_decls, Class_Bind{strings.clone(name), strings.clone(cls)})
|
||||
}
|
||||
}
|
||||
|
||||
collect_bindings :: proc(g: ^Gen, f: ^ast.File) {
|
||||
for decl in f.decls {
|
||||
vd, ok := decl.derived.(^ast.Value_Decl)
|
||||
if !ok || len(vd.attributes) == 0 || len(vd.values) == 0 || len(vd.names) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
pt: ^ast.Proc_Type
|
||||
#partial switch v in vd.values[0].derived {
|
||||
case ^ast.Proc_Type:
|
||||
pt = v
|
||||
case ^ast.Proc_Lit:
|
||||
pt = v.type
|
||||
case:
|
||||
continue
|
||||
}
|
||||
|
||||
attrs := attr_map(f, vd.attributes)
|
||||
defer delete(attrs)
|
||||
java_class, has_class := attrs["java_class"]
|
||||
if !has_class {
|
||||
continue
|
||||
}
|
||||
|
||||
b: Binding
|
||||
b.line = vd.pos.line
|
||||
b.odin_name = strings.clone(src_of(f, vd.names[0]))
|
||||
b.java_class = strings.clone(java_class)
|
||||
_, b.is_static = attrs["java_static"]
|
||||
|
||||
if name, has := attrs["java_name"]; has {
|
||||
b.java_name = strings.clone(name)
|
||||
} else {
|
||||
b.java_name = b.odin_name
|
||||
}
|
||||
|
||||
_, is_ctor := attrs["java_ctor"]
|
||||
_, is_field := attrs["java_field"]
|
||||
|
||||
params := parse_params(g, f, pt, b.line)
|
||||
results := parse_results(g, f, pt, b.line)
|
||||
|
||||
if len(results) > 1 {
|
||||
errorf(g, b.line, "'%s' declares %d results; bindings return at most one value", b.odin_name, len(results))
|
||||
continue
|
||||
}
|
||||
if len(results) == 1 {
|
||||
b.result = results[0]
|
||||
} else {
|
||||
b.result = Type_Ref{odin = "", sig = "V", of = .Void, call = "void"}
|
||||
}
|
||||
|
||||
switch {
|
||||
case is_ctor:
|
||||
b.kind = .Ctor
|
||||
b.java_name = "<init>"
|
||||
if b.result.of != .Object {
|
||||
errorf(g, b.line, "'%s' is a constructor so it must return a registered object type", b.odin_name)
|
||||
continue
|
||||
}
|
||||
b.params = params
|
||||
b.sig = build_sig(params, Type_Ref{sig = "V", of = .Void})
|
||||
case is_field:
|
||||
if b.result.of == .Void {
|
||||
b.kind = .Field_Set
|
||||
// instance setter: (self, value); static setter: (value)
|
||||
if !b.is_static {
|
||||
if len(params) != 2 {
|
||||
errorf(g, b.line, "'%s' is an instance field setter so it must take (self, value)", b.odin_name)
|
||||
continue
|
||||
}
|
||||
b.recv, b.has_recv = params[0], true
|
||||
b.params = params[1:]
|
||||
} else {
|
||||
if len(params) != 1 {
|
||||
errorf(g, b.line, "'%s' is a static field setter so it must take exactly (value)", b.odin_name)
|
||||
continue
|
||||
}
|
||||
b.params = params
|
||||
}
|
||||
b.sig = b.params[0].type.sig
|
||||
} else {
|
||||
b.kind = .Field_Get
|
||||
if !b.is_static {
|
||||
if len(params) != 1 {
|
||||
errorf(g, b.line, "'%s' is an instance field getter so it must take exactly (self)", b.odin_name)
|
||||
continue
|
||||
}
|
||||
b.recv, b.has_recv = params[0], true
|
||||
} else if len(params) != 0 {
|
||||
errorf(g, b.line, "'%s' is a static field getter so it must take no parameters", b.odin_name)
|
||||
continue
|
||||
}
|
||||
b.params = nil
|
||||
b.sig = b.result.sig
|
||||
}
|
||||
case:
|
||||
b.kind = .Method
|
||||
if !b.is_static {
|
||||
if len(params) == 0 {
|
||||
errorf(g, b.line, "'%s' is an instance method so its first parameter must be the receiver", b.odin_name)
|
||||
continue
|
||||
}
|
||||
b.recv, b.has_recv = params[0], true
|
||||
b.params = params[1:]
|
||||
} else {
|
||||
b.params = params
|
||||
}
|
||||
b.sig = build_sig(b.params, b.result)
|
||||
}
|
||||
|
||||
if override, has := attrs["java_sig"]; has {
|
||||
b.sig = strings.clone(override)
|
||||
}
|
||||
|
||||
append(&g.bindings, b)
|
||||
}
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
parse_params :: proc(g: ^Gen, f: ^ast.File, pt: ^ast.Proc_Type, line: int) -> []Param {
|
||||
out := make([dynamic]Param)
|
||||
if pt.params == nil {
|
||||
return out[:]
|
||||
}
|
||||
for field in pt.params.list {
|
||||
t, ok := resolve_type(g, src_of(f, field.type), line)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// `a, b: i32` declares two params of one type.
|
||||
if len(field.names) == 0 {
|
||||
append(&out, Param{fmt.aprintf("_arg%d", len(out)), t})
|
||||
continue
|
||||
}
|
||||
for n in field.names {
|
||||
append(&out, Param{strings.clone(src_of(f, n)), t})
|
||||
}
|
||||
}
|
||||
return out[:]
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
parse_results :: proc(g: ^Gen, f: ^ast.File, pt: ^ast.Proc_Type, line: int) -> []Type_Ref {
|
||||
out := make([dynamic]Type_Ref)
|
||||
if pt.results == nil {
|
||||
return out[:]
|
||||
}
|
||||
for field in pt.results.list {
|
||||
t, ok := resolve_type(g, src_of(f, field.type), line)
|
||||
if ok {
|
||||
append(&out, t)
|
||||
}
|
||||
}
|
||||
return out[:]
|
||||
}
|
||||
|
||||
build_sig :: proc(params: []Param, result: Type_Ref) -> string {
|
||||
b := strings.builder_make()
|
||||
strings.write_byte(&b, '(')
|
||||
for p in params {
|
||||
strings.write_string(&b, p.type.sig)
|
||||
}
|
||||
strings.write_byte(&b, ')')
|
||||
strings.write_string(&b, result.sig)
|
||||
return strings.to_string(b)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package jnigen
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
Prim :: struct {
|
||||
odin: string,
|
||||
sig: string,
|
||||
call: string,
|
||||
jval: string,
|
||||
jcast: string,
|
||||
}
|
||||
|
||||
// Odin's u16 stands in for Java's char: both are unsigned 16-bit.
|
||||
prims := []Prim{
|
||||
{"bool", "Z", "boolean", "z", "android.jboolean"},
|
||||
{"i8", "B", "byte", "b", "android.jbyte"},
|
||||
{"u16", "C", "char", "c", "android.jchar"},
|
||||
{"i16", "S", "short", "s", "android.jshort"},
|
||||
{"i32", "I", "int", "i", "android.jint"},
|
||||
{"i64", "J", "long", "j", "android.jlong"},
|
||||
{"f32", "F", "float", "f", "android.jfloat"},
|
||||
{"f64", "D", "double", "d", "android.jdouble"},
|
||||
}
|
||||
|
||||
resolve_type :: proc(g: ^Gen, odin: string, line: int) -> (Type_Ref, bool) {
|
||||
src := strings.trim_space(odin)
|
||||
if src == "" {
|
||||
return Type_Ref{sig = "V", of = .Void, call = "void"}, true
|
||||
}
|
||||
|
||||
for p in prims {
|
||||
if src == p.odin {
|
||||
return Type_Ref{
|
||||
odin = src, sig = p.sig, of = .Prim,
|
||||
call = p.call, jval = p.jval, jcast = p.jcast,
|
||||
}, true
|
||||
}
|
||||
}
|
||||
|
||||
if src == "string" || src == "cstring" {
|
||||
return Type_Ref{
|
||||
odin = src, sig = "Ljava/lang/String;", of = .String,
|
||||
call = "object", jval = "l",
|
||||
}, true
|
||||
}
|
||||
|
||||
if strings.has_prefix(src, "[]") {
|
||||
elem := strings.trim_space(src[2:])
|
||||
et, ok := resolve_type(g, elem, line)
|
||||
if !ok {
|
||||
return {}, false
|
||||
}
|
||||
if et.of == .Array {
|
||||
errorf(g, line, "nested slice '%s' is not supported; declare it as a jobject instead", src)
|
||||
return {}, false
|
||||
}
|
||||
return Type_Ref{
|
||||
odin = src, sig = fmt.aprintf("[%s", et.sig), of = .Array,
|
||||
call = "object", jval = "l", elem = elem,
|
||||
}, true
|
||||
}
|
||||
|
||||
// A raw jobject is untyped on the Java side, so it signs as Object.
|
||||
if src == "android.jobject" || src == "jobject" {
|
||||
return Type_Ref{
|
||||
odin = src, sig = "Ljava/lang/Object;", of = .Object,
|
||||
call = "object", jval = "l",
|
||||
}, true
|
||||
}
|
||||
|
||||
if cls, ok := g.classes[src]; ok {
|
||||
return Type_Ref{
|
||||
odin = src, sig = fmt.aprintf("L%s;", cls), of = .Object,
|
||||
call = "object", jval = "l",
|
||||
}, true
|
||||
}
|
||||
|
||||
errorf(g, line, "unknown type '%s'; register it with @(java_class=\"...\") or use a primitive", src)
|
||||
return {}, false
|
||||
}
|
||||
|
||||
// Whether a returned value allocates and so needs an allocator parameter.
|
||||
allocates :: proc(t: Type_Ref) -> bool {
|
||||
if t.of == .String {
|
||||
return true
|
||||
}
|
||||
return t.of == .Array
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package main
|
||||
|
||||
import "shared:android"
|
||||
import "core:fmt"
|
||||
import "base:runtime"
|
||||
import engine "../zephr"
|
||||
import "jni"
|
||||
|
||||
counter := 0
|
||||
|
||||
device_model: string
|
||||
device_sdk: i32
|
||||
toast_ctx: Context
|
||||
main_activity: Activity
|
||||
|
||||
Edge_Insets :: struct {
|
||||
left, top, right, bottom: f32
|
||||
}
|
||||
|
||||
insets: Edge_Insets
|
||||
insets_valid: bool
|
||||
|
||||
@export
|
||||
android_main :: proc "c" (state: ^android.android_app) {
|
||||
context = runtime.default_context()
|
||||
context.user_ptr = state
|
||||
|
||||
engine.init("", "", {1000, 600}, false, "")
|
||||
defer engine.deinit()
|
||||
|
||||
// Static field reads only -- safe from the engine's thread. Anything
|
||||
// touching the UI toolkit (Toast, dialogs) needs the main looper instead.
|
||||
if jni.init(state) {
|
||||
device_model, _ = build_model()
|
||||
device_sdk, _ = build_sdk_int()
|
||||
toast_ctx = Context(jni.activity(state))
|
||||
main_activity = Activity(jni.activity(state))
|
||||
}
|
||||
|
||||
_, font_ok := engine.ui_text_register_font(#load("/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf"))
|
||||
ensure(font_ok)
|
||||
|
||||
for !engine.should_quit() {
|
||||
engine.frame_start()
|
||||
defer engine.frame_end()
|
||||
|
||||
e := engine.iter_events()
|
||||
for e != nil {
|
||||
#partial switch e.type {
|
||||
case .WINDOW_CLOSED:
|
||||
engine.quit()
|
||||
case .WINDOW_RESIZED:
|
||||
insets_valid = false
|
||||
}
|
||||
|
||||
e = engine.iter_events()
|
||||
}
|
||||
|
||||
if !insets_valid {
|
||||
insets_valid = refresh_insets()
|
||||
}
|
||||
|
||||
window_size := engine.get_window_size()
|
||||
|
||||
engine.ui_begin_build(window_size)
|
||||
engine.ui_text_set_default_pixel_size(108)
|
||||
engine.ui_push_text_color(0xFFFFFF)
|
||||
draw_ui()
|
||||
engine.ui_pop_text_color()
|
||||
engine.ui_end_build()
|
||||
|
||||
engine.ui_draw(engine.get_projection())
|
||||
}
|
||||
}
|
||||
|
||||
draw_ui :: proc() {
|
||||
engine.ui_set_next_width(engine.ui_children_sum(1))
|
||||
engine.ui_column(); {
|
||||
engine.ui_padding(insets.left, {.Left})
|
||||
engine.ui_padding(insets.right, {.Right})
|
||||
engine.ui_padding(insets.top, {.Top})
|
||||
engine.ui_padding(insets.bottom, {.Bottom})
|
||||
engine.ui_text(fmt.tprintf("%d", counter))
|
||||
engine.ui_text(fmt.tprintf("%s / API %d", device_model, device_sdk))
|
||||
engine.ui_text(fmt.tprintf("ins %v %v %v %v ok=%v", insets.left, insets.top, insets.right, insets.bottom, insets_valid))
|
||||
if click, _ := engine.ui_button("Hello"); click {
|
||||
counter += 1
|
||||
show_toast(fmt.tprintf("Clicked %d times", counter))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Toast needs a Java looper on this thread. Preparing one is a one-time cost
|
||||
// and the native ALooper the engine polls is unaffected -- they're separate.
|
||||
show_toast :: proc(msg: string) {
|
||||
if l, ok := looper_my_looper(); ok && l == nil {
|
||||
looper_prepare()
|
||||
}
|
||||
|
||||
toast, ok := toast_make_text(toast_ctx, msg, 0) // 0 = Toast.LENGTH_SHORT
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
toast_show(toast)
|
||||
}
|
||||
|
||||
// Walks Activity -> Window -> decor View -> WindowInsets. Returns false while
|
||||
// the decor view is still unattached, which is normal for the first few frames.
|
||||
refresh_insets :: proc() -> bool {
|
||||
// This runs every frame until it succeeds, and each call below returns a
|
||||
// local ref. Without a frame to scope them the ref table fills and aborts.
|
||||
env := jni.env()
|
||||
if !jni.push_frame(env) {
|
||||
return false
|
||||
}
|
||||
defer jni.pop_frame(env)
|
||||
|
||||
win := activity_get_window(main_activity) or_return
|
||||
decor := window_get_decor_view(win) or_return
|
||||
wi := view_get_root_window_insets(decor) or_return
|
||||
if wi == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// API 30+ typed insets. Below that both calls fail and we fall through.
|
||||
if mask, ok := insets_type_system_bars(); ok {
|
||||
if cut, got_cut := insets_type_display_cutout(); got_cut {
|
||||
mask |= cut
|
||||
}
|
||||
if ins, got := window_insets_get_ignoring_visibility(wi, mask); got && ins != nil {
|
||||
l := insets_left(ins) or_return
|
||||
t := insets_top(ins) or_return
|
||||
r := insets_right(ins) or_return
|
||||
b := insets_bottom(ins) or_return
|
||||
insets = {f32(l), f32(t), f32(r), f32(b)}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
l := window_insets_sys_left(wi) or_return
|
||||
t := window_insets_sys_top(wi) or_return
|
||||
r := window_insets_sys_right(wi) or_return
|
||||
b := window_insets_sys_bottom(wi) or_return
|
||||
insets = {f32(l), f32(t), f32(r), f32(b)}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user