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