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