use std::time::{Duration, Instant};
pub use android_activity::AndroidApp;
use android_activity::input::{TextInputState, TextSpan};
use super::text_input::{Buffer, TextInputBridge};
use super::{WindowOptions, open};
use crate::gpu;
pub fn run_android(
android_app: AndroidApp,
options: WindowOptions,
create: impl FnOnce(&eframe::CreationContext<'_>) -> Box<dyn eframe::App>,
) -> eframe::Result<()> {
let mut options = options;
let create = options.wrap_create(create);
let WindowOptions { title, fonts, .. } = options;
gpu::install_panic_hook();
crate::platform::set_android_activity(android_app.activity_as_ptr());
let keyboard = android_app.clone();
let native = eframe::NativeOptions {
android_app: Some(android_app),
wgpu_options: gpu::wgpu_options(),
..Default::default()
};
open(&title, native, fonts, move |cc| {
Box::new(PhoneWindow::new(create(cc), keyboard))
})
}
const INSETS_EVERY: Duration = Duration::from_millis(250);
struct PhoneWindow {
app: Box<dyn eframe::App>,
android: AndroidApp,
text: TextInputBridge,
focused: Option<egui::Id>,
insets: [i32; 4],
insets_read: Option<Instant>,
}
impl PhoneWindow {
fn new(app: Box<dyn eframe::App>, android: AndroidApp) -> PhoneWindow {
PhoneWindow {
app,
android,
text: TextInputBridge::default(),
focused: None,
insets: [0; 4],
insets_read: None,
}
}
fn refresh_insets(&mut self) {
if self
.insets_read
.is_some_and(|at| at.elapsed() < INSETS_EVERY)
{
return;
}
self.insets_read = Some(Instant::now());
if let Some(insets) = crate::platform::window_insets() {
self.insets = insets;
}
}
}
impl eframe::App for PhoneWindow {
fn raw_input_hook(&mut self, ctx: &egui::Context, raw_input: &mut egui::RawInput) {
let focused = ctx.memory(|m| m.focused());
let refocused = focused != self.focused;
if refocused {
self.focused = focused;
self.text.refocus();
self.android.set_text_input_state(TextInputState {
text: String::new(),
selection: TextSpan { start: 0, end: 0 },
compose_region: None,
});
}
if self.focused.is_some() && !refocused {
let state = self.android.text_input_state();
let buffer = Buffer {
text: state.text,
compose: state.compose_region.map(|span| (span.start, span.end)),
};
raw_input.events.extend(self.text.translate(&buffer));
}
if ctx.egui_wants_keyboard_input() {
ctx.request_repaint_after(Duration::from_millis(50));
}
self.app.raw_input_hook(ctx, raw_input);
}
fn logic(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
self.app.logic(ctx, frame);
}
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
self.refresh_insets();
let ppp = ui.ctx().pixels_per_point();
let [left, top, right, bottom] = self.insets.map(|px| px.max(0) as f32 / ppp);
let full = ui.max_rect();
let inner = egui::Rect::from_min_max(
full.min + egui::vec2(left, top),
full.max - egui::vec2(right, bottom),
);
ui.painter().rect_filled(full, 0.0, ui.visuals().panel_fill);
ui.scope_builder(egui::UiBuilder::new().max_rect(inner), |ui| {
self.app.ui(ui, frame);
});
ui.ctx().request_repaint_after(INSETS_EVERY);
}
fn save(&mut self, storage: &mut dyn eframe::Storage) {
self.app.save(storage);
}
fn on_exit(&mut self) {
self.app.on_exit();
}
}