pub mod app;
pub mod command;
pub mod compute;
pub mod drawer;
pub mod reorder;
use std::{any::TypeId, sync::Arc, thread, time::Instant};
use accesskit::{self, TreeUpdate};
use accesskit_winit::{Adapter as AccessKitAdapter, Event as AccessKitEvent};
use tessera_ui_macros::tessera;
use tracing::{debug, error, instrument, warn};
use winit::{
application::ApplicationHandler,
error::EventLoopError,
event::WindowEvent,
event_loop::{ActiveEventLoop, EventLoop},
window::{Window, WindowId},
};
use crate::{
Clipboard, ImeState, PxPosition,
component_tree::WindowRequests,
cursor::{CursorEvent, CursorEventContent, CursorState, GestureState},
dp::SCALE_FACTOR,
keyboard_state::KeyboardState,
px::PxSize,
runtime::TesseraRuntime,
thread_utils,
};
pub use app::WgpuApp;
pub use command::{BarrierRequirement, Command};
pub use compute::{
ComputablePipeline, ComputeBatchItem, ComputePipelineRegistry, ErasedComputeBatchItem,
};
pub use drawer::{DrawCommand, DrawablePipeline, PipelineRegistry};
#[cfg(target_os = "android")]
use winit::platform::android::{
ActiveEventLoopExtAndroid, EventLoopBuilderExtAndroid, activity::AndroidApp,
};
#[derive(Debug, Clone)]
pub struct TesseraConfig {
pub sample_count: u32,
pub window_title: String,
}
impl Default for TesseraConfig {
fn default() -> Self {
Self {
sample_count: 1,
window_title: "Tessera".to_string(),
}
}
}
pub struct Renderer<F: Fn(), R: Fn(&mut WgpuApp) + Clone + 'static> {
app: Option<WgpuApp>,
entry_point: F,
cursor_state: CursorState,
keyboard_state: KeyboardState,
ime_state: ImeState,
register_pipelines_fn: R,
config: TesseraConfig,
clipboard: Clipboard,
previous_commands: Vec<(Command, TypeId, PxSize, PxPosition)>,
accessibility_adapter: Option<AccessKitAdapter>,
event_loop_proxy: Option<winit::event_loop::EventLoopProxy<AccessKitEvent>>,
#[cfg(target_os = "android")]
android_ime_opened: bool,
}
impl<F: Fn(), R: Fn(&mut WgpuApp) + Clone + 'static> Renderer<F, R> {
#[cfg(not(target_os = "android"))]
#[tracing::instrument(level = "info", skip(entry_point, register_pipelines_fn))]
pub fn run(entry_point: F, register_pipelines_fn: R) -> Result<(), EventLoopError> {
Self::run_with_config(entry_point, register_pipelines_fn, Default::default())
}
#[tracing::instrument(level = "info", skip(entry_point, register_pipelines_fn))]
#[cfg(not(any(target_os = "android")))]
pub fn run_with_config(
entry_point: F,
register_pipelines_fn: R,
config: TesseraConfig,
) -> Result<(), EventLoopError> {
let event_loop = EventLoop::<AccessKitEvent>::with_user_event()
.build()
.unwrap();
let event_loop_proxy = event_loop.create_proxy();
let app = None;
let cursor_state = CursorState::default();
let keyboard_state = KeyboardState::default();
let ime_state = ImeState::default();
let clipboard = Clipboard::new();
let mut renderer = Self {
app,
entry_point,
cursor_state,
keyboard_state,
register_pipelines_fn,
ime_state,
config,
clipboard,
previous_commands: Vec::new(),
accessibility_adapter: None,
event_loop_proxy: Some(event_loop_proxy),
};
thread_utils::set_thread_name("Tessera Renderer");
event_loop.run_app(&mut renderer)
}
#[cfg(target_os = "android")]
#[tracing::instrument(level = "info", skip(entry_point, register_pipelines_fn, android_app))]
pub fn run(
entry_point: F,
register_pipelines_fn: R,
android_app: AndroidApp,
) -> Result<(), EventLoopError> {
Self::run_with_config(
entry_point,
register_pipelines_fn,
android_app,
Default::default(),
)
}
#[cfg(target_os = "android")]
#[tracing::instrument(level = "info", skip(entry_point, register_pipelines_fn, android_app))]
pub fn run_with_config(
entry_point: F,
register_pipelines_fn: R,
android_app: AndroidApp,
config: TesseraConfig,
) -> Result<(), EventLoopError> {
let event_loop = EventLoop::<AccessKitEvent>::with_user_event()
.with_android_app(android_app.clone())
.build()
.unwrap();
let event_loop_proxy = event_loop.create_proxy();
let app = None;
let cursor_state = CursorState::default();
let keyboard_state = KeyboardState::default();
let ime_state = ImeState::default();
let clipboard = Clipboard::new(android_app);
let mut renderer = Self {
app,
entry_point,
cursor_state,
keyboard_state,
register_pipelines_fn,
ime_state,
android_ime_opened: false,
config,
clipboard,
previous_commands: Vec::new(),
accessibility_adapter: None,
event_loop_proxy: Some(event_loop_proxy),
};
thread_utils::set_thread_name("Tessera Renderer");
event_loop.run_app(&mut renderer)
}
}
struct RenderFrameArgs<'a> {
pub resized: bool,
pub cursor_state: &'a mut CursorState,
pub keyboard_state: &'a mut KeyboardState,
pub ime_state: &'a mut ImeState,
#[cfg(target_os = "android")]
pub android_ime_opened: &'a mut bool,
pub app: &'a mut WgpuApp,
#[cfg(target_os = "android")]
pub event_loop: &'a ActiveEventLoop,
pub clipboard: &'a mut Clipboard,
}
impl<F: Fn(), R: Fn(&mut WgpuApp) + Clone + 'static> Renderer<F, R> {
fn should_set_cursor_pos(
cursor_position: Option<crate::PxPosition>,
window_width: f64,
window_height: f64,
edge_threshold: f64,
) -> bool {
if let Some(pos) = cursor_position {
let x = pos.x.0 as f64;
let y = pos.y.0 as f64;
x > edge_threshold
&& x < window_width - edge_threshold
&& y > edge_threshold
&& y < window_height - edge_threshold
} else {
false
}
}
#[instrument(level = "debug", skip(entry_point))]
fn build_component_tree(entry_point: &F) -> std::time::Duration {
let tree_timer = Instant::now();
debug!("Building component tree...");
entry_wrapper(entry_point);
let build_tree_cost = tree_timer.elapsed();
debug!("Component tree built in {build_tree_cost:?}");
build_tree_cost
}
fn log_frame_stats(
build_tree_cost: std::time::Duration,
draw_cost: std::time::Duration,
render_cost: std::time::Duration,
) {
let total = build_tree_cost + draw_cost + render_cost;
let fps = 1.0 / total.as_secs_f32();
if fps < 60.0 {
warn!(
"Jank detected! Frame statistics:
Build tree cost: {:?}
Draw commands cost: {:?}
Render cost: {:?}
Total frame cost: {:?}
Fps: {:.2}
",
build_tree_cost,
draw_cost,
render_cost,
total,
1.0 / total.as_secs_f32()
);
}
}
#[instrument(level = "debug", skip(args))]
fn compute_draw_commands<'a>(
args: &mut RenderFrameArgs<'a>,
screen_size: PxSize,
) -> (
Vec<(Command, TypeId, PxSize, PxPosition)>,
WindowRequests,
std::time::Duration,
) {
let draw_timer = Instant::now();
debug!("Computing draw commands...");
let cursor_position = args.cursor_state.position();
let cursor_events = args.cursor_state.take_events();
let keyboard_events = args.keyboard_state.take_events();
let ime_events = args.ime_state.take_events();
args.app.resource_manager.write().clear();
let (commands, window_requests) = TesseraRuntime::with_mut(|rt| {
rt.component_tree
.compute(crate::component_tree::ComputeParams {
screen_size,
cursor_position,
cursor_events,
keyboard_events,
ime_events,
modifiers: args.keyboard_state.modifiers(),
compute_resource_manager: args.app.resource_manager.clone(),
gpu: &args.app.gpu,
clipboard: args.clipboard,
})
});
let draw_cost = draw_timer.elapsed();
debug!("Draw commands computed in {draw_cost:?}");
(commands, window_requests, draw_cost)
}
#[instrument(level = "debug", skip(args, commands))]
fn perform_render<'a>(
args: &mut RenderFrameArgs<'a>,
commands: impl IntoIterator<Item = (Command, TypeId, PxSize, PxPosition)>,
) -> std::time::Duration {
let render_timer = Instant::now();
if TesseraRuntime::with(|rt| rt.window_minimized) {
args.app.window.request_redraw();
return render_timer.elapsed();
}
debug!("Rendering draw commands...");
if let Err(e) = args.app.render(commands) {
match e {
wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost => {
debug!("Surface outdated/lost, resizing...");
args.app.resize_surface();
}
wgpu::SurfaceError::Timeout => warn!("Surface timeout. Frame will be dropped."),
wgpu::SurfaceError::OutOfMemory => {
error!("Surface out of memory. Panicking.");
panic!("Surface out of memory");
}
_ => {
error!("Surface error: {e}. Attempting to continue.");
}
}
}
let render_cost = render_timer.elapsed();
debug!("Rendered to surface in {render_cost:?}");
render_cost
}
#[instrument(level = "debug", skip(entry_point, args, previous_commands))]
fn execute_render_frame(
entry_point: &F,
args: &mut RenderFrameArgs<'_>,
previous_commands: &mut Vec<(Command, TypeId, PxSize, PxPosition)>,
accessibility_enabled: bool,
window_label: &str,
) -> Option<TreeUpdate> {
args.app.window.pre_present_notify();
TesseraRuntime::with_mut(|rt: &mut TesseraRuntime| rt.window_size = args.app.size().into());
TesseraRuntime::with_mut(|rt| rt.clear_frame_callbacks());
let build_tree_cost = Self::build_component_tree(entry_point);
let screen_size: PxSize = args.app.size().into();
let (new_commands, window_requests, draw_cost) =
Self::compute_draw_commands(args, screen_size);
let mut dirty = false;
if args.resized || new_commands.len() != previous_commands.len() {
dirty = true;
} else {
for (new_cmd_tuple, old_cmd_tuple) in new_commands.iter().zip(previous_commands.iter())
{
let (new_cmd, _, new_size, new_pos) = new_cmd_tuple;
let (old_cmd, _, old_size, old_pos) = old_cmd_tuple;
let content_are_equal = match (new_cmd, old_cmd) {
(Command::Draw(new_draw_cmd), Command::Draw(old_draw_cmd)) => {
new_draw_cmd.dyn_eq(old_draw_cmd.as_ref())
}
(Command::Compute(new_compute_cmd), Command::Compute(old_compute_cmd)) => {
new_compute_cmd.dyn_eq(old_compute_cmd.as_ref())
}
(Command::ClipPop, Command::ClipPop) => true,
(Command::ClipPush(new_rect), Command::ClipPush(old_rect)) => {
new_rect == old_rect
}
_ => false, };
if !content_are_equal || new_size != old_size || new_pos != old_pos {
dirty = true;
break;
}
}
}
if dirty {
let render_cost = Self::perform_render(args, new_commands.clone());
Self::log_frame_stats(build_tree_cost, draw_cost, render_cost);
} else {
thread::sleep(std::time::Duration::from_millis(4)); }
let accessibility_update = if accessibility_enabled {
Self::build_accessibility_update(window_label)
} else {
None
};
TesseraRuntime::with_mut(|rt| rt.component_tree.clear());
let cursor_position = args.cursor_state.position();
let window_size = args.app.size();
let edge_threshold = 8.0;
let should_set_cursor = Self::should_set_cursor_pos(
cursor_position,
window_size.width as f64,
window_size.height as f64,
edge_threshold,
);
if should_set_cursor {
args.app
.window
.set_cursor(winit::window::Cursor::Icon(window_requests.cursor_icon));
}
if let Some(ime_request) = window_requests.ime_request {
#[cfg(not(target_os = "android"))]
args.app.window.set_ime_allowed(true);
#[cfg(target_os = "android")]
{
if !*args.android_ime_opened {
args.app.window.set_ime_allowed(true);
show_soft_input(true, args.event_loop.android_app());
*args.android_ime_opened = true;
}
}
args.app.window.set_ime_cursor_area::<PxPosition, PxSize>(
ime_request.position.unwrap(),
ime_request.size,
);
} else {
#[cfg(not(target_os = "android"))]
args.app.window.set_ime_allowed(false);
#[cfg(target_os = "android")]
{
if *args.android_ime_opened {
args.app.window.set_ime_allowed(false);
hide_soft_input(args.event_loop.android_app());
*args.android_ime_opened = false;
}
}
}
args.cursor_state.frame_cleanup();
*previous_commands = new_commands;
args.app.window.request_redraw();
accessibility_update
}
}
impl<F: Fn(), R: Fn(&mut WgpuApp) + Clone + 'static> Renderer<F, R> {
fn handle_close_requested(&mut self, event_loop: &ActiveEventLoop) {
TesseraRuntime::with(|rt| rt.trigger_close_callbacks());
if let Some(ref app) = self.app
&& let Err(e) = app.save_pipeline_cache()
{
warn!("Failed to save pipeline cache: {}", e);
}
event_loop.exit();
}
fn handle_resized(&mut self, size: winit::dpi::PhysicalSize<u32>) {
let app = match self.app.as_mut() {
Some(app) => app,
None => return,
};
if size.width == 0 || size.height == 0 {
TesseraRuntime::with_mut(|rt| {
if !rt.window_minimized {
rt.window_minimized = true;
rt.trigger_minimize_callbacks(true);
}
});
} else {
TesseraRuntime::with_mut(|rt| {
if rt.window_minimized {
rt.window_minimized = false;
rt.trigger_minimize_callbacks(false);
}
});
app.resize(size);
}
}
fn handle_cursor_moved(&mut self, position: winit::dpi::PhysicalPosition<f64>) {
self.cursor_state
.update_position(PxPosition::from_f64_arr2([position.x, position.y]));
debug!("Cursor moved to: {}, {}", position.x, position.y);
}
fn handle_cursor_left(&mut self) {
self.cursor_state.clear();
debug!("Cursor left the window");
}
fn push_accessibility_update(&mut self, tree_update: TreeUpdate) {
if let Some(adapter) = self.accessibility_adapter.as_mut() {
adapter.update_if_active(|| tree_update);
}
}
fn send_accessibility_update(&mut self) {
if let Some(tree_update) = Self::build_accessibility_update(&self.config.window_title) {
self.push_accessibility_update(tree_update);
}
}
fn build_accessibility_update(window_label: &str) -> Option<TreeUpdate> {
TesseraRuntime::with(|runtime| {
let tree = runtime.component_tree.tree();
let metadatas = runtime.component_tree.metadatas();
let root_node_id = tree.get_node_id_at(std::num::NonZero::new(1).unwrap())?;
crate::accessibility::build_tree_update(
tree,
metadatas,
root_node_id,
Some(window_label),
)
})
}
fn handle_mouse_input(
&mut self,
state: winit::event::ElementState,
button: winit::event::MouseButton,
) {
let Some(event_content) = CursorEventContent::from_press_event(state, button) else {
return; };
let event = CursorEvent {
timestamp: Instant::now(),
content: event_content,
gesture_state: GestureState::TapCandidate,
};
self.cursor_state.push_event(event);
debug!("Mouse input: {state:?} button {button:?}");
}
fn handle_mouse_wheel(&mut self, delta: winit::event::MouseScrollDelta) {
let event_content = CursorEventContent::from_scroll_event(delta);
let event = CursorEvent {
timestamp: Instant::now(),
content: event_content,
gesture_state: GestureState::Dragged,
};
self.cursor_state.push_event(event);
debug!("Mouse scroll: {delta:?}");
}
fn handle_touch(&mut self, touch_event: winit::event::Touch) {
let pos = PxPosition::from_f64_arr2([touch_event.location.x, touch_event.location.y]);
debug!(
"Touch event: id {}, phase {:?}, position {:?}",
touch_event.id, touch_event.phase, pos
);
match touch_event.phase {
winit::event::TouchPhase::Started => {
self.cursor_state.handle_touch_start(touch_event.id, pos);
}
winit::event::TouchPhase::Moved => {
if let Some(scroll_event) = self.cursor_state.handle_touch_move(touch_event.id, pos)
{
self.cursor_state.push_event(scroll_event);
}
}
winit::event::TouchPhase::Ended | winit::event::TouchPhase::Cancelled => {
self.cursor_state.handle_touch_end(touch_event.id);
}
}
}
fn handle_keyboard_input(&mut self, event: winit::event::KeyEvent) {
debug!("Keyboard input: {event:?}");
self.keyboard_state.push_event(event);
}
fn handle_redraw_requested(
&mut self,
#[cfg(target_os = "android")] event_loop: &ActiveEventLoop,
) {
let app = match self.app.as_mut() {
Some(app) => app,
None => return,
};
let resized = app.resize_if_needed();
let mut args = RenderFrameArgs {
resized,
cursor_state: &mut self.cursor_state,
keyboard_state: &mut self.keyboard_state,
ime_state: &mut self.ime_state,
#[cfg(target_os = "android")]
android_ime_opened: &mut self.android_ime_opened,
app,
#[cfg(target_os = "android")]
event_loop,
clipboard: &mut self.clipboard,
};
let accessibility_update = Self::execute_render_frame(
&self.entry_point,
&mut args,
&mut self.previous_commands,
self.accessibility_adapter.is_some(),
&self.config.window_title,
);
if let Some(tree_update) = accessibility_update {
self.push_accessibility_update(tree_update);
}
}
}
impl<F: Fn(), R: Fn(&mut WgpuApp) + Clone + 'static> ApplicationHandler<AccessKitEvent>
for Renderer<F, R>
{
#[tracing::instrument(level = "debug", skip(self, event_loop))]
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.app.is_some() {
return;
}
let window_attributes = Window::default_attributes()
.with_title(&self.config.window_title)
.with_transparent(true)
.with_visible(false); let window = Arc::new(event_loop.create_window(window_attributes).unwrap());
if let Some(proxy) = self.event_loop_proxy.clone() {
self.accessibility_adapter = Some(AccessKitAdapter::with_event_loop_proxy(
event_loop, &window, proxy,
));
}
window.set_visible(true);
let register_pipelines_fn = self.register_pipelines_fn.clone();
let mut wgpu_app =
pollster::block_on(WgpuApp::new(window.clone(), self.config.sample_count));
wgpu_app.register_pipelines(register_pipelines_fn);
self.app = Some(wgpu_app);
#[cfg(target_os = "android")]
{
self.clipboard = Clipboard::new(event_loop.android_app().clone());
}
#[cfg(not(target_os = "android"))]
{
self.clipboard = Clipboard::new();
}
}
fn suspended(&mut self, _event_loop: &ActiveEventLoop) {
debug!("Suspending renderer; tearing down WGPU resources.");
if let Some(app) = self.app.take() {
app.resource_manager.write().clear();
}
self.accessibility_adapter = None;
self.previous_commands.clear();
self.cursor_state = CursorState::default();
self.keyboard_state = KeyboardState::default();
self.ime_state = ImeState::default();
#[cfg(target_os = "android")]
{
self.android_ime_opened = false;
}
TesseraRuntime::with_mut(|runtime| {
runtime.component_tree.clear();
runtime.cursor_icon_request = None;
runtime.window_minimized = false;
runtime.window_size = [0, 0];
});
}
#[tracing::instrument(level = "debug", skip(self, event_loop))]
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
if let (Some(adapter), Some(app)) = (&mut self.accessibility_adapter, &self.app) {
adapter.process_event(&app.window, &event);
}
match event {
WindowEvent::CloseRequested => {
self.handle_close_requested(event_loop);
}
WindowEvent::Resized(size) => {
self.handle_resized(size);
}
WindowEvent::CursorMoved {
device_id: _,
position,
} => {
self.handle_cursor_moved(position);
}
WindowEvent::CursorLeft { device_id: _ } => {
self.handle_cursor_left();
}
WindowEvent::MouseInput {
device_id: _,
state,
button,
} => {
self.handle_mouse_input(state, button);
}
WindowEvent::MouseWheel {
device_id: _,
delta,
phase: _,
} => {
self.handle_mouse_wheel(delta);
}
WindowEvent::Touch(touch_event) => {
self.handle_touch(touch_event);
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
*SCALE_FACTOR.get().unwrap().write() = scale_factor;
}
WindowEvent::KeyboardInput { event, .. } => {
self.handle_keyboard_input(event);
}
WindowEvent::ModifiersChanged(modifiers) => {
debug!("Modifiers changed: {modifiers:?}");
self.keyboard_state.update_modifiers(modifiers.state());
}
WindowEvent::Ime(ime_event) => {
debug!("IME event: {ime_event:?}");
self.ime_state.push_event(ime_event);
}
WindowEvent::RedrawRequested => {
#[cfg(target_os = "android")]
self.handle_redraw_requested(event_loop);
#[cfg(not(target_os = "android"))]
self.handle_redraw_requested();
}
_ => (),
}
}
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: AccessKitEvent) {
use accesskit_winit::WindowEvent as AccessKitWindowEvent;
if self.accessibility_adapter.is_none() {
return;
}
match event.window_event {
AccessKitWindowEvent::InitialTreeRequested => {
self.send_accessibility_update();
}
AccessKitWindowEvent::ActionRequested(action_request) => {
println!(
"[tessera-ui][accessibility] Action requested: {:?}",
action_request
);
let handled = TesseraRuntime::with(|runtime| {
let tree = runtime.component_tree.tree();
let metadatas = runtime.component_tree.metadatas();
crate::accessibility::dispatch_action(tree, metadatas, action_request)
});
if !handled {
debug!("Action was not handled by any component");
}
}
AccessKitWindowEvent::AccessibilityDeactivated => {
debug!("AccessKit deactivated");
}
}
}
}
#[cfg(target_os = "android")]
pub fn show_soft_input(show_implicit: bool, android_app: &AndroidApp) {
let ctx = android_app;
let jvm = unsafe { jni::JavaVM::from_raw(ctx.vm_as_ptr().cast()) }.unwrap();
let na = unsafe { jni::objects::JObject::from_raw(ctx.activity_as_ptr().cast()) };
let mut env = jvm.attach_current_thread().unwrap();
if env.exception_check().unwrap() {
return;
}
let class_ctxt = env.find_class("android/content/Context").unwrap();
if env.exception_check().unwrap() {
return;
}
let ims = env
.get_static_field(class_ctxt, "INPUT_METHOD_SERVICE", "Ljava/lang/String;")
.unwrap();
if env.exception_check().unwrap() {
return;
}
let im_manager = env
.call_method(
&na,
"getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;",
&[(&ims).into()],
)
.unwrap()
.l()
.unwrap();
if env.exception_check().unwrap() {
return;
}
let jni_window = env
.call_method(&na, "getWindow", "()Landroid/view/Window;", &[])
.unwrap()
.l()
.unwrap();
if env.exception_check().unwrap() {
return;
}
let view = env
.call_method(&jni_window, "getDecorView", "()Landroid/view/View;", &[])
.unwrap()
.l()
.unwrap();
if env.exception_check().unwrap() {
return;
}
let _ = env.call_method(
im_manager,
"showSoftInput",
"(Landroid/view/View;I)Z",
&[
jni::objects::JValue::Object(&view),
if show_implicit {
(ndk_sys::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT as i32).into()
} else {
0i32.into()
},
],
);
if env.exception_check().unwrap() {
let _ = env.exception_clear();
}
}
#[cfg(target_os = "android")]
pub fn hide_soft_input(android_app: &AndroidApp) {
use jni::objects::JValue;
let ctx = android_app;
let jvm = match unsafe { jni::JavaVM::from_raw(ctx.vm_as_ptr().cast()) } {
Ok(jvm) => jvm,
Err(_) => return, };
let activity = unsafe { jni::objects::JObject::from_raw(ctx.activity_as_ptr().cast()) };
let mut env = match jvm.attach_current_thread() {
Ok(env) => env,
Err(_) => return,
};
let class_ctxt = match env.find_class("android/content/Context") {
Ok(c) => c,
Err(_) => return,
};
let ims_field =
match env.get_static_field(class_ctxt, "INPUT_METHOD_SERVICE", "Ljava/lang/String;") {
Ok(f) => f,
Err(_) => return,
};
let ims = match ims_field.l() {
Ok(s) => s,
Err(_) => return,
};
let im_manager = match env.call_method(
&activity,
"getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;",
&[(&ims).into()],
) {
Ok(m) => match m.l() {
Ok(im) => im,
Err(_) => return,
},
Err(_) => return,
};
let window = match env.call_method(&activity, "getWindow", "()Landroid/view/Window;", &[]) {
Ok(w) => match w.l() {
Ok(win) => win,
Err(_) => return,
},
Err(_) => return,
};
let decor_view = match env.call_method(&window, "getDecorView", "()Landroid/view/View;", &[]) {
Ok(v) => match v.l() {
Ok(view) => view,
Err(_) => return,
},
Err(_) => return,
};
let window_token =
match env.call_method(&decor_view, "getWindowToken", "()Landroid/os/IBinder;", &[]) {
Ok(t) => match t.l() {
Ok(token) => token,
Err(_) => return,
},
Err(_) => return,
};
let _ = env.call_method(
&im_manager,
"hideSoftInputFromWindow",
"(Landroid/os/IBinder;I)Z",
&[
JValue::Object(&window_token),
JValue::Int(0), ],
);
if env.exception_check().unwrap_or(false) {
let _ = env.exception_clear();
}
}
#[tessera(crate)]
fn entry_wrapper(entry: impl Fn()) {
entry();
}