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