tessera_ui/
renderer.rs

1//! # Tessera Renderer
2//!
3//! The core rendering system for the Tessera UI framework. This module provides the main
4//! [`Renderer`] struct that manages the application lifecycle, event handling, and rendering
5//! pipeline for cross-platform UI applications.
6//!
7//! ## Overview
8//!
9//! The renderer is built on top of WGPU and winit, providing:
10//! - Cross-platform window management (Windows, Linux, macOS, Android)
11//! - Event handling (mouse, touch, keyboard, IME)
12//! - Pluggable rendering pipeline system
13//! - Component tree management and rendering
14//! - Performance monitoring and optimization
15//!
16//! ## Architecture
17//!
18//! The renderer follows a modular architecture with several key components:
19//!
20//! - **[`app`]**: WGPU application management and surface handling
21//! - **[`command`]**: Rendering command abstraction
22//! - **[`compute`]**: Compute shader pipeline management
23//! - **[`drawer`]**: Drawing pipeline management and execution
24//!
25//! ## Basic Usage
26//!
27//! The most common way to use the renderer is through the [`Renderer::run`] method:
28//!
29//! ```no_run
30//! use tessera_ui::Renderer;
31//!
32//! // Define your UI entry point
33//! fn my_app() {
34//!     // Your UI components go here
35//! }
36//!
37//! // Run the application
38//! Renderer::run(
39//!     my_app,  // Entry point function
40//!     |app| {
41//!         // Register rendering pipelines
42//!         // For example, tessera_ui_basic_components::pipelines::register_pipelines(app);
43//!     }
44//! ).unwrap();
45//! ```
46//!
47//! ## Configuration
48//!
49//! You can customize the renderer behavior using [`TesseraConfig`]:
50//!
51//! ```no_run
52//! use tessera_ui::{Renderer, renderer::TesseraConfig};
53//!
54//! # fn foo() -> Result<(), Box<dyn std::error::Error>> {
55//! let config = TesseraConfig {
56//!     sample_count: 8,  // 8x MSAA
57//!     ..Default::default()
58//! };
59//!
60//! Renderer::run_with_config(
61//!     || { /* my_app */ },
62//!     |_app| { /* register_pipelines */ },
63//!     config
64//! )?;
65//! # Ok(())
66//! # }
67//! ```
68//!
69//! ## Platform Support
70//!
71//! ### Desktop Platforms (Windows, Linux, macOS)
72//!
73//! ```rust,ignore
74//! use tessera_ui::Renderer;
75//! use tessera_ui_macros::tessera;
76//!
77//! #[tessera] // You need to mark every component function with `#[tessera_macros::tessera]`
78//! fn entry_point() {}
79//! fn register_pipelines(_: &mut tessera_ui::renderer::WgpuApp) {}
80//!
81//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
82//! Renderer::run(entry_point, register_pipelines)?;
83//! # Ok(())
84//! # }
85//! ```
86//!
87//! ### Android
88//!
89//! ```no_run
90//! use tessera_ui::Renderer;
91//! #[cfg(target_os = "android")]
92//! use winit::platform::android::activity::AndroidApp;
93//!
94//! fn entry_point() {}
95//! fn register_pipelines(_: &mut tessera_ui::renderer::WgpuApp) {}
96//!
97//! #[cfg(target_os = "android")]
98//! fn android_main(android_app: AndroidApp) {
99//!     Renderer::run(entry_point, register_pipelines, android_app).unwrap();
100//! }
101//! ```
102//!
103//! ## Event Handling
104//!
105//! The renderer automatically handles various input events:
106//!
107//! - **Mouse Events**: Click, move, scroll, enter/leave
108//! - **Touch Events**: Multi-touch support with gesture recognition
109//! - **Keyboard Events**: Key press/release, with platform-specific handling
110//! - **IME Events**: Input method support for international text input
111//!
112//! Events are processed and forwarded to the component tree for handling.
113//!
114//! ## Performance Monitoring
115//!
116//! The renderer includes built-in performance monitoring that logs frame statistics
117//! when performance drops below 60 FPS:
118//!
119//! ```text
120//! WARN Jank detected! Frame statistics:
121//!     Build tree cost: 2.1ms
122//!     Draw commands cost: 1.8ms
123//!     Render cost: 12.3ms
124//!     Total frame cost: 16.2ms
125//!     Fps: 61.73
126//! ```
127//!
128//! ## Examples
129//!
130//! ### Simple Counter Application
131//!
132//! ```rust,ignore
133//! use std::sync::{Arc, atomic::{AtomicU32, Ordering}};
134//!
135//! use tessera_ui::{Renderer, Color, Dp};
136//! use tessera_ui_macros::tessera;
137//!
138//! struct AppState {
139//!     count: AtomicU32,
140//! }
141//!
142//! #[tessera] // You need to mark every component function with `#[tessera_macros::tessera]`
143//! fn counter_app(state: Arc<AppState>) {
144//!     let _count = state.count.load(Ordering::Relaxed);
145//!     // Your UI components would go here
146//!     // This is a simplified example without actual UI components
147//! }
148//!
149//! fn main() -> Result<(), Box<dyn std::error::Error>> {
150//!     let state = Arc::new(AppState {
151//!         count: AtomicU32::new(0),
152//!     });
153//!
154//!     Renderer::run(
155//!         move || counter_app(state.clone()),
156//!         |_app| {
157//!             // Register your rendering pipelines here
158//!             // tessera_ui_basic_components::pipelines::register_pipelines(app);
159//!         }
160//!     )?;
161//!     
162//!     Ok(())
163//! }
164//! ```
165//!
166//! ### Custom Rendering Pipeline
167//!
168//! ```no_run
169//! use tessera_ui::{Renderer, renderer::WgpuApp};
170//!
171//! fn register_custom_pipelines(app: &mut WgpuApp) {
172//!     // Register basic components first
173//!     // tessera_ui_basic_components::pipelines::register_pipelines(app);
174//!     
175//!     // Add your custom pipelines
176//!     // app.drawer.register_pipeline("my_custom_shader", my_pipeline);
177//! }
178//!
179//! fn main() -> Result<(), Box<dyn std::error::Error>> {
180//!     Renderer::run(
181//!         || { /* your UI */ },
182//!         register_custom_pipelines
183//!     )?;
184//!     Ok(())
185//! }
186//! ```
187
188pub mod app;
189pub mod command;
190pub mod compute;
191pub mod drawer;
192pub mod reorder;
193
194use std::{any::TypeId, sync::Arc, thread, time::Instant};
195
196use tessera_ui_macros::tessera;
197use tracing::{debug, error, instrument, warn};
198use winit::{
199    application::ApplicationHandler,
200    error::EventLoopError,
201    event::WindowEvent,
202    event_loop::{ActiveEventLoop, EventLoop},
203    window::{Window, WindowId},
204};
205
206use crate::{
207    Clipboard, ImeState, PxPosition,
208    component_tree::WindowRequests,
209    cursor::{CursorEvent, CursorEventContent, CursorState},
210    dp::SCALE_FACTOR,
211    keyboard_state::KeyboardState,
212    px::PxSize,
213    runtime::TesseraRuntime,
214    thread_utils,
215};
216
217pub use app::WgpuApp;
218pub use command::{BarrierRequirement, Command};
219pub use compute::{ComputablePipeline, ComputePipelineRegistry};
220pub use drawer::{DrawCommand, DrawablePipeline, PipelineRegistry};
221
222#[cfg(target_os = "android")]
223use winit::platform::android::{
224    ActiveEventLoopExtAndroid, EventLoopBuilderExtAndroid, activity::AndroidApp,
225};
226
227/// Configuration for the Tessera runtime and renderer.
228///
229/// This struct allows you to customize various aspects of the renderer's behavior,
230/// including anti-aliasing settings and other rendering parameters.
231///
232/// # Examples
233///
234/// ```
235/// use tessera_ui::renderer::TesseraConfig;
236///
237/// // Default configuration (4x MSAA)
238/// let config = TesseraConfig::default();
239///
240/// // Custom configuration with 8x MSAA
241/// let config = TesseraConfig {
242///     sample_count: 8,
243///     ..Default::default()
244/// };
245///
246/// // Disable MSAA for better performance
247/// let config = TesseraConfig {
248///     sample_count: 1,
249///     ..Default::default()
250/// };
251/// ```
252#[derive(Debug, Clone)]
253pub struct TesseraConfig {
254    /// The number of samples to use for Multi-Sample Anti-Aliasing (MSAA).
255    ///
256    /// MSAA helps reduce aliasing artifacts (jagged edges) in rendered graphics
257    /// by sampling multiple points per pixel and averaging the results.
258    ///
259    /// ## Supported Values
260    /// - `1`: Disables MSAA (best performance, lower quality)
261    /// - `4`: 4x MSAA (balanced quality/performance)
262    /// - `8`: 8x MSAA (high quality, higher performance cost)
263    ///
264    /// ## Notes
265    /// - Higher sample counts provide better visual quality but consume more GPU resources
266    /// - The GPU must support the chosen sample count; unsupported values may cause errors
267    /// - Mobile devices may have limited support for higher sample counts
268    /// - Consider using lower values on resource-constrained devices
269    pub sample_count: u32,
270    /// The title of the application window.
271    /// Defaults to "Tessera" if not specified.
272    pub window_title: String,
273}
274
275impl Default for TesseraConfig {
276    /// Creates a default configuration with 4x MSAA and "Tessera" as the window title.
277    fn default() -> Self {
278        Self {
279            sample_count: 4,
280            window_title: "Tessera".to_string(),
281        }
282    }
283}
284
285/// The main renderer struct that manages the application lifecycle and rendering.
286///
287/// The `Renderer` is the core component of the Tessera UI framework, responsible for:
288/// - Managing the application window and WGPU context
289/// - Handling input events (mouse, touch, keyboard, IME)
290/// - Coordinating the component tree building and rendering process
291/// - Managing rendering pipelines and resources
292///
293/// ## Type Parameters
294///
295/// - `F`: The entry point function type that defines your UI. Must implement `Fn()`.
296/// - `R`: The pipeline registration function type. Must implement `Fn(&mut WgpuApp) + Clone + 'static`.
297///
298/// ## Lifecycle
299///
300/// The renderer follows this lifecycle:
301/// 1. **Initialization**: Create window, initialize WGPU context, register pipelines
302/// 2. **Event Loop**: Handle window events, input events, and render requests
303/// 3. **Frame Rendering**: Build component tree → Compute draw commands → Render to surface
304/// 4. **Cleanup**: Automatic cleanup when the application exits
305///
306/// ## Thread Safety
307///
308/// The renderer runs on the main thread and coordinates with other threads for:
309/// - Component tree building (potentially parallelized)
310/// - Resource management
311/// - Event processing
312///
313/// ## Examples
314///
315/// See the module-level documentation for usage examples.
316pub struct Renderer<F: Fn(), R: Fn(&mut WgpuApp) + Clone + 'static> {
317    /// The WGPU application context, initialized after window creation
318    app: Option<WgpuApp>,
319    /// The entry point function that defines the root of your UI component tree
320    entry_point: F,
321    /// Tracks cursor/mouse position and button states
322    cursor_state: CursorState,
323    /// Tracks keyboard key states and events
324    keyboard_state: KeyboardState,
325    /// Tracks Input Method Editor (IME) state for international text input
326    ime_state: ImeState,
327    /// Function called during initialization to register rendering pipelines
328    register_pipelines_fn: R,
329    /// Configuration settings for the renderer
330    config: TesseraConfig,
331    /// Clipboard manager
332    clipboard: Clipboard,
333    /// Commands from the previous frame, for dirty rectangle optimization
334    previous_commands: Vec<(Command, TypeId, PxSize, PxPosition)>,
335    #[cfg(target_os = "android")]
336    /// Android-specific state tracking whether the soft keyboard is currently open
337    android_ime_opened: bool,
338}
339
340impl<F: Fn(), R: Fn(&mut WgpuApp) + Clone + 'static> Renderer<F, R> {
341    /// Runs the Tessera application with default configuration on desktop platforms.
342    ///
343    /// This is the most convenient way to start a Tessera application on Windows, Linux, or macOS.
344    /// It uses the default [`TesseraConfig`] settings (4x MSAA).
345    ///
346    /// # Parameters
347    ///
348    /// - `entry_point`: A function that defines your UI. This function will be called every frame
349    ///   to build the component tree. It should contain your root UI components.
350    /// - `register_pipelines_fn`: A function that registers rendering pipelines with the WGPU app.
351    ///   Typically, you'll call `tessera_ui_basic_components::pipelines::register_pipelines(app)` here.
352    ///
353    /// # Returns
354    ///
355    /// Returns `Ok(())` when the application exits normally, or an `EventLoopError` if the
356    /// event loop fails to start or encounters a critical error.
357    ///
358    /// # Examples
359    ///
360    /// ```no_run
361    /// use tessera_ui::Renderer;
362    ///
363    /// fn my_ui() {
364    ///     // Your UI components go here
365    /// }
366    ///
367    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
368    ///     Renderer::run(
369    ///         my_ui,
370    ///         |_app| {
371    ///             // Register your rendering pipelines here
372    ///             // tessera_ui_basic_components::pipelines::register_pipelines(app);
373    ///         }
374    ///     )?;
375    ///     Ok(())
376    /// }
377    /// ```
378    #[cfg(not(target_os = "android"))]
379    #[tracing::instrument(level = "info", skip(entry_point, register_pipelines_fn))]
380    pub fn run(entry_point: F, register_pipelines_fn: R) -> Result<(), EventLoopError> {
381        Self::run_with_config(entry_point, register_pipelines_fn, Default::default())
382    }
383
384    /// Runs the Tessera application with custom configuration on desktop platforms.
385    ///
386    /// This method allows you to customize the renderer behavior through [`TesseraConfig`].
387    /// Use this when you need to adjust settings like MSAA sample count or other rendering parameters.
388    ///
389    /// # Parameters
390    ///
391    /// - `entry_point`: A function that defines your UI
392    /// - `register_pipelines_fn`: A function that registers rendering pipelines
393    /// - `config`: Custom configuration for the renderer
394    ///
395    /// # Returns
396    ///
397    /// Returns `Ok(())` when the application exits normally, or an `EventLoopError` if the
398    /// event loop fails to start.
399    ///
400    /// # Examples
401    ///
402    /// ```no_run
403    /// use tessera_ui::{Renderer, renderer::TesseraConfig};
404    ///
405    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
406    /// let config = TesseraConfig {
407    ///     sample_count: 8,  // 8x MSAA for higher quality
408    ///     ..Default::default()
409    /// };
410    ///
411    /// Renderer::run_with_config(
412    ///     || { /* my_ui */ },
413    ///     |_app| { /* register_pipelines */ },
414    ///     config
415    /// )?;
416    /// # Ok(())
417    /// # }
418    /// ```
419    #[tracing::instrument(level = "info", skip(entry_point, register_pipelines_fn))]
420    #[cfg(not(any(target_os = "android")))]
421    pub fn run_with_config(
422        entry_point: F,
423        register_pipelines_fn: R,
424        config: TesseraConfig,
425    ) -> Result<(), EventLoopError> {
426        let event_loop = EventLoop::new().unwrap();
427        let app = None;
428        let cursor_state = CursorState::default();
429        let keyboard_state = KeyboardState::default();
430        let ime_state = ImeState::default();
431        let clipboard = Clipboard::new();
432        let mut renderer = Self {
433            app,
434            entry_point,
435            cursor_state,
436            keyboard_state,
437            register_pipelines_fn,
438            ime_state,
439            config,
440            clipboard,
441            previous_commands: Vec::new(),
442        };
443        thread_utils::set_thread_name("Tessera Renderer");
444        event_loop.run_app(&mut renderer)
445    }
446
447    /// Runs the Tessera application with default configuration on Android.
448    ///
449    /// This method is specifically for Android applications and requires an `AndroidApp` instance
450    /// that is typically provided by the `android_main` function.
451    ///
452    /// # Parameters
453    ///
454    /// - `entry_point`: A function that defines your UI
455    /// - `register_pipelines_fn`: A function that registers rendering pipelines
456    /// - `android_app`: The Android application context
457    ///
458    /// # Returns
459    ///
460    /// Returns `Ok(())` when the application exits normally, or an `EventLoopError` if the
461    /// event loop fails to start.
462    ///
463    /// # Examples
464    ///
465    /// ```no_run
466    /// use tessera_ui::Renderer;
467    /// use winit::platform::android::activity::AndroidApp;
468    ///
469    /// fn my_ui() {}
470    /// fn register_pipelines(_: &mut tessera_ui::renderer::WgpuApp) {}
471    ///
472    /// #[unsafe(no_mangle)]
473    /// fn android_main(android_app: AndroidApp) {
474    ///     Renderer::run(
475    ///         my_ui,
476    ///         register_pipelines,
477    ///         android_app
478    ///     ).unwrap();
479    /// }
480    /// ```
481    #[cfg(target_os = "android")]
482    #[tracing::instrument(level = "info", skip(entry_point, register_pipelines_fn, android_app))]
483    pub fn run(
484        entry_point: F,
485        register_pipelines_fn: R,
486        android_app: AndroidApp,
487    ) -> Result<(), EventLoopError> {
488        Self::run_with_config(
489            entry_point,
490            register_pipelines_fn,
491            android_app,
492            Default::default(),
493        )
494    }
495
496    /// Runs the Tessera application with custom configuration on Android.
497    ///
498    /// This method allows you to customize the renderer behavior on Android through [`TesseraConfig`].
499    ///
500    /// # Parameters
501    ///
502    /// - `entry_point`: A function that defines your UI
503    /// - `register_pipelines_fn`: A function that registers rendering pipelines
504    /// - `android_app`: The Android application context
505    /// - `config`: Custom configuration for the renderer
506    ///
507    /// # Returns
508    ///
509    /// Returns `Ok(())` when the application exits normally, or an `EventLoopError` if the
510    /// event loop fails to start.
511    ///
512    /// # Examples
513    ///
514    /// ```no_run
515    /// use tessera_ui::{Renderer, renderer::TesseraConfig};
516    /// use winit::platform::android::activity::AndroidApp;
517    ///
518    /// fn my_ui() {}
519    /// fn register_pipelines(_: &mut tessera_ui::renderer::WgpuApp) {}
520    ///
521    /// #[unsafe(no_mangle)]
522    /// fn android_main(android_app: AndroidApp) {
523    ///     let config = TesseraConfig {
524    ///         sample_count: 2,  // Lower MSAA for mobile performance
525    ///     };
526    ///     
527    ///     Renderer::run_with_config(
528    ///         my_ui,
529    ///         register_pipelines,
530    ///         android_app,
531    ///         config
532    ///     ).unwrap();
533    /// }
534    /// ```
535    #[cfg(target_os = "android")]
536    #[tracing::instrument(level = "info", skip(entry_point, register_pipelines_fn, android_app))]
537    pub fn run_with_config(
538        entry_point: F,
539        register_pipelines_fn: R,
540        android_app: AndroidApp,
541        config: TesseraConfig,
542    ) -> Result<(), EventLoopError> {
543        let event_loop = EventLoop::builder()
544            .with_android_app(android_app.clone())
545            .build()
546            .unwrap();
547        let app = None;
548        let cursor_state = CursorState::default();
549        let keyboard_state = KeyboardState::default();
550        let ime_state = ImeState::default();
551        let clipboard = Clipboard::new(android_app);
552        let mut renderer = Self {
553            app,
554            entry_point,
555            cursor_state,
556            keyboard_state,
557            register_pipelines_fn,
558            ime_state,
559            android_ime_opened: false,
560            config,
561            clipboard,
562            previous_commands: Vec::new(),
563        };
564        thread_utils::set_thread_name("Tessera Renderer");
565        event_loop.run_app(&mut renderer)
566    }
567}
568
569// Helper struct to group render-frame arguments and reduce parameter count.
570// Kept private to this module.
571struct RenderFrameArgs<'a> {
572    pub resized: bool,
573    pub cursor_state: &'a mut CursorState,
574    pub keyboard_state: &'a mut KeyboardState,
575    pub ime_state: &'a mut ImeState,
576    #[cfg(target_os = "android")]
577    pub android_ime_opened: &'a mut bool,
578    pub app: &'a mut WgpuApp,
579    #[cfg(target_os = "android")]
580    pub event_loop: &'a ActiveEventLoop,
581    pub clipboard: &'a mut Clipboard,
582}
583
584impl<F: Fn(), R: Fn(&mut WgpuApp) + Clone + 'static> Renderer<F, R> {
585    fn should_set_cursor_pos(
586        cursor_position: Option<crate::PxPosition>,
587        window_width: f64,
588        window_height: f64,
589        edge_threshold: f64,
590    ) -> bool {
591        if let Some(pos) = cursor_position {
592            let x = pos.x.0 as f64;
593            let y = pos.y.0 as f64;
594            x > edge_threshold
595                && x < window_width - edge_threshold
596                && y > edge_threshold
597                && y < window_height - edge_threshold
598        } else {
599            false
600        }
601    }
602
603    /// Executes a single frame rendering cycle.
604    ///
605    /// This is the core rendering method that orchestrates the entire frame rendering process.
606    /// It follows a three-phase approach:
607    ///
608    /// 1. **Component Tree Building**: Calls the entry point function to build the UI component tree
609    /// 2. **Draw Command Computation**: Processes the component tree to generate rendering commands
610    /// 3. **Surface Rendering**: Executes the commands to render the final frame
611    ///
612    /// ## Performance Monitoring
613    ///
614    /// This method includes built-in performance monitoring that logs detailed timing information
615    /// when frame rates drop below 60 FPS, helping identify performance bottlenecks.
616    ///
617    /// ## Parameters
618    ///
619    /// - `entry_point`: The UI entry point function to build the component tree
620    /// - `cursor_state`: Mutable reference to cursor/mouse state for event processing
621    /// - `keyboard_state`: Mutable reference to keyboard state for event processing
622    /// - `ime_state`: Mutable reference to IME state for text input processing
623    /// - `android_ime_opened`: (Android only) Tracks soft keyboard state
624    /// - `app`: Mutable reference to the WGPU application context
625    /// - `event_loop`: (Android only) Event loop for IME management
626    ///
627    /// ## Frame Timing Breakdown
628    ///
629    /// - **Build Tree Cost**: Time spent building the component tree
630    /// - **Draw Commands Cost**: Time spent computing rendering commands
631    /// - **Render Cost**: Time spent executing GPU rendering commands
632    ///
633    /// ## Thread Safety
634    ///
635    /// This method runs on the main thread but coordinates with other threads for
636    /// component tree processing and resource management.
637    #[instrument(level = "debug", skip(entry_point))]
638    fn build_component_tree(entry_point: &F) -> std::time::Duration {
639        let tree_timer = Instant::now();
640        debug!("Building component tree...");
641        entry_wrapper(entry_point);
642        let build_tree_cost = tree_timer.elapsed();
643        debug!("Component tree built in {build_tree_cost:?}");
644        build_tree_cost
645    }
646
647    fn log_frame_stats(
648        build_tree_cost: std::time::Duration,
649        draw_cost: std::time::Duration,
650        render_cost: std::time::Duration,
651    ) {
652        let total = build_tree_cost + draw_cost + render_cost;
653        let fps = 1.0 / total.as_secs_f32();
654        if fps < 60.0 {
655            warn!(
656                "Jank detected! Frame statistics:
657Build tree cost: {:?}
658Draw commands cost: {:?}
659Render cost: {:?}
660Total frame cost: {:?}
661Fps: {:.2}
662",
663                build_tree_cost,
664                draw_cost,
665                render_cost,
666                total,
667                1.0 / total.as_secs_f32()
668            );
669        }
670    }
671
672    #[instrument(level = "debug", skip(args))]
673    fn compute_draw_commands<'a>(
674        args: &mut RenderFrameArgs<'a>,
675        screen_size: PxSize,
676    ) -> (
677        Vec<(Command, TypeId, PxSize, PxPosition)>,
678        WindowRequests,
679        std::time::Duration,
680    ) {
681        let draw_timer = Instant::now();
682        debug!("Computing draw commands...");
683        let cursor_position = args.cursor_state.position();
684        let cursor_events = args.cursor_state.take_events();
685        let keyboard_events = args.keyboard_state.take_events();
686        let ime_events = args.ime_state.take_events();
687
688        // Clear any existing compute resources
689        args.app.resource_manager.write().clear();
690
691        let (commands, window_requests) = TesseraRuntime::with_mut(|rt| {
692            rt.component_tree
693                .compute(crate::component_tree::ComputeParams {
694                    screen_size,
695                    cursor_position,
696                    cursor_events,
697                    keyboard_events,
698                    ime_events,
699                    modifiers: args.keyboard_state.modifiers(),
700                    compute_resource_manager: args.app.resource_manager.clone(),
701                    gpu: &args.app.gpu,
702                    clipboard: args.clipboard,
703                })
704        });
705
706        let draw_cost = draw_timer.elapsed();
707        debug!("Draw commands computed in {draw_cost:?}");
708        (commands, window_requests, draw_cost)
709    }
710
711    /// Perform the actual GPU rendering for the provided commands and return the render duration.
712    #[instrument(level = "debug", skip(args, commands))]
713    fn perform_render<'a>(
714        args: &mut RenderFrameArgs<'a>,
715        commands: impl IntoIterator<Item = (Command, TypeId, PxSize, PxPosition)>,
716    ) -> std::time::Duration {
717        let render_timer = Instant::now();
718
719        // skip actual rendering if window is minimized
720        if TesseraRuntime::with(|rt| rt.window_minimized) {
721            args.app.window.request_redraw();
722            return render_timer.elapsed();
723        }
724
725        debug!("Rendering draw commands...");
726        if let Err(e) = args.app.render(commands) {
727            match e {
728                wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost => {
729                    debug!("Surface outdated/lost, resizing...");
730                    args.app.resize_surface();
731                }
732                wgpu::SurfaceError::Timeout => warn!("Surface timeout. Frame will be dropped."),
733                wgpu::SurfaceError::OutOfMemory => {
734                    error!("Surface out of memory. Panicking.");
735                    panic!("Surface out of memory");
736                }
737                _ => {
738                    error!("Surface error: {e}. Attempting to continue.");
739                }
740            }
741        }
742        let render_cost = render_timer.elapsed();
743        debug!("Rendered to surface in {render_cost:?}");
744        render_cost
745    }
746
747    #[instrument(level = "debug", skip(entry_point, args, previous_commands))]
748    fn execute_render_frame(
749        entry_point: &F,
750        args: &mut RenderFrameArgs<'_>,
751        previous_commands: &mut Vec<(Command, TypeId, PxSize, PxPosition)>,
752    ) {
753        // notify the windowing system before rendering
754        // this will help winit to properly schedule and make assumptions about its internal state
755        args.app.window.pre_present_notify();
756        // and tell runtime the new size
757        TesseraRuntime::with_mut(|rt: &mut TesseraRuntime| rt.window_size = args.app.size().into());
758        // Clear any registered callbacks
759        TesseraRuntime::with_mut(|rt| rt.clear_frame_callbacks());
760
761        // Build the component tree and measure time
762        let build_tree_cost = Self::build_component_tree(entry_point);
763
764        // Compute draw commands
765        let screen_size: PxSize = args.app.size().into();
766        let (new_commands, window_requests, draw_cost) =
767            Self::compute_draw_commands(args, screen_size);
768
769        // --- Dirty Rectangle Logic ---
770        let mut dirty = false;
771        if args.resized || new_commands.len() != previous_commands.len() {
772            dirty = true;
773        } else {
774            for (new_cmd_tuple, old_cmd_tuple) in new_commands.iter().zip(previous_commands.iter())
775            {
776                let (new_cmd, _, new_size, new_pos) = new_cmd_tuple;
777                let (old_cmd, _, old_size, old_pos) = old_cmd_tuple;
778
779                let content_are_equal = match (new_cmd, old_cmd) {
780                    (Command::Draw(new_draw_cmd), Command::Draw(old_draw_cmd)) => {
781                        new_draw_cmd.dyn_eq(old_draw_cmd.as_ref())
782                    }
783                    (Command::Compute(new_compute_cmd), Command::Compute(old_compute_cmd)) => {
784                        new_compute_cmd.dyn_eq(old_compute_cmd.as_ref())
785                    }
786                    (Command::ClipPop, Command::ClipPop) => true,
787                    (Command::ClipPush(new_rect), Command::ClipPush(old_rect)) => {
788                        new_rect == old_rect
789                    }
790                    _ => false, // Mismatched command types
791                };
792
793                if !content_are_equal || new_size != old_size || new_pos != old_pos {
794                    dirty = true;
795                    break;
796                }
797            }
798        }
799
800        if dirty {
801            // Perform GPU render
802            let render_cost = Self::perform_render(args, new_commands.clone());
803            // Log frame statistics
804            Self::log_frame_stats(build_tree_cost, draw_cost, render_cost);
805        } else {
806            thread::sleep(std::time::Duration::from_millis(4)); // Sleep briefly to avoid busy-waiting
807        }
808
809        // Clear the component tree (free for next frame)
810        TesseraRuntime::with_mut(|rt| rt.component_tree.clear());
811
812        // Handle the window requests (cursor / IME)
813        // Only set cursor when not at window edges to let window manager handle resize cursors
814        let cursor_position = args.cursor_state.position();
815        let window_size = args.app.size();
816        let edge_threshold = 8.0; // Slightly larger threshold for better UX
817
818        let should_set_cursor = Self::should_set_cursor_pos(
819            cursor_position,
820            window_size.width as f64,
821            window_size.height as f64,
822            edge_threshold,
823        );
824
825        if should_set_cursor {
826            args.app
827                .window
828                .set_cursor(winit::window::Cursor::Icon(window_requests.cursor_icon));
829        }
830
831        if let Some(ime_request) = window_requests.ime_request {
832            #[cfg(not(target_os = "android"))]
833            args.app.window.set_ime_allowed(true);
834            #[cfg(target_os = "android")]
835            {
836                if !*args.android_ime_opened {
837                    args.app.window.set_ime_allowed(true);
838                    show_soft_input(true, args.event_loop.android_app());
839                    *args.android_ime_opened = true;
840                }
841            }
842            args.app.window.set_ime_cursor_area::<PxPosition, PxSize>(
843                ime_request.position.unwrap(),
844                ime_request.size,
845            );
846        } else {
847            #[cfg(not(target_os = "android"))]
848            args.app.window.set_ime_allowed(false);
849            #[cfg(target_os = "android")]
850            {
851                if *args.android_ime_opened {
852                    args.app.window.set_ime_allowed(false);
853                    hide_soft_input(args.event_loop.android_app());
854                    *args.android_ime_opened = false;
855                }
856            }
857        }
858
859        // End of frame cleanup
860        args.cursor_state.frame_cleanup();
861
862        // Store the commands for the next frame's comparison
863        *previous_commands = new_commands;
864
865        // Currently we render every frame, but with dirty checking, this could be conditional.
866        // For now, we still request a redraw to keep the event loop spinning for animations.
867        args.app.window.request_redraw();
868    }
869}
870
871impl<F: Fn(), R: Fn(&mut WgpuApp) + Clone + 'static> Renderer<F, R> {
872    // --- Private helper methods extracted from the large match in window_event ---
873    // These keep behavior identical but reduce per-function complexity.
874    fn handle_close_requested(&mut self, event_loop: &ActiveEventLoop) {
875        TesseraRuntime::with(|rt| rt.trigger_close_callbacks());
876        event_loop.exit();
877    }
878
879    fn handle_resized(&mut self, size: winit::dpi::PhysicalSize<u32>) {
880        // Obtain the app inside the method to avoid holding a mutable borrow across other
881        // borrows of `self`.
882        let app = match self.app.as_mut() {
883            Some(app) => app,
884            None => return,
885        };
886
887        if size.width == 0 || size.height == 0 {
888            // Window minimize handling & callback API
889            TesseraRuntime::with_mut(|rt| {
890                if !rt.window_minimized {
891                    rt.window_minimized = true;
892                    rt.trigger_minimize_callbacks(true);
893                }
894            });
895        } else {
896            // Window (un)minimize handling & callback API
897            TesseraRuntime::with_mut(|rt| {
898                if rt.window_minimized {
899                    rt.window_minimized = false;
900                    rt.trigger_minimize_callbacks(false);
901                }
902            });
903            app.resize(size);
904        }
905    }
906
907    fn handle_cursor_moved(&mut self, position: winit::dpi::PhysicalPosition<f64>) {
908        // Update cursor position
909        self.cursor_state
910            .update_position(PxPosition::from_f64_arr2([position.x, position.y]));
911        debug!("Cursor moved to: {}, {}", position.x, position.y);
912    }
913
914    fn handle_cursor_left(&mut self) {
915        // Clear cursor position when it leaves the window
916        // This also set the position to None
917        self.cursor_state.clear();
918        debug!("Cursor left the window");
919    }
920
921    fn handle_mouse_input(
922        &mut self,
923        state: winit::event::ElementState,
924        button: winit::event::MouseButton,
925    ) {
926        let Some(event_content) = CursorEventContent::from_press_event(state, button) else {
927            return; // Ignore unsupported buttons
928        };
929        let event = CursorEvent {
930            timestamp: Instant::now(),
931            content: event_content,
932        };
933        self.cursor_state.push_event(event);
934        debug!("Mouse input: {state:?} button {button:?}");
935    }
936
937    fn handle_mouse_wheel(&mut self, delta: winit::event::MouseScrollDelta) {
938        let event_content = CursorEventContent::from_scroll_event(delta);
939        let event = CursorEvent {
940            timestamp: Instant::now(),
941            content: event_content,
942        };
943        self.cursor_state.push_event(event);
944        debug!("Mouse scroll: {delta:?}");
945    }
946
947    fn handle_touch(&mut self, touch_event: winit::event::Touch) {
948        let pos = PxPosition::from_f64_arr2([touch_event.location.x, touch_event.location.y]);
949        debug!(
950            "Touch event: id {}, phase {:?}, position {:?}",
951            touch_event.id, touch_event.phase, pos
952        );
953        match touch_event.phase {
954            winit::event::TouchPhase::Started => {
955                // Use new touch start handling method
956                self.cursor_state.handle_touch_start(touch_event.id, pos);
957            }
958            winit::event::TouchPhase::Moved => {
959                // Use new touch move handling method, may generate scroll event
960                if let Some(scroll_event) = self.cursor_state.handle_touch_move(touch_event.id, pos)
961                {
962                    // Scroll event is already added to event queue in handle_touch_move
963                    self.cursor_state.push_event(scroll_event);
964                }
965            }
966            winit::event::TouchPhase::Ended | winit::event::TouchPhase::Cancelled => {
967                // Use new touch end handling method
968                self.cursor_state.handle_touch_end(touch_event.id);
969            }
970        }
971    }
972
973    fn handle_keyboard_input(&mut self, event: winit::event::KeyEvent) {
974        debug!("Keyboard input: {event:?}");
975        self.keyboard_state.push_event(event);
976    }
977
978    fn handle_redraw_requested(
979        &mut self,
980        #[cfg(target_os = "android")] event_loop: &ActiveEventLoop,
981    ) {
982        // Borrow the app here to avoid simultaneous mutable borrows of `self`
983        let app = match self.app.as_mut() {
984            Some(app) => app,
985            None => return,
986        };
987
988        let resized = app.resize_if_needed();
989        let mut args = RenderFrameArgs {
990            resized,
991            cursor_state: &mut self.cursor_state,
992            keyboard_state: &mut self.keyboard_state,
993            ime_state: &mut self.ime_state,
994            #[cfg(target_os = "android")]
995            android_ime_opened: &mut self.android_ime_opened,
996            app,
997            #[cfg(target_os = "android")]
998            event_loop,
999            clipboard: &mut self.clipboard,
1000        };
1001        Self::execute_render_frame(&self.entry_point, &mut args, &mut self.previous_commands);
1002    }
1003}
1004
1005/// Implementation of winit's `ApplicationHandler` trait for the Tessera renderer.
1006///
1007/// This implementation handles the application lifecycle events from winit, including
1008/// window creation, suspension/resumption, and various window events. It bridges the
1009/// gap between winit's event system and Tessera's component-based UI framework.
1010impl<F: Fn(), R: Fn(&mut WgpuApp) + Clone + 'static> ApplicationHandler for Renderer<F, R> {
1011    /// Called when the application is resumed or started.
1012    ///
1013    /// This method is responsible for:
1014    /// - Creating the application window with appropriate attributes
1015    /// - Initializing the WGPU context and surface
1016    /// - Registering rendering pipelines
1017    /// - Setting up the initial application state
1018    ///
1019    /// On desktop platforms, this is typically called once at startup.
1020    /// On mobile platforms (especially Android), this may be called multiple times
1021    /// as the app is suspended and resumed.
1022    ///
1023    /// ## Window Configuration
1024    ///
1025    /// The window is created with:
1026    /// - Title: "Tessera"
1027    /// - Transparency: Enabled (allows for transparent backgrounds)
1028    /// - Default size and position (platform-dependent)
1029    ///
1030    /// ## Pipeline Registration
1031    ///
1032    /// After WGPU initialization, the `register_pipelines_fn` is called to set up
1033    /// all rendering pipelines. This typically includes basic component pipelines
1034    /// and any custom shaders your application requires.
1035    #[tracing::instrument(level = "debug", skip(self, event_loop))]
1036    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
1037        // Just return if the app is already created
1038        if self.app.is_some() {
1039            return;
1040        }
1041
1042        // Create a new window
1043        let window_attributes = Window::default_attributes()
1044            .with_title(&self.config.window_title)
1045            .with_transparent(true);
1046        let window = Arc::new(event_loop.create_window(window_attributes).unwrap());
1047        let register_pipelines_fn = self.register_pipelines_fn.clone();
1048
1049        let mut wgpu_app = pollster::block_on(WgpuApp::new(window, self.config.sample_count));
1050
1051        // Register pipelines
1052        wgpu_app.register_pipelines(register_pipelines_fn);
1053
1054        self.app = Some(wgpu_app);
1055    }
1056
1057    /// Called when the application is suspended.
1058    ///
1059    /// This method should handle cleanup and state preservation when the application
1060    /// is being suspended (e.g., on mobile platforms when the app goes to background).
1061    ///
1062    /// ## Current Status
1063    ///
1064    /// This method is currently not fully implemented (`todo!`). In a complete
1065    /// implementation, it should:
1066    /// - Save application state
1067    /// - Release GPU resources if necessary
1068    /// - Prepare for potential termination
1069    ///
1070    /// ## Platform Considerations
1071    ///
1072    /// - **Desktop**: Rarely called, mainly during shutdown
1073    /// - **Android**: Called when app goes to background
1074    /// - **iOS**: Called during app lifecycle transitions
1075    fn suspended(&mut self, _event_loop: &ActiveEventLoop) {
1076        todo!("Handle suspend event");
1077    }
1078
1079    /// Handles window-specific events from the windowing system.
1080    ///
1081    /// This method processes all window events including user input, window state changes,
1082    /// and rendering requests. It's the main event processing hub that translates winit
1083    /// events into Tessera's internal event system.
1084    ///
1085    /// ## Event Categories
1086    ///
1087    /// ### Window Management
1088    /// - `CloseRequested`: User requested to close the window
1089    /// - `Resized`: Window size changed
1090    /// - `ScaleFactorChanged`: Display scaling changed (high-DPI support)
1091    ///
1092    /// ### Input Events
1093    /// - `CursorMoved`: Mouse cursor position changed
1094    /// - `CursorLeft`: Mouse cursor left the window
1095    /// - `MouseInput`: Mouse button press/release
1096    /// - `MouseWheel`: Mouse wheel scrolling
1097    /// - `Touch`: Touch screen interactions (mobile)
1098    /// - `KeyboardInput`: Keyboard key press/release
1099    /// - `Ime`: Input Method Editor events (international text input)
1100    ///
1101    /// ### Rendering
1102    /// - `RedrawRequested`: System requests a frame to be rendered
1103    ///
1104    /// ## Event Processing Flow
1105    ///
1106    /// 1. **Input Events**: Captured and stored in respective state managers
1107    /// 2. **State Updates**: Internal state (cursor, keyboard, IME) is updated
1108    /// 3. **Rendering**: On redraw requests, the full rendering pipeline is executed
1109    ///
1110    /// ## Platform-Specific Handling
1111    ///
1112    /// Some events have platform-specific behavior, particularly:
1113    /// - Touch events (mobile platforms)
1114    /// - IME events (different implementations per platform)
1115    /// - Scale factor changes (high-DPI displays)
1116    #[tracing::instrument(level = "debug", skip(self, event_loop))]
1117    fn window_event(
1118        &mut self,
1119        event_loop: &ActiveEventLoop,
1120        _window_id: WindowId,
1121        event: WindowEvent,
1122    ) {
1123        // Defer borrowing `app` into specific event handlers to avoid overlapping mutable borrows.
1124        // Handlers will obtain a mutable reference to `self.app` as needed.
1125
1126        // Handle window events
1127        match event {
1128            WindowEvent::CloseRequested => {
1129                self.handle_close_requested(event_loop);
1130            }
1131            WindowEvent::Resized(size) => {
1132                self.handle_resized(size);
1133            }
1134            WindowEvent::CursorMoved {
1135                device_id: _,
1136                position,
1137            } => {
1138                self.handle_cursor_moved(position);
1139            }
1140            WindowEvent::CursorLeft { device_id: _ } => {
1141                self.handle_cursor_left();
1142            }
1143            WindowEvent::MouseInput {
1144                device_id: _,
1145                state,
1146                button,
1147            } => {
1148                self.handle_mouse_input(state, button);
1149            }
1150            WindowEvent::MouseWheel {
1151                device_id: _,
1152                delta,
1153                phase: _,
1154            } => {
1155                self.handle_mouse_wheel(delta);
1156            }
1157            WindowEvent::Touch(touch_event) => {
1158                self.handle_touch(touch_event);
1159            }
1160            WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
1161                *SCALE_FACTOR.get().unwrap().write() = scale_factor;
1162            }
1163            WindowEvent::KeyboardInput { event, .. } => {
1164                self.handle_keyboard_input(event);
1165            }
1166            WindowEvent::ModifiersChanged(modifiers) => {
1167                debug!("Modifiers changed: {modifiers:?}");
1168                self.keyboard_state.update_modifiers(modifiers.state());
1169            }
1170            WindowEvent::Ime(ime_event) => {
1171                debug!("IME event: {ime_event:?}");
1172                self.ime_state.push_event(ime_event);
1173            }
1174            WindowEvent::RedrawRequested => {
1175                #[cfg(target_os = "android")]
1176                self.handle_redraw_requested(event_loop);
1177                #[cfg(not(target_os = "android"))]
1178                self.handle_redraw_requested();
1179            }
1180            _ => (),
1181        }
1182    }
1183}
1184
1185/// Shows the Android soft keyboard (virtual keyboard).
1186///
1187/// This function uses JNI to interact with the Android system to display the soft keyboard.
1188/// It's specifically designed for Android applications and handles the complex JNI calls
1189/// required to show the input method.
1190///
1191/// ## Parameters
1192///
1193/// - `show_implicit`: Whether to show the keyboard implicitly (without explicit user action)
1194/// - `android_app`: Reference to the Android application context
1195///
1196/// ## Platform Support
1197///
1198/// This function is only available on Android (`target_os = "android"`). It will not be
1199/// compiled on other platforms.
1200///
1201/// ## Error Handling
1202///
1203/// The function includes comprehensive error handling for JNI operations. If any JNI
1204/// call fails, the function will return early without crashing the application.
1205/// Exception handling is also included to clear any Java exceptions that might occur.
1206///
1207/// ## Implementation Notes
1208///
1209/// This implementation is based on the android-activity crate and follows the pattern
1210/// established in: https://github.com/rust-mobile/android-activity/pull/178
1211///
1212/// The function performs these steps:
1213/// 1. Get the Java VM and activity context
1214/// 2. Find the InputMethodManager system service
1215/// 3. Get the current window's decor view
1216/// 4. Call `showSoftInput` on the InputMethodManager
1217///
1218/// ## Usage
1219///
1220/// This function is typically called internally by the renderer when IME input is requested.
1221/// You generally don't need to call this directly in application code.
1222// https://github.com/rust-mobile/android-activity/pull/178
1223#[cfg(target_os = "android")]
1224pub fn show_soft_input(show_implicit: bool, android_app: &AndroidApp) {
1225    let ctx = android_app;
1226
1227    let jvm = unsafe { jni::JavaVM::from_raw(ctx.vm_as_ptr().cast()) }.unwrap();
1228    let na = unsafe { jni::objects::JObject::from_raw(ctx.activity_as_ptr().cast()) };
1229
1230    let mut env = jvm.attach_current_thread().unwrap();
1231    if env.exception_check().unwrap() {
1232        return;
1233    }
1234    let class_ctxt = env.find_class("android/content/Context").unwrap();
1235    if env.exception_check().unwrap() {
1236        return;
1237    }
1238    let ims = env
1239        .get_static_field(class_ctxt, "INPUT_METHOD_SERVICE", "Ljava/lang/String;")
1240        .unwrap();
1241    if env.exception_check().unwrap() {
1242        return;
1243    }
1244
1245    let im_manager = env
1246        .call_method(
1247            &na,
1248            "getSystemService",
1249            "(Ljava/lang/String;)Ljava/lang/Object;",
1250            &[(&ims).into()],
1251        )
1252        .unwrap()
1253        .l()
1254        .unwrap();
1255    if env.exception_check().unwrap() {
1256        return;
1257    }
1258
1259    let jni_window = env
1260        .call_method(&na, "getWindow", "()Landroid/view/Window;", &[])
1261        .unwrap()
1262        .l()
1263        .unwrap();
1264    if env.exception_check().unwrap() {
1265        return;
1266    }
1267    let view = env
1268        .call_method(&jni_window, "getDecorView", "()Landroid/view/View;", &[])
1269        .unwrap()
1270        .l()
1271        .unwrap();
1272    if env.exception_check().unwrap() {
1273        return;
1274    }
1275
1276    let _ = env.call_method(
1277        im_manager,
1278        "showSoftInput",
1279        "(Landroid/view/View;I)Z",
1280        &[
1281            jni::objects::JValue::Object(&view),
1282            if show_implicit {
1283                (ndk_sys::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT as i32).into()
1284            } else {
1285                0i32.into()
1286            },
1287        ],
1288    );
1289    // showSoftInput can trigger exceptions if the keyboard is currently animating open/closed
1290    if env.exception_check().unwrap() {
1291        let _ = env.exception_clear();
1292    }
1293}
1294
1295/// Hides the Android soft keyboard (virtual keyboard).
1296///
1297/// This function uses JNI to interact with the Android system to hide the soft keyboard.
1298/// It's the counterpart to [`show_soft_input`] and handles the complex JNI calls required
1299/// to dismiss the input method.
1300///
1301/// ## Parameters
1302///
1303/// - `android_app`: Reference to the Android application context
1304///
1305/// ## Platform Support
1306///
1307/// This function is only available on Android (`target_os = "android"`). It will not be
1308/// compiled on other platforms.
1309///
1310/// ## Error Handling
1311///
1312/// Like [`show_soft_input`], this function includes comprehensive error handling for JNI
1313/// operations. If any step fails, the function returns early without crashing. Java
1314/// exceptions are also properly handled and cleared.
1315///
1316/// ## Implementation Details
1317///
1318/// The function performs these steps:
1319/// 1. Get the Java VM and activity context
1320/// 2. Find the InputMethodManager system service
1321/// 3. Get the current window and its decor view
1322/// 4. Get the window token from the decor view
1323/// 5. Call `hideSoftInputFromWindow` on the InputMethodManager
1324///
1325/// ## Usage
1326///
1327/// This function is typically called internally by the renderer when IME input is no longer
1328/// needed. You generally don't need to call this directly in application code.
1329///
1330/// ## Relationship to show_soft_input
1331///
1332/// This function is designed to work in tandem with [`show_soft_input`]. The renderer
1333/// automatically manages the keyboard visibility based on IME requests from components.
1334#[cfg(target_os = "android")]
1335pub fn hide_soft_input(android_app: &AndroidApp) {
1336    use jni::objects::JValue;
1337
1338    let ctx = android_app;
1339    let jvm = match unsafe { jni::JavaVM::from_raw(ctx.vm_as_ptr().cast()) } {
1340        Ok(jvm) => jvm,
1341        Err(_) => return, // Early exit if failing to get the JVM
1342    };
1343    let activity = unsafe { jni::objects::JObject::from_raw(ctx.activity_as_ptr().cast()) };
1344
1345    let mut env = match jvm.attach_current_thread() {
1346        Ok(env) => env,
1347        Err(_) => return,
1348    };
1349
1350    // --- 1. Get the InputMethodManager ---
1351    // This part is the same as in show_soft_input.
1352    let class_ctxt = match env.find_class("android/content/Context") {
1353        Ok(c) => c,
1354        Err(_) => return,
1355    };
1356    let ims_field =
1357        match env.get_static_field(class_ctxt, "INPUT_METHOD_SERVICE", "Ljava/lang/String;") {
1358            Ok(f) => f,
1359            Err(_) => return,
1360        };
1361    let ims = match ims_field.l() {
1362        Ok(s) => s,
1363        Err(_) => return,
1364    };
1365
1366    let im_manager = match env.call_method(
1367        &activity,
1368        "getSystemService",
1369        "(Ljava/lang/String;)Ljava/lang/Object;",
1370        &[(&ims).into()],
1371    ) {
1372        Ok(m) => match m.l() {
1373            Ok(im) => im,
1374            Err(_) => return,
1375        },
1376        Err(_) => return,
1377    };
1378
1379    // --- 2. Get the current window's token ---
1380    // This is the key step that differs from show_soft_input.
1381    let window = match env.call_method(&activity, "getWindow", "()Landroid/view/Window;", &[]) {
1382        Ok(w) => match w.l() {
1383            Ok(win) => win,
1384            Err(_) => return,
1385        },
1386        Err(_) => return,
1387    };
1388
1389    let decor_view = match env.call_method(&window, "getDecorView", "()Landroid/view/View;", &[]) {
1390        Ok(v) => match v.l() {
1391            Ok(view) => view,
1392            Err(_) => return,
1393        },
1394        Err(_) => return,
1395    };
1396
1397    let window_token =
1398        match env.call_method(&decor_view, "getWindowToken", "()Landroid/os/IBinder;", &[]) {
1399            Ok(t) => match t.l() {
1400                Ok(token) => token,
1401                Err(_) => return,
1402            },
1403            Err(_) => return,
1404        };
1405
1406    // --- 3. Call hideSoftInputFromWindow ---
1407    let _ = env.call_method(
1408        &im_manager,
1409        "hideSoftInputFromWindow",
1410        "(Landroid/os/IBinder;I)Z",
1411        &[
1412            JValue::Object(&window_token),
1413            JValue::Int(0), // flags, usually 0
1414        ],
1415    );
1416
1417    // Hiding the keyboard can also cause exceptions, so we clear them.
1418    if env.exception_check().unwrap_or(false) {
1419        let _ = env.exception_clear();
1420    }
1421}
1422
1423/// Entry point wrapper for tessera applications.
1424///
1425/// # Why this is needed
1426///
1427/// Tessera component entry points must be functions annotated with the `tessera` macro.
1428/// Unlike some other frameworks, we cannot detect whether a provided closure has been
1429/// annotated with `tessera`. Wrapping the entry function guarantees it is invoked from
1430/// a `tessera`-annotated function, ensuring correct behavior regardless of how the user
1431/// supplied their entry point.
1432#[tessera(crate)]
1433fn entry_wrapper(entry: impl Fn()) {
1434    entry();
1435}