tessera_ui/
renderer.rs

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