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    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 - default)
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    #[cfg(target_os = "android")]
321    /// Android-specific state tracking whether the soft keyboard is currently open
322    android_ime_opened: bool,
323}
324
325impl<F: Fn(), R: Fn(&mut WgpuApp) + Clone + 'static> Renderer<F, R> {
326    #[cfg(not(target_os = "android"))]
327    /// Runs the Tessera application with default configuration on desktop platforms.
328    ///
329    /// This is the most convenient way to start a Tessera application on Windows, Linux, or macOS.
330    /// It uses the default [`TesseraConfig`] settings (4x MSAA).
331    ///
332    /// # Parameters
333    ///
334    /// - `entry_point`: A function that defines your UI. This function will be called every frame
335    ///   to build the component tree. It should contain your root UI components.
336    /// - `register_pipelines_fn`: A function that registers rendering pipelines with the WGPU app.
337    ///   Typically, you'll call `tessera_ui_basic_components::pipelines::register_pipelines(app)` here.
338    ///
339    /// # Returns
340    ///
341    /// Returns `Ok(())` when the application exits normally, or an `EventLoopError` if the
342    /// event loop fails to start or encounters a critical error.
343    ///
344    /// # Examples
345    ///
346    /// ```rust,no_run
347    /// use tessera_ui::Renderer;
348    ///
349    /// fn my_ui() {
350    ///     // Your UI components go here
351    /// }
352    ///
353    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
354    ///     Renderer::run(
355    ///         my_ui,
356    ///         |_app| {
357    ///             // Register your rendering pipelines here
358    ///             // tessera_ui_basic_components::pipelines::register_pipelines(app);
359    ///         }
360    ///     )?;
361    ///     Ok(())
362    /// }
363    /// ```
364    pub fn run(entry_point: F, register_pipelines_fn: R) -> Result<(), EventLoopError> {
365        Self::run_with_config(entry_point, register_pipelines_fn, Default::default())
366    }
367
368    #[cfg(not(target_os = "android"))]
369    /// Runs the Tessera application with custom configuration on desktop platforms.
370    ///
371    /// This method allows you to customize the renderer behavior through [`TesseraConfig`].
372    /// Use this when you need to adjust settings like MSAA sample count or other rendering parameters.
373    ///
374    /// # Parameters
375    ///
376    /// - `entry_point`: A function that defines your UI
377    /// - `register_pipelines_fn`: A function that registers rendering pipelines
378    /// - `config`: Custom configuration for the renderer
379    ///
380    /// # Returns
381    ///
382    /// Returns `Ok(())` when the application exits normally, or an `EventLoopError` if the
383    /// event loop fails to start.
384    ///
385    /// # Examples
386    ///
387    /// ```rust,no_run
388    /// use tessera_ui::{Renderer, renderer::TesseraConfig};
389    ///
390    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
391    /// let config = TesseraConfig {
392    ///     sample_count: 8,  // 8x MSAA for higher quality
393    /// };
394    ///
395    /// Renderer::run_with_config(
396    ///     || { /* my_ui */ },
397    ///     |_app| { /* register_pipelines */ },
398    ///     config
399    /// )?;
400    /// # Ok(())
401    /// # }
402    /// ```
403    pub fn run_with_config(
404        entry_point: F,
405        register_pipelines_fn: R,
406        config: TesseraConfig,
407    ) -> Result<(), EventLoopError> {
408        let event_loop = EventLoop::new().unwrap();
409        let app = None;
410        let cursor_state = CursorState::default();
411        let keyboard_state = KeyboardState::default();
412        let ime_state = ImeState::default();
413        let mut renderer = Self {
414            app,
415            entry_point,
416            cursor_state,
417            keyboard_state,
418            register_pipelines_fn,
419            ime_state,
420            config,
421        };
422        thread_utils::set_thread_name("Tessera Renderer");
423        event_loop.run_app(&mut renderer)
424    }
425
426    #[cfg(target_os = "android")]
427    /// Runs the Tessera application with default configuration on Android.
428    ///
429    /// This method is specifically for Android applications and requires an `AndroidApp` instance
430    /// that is typically provided by the `android_main` function.
431    ///
432    /// # Parameters
433    ///
434    /// - `entry_point`: A function that defines your UI
435    /// - `register_pipelines_fn`: A function that registers rendering pipelines
436    /// - `android_app`: The Android application context
437    ///
438    /// # Returns
439    ///
440    /// Returns `Ok(())` when the application exits normally, or an `EventLoopError` if the
441    /// event loop fails to start.
442    ///
443    /// # Examples
444    ///
445    /// ```rust,no_run
446    /// use tessera_ui::Renderer;
447    /// use winit::platform::android::activity::AndroidApp;
448    ///
449    /// fn my_ui() {}
450    /// fn register_pipelines(_: &mut tessera_ui::renderer::WgpuApp) {}
451    ///
452    /// #[unsafe(no_mangle)]
453    /// fn android_main(android_app: AndroidApp) {
454    ///     Renderer::run(
455    ///         my_ui,
456    ///         register_pipelines,
457    ///         android_app
458    ///     ).unwrap();
459    /// }
460    /// ```
461    pub fn run(
462        entry_point: F,
463        register_pipelines_fn: R,
464        android_app: AndroidApp,
465    ) -> Result<(), EventLoopError> {
466        Self::run_with_config(
467            entry_point,
468            register_pipelines_fn,
469            android_app,
470            Default::default(),
471        )
472    }
473
474    #[cfg(target_os = "android")]
475    /// Runs the Tessera application with custom configuration on Android.
476    ///
477    /// This method allows you to customize the renderer behavior on Android through [`TesseraConfig`].
478    ///
479    /// # Parameters
480    ///
481    /// - `entry_point`: A function that defines your UI
482    /// - `register_pipelines_fn`: A function that registers rendering pipelines
483    /// - `android_app`: The Android application context
484    /// - `config`: Custom configuration for the renderer
485    ///
486    /// # Returns
487    ///
488    /// Returns `Ok(())` when the application exits normally, or an `EventLoopError` if the
489    /// event loop fails to start.
490    ///
491    /// # Examples
492    ///
493    /// ```rust,no_run
494    /// use tessera_ui::{Renderer, renderer::TesseraConfig};
495    /// use winit::platform::android::activity::AndroidApp;
496    ///
497    /// fn my_ui() {}
498    /// fn register_pipelines(_: &mut tessera_ui::renderer::WgpuApp) {}
499    ///
500    /// #[unsafe(no_mangle)]
501    /// fn android_main(android_app: AndroidApp) {
502    ///     let config = TesseraConfig {
503    ///         sample_count: 2,  // Lower MSAA for mobile performance
504    ///     };
505    ///     
506    ///     Renderer::run_with_config(
507    ///         my_ui,
508    ///         register_pipelines,
509    ///         android_app,
510    ///         config
511    ///     ).unwrap();
512    /// }
513    /// ```
514    pub fn run_with_config(
515        entry_point: F,
516        register_pipelines_fn: R,
517        android_app: AndroidApp,
518        config: TesseraConfig,
519    ) -> Result<(), EventLoopError> {
520        let event_loop = EventLoop::builder()
521            .with_android_app(android_app)
522            .build()
523            .unwrap();
524        let app = None;
525        let cursor_state = CursorState::default();
526        let keyboard_state = KeyboardState::default();
527        let ime_state = ImeState::default();
528        let mut renderer = Self {
529            app,
530            entry_point,
531            cursor_state,
532            keyboard_state,
533            register_pipelines_fn,
534            ime_state,
535            android_ime_opened: false,
536            config,
537        };
538        thread_utils::set_thread_name("Tessera Renderer");
539        event_loop.run_app(&mut renderer)
540    }
541}
542
543impl<F: Fn(), R: Fn(&mut WgpuApp) + Clone + 'static> Renderer<F, R> {
544    /// Executes a single frame rendering cycle.
545    ///
546    /// This is the core rendering method that orchestrates the entire frame rendering process.
547    /// It follows a three-phase approach:
548    ///
549    /// 1. **Component Tree Building**: Calls the entry point function to build the UI component tree
550    /// 2. **Draw Command Computation**: Processes the component tree to generate rendering commands
551    /// 3. **Surface Rendering**: Executes the commands to render the final frame
552    ///
553    /// ## Performance Monitoring
554    ///
555    /// This method includes built-in performance monitoring that logs detailed timing information
556    /// when frame rates drop below 60 FPS, helping identify performance bottlenecks.
557    ///
558    /// ## Parameters
559    ///
560    /// - `entry_point`: The UI entry point function to build the component tree
561    /// - `cursor_state`: Mutable reference to cursor/mouse state for event processing
562    /// - `keyboard_state`: Mutable reference to keyboard state for event processing
563    /// - `ime_state`: Mutable reference to IME state for text input processing
564    /// - `android_ime_opened`: (Android only) Tracks soft keyboard state
565    /// - `app`: Mutable reference to the WGPU application context
566    /// - `event_loop`: (Android only) Event loop for IME management
567    ///
568    /// ## Frame Timing Breakdown
569    ///
570    /// - **Build Tree Cost**: Time spent building the component tree
571    /// - **Draw Commands Cost**: Time spent computing rendering commands
572    /// - **Render Cost**: Time spent executing GPU rendering commands
573    ///
574    /// ## Thread Safety
575    ///
576    /// This method runs on the main thread but coordinates with other threads for
577    /// component tree processing and resource management.
578    fn execute_render_frame(
579        entry_point: &F,
580        cursor_state: &mut CursorState,
581        keyboard_state: &mut KeyboardState,
582        ime_state: &mut ImeState,
583        #[cfg(target_os = "android")] android_ime_opened: &mut bool,
584        app: &mut WgpuApp,
585        #[cfg(target_os = "android")] event_loop: &ActiveEventLoop,
586    ) {
587        // notify the windowing system before rendering
588        // this will help winit to properly schedule and make assumptions about its internal state
589        app.window.pre_present_notify();
590        // and tell runtime the new size
591        TesseraRuntime::write().window_size = app.size().into();
592        // render the surface
593        // timer for performance measurement
594        let tree_timer = Instant::now();
595        // build the component tree
596        debug!("Building component tree...");
597        entry_point();
598        let build_tree_cost = tree_timer.elapsed();
599        debug!("Component tree built in {build_tree_cost:?}");
600        // get the component tree from the runtime
601        let component_tree = &mut TesseraRuntime::write().component_tree;
602        // timer for performance measurement
603        let draw_timer = Instant::now();
604        // Compute the draw commands then we can clear component tree for next build
605        debug!("Computing draw commands...");
606        let cursor_position = cursor_state.position();
607        let cursor_events = cursor_state.take_events();
608        let keyboard_events = keyboard_state.take_events();
609        let ime_events = ime_state.take_events();
610        let screen_size: PxSize = app.size().into();
611        // Clear any existing compute resources
612        app.resource_manager.write().clear();
613        // Compute the draw commands
614        let (commands, window_requests) = component_tree.compute(
615            screen_size,
616            cursor_position,
617            cursor_events,
618            keyboard_events,
619            ime_events,
620            app.resource_manager.clone(),
621            &app.gpu,
622        );
623        let draw_cost = draw_timer.elapsed();
624        debug!("Draw commands computed in {draw_cost:?}");
625        component_tree.clear();
626        // Handle the window requests
627        // After compute, check for cursor change requests
628        app.window
629            .set_cursor(winit::window::Cursor::Icon(window_requests.cursor_icon));
630        // Handle IME requests
631        if let Some(ime_request) = window_requests.ime_request {
632            app.window.set_ime_allowed(true);
633            #[cfg(target_os = "android")]
634            {
635                if !*android_ime_opened {
636                    show_soft_input(true, event_loop.android_app());
637                    *android_ime_opened = true;
638                }
639            }
640            app.window.set_ime_cursor_area::<PxPosition, PxSize>(
641                ime_request.position.unwrap(),
642                ime_request.size,
643            );
644        } else {
645            app.window.set_ime_allowed(false);
646            #[cfg(target_os = "android")]
647            {
648                if *android_ime_opened {
649                    hide_soft_input(event_loop.android_app());
650                    *android_ime_opened = false;
651                }
652            }
653        }
654        // timer for performance measurement
655        let render_timer = Instant::now();
656        // Render the commands
657        debug!("Rendering draw commands...");
658        // Render the commands to the surface
659        app.render(commands).unwrap();
660        let render_cost = render_timer.elapsed();
661        debug!("Rendered to surface in {render_cost:?}");
662
663        // print frame statistics
664        let fps = 1.0 / (build_tree_cost + draw_cost + render_cost).as_secs_f32();
665        if fps < 60.0 {
666            warn!(
667                "Jank detected! Frame statistics:
668    Build tree cost: {:?}
669    Draw commands cost: {:?}
670    Render cost: {:?}
671    Total frame cost: {:?}
672    Fps: {:.2}
673",
674                build_tree_cost,
675                draw_cost,
676                render_cost,
677                build_tree_cost + draw_cost + render_cost,
678                1.0 / (build_tree_cost + draw_cost + render_cost).as_secs_f32()
679            );
680        }
681
682        // Currently we render every frame
683        app.window.request_redraw();
684    }
685}
686
687/// Implementation of winit's `ApplicationHandler` trait for the Tessera renderer.
688///
689/// This implementation handles the application lifecycle events from winit, including
690/// window creation, suspension/resumption, and various window events. It bridges the
691/// gap between winit's event system and Tessera's component-based UI framework.
692impl<F: Fn(), R: Fn(&mut WgpuApp) + Clone + 'static> ApplicationHandler for Renderer<F, R> {
693    /// Called when the application is resumed or started.
694    ///
695    /// This method is responsible for:
696    /// - Creating the application window with appropriate attributes
697    /// - Initializing the WGPU context and surface
698    /// - Registering rendering pipelines
699    /// - Setting up the initial application state
700    ///
701    /// On desktop platforms, this is typically called once at startup.
702    /// On mobile platforms (especially Android), this may be called multiple times
703    /// as the app is suspended and resumed.
704    ///
705    /// ## Window Configuration
706    ///
707    /// The window is created with:
708    /// - Title: "Tessera"
709    /// - Transparency: Enabled (allows for transparent backgrounds)
710    /// - Default size and position (platform-dependent)
711    ///
712    /// ## Pipeline Registration
713    ///
714    /// After WGPU initialization, the `register_pipelines_fn` is called to set up
715    /// all rendering pipelines. This typically includes basic component pipelines
716    /// and any custom shaders your application requires.
717    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
718        // Just return if the app is already created
719        if self.app.is_some() {
720            return;
721        }
722
723        // Create a new window
724        let window_attributes = Window::default_attributes()
725            .with_title("Tessera")
726            .with_transparent(true);
727        let window = Arc::new(event_loop.create_window(window_attributes).unwrap());
728        let register_pipelines_fn = self.register_pipelines_fn.clone();
729
730        let mut wgpu_app =
731            tokio_runtime::get().block_on(WgpuApp::new(window, self.config.sample_count));
732
733        // Register pipelines
734        wgpu_app.register_pipelines(register_pipelines_fn);
735
736        self.app = Some(wgpu_app);
737    }
738
739    /// Called when the application is suspended.
740    ///
741    /// This method should handle cleanup and state preservation when the application
742    /// is being suspended (e.g., on mobile platforms when the app goes to background).
743    ///
744    /// ## Current Status
745    ///
746    /// This method is currently not fully implemented (`todo!`). In a complete
747    /// implementation, it should:
748    /// - Save application state
749    /// - Release GPU resources if necessary
750    /// - Prepare for potential termination
751    ///
752    /// ## Platform Considerations
753    ///
754    /// - **Desktop**: Rarely called, mainly during shutdown
755    /// - **Android**: Called when app goes to background
756    /// - **iOS**: Called during app lifecycle transitions
757    fn suspended(&mut self, _event_loop: &ActiveEventLoop) {
758        todo!("Handle suspend event");
759    }
760
761    /// Handles window-specific events from the windowing system.
762    ///
763    /// This method processes all window events including user input, window state changes,
764    /// and rendering requests. It's the main event processing hub that translates winit
765    /// events into Tessera's internal event system.
766    ///
767    /// ## Event Categories
768    ///
769    /// ### Window Management
770    /// - `CloseRequested`: User requested to close the window
771    /// - `Resized`: Window size changed
772    /// - `ScaleFactorChanged`: Display scaling changed (high-DPI support)
773    ///
774    /// ### Input Events
775    /// - `CursorMoved`: Mouse cursor position changed
776    /// - `CursorLeft`: Mouse cursor left the window
777    /// - `MouseInput`: Mouse button press/release
778    /// - `MouseWheel`: Mouse wheel scrolling
779    /// - `Touch`: Touch screen interactions (mobile)
780    /// - `KeyboardInput`: Keyboard key press/release
781    /// - `Ime`: Input Method Editor events (international text input)
782    ///
783    /// ### Rendering
784    /// - `RedrawRequested`: System requests a frame to be rendered
785    ///
786    /// ## Event Processing Flow
787    ///
788    /// 1. **Input Events**: Captured and stored in respective state managers
789    /// 2. **State Updates**: Internal state (cursor, keyboard, IME) is updated
790    /// 3. **Rendering**: On redraw requests, the full rendering pipeline is executed
791    ///
792    /// ## Platform-Specific Handling
793    ///
794    /// Some events have platform-specific behavior, particularly:
795    /// - Touch events (mobile platforms)
796    /// - IME events (different implementations per platform)
797    /// - Scale factor changes (high-DPI displays)
798    fn window_event(
799        &mut self,
800        event_loop: &ActiveEventLoop,
801        _window_id: WindowId,
802        event: WindowEvent,
803    ) {
804        let app = match self.app.as_mut() {
805            Some(app) => app,
806            None => return,
807        };
808
809        // Handle window events
810        match event {
811            WindowEvent::CloseRequested => {
812                event_loop.exit();
813            }
814            WindowEvent::Resized(size) => {
815                if size.width == 0 || size.height == 0 {
816                    todo!("Handle minimize");
817                } else {
818                    app.resize(size);
819                }
820            }
821            WindowEvent::CursorMoved {
822                device_id: _,
823                position,
824            } => {
825                // Update cursor position
826                self.cursor_state
827                    .update_position(PxPosition::from_f64_arr2([position.x, position.y]));
828                debug!("Cursor moved to: {}, {}", position.x, position.y);
829            }
830            WindowEvent::CursorLeft { device_id: _ } => {
831                // Clear cursor position when it leaves the window
832                // This also set the position to None
833                self.cursor_state.clear();
834                debug!("Cursor left the window");
835            }
836            WindowEvent::MouseInput {
837                device_id: _,
838                state,
839                button,
840            } => {
841                let Some(event_content) = CursorEventContent::from_press_event(state, button)
842                else {
843                    return; // Ignore unsupported buttons
844                };
845                let event = CursorEvent {
846                    timestamp: Instant::now(),
847                    content: event_content,
848                };
849                self.cursor_state.push_event(event);
850                debug!("Mouse input: {state:?} button {button:?}");
851            }
852            WindowEvent::MouseWheel {
853                device_id: _,
854                delta,
855                phase: _,
856            } => {
857                let event_content = CursorEventContent::from_scroll_event(delta);
858                let event = CursorEvent {
859                    timestamp: Instant::now(),
860                    content: event_content,
861                };
862                self.cursor_state.push_event(event);
863                debug!("Mouse scroll: {delta:?}");
864            }
865            WindowEvent::Touch(touch_event) => {
866                let pos =
867                    PxPosition::from_f64_arr2([touch_event.location.x, touch_event.location.y]);
868                debug!(
869                    "Touch event: id {}, phase {:?}, position {:?}",
870                    touch_event.id, touch_event.phase, pos
871                );
872                match touch_event.phase {
873                    winit::event::TouchPhase::Started => {
874                        // Use new touch start handling method
875                        self.cursor_state.handle_touch_start(touch_event.id, pos);
876                    }
877                    winit::event::TouchPhase::Moved => {
878                        // Use new touch move handling method, may generate scroll event
879                        if let Some(scroll_event) =
880                            self.cursor_state.handle_touch_move(touch_event.id, pos)
881                        {
882                            // Scroll event is already added to event queue in handle_touch_move
883                            self.cursor_state.push_event(scroll_event);
884                        }
885                    }
886                    winit::event::TouchPhase::Ended | winit::event::TouchPhase::Cancelled => {
887                        // Use new touch end handling method
888                        self.cursor_state.handle_touch_end(touch_event.id);
889                    }
890                }
891            }
892            WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
893                *SCALE_FACTOR.get().unwrap().write() = scale_factor;
894            }
895            WindowEvent::KeyboardInput { event, .. } => {
896                debug!("Keyboard input: {event:?}");
897                self.keyboard_state.push_event(event);
898            }
899            WindowEvent::Ime(ime_event) => {
900                debug!("IME event: {ime_event:?}");
901                self.ime_state.push_event(ime_event);
902            }
903            WindowEvent::RedrawRequested => {
904                app.resize_if_needed();
905                Self::execute_render_frame(
906                    &self.entry_point,
907                    &mut self.cursor_state,
908                    &mut self.keyboard_state,
909                    &mut self.ime_state,
910                    #[cfg(target_os = "android")]
911                    &mut self.android_ime_opened,
912                    app,
913                    #[cfg(target_os = "android")]
914                    event_loop,
915                );
916            }
917            _ => (),
918        }
919    }
920}
921
922/// Shows the Android soft keyboard (virtual keyboard).
923///
924/// This function uses JNI to interact with the Android system to display the soft keyboard.
925/// It's specifically designed for Android applications and handles the complex JNI calls
926/// required to show the input method.
927///
928/// ## Parameters
929///
930/// - `show_implicit`: Whether to show the keyboard implicitly (without explicit user action)
931/// - `android_app`: Reference to the Android application context
932///
933/// ## Platform Support
934///
935/// This function is only available on Android (`target_os = "android"`). It will not be
936/// compiled on other platforms.
937///
938/// ## Error Handling
939///
940/// The function includes comprehensive error handling for JNI operations. If any JNI
941/// call fails, the function will return early without crashing the application.
942/// Exception handling is also included to clear any Java exceptions that might occur.
943///
944/// ## Implementation Notes
945///
946/// This implementation is based on the android-activity crate and follows the pattern
947/// established in: https://github.com/rust-mobile/android-activity/pull/178
948///
949/// The function performs these steps:
950/// 1. Get the Java VM and activity context
951/// 2. Find the InputMethodManager system service
952/// 3. Get the current window's decor view
953/// 4. Call `showSoftInput` on the InputMethodManager
954///
955/// ## Usage
956///
957/// This function is typically called internally by the renderer when IME input is requested.
958/// You generally don't need to call this directly in application code.
959// https://github.com/rust-mobile/android-activity/pull/178
960#[cfg(target_os = "android")]
961pub fn show_soft_input(show_implicit: bool, android_app: &AndroidApp) {
962    let ctx = android_app;
963
964    let jvm = unsafe { jni::JavaVM::from_raw(ctx.vm_as_ptr().cast()) }.unwrap();
965    let na = unsafe { jni::objects::JObject::from_raw(ctx.activity_as_ptr().cast()) };
966
967    let mut env = jvm.attach_current_thread().unwrap();
968    if env.exception_check().unwrap() {
969        return;
970    }
971    let class_ctxt = env.find_class("android/content/Context").unwrap();
972    if env.exception_check().unwrap() {
973        return;
974    }
975    let ims = env
976        .get_static_field(class_ctxt, "INPUT_METHOD_SERVICE", "Ljava/lang/String;")
977        .unwrap();
978    if env.exception_check().unwrap() {
979        return;
980    }
981
982    let im_manager = env
983        .call_method(
984            &na,
985            "getSystemService",
986            "(Ljava/lang/String;)Ljava/lang/Object;",
987            &[(&ims).into()],
988        )
989        .unwrap()
990        .l()
991        .unwrap();
992    if env.exception_check().unwrap() {
993        return;
994    }
995
996    let jni_window = env
997        .call_method(&na, "getWindow", "()Landroid/view/Window;", &[])
998        .unwrap()
999        .l()
1000        .unwrap();
1001    if env.exception_check().unwrap() {
1002        return;
1003    }
1004    let view = env
1005        .call_method(&jni_window, "getDecorView", "()Landroid/view/View;", &[])
1006        .unwrap()
1007        .l()
1008        .unwrap();
1009    if env.exception_check().unwrap() {
1010        return;
1011    }
1012
1013    let _ = env.call_method(
1014        im_manager,
1015        "showSoftInput",
1016        "(Landroid/view/View;I)Z",
1017        &[
1018            jni::objects::JValue::Object(&view),
1019            if show_implicit {
1020                (ndk_sys::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT as i32).into()
1021            } else {
1022                0i32.into()
1023            },
1024        ],
1025    );
1026    // showSoftInput can trigger exceptions if the keyboard is currently animating open/closed
1027    if env.exception_check().unwrap() {
1028        let _ = env.exception_clear();
1029    }
1030}
1031
1032/// Hides the Android soft keyboard (virtual keyboard).
1033///
1034/// This function uses JNI to interact with the Android system to hide the soft keyboard.
1035/// It's the counterpart to [`show_soft_input`] and handles the complex JNI calls required
1036/// to dismiss the input method.
1037///
1038/// ## Parameters
1039///
1040/// - `android_app`: Reference to the Android application context
1041///
1042/// ## Platform Support
1043///
1044/// This function is only available on Android (`target_os = "android"`). It will not be
1045/// compiled on other platforms.
1046///
1047/// ## Error Handling
1048///
1049/// Like [`show_soft_input`], this function includes comprehensive error handling for JNI
1050/// operations. If any step fails, the function returns early without crashing. Java
1051/// exceptions are also properly handled and cleared.
1052///
1053/// ## Implementation Details
1054///
1055/// The function performs these steps:
1056/// 1. Get the Java VM and activity context
1057/// 2. Find the InputMethodManager system service
1058/// 3. Get the current window and its decor view
1059/// 4. Get the window token from the decor view
1060/// 5. Call `hideSoftInputFromWindow` on the InputMethodManager
1061///
1062/// ## Usage
1063///
1064/// This function is typically called internally by the renderer when IME input is no longer
1065/// needed. You generally don't need to call this directly in application code.
1066///
1067/// ## Relationship to show_soft_input
1068///
1069/// This function is designed to work in tandem with [`show_soft_input`]. The renderer
1070/// automatically manages the keyboard visibility based on IME requests from components.
1071#[cfg(target_os = "android")]
1072pub fn hide_soft_input(android_app: &AndroidApp) {
1073    use jni::objects::JValue;
1074
1075    let ctx = android_app;
1076    let jvm = match unsafe { jni::JavaVM::from_raw(ctx.vm_as_ptr().cast()) } {
1077        Ok(jvm) => jvm,
1078        Err(_) => return, // Early exit if failing to get the JVM
1079    };
1080    let activity = unsafe { jni::objects::JObject::from_raw(ctx.activity_as_ptr().cast()) };
1081
1082    let mut env = match jvm.attach_current_thread() {
1083        Ok(env) => env,
1084        Err(_) => return,
1085    };
1086
1087    // --- 1. Get the InputMethodManager ---
1088    // This part is the same as in show_soft_input.
1089    let class_ctxt = match env.find_class("android/content/Context") {
1090        Ok(c) => c,
1091        Err(_) => return,
1092    };
1093    let ims_field =
1094        match env.get_static_field(class_ctxt, "INPUT_METHOD_SERVICE", "Ljava/lang/String;") {
1095            Ok(f) => f,
1096            Err(_) => return,
1097        };
1098    let ims = match ims_field.l() {
1099        Ok(s) => s,
1100        Err(_) => return,
1101    };
1102
1103    let im_manager = match env.call_method(
1104        &activity,
1105        "getSystemService",
1106        "(Ljava/lang/String;)Ljava/lang/Object;",
1107        &[(&ims).into()],
1108    ) {
1109        Ok(m) => match m.l() {
1110            Ok(im) => im,
1111            Err(_) => return,
1112        },
1113        Err(_) => return,
1114    };
1115
1116    // --- 2. Get the current window's token ---
1117    // This is the key step that differs from show_soft_input.
1118    let window = match env.call_method(&activity, "getWindow", "()Landroid/view/Window;", &[]) {
1119        Ok(w) => match w.l() {
1120            Ok(win) => win,
1121            Err(_) => return,
1122        },
1123        Err(_) => return,
1124    };
1125
1126    let decor_view = match env.call_method(&window, "getDecorView", "()Landroid/view/View;", &[]) {
1127        Ok(v) => match v.l() {
1128            Ok(view) => view,
1129            Err(_) => return,
1130        },
1131        Err(_) => return,
1132    };
1133
1134    let window_token =
1135        match env.call_method(&decor_view, "getWindowToken", "()Landroid/os/IBinder;", &[]) {
1136            Ok(t) => match t.l() {
1137                Ok(token) => token,
1138                Err(_) => return,
1139            },
1140            Err(_) => return,
1141        };
1142
1143    // --- 3. Call hideSoftInputFromWindow ---
1144    let _ = env.call_method(
1145        &im_manager,
1146        "hideSoftInputFromWindow",
1147        "(Landroid/os/IBinder;I)Z",
1148        &[
1149            JValue::Object(&window_token),
1150            JValue::Int(0), // flags, usually 0
1151        ],
1152    );
1153
1154    // Hiding the keyboard can also cause exceptions, so we clear them.
1155    if env.exception_check().unwrap_or(false) {
1156        let _ = env.exception_clear();
1157    }
1158}