use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Condvar, LazyLock, Mutex, OnceLock};
use std::thread::JoinHandle;
use std::time::Instant;
use jni::objects::Global;
use jni::objects::JClass;
use jni::JavaVM;
use ndk::native_window::NativeWindow;
pub(crate) static TEXT_BRIDGE: LazyLock<Mutex<TextBridgeState>> =
LazyLock::new(|| Mutex::new(TextBridgeState::default()));
pub(crate) static IMAGE_PICKER: LazyLock<Mutex<ImagePickerState>> =
LazyLock::new(|| Mutex::new(ImagePickerState::default()));
static APP: OnceLock<Arc<AppState>> = OnceLock::new();
pub(crate) struct AppState {
pub(crate) java_vm: Mutex<Option<JavaVM>>,
pub(crate) egui_class: Mutex<Option<Global<JClass<'static>>>>,
pub(crate) density: Mutex<f32>,
pub(crate) size_px: Mutex<(u32, u32)>,
pub(crate) window: Mutex<Option<NativeWindow>>,
pub(crate) running: AtomicBool,
pub(crate) rebind: AtomicBool,
pub(crate) touches: Mutex<VecDeque<TouchEvent>>,
pub(crate) egui_ctx: Mutex<Option<egui::Context>>,
pub(crate) extra_events: Mutex<Vec<egui::Event>>,
pub(crate) egui_memory: Mutex<Option<egui::Memory>>,
pub(crate) render_thread: Mutex<Option<JoinHandle<()>>>,
pub(crate) wake: Condvar,
pub(crate) wake_lock: Mutex<()>,
pub(crate) repaint_deadline: Mutex<Option<Instant>>,
}
#[derive(Default)]
pub(crate) struct TextBridgeState {
pub(crate) pending_request: Option<TextEditRequest>,
pub(crate) dialog_open: bool,
pub(crate) results: VecDeque<(u64, String)>,
}
#[derive(Default)]
pub(crate) struct ImagePickerState {
pub(crate) open: bool,
pub(crate) results: VecDeque<(u64, Option<String>)>,
}
#[derive(Clone)]
pub(crate) struct TextEditRequest {
pub(crate) widget_id: u64,
pub(crate) initial_text: String,
pub(crate) title: String,
pub(crate) hint: String,
pub(crate) input_type: i32,
}
#[derive(Clone, Copy)]
pub(crate) struct TouchEvent {
pub(crate) x: f32,
pub(crate) y: f32,
pub(crate) action: TouchAction,
}
#[derive(Clone, Copy, PartialEq)]
pub(crate) enum TouchAction {
Down,
Up,
Move,
}
pub(crate) fn app_state() -> Arc<AppState> {
APP.get()
.expect("nativeAttach must be called before touching app state")
.clone()
}
pub(crate) fn try_app_state() -> Option<Arc<AppState>> {
APP.get().cloned()
}
pub(crate) fn init_app_state() -> Arc<AppState> {
APP.get_or_init(|| Arc::new(AppState::new())).clone()
}
impl AppState {
fn new() -> Self {
Self {
java_vm: Mutex::new(None),
egui_class: Mutex::new(None),
density: Mutex::new(1.0),
size_px: Mutex::new((0, 0)),
window: Mutex::new(None),
running: AtomicBool::new(false),
rebind: AtomicBool::new(false),
touches: Mutex::new(VecDeque::new()),
egui_ctx: Mutex::new(None),
extra_events: Mutex::new(Vec::new()),
egui_memory: Mutex::new(None),
render_thread: Mutex::new(None),
wake: Condvar::new(),
wake_lock: Mutex::new(()),
repaint_deadline: Mutex::new(None),
}
}
pub(crate) fn wake_renderer(&self) {
self.wake.notify_all();
}
pub(crate) fn request_stop(&self) {
let _guard = self.wake_lock.lock().unwrap();
self.running.store(false, Ordering::SeqCst);
self.wake.notify_all();
}
pub(crate) fn request_rebind(&self) {
let _guard = self.wake_lock.lock().unwrap();
self.rebind.store(true, Ordering::SeqCst);
self.wake.notify_all();
}
pub(crate) fn take_rebind(&self) -> bool {
self.rebind.swap(false, Ordering::SeqCst)
}
pub(crate) fn push_touch(&self, event: TouchEvent) {
let _guard = self.wake_lock.lock().unwrap();
self.touches.lock().unwrap().push_back(event);
self.wake.notify_one();
}
pub(crate) fn push_text_result(&self, request_id: u64, text: String) {
let _guard = self.wake_lock.lock().unwrap();
let mut bridge = TEXT_BRIDGE.lock().unwrap();
bridge.dialog_open = false;
bridge.results.push_back((request_id, text));
self.wake.notify_one();
}
pub(crate) fn push_image_result(&self, request_id: u64, path: Option<String>) {
let _guard = self.wake_lock.lock().unwrap();
let mut picker = IMAGE_PICKER.lock().unwrap();
picker.open = false;
picker.results.push_back((request_id, path));
self.wake.notify_one();
}
pub(crate) fn push_extra_event(&self, event: egui::Event) {
let _guard = self.wake_lock.lock().unwrap();
self.extra_events.lock().unwrap().push(event);
self.wake.notify_one();
}
}