#![allow(unused)]
use {
compo::prelude::*,
compo_platform_loop::prelude::vm_exec,
jni::{
JNIEnv,
errors::Result as JniResult,
objects::{GlobalRef, JObject},
},
std::cell::Cell,
tracing::{error, info},
};
thread_local! {
static ACTIVITY: Cell<Option<GlobalRef>> = Cell::new(None);
static ACTIVITY_REQUEST_RENDERING: EventListener<'static, ()> = EventListener::default();
}
#[allow(non_snake_case)]
#[unsafe(no_mangle)]
unsafe extern "system" fn Java_rust_compo_CompoActivity_on_1created(env: JNIEnv, this: JObject) {
ACTIVITY.set(env.new_global_ref(this).ok());
ACTIVITY_REQUEST_RENDERING.with(|i| i.new_emitter().emit(()));
}
#[allow(non_snake_case)]
#[unsafe(no_mangle)]
unsafe extern "system" fn Java_rust_compo_CompoActivity_on_1destroyed(env: JNIEnv, this: JObject) {
ACTIVITY.set(None);
ACTIVITY_REQUEST_RENDERING.with(|i| i.new_emitter().emit(()));
}
async fn get_activity() -> GlobalRef {
match ACTIVITY.with(|i| unsafe { transmute::<_, &mut Option<GlobalRef>>(i.as_ptr()) }.clone()) {
Some(a) if !a.is_null() => a,
_ => {
ACTIVITY_REQUEST_RENDERING.with(|i| i.listen()).await;
Box::pin(get_activity()).await
}
}
}
#[component]
pub async fn window(
#[default = "Window"] title: &str,
#[default = 360] width: i32,
#[default = 640] height: i32,
#[default = true] visible: bool,
#[default = true] enabled: bool,
) {
let activity_obj = get_activity().await;
let this2 = this.clone();
this.spawn(async move {
ACTIVITY_REQUEST_RENDERING.with(|i| i.listen()).await;
this2.update();
});
#[field]
let window_initialized: bool = false;
if *visible {
if !*window_initialized {
if let Err(e) = setup_android_window(&activity_obj, title, *width, *height, *enabled) {
error!("Failed to setup Android window: {:?}", e);
return;
}
*window_initialized = true;
info!(
"Android window initialized: {} ({}x{})",
title, width, height
);
} else {
let mut needs_update = false;
if let Err(e) = update_window_title(&activity_obj, title) {
error!("Failed to update window title: {:?}", e);
} else {
needs_update = true;
}
if let Err(e) = update_window_size(&activity_obj, *width, *height) {
error!("Failed to update window size: {:?}", e);
} else {
needs_update = true;
}
if let Err(e) = update_window_enabled(&activity_obj, *enabled) {
error!("Failed to update window enabled state: {:?}", e);
}
if needs_update {
info!(
"Android window updated: {} ({}x{}), enabled: {}",
title, width, height, enabled
);
}
}
} else if *window_initialized {
if let Err(e) = hide_android_window(&activity_obj) {
error!("Failed to hide Android window: {:?}", e);
} else {
info!("Android window hidden");
}
}
}
fn setup_android_window(
activity: &JObject,
title: &str,
width: i32,
height: i32,
enabled: bool,
) -> JniResult<()> {
vm_exec(|mut env| {
let title = env.new_string(title)?;
env.call_method(
activity,
"setTitle",
"(Ljava/lang/CharSequence;)V",
&[(&title).into()],
)?;
let window = env.call_method(activity, "getWindow", "()Landroid/view/Window;", &[])?;
let window_obj = window.l()?;
if enabled {
env.call_method(
&window_obj,
"clearFlags",
"(I)V",
&[0x00000010.into()], )?;
} else {
env.call_method(
&window_obj,
"addFlags",
"(I)V",
&[0x00000010.into()], )?;
}
let attributes = env.call_method(
&window_obj,
"getAttributes",
"()Landroid/view/WindowManager$LayoutParams;",
&[],
)?;
let attributes_obj = attributes.l()?;
env.set_field(&attributes_obj, "width", "I", width.into())?;
env.set_field(&attributes_obj, "height", "I", height.into())?;
env.call_method(
&window_obj,
"setAttributes",
"(Landroid/view/WindowManager$LayoutParams;)V",
&[(&attributes_obj).into()],
)?;
Ok(())
})
}
fn update_window_title(activity: &JObject, title: &str) -> JniResult<()> {
vm_exec(|mut env| {
let title = env.new_string(title)?;
env.call_method(
activity,
"setTitle",
"(Ljava/lang/CharSequence;)V",
&[(&title).into()],
)?;
Ok(())
})
}
fn update_window_size(activity: &JObject, width: i32, height: i32) -> JniResult<()> {
vm_exec(|mut env| {
let window = env.call_method(activity, "getWindow", "()Landroid/view/Window;", &[])?;
let window_obj = window.l()?;
let attributes = env.call_method(
&window_obj,
"getAttributes",
"()Landroid/view/WindowManager$LayoutParams;",
&[],
)?;
let attributes_obj = attributes.l()?;
env.set_field(&attributes_obj, "width", "I", width.into())?;
env.set_field(&attributes_obj, "height", "I", height.into())?;
env.call_method(
&window_obj,
"setAttributes",
"(Landroid/view/WindowManager$LayoutParams;)V",
&[(&attributes_obj).into()],
)?;
Ok(())
})
}
fn update_window_enabled(activity: &JObject, enabled: bool) -> JniResult<()> {
vm_exec(|mut env| {
let window = env.call_method(activity, "getWindow", "()Landroid/view/Window;", &[])?;
let window_obj = window.l()?;
if enabled {
env.call_method(
&window_obj,
"clearFlags",
"(I)V",
&[0x00000010.into()], )?;
} else {
env.call_method(
&window_obj,
"addFlags",
"(I)V",
&[0x00000010.into()], )?;
}
Ok(())
})
}
fn hide_android_window(activity: &JObject) -> JniResult<()> {
vm_exec(|mut env| {
env.call_method(activity, "moveTaskToBack", "(Z)Z", &[true.into()])?;
Ok(())
})
}