initial commit

This commit is contained in:
2026-09-03 10:44:30 +02:00
commit 891fadca85
18 changed files with 2518 additions and 0 deletions
+146
View File
@@ -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
}