1pub mod composite;
6pub mod compute;
7pub mod core;
8pub mod drawer;
9pub mod external;
10
11use std::sync::{
12 Arc,
13 atomic::{AtomicBool, Ordering},
14};
15
16pub use core::{RenderCore, RenderResources};
17
18use accesskit::{self, TreeUpdate};
19use accesskit_winit::{Adapter as AccessKitAdapter, Event as AccessKitEvent};
20use parking_lot::RwLock;
21use tracing::{debug, error, instrument, warn};
22use winit::{
23 application::ApplicationHandler,
24 error::EventLoopError,
25 event::WindowEvent,
26 event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
27 window::{ResizeDirection, Window, WindowId},
28};
29
30use crate::{
31 ImeRequest, ImeState, PxPosition,
32 build_tree::build_component_tree,
33 component_tree::{LayoutFrameDiagnostics, WindowRequests, clear_layout_snapshots},
34 context::{reset_component_context_tracking, reset_context_read_dependencies},
35 cursor::{
36 CursorEventContent, CursorState, GestureState, MOUSE_POINTER_ID, PointerChange,
37 PressKeyEventType,
38 },
39 dp::SCALE_FACTOR,
40 focus::{FocusDirection, flush_pending_focus_callbacks},
41 keyboard_state::KeyboardState,
42 pipeline_context::PipelineContext,
43 plugin::{DesktopPlatformContext, DesktopWindowAction, PluginContext, PluginHost},
44 px::PxSize,
45 render_graph::{RenderGraph, RenderGraphExecution},
46 render_module::RenderModule,
47 runtime::{
48 TesseraRuntime, begin_frame_clock, clear_persistent_focus_handles, clear_redraw_waker,
49 has_pending_build_invalidations, has_pending_frame_nanos_receivers, install_redraw_waker,
50 reset_build_invalidations, reset_component_replay_tracking, reset_focus_read_dependencies,
51 reset_frame_clock, reset_layout_dirty_tracking, reset_render_slot_read_dependencies,
52 reset_state_read_dependencies, retain_persistent_focus_handles, take_layout_dirty_nodes,
53 tick_frame_nanos_receivers,
54 },
55 thread_utils,
56 time::Instant,
57};
58
59pub use crate::render_scene::{Command, DrawRegion, PaddingRect, SampleRegion};
60
61use self::core::RenderTimingBreakdown;
62
63pub use compute::{
64 ComputablePipeline, ComputeBatchItem, ComputePipelineRegistry, ErasedComputeBatchItem,
65};
66pub use drawer::{DrawCommand, DrawablePipeline, PipelineRegistry};
67pub use external::{ExternalTextureHandle, ExternalTextureRegistry};
68
69#[cfg(feature = "debug-dirty-overlay")]
70use crate::PxRect;
71#[cfg(feature = "debug-dirty-overlay")]
72use crate::build_tree::{BuildTreeMode, BuildTreeResult};
73
74#[cfg(feature = "profiling")]
75use crate::profiler::{
76 FrameMeta, Phase as ProfilerPhase, RedrawReason, RuntimeEventKind, RuntimeMeta,
77 ScopeGuard as ProfilerScopeGuard, WakeMeta, WakeSource, begin_frame as profiler_begin_frame,
78 end_frame as profiler_end_frame, submit_frame_meta, submit_runtime_meta, submit_wake_meta,
79};
80#[cfg(feature = "profiling")]
81use crate::runtime::frame_delta;
82#[cfg(feature = "profiling")]
83use std::collections::BTreeSet;
84#[cfg(feature = "profiling")]
85use std::path::PathBuf;
86
87#[cfg(target_family = "wasm")]
88use std::{cell::RefCell, rc::Rc};
89#[cfg(target_family = "wasm")]
90use wasm_bindgen_futures::spawn_local;
91#[cfg(target_family = "wasm")]
92use web_sys::HtmlCanvasElement;
93#[cfg(target_family = "wasm")]
94use web_sys::wasm_bindgen::JsCast;
95#[cfg(target_family = "wasm")]
96use winit::platform::web::EventLoopExtWebSys;
97
98#[cfg(target_os = "android")]
99use winit::platform::android::{
100 ActiveEventLoopExtAndroid, EventLoopBuilderExtAndroid, activity::AndroidApp,
101};
102
103#[derive(Debug)]
104enum RendererUserEvent {
105 AccessKit(AccessKitEvent),
106 RuntimeRedrawWake,
107 #[cfg(target_family = "wasm")]
108 WebInitReady(u64),
109}
110
111impl From<AccessKitEvent> for RendererUserEvent {
112 fn from(event: AccessKitEvent) -> Self {
113 Self::AccessKit(event)
114 }
115}
116
117type RenderComputationOutput = (
118 RenderGraph,
119 WindowRequests,
120 std::time::Duration,
121 LayoutFrameDiagnostics,
122 std::time::Duration,
123 Option<FocusDirection>,
124 bool,
125);
126
127#[derive(Clone, Copy, Debug, Default)]
128struct RuntimePendingWork {
129 invalidation_pending: bool,
130 frame_receiver_pending: bool,
131}
132
133impl RuntimePendingWork {
134 fn requires_redraw(self) -> bool {
135 self.invalidation_pending || self.frame_receiver_pending
136 }
137
138 #[cfg(feature = "profiling")]
139 fn redraw_reasons(self) -> Vec<RedrawReason> {
140 let mut reasons = Vec::new();
141 if self.invalidation_pending {
142 reasons.push(RedrawReason::RuntimeInvalidation);
143 }
144 if self.frame_receiver_pending {
145 reasons.push(RedrawReason::RuntimeFrameAwaiter);
146 }
147 reasons
148 }
149}
150
151#[cfg(feature = "profiling")]
152fn resolve_profiler_output_path(config: &TesseraConfig) -> PathBuf {
153 if let Ok(path) = std::env::var("TESSERA_PROFILING_OUTPUT")
154 && !path.trim().is_empty()
155 {
156 return PathBuf::from(path);
157 }
158 if let Some(path) = option_env!("TESSERA_PROFILING_OUTPUT")
159 && !path.trim().is_empty()
160 {
161 return PathBuf::from(path);
162 }
163 config.profiler_output_path.clone()
164}
165
166#[derive(Debug, Clone)]
168pub struct WindowConfig {
169 pub decorations: bool,
171 pub transparent: bool,
173 pub resizable: bool,
175}
176
177impl Default for WindowConfig {
178 fn default() -> Self {
179 Self {
180 decorations: true,
181 transparent: true,
182 resizable: true,
183 }
184 }
185}
186
187#[derive(Debug, Clone, Default)]
189pub struct WebConfig {
190 pub canvas_id: Option<String>,
193}
194
195impl WebConfig {
196 pub fn with_canvas_id(mut self, canvas_id: impl Into<String>) -> Self {
198 self.canvas_id = Some(canvas_id.into());
199 self
200 }
201}
202
203#[derive(Debug, Clone)]
229pub struct TesseraConfig {
230 pub sample_count: u32,
248 pub window_title: String,
251 pub window: WindowConfig,
253 pub web: WebConfig,
255 #[cfg(feature = "profiling")]
257 pub profiler_output_path: PathBuf,
258}
259
260impl Default for TesseraConfig {
261 fn default() -> Self {
264 Self {
265 sample_count: 1,
266 window_title: "Tessera".to_string(),
267 window: WindowConfig::default(),
268 web: WebConfig::default(),
269 #[cfg(feature = "profiling")]
270 profiler_output_path: PathBuf::from("tessera-profiler.jsonl"),
271 }
272 }
273}
274
275pub struct Renderer<F: Fn()> {
373 app: Option<RenderCore>,
375 entry_point: F,
377 cursor_state: CursorState,
379 keyboard_state: KeyboardState,
381 ime_state: ImeState,
383 ime_bridge_state: RendererImeBridgeState,
385 modules: Vec<Box<dyn RenderModule>>,
387 plugins: PluginHost,
389 config: TesseraConfig,
391 accessibility_adapter: Option<AccessKitAdapter>,
393 event_loop_proxy: Option<winit::event_loop::EventLoopProxy<RendererUserEvent>>,
395 pending_desktop_window_action: Arc<RwLock<Option<DesktopWindowAction>>>,
398 frame_index: u64,
400 redraw_request_pending: Arc<AtomicBool>,
403 pending_close_requested: bool,
405 resize_in_progress: bool,
409 #[cfg(target_family = "wasm")]
410 pending_web_inits: Rc<RefCell<Vec<(u64, RenderCore)>>>,
413 #[cfg(target_family = "wasm")]
414 web_init_epoch: u64,
417 #[cfg(target_family = "wasm")]
418 web_init_in_progress: bool,
421 #[cfg(feature = "profiling")]
422 pending_redraw_reasons: BTreeSet<RedrawReason>,
425 #[cfg(target_os = "android")]
426 android_ime_opened: bool,
429}
430
431impl<F: Fn()> Renderer<F> {
432 #[cfg(target_family = "wasm")]
433 fn resolve_web_canvas(config: &TesseraConfig) -> Option<HtmlCanvasElement> {
434 let canvas_id = config.web.canvas_id.as_deref()?;
435 let window = web_sys::window()?;
436 let document = window.document()?;
437 let element = document.get_element_by_id(canvas_id)?;
438 element.dyn_into::<HtmlCanvasElement>().ok()
439 }
440
441 #[cfg(all(not(target_os = "android"), not(target_family = "wasm")))]
483 #[tracing::instrument(level = "info", skip(entry_point, modules))]
484 pub fn run(entry_point: F, modules: Vec<Box<dyn RenderModule>>) -> Result<(), EventLoopError> {
485 Self::run_with_config(entry_point, modules, Default::default())
486 }
487
488 #[tracing::instrument(level = "info", skip(entry_point, modules))]
529 #[cfg(all(not(target_os = "android"), not(target_family = "wasm")))]
530 pub fn run_with_config(
531 entry_point: F,
532 modules: Vec<Box<dyn RenderModule>>,
533 config: TesseraConfig,
534 ) -> Result<(), EventLoopError> {
535 let event_loop = EventLoop::<RendererUserEvent>::with_user_event().build()?;
536 let event_loop_proxy = event_loop.create_proxy();
537 let app = None;
538 let cursor_state = CursorState::default();
539 let keyboard_state = KeyboardState::default();
540 let ime_state = ImeState::default();
541 let ime_bridge_state = RendererImeBridgeState::default();
542 #[cfg(feature = "profiling")]
543 crate::profiler::set_output_path(resolve_profiler_output_path(&config));
544 let mut renderer = Self {
545 app,
546 entry_point,
547 cursor_state,
548 keyboard_state,
549 modules,
550 plugins: PluginHost::new(),
551 ime_state,
552 ime_bridge_state,
553 config,
554 accessibility_adapter: None,
555 event_loop_proxy: Some(event_loop_proxy),
556 pending_desktop_window_action: Arc::new(RwLock::new(None)),
557 frame_index: 0,
558 redraw_request_pending: Arc::new(AtomicBool::new(false)),
559 pending_close_requested: false,
560 resize_in_progress: false,
561 #[cfg(target_family = "wasm")]
562 pending_web_inits: Rc::new(RefCell::new(Vec::new())),
563 #[cfg(target_family = "wasm")]
564 web_init_epoch: 0,
565 #[cfg(target_family = "wasm")]
566 web_init_in_progress: false,
567 #[cfg(feature = "profiling")]
568 pending_redraw_reasons: BTreeSet::new(),
569 };
570 thread_utils::set_thread_name("TesseraMain");
571 event_loop.run_app(&mut renderer)
572 }
573
574 #[cfg(target_family = "wasm")]
577 #[tracing::instrument(level = "info", skip(entry_point, modules))]
578 pub fn run(entry_point: F, modules: Vec<Box<dyn RenderModule>>) -> Result<(), EventLoopError>
579 where
580 F: 'static,
581 {
582 Self::run_web_with_config(entry_point, modules, Default::default())
583 }
584
585 #[cfg(target_family = "wasm")]
588 #[tracing::instrument(level = "info", skip(entry_point, modules))]
589 pub fn run_web_with_config(
590 entry_point: F,
591 modules: Vec<Box<dyn RenderModule>>,
592 config: TesseraConfig,
593 ) -> Result<(), EventLoopError>
594 where
595 F: 'static,
596 {
597 let event_loop = EventLoop::<RendererUserEvent>::with_user_event().build()?;
598 let event_loop_proxy = event_loop.create_proxy();
599 let app = None;
600 let cursor_state = CursorState::default();
601 let keyboard_state = KeyboardState::default();
602 let ime_state = ImeState::default();
603 let ime_bridge_state = RendererImeBridgeState::default();
604 #[cfg(feature = "profiling")]
605 crate::profiler::set_output_path(resolve_profiler_output_path(&config));
606 let renderer = Self {
607 app,
608 entry_point,
609 cursor_state,
610 keyboard_state,
611 modules,
612 plugins: PluginHost::new(),
613 ime_state,
614 ime_bridge_state,
615 config,
616 accessibility_adapter: None,
617 event_loop_proxy: Some(event_loop_proxy),
618 pending_desktop_window_action: Arc::new(RwLock::new(None)),
619 frame_index: 0,
620 redraw_request_pending: Arc::new(AtomicBool::new(false)),
621 pending_close_requested: false,
622 resize_in_progress: false,
623 pending_web_inits: Rc::new(RefCell::new(Vec::new())),
624 web_init_epoch: 0,
625 web_init_in_progress: false,
626 #[cfg(feature = "profiling")]
627 pending_redraw_reasons: BTreeSet::new(),
628 };
629 thread_utils::set_thread_name("TesseraMain");
630 event_loop.spawn_app(renderer);
631 Ok(())
632 }
633
634 #[cfg(target_os = "android")]
672 #[tracing::instrument(level = "info", skip(entry_point, modules, android_app))]
673 pub fn run(
674 entry_point: F,
675 modules: Vec<Box<dyn RenderModule>>,
676 android_app: AndroidApp,
677 ) -> Result<(), EventLoopError> {
678 Self::run_with_config(entry_point, modules, android_app, Default::default())
679 }
680
681 #[cfg(target_os = "android")]
723 #[tracing::instrument(level = "info", skip(entry_point, modules, android_app))]
724 pub fn run_with_config(
725 entry_point: F,
726 modules: Vec<Box<dyn RenderModule>>,
727 android_app: AndroidApp,
728 config: TesseraConfig,
729 ) -> Result<(), EventLoopError> {
730 let event_loop = EventLoop::<RendererUserEvent>::with_user_event()
731 .with_android_app(android_app.clone())
732 .build()
733 .unwrap();
734 let event_loop_proxy = event_loop.create_proxy();
735 let app = None;
736 let cursor_state = CursorState::default();
737 let keyboard_state = KeyboardState::default();
738 let ime_state = ImeState::default();
739 let ime_bridge_state = RendererImeBridgeState::default();
740 #[cfg(feature = "profiling")]
741 crate::profiler::set_output_path(resolve_profiler_output_path(&config));
742 let mut renderer = Self {
743 app,
744 entry_point,
745 cursor_state,
746 keyboard_state,
747 modules,
748 plugins: PluginHost::new(),
749 ime_state,
750 ime_bridge_state,
751 android_ime_opened: false,
752 config,
753 accessibility_adapter: None,
754 event_loop_proxy: Some(event_loop_proxy),
755 pending_desktop_window_action: Arc::new(RwLock::new(None)),
756 frame_index: 0,
757 redraw_request_pending: Arc::new(AtomicBool::new(false)),
758 pending_close_requested: false,
759 resize_in_progress: false,
760 #[cfg(feature = "profiling")]
761 pending_redraw_reasons: BTreeSet::new(),
762 };
763 thread_utils::set_thread_name("TesseraMain");
764 event_loop.run_app(&mut renderer)
765 }
766}
767
768struct RenderFrameArgs<'a> {
771 pub cursor_state: &'a mut CursorState,
772 pub keyboard_state: &'a mut KeyboardState,
773 pub ime_state: &'a mut ImeState,
774 pub ime_bridge_state: &'a mut RendererImeBridgeState,
775 #[cfg(target_os = "android")]
776 pub android_ime_opened: &'a mut bool,
777 pub app: &'a mut RenderCore,
778 #[cfg(target_os = "android")]
779 pub event_loop: &'a ActiveEventLoop,
780}
781
782struct RenderFrameContext<'a, F: Fn()> {
783 entry_point: &'a F,
784 args: &'a mut RenderFrameArgs<'a>,
785 accessibility_enabled: bool,
786 decorations: bool,
787 window_label: &'a str,
788 frame_idx: u64,
789 #[cfg(feature = "profiling")]
790 redraw_reasons: Vec<RedrawReason>,
791}
792
793struct RenderFrameOutcome {
794 accessibility_update: Option<TreeUpdate>,
795 request_window_drag: bool,
796 runtime_pending_work: RuntimePendingWork,
797 #[cfg(feature = "debug-dirty-overlay")]
798 overlay_clear_pending: bool,
799}
800
801#[derive(Clone, Debug, Default, PartialEq, Eq)]
802struct RendererImeBridgeState {
803 current_request: Option<ImeRequest>,
804 ime_allowed: bool,
805}
806
807#[derive(Clone, Debug, Default, PartialEq, Eq)]
808struct RendererImeBridgeUpdate {
809 allowed: Option<bool>,
810 cursor_area: Option<(PxPosition, PxSize)>,
811 request: Option<ImeRequest>,
812 snapshot_changed: bool,
813}
814
815impl RendererImeBridgeState {
816 #[cfg(test)]
817 fn request(&self) -> Option<&ImeRequest> {
818 self.current_request.as_ref()
819 }
820
821 fn reset(&mut self) {
822 *self = Self::default();
823 }
824
825 fn update_request(&mut self, request: Option<ImeRequest>) -> RendererImeBridgeUpdate {
826 let snapshot_changed = self.current_request != request;
827 let allowed = (self.ime_allowed != request.is_some()).then_some(request.is_some());
828 let cursor_area = request.as_ref().and_then(|next_request| {
829 let previous_area = self.current_request.as_ref().and_then(|previous_request| {
830 previous_request
831 .position
832 .map(|position| (position, previous_request.size))
833 });
834 let next_area = next_request
835 .position
836 .map(|position| (position, next_request.size));
837 (previous_area != next_area).then_some(next_area).flatten()
838 });
839
840 self.ime_allowed = request.is_some();
841 self.current_request = request.clone();
842
843 RendererImeBridgeUpdate {
844 allowed,
845 cursor_area,
846 request,
847 snapshot_changed,
848 }
849 }
850}
851
852impl<F: Fn()> Renderer<F> {
853 const RESIZE_EDGE_THRESHOLD: f64 = 8.0;
854 const MAX_FOCUS_BEYOND_BOUNDS_RETRIES: usize = 8;
855
856 const fn supports_native_window_frame_controls() -> bool {
857 !cfg!(any(
858 target_family = "wasm",
859 target_os = "android",
860 target_os = "ios"
861 ))
862 }
863
864 #[cfg(target_os = "windows")]
865 fn update_native_window_shape(&self, window: &Window) {
866 use winit::platform::windows::{CornerPreference, WindowExtWindows};
867
868 if self.config.window.decorations {
869 window.set_corner_preference(CornerPreference::Default);
870 return;
871 }
872
873 let preference = if window.is_maximized() || window.fullscreen().is_some() {
874 CornerPreference::DoNotRound
875 } else {
876 CornerPreference::Round
877 };
878 window.set_corner_preference(preference);
879 }
880
881 #[cfg(target_os = "macos")]
882 fn update_native_window_shape(&self, window: &Window) {
883 use objc2::rc::Retained;
884 use objc2_app_kit::NSView;
885 use winit::raw_window_handle::{HasWindowHandle, RawWindowHandle};
886
887 let raw_window_handle = match window.window_handle() {
888 Ok(handle) => handle.as_raw(),
889 Err(err) => {
890 warn!("Failed to fetch native window handle: {}", err);
891 return;
892 }
893 };
894 let RawWindowHandle::AppKit(appkit) = raw_window_handle else {
895 return;
896 };
897
898 let create_retained = Retained::<NSView>::retain;
901 let Some(ns_view) = (unsafe { create_retained(appkit.ns_view.as_ptr().cast()) }) else {
902 warn!("Failed to retain NSView from raw window handle");
903 return;
904 };
905
906 let radius_px = if self.config.window.decorations
907 || window.is_maximized()
908 || window.fullscreen().is_some()
909 {
910 0.0
911 } else {
912 8.0 * window.scale_factor()
913 };
914
915 ns_view.setWantsLayer(true);
916 let layer = if let Some(layer) = ns_view.layer() {
917 layer
918 } else {
919 let layer = ns_view.makeBackingLayer();
920 ns_view.setLayer(Some(&layer));
921 layer
922 };
923 layer.setCornerRadius(radius_px as _);
924 layer.setMasksToBounds(radius_px > 0.0);
925 }
926
927 #[cfg(not(any(target_os = "windows", target_os = "macos")))]
928 fn update_native_window_shape(&self, _window: &Window) {}
929
930 fn should_set_cursor_pos(
931 cursor_position: Option<crate::PxPosition>,
932 window_width: f64,
933 window_height: f64,
934 edge_threshold: f64,
935 ) -> bool {
936 if let Some(pos) = cursor_position {
937 let x = pos.x.0 as f64;
938 let y = pos.y.0 as f64;
939 x > edge_threshold
940 && x < window_width - edge_threshold
941 && y > edge_threshold
942 && y < window_height - edge_threshold
943 } else {
944 false
945 }
946 }
947
948 fn cursor_resize_direction(
949 cursor_position: Option<crate::PxPosition>,
950 window_size: winit::dpi::PhysicalSize<u32>,
951 edge_threshold: f64,
952 ) -> Option<ResizeDirection> {
953 let position = cursor_position?;
954 let x = position.x.0 as f64;
955 let y = position.y.0 as f64;
956 let width = window_size.width as f64;
957 let height = window_size.height as f64;
958
959 let near_left = x <= edge_threshold;
960 let near_right = x >= width - edge_threshold;
961 let near_top = y <= edge_threshold;
962 let near_bottom = y >= height - edge_threshold;
963
964 match (near_left, near_right, near_top, near_bottom) {
965 (true, _, true, _) => Some(ResizeDirection::NorthWest),
966 (_, true, true, _) => Some(ResizeDirection::NorthEast),
967 (true, _, _, true) => Some(ResizeDirection::SouthWest),
968 (_, true, _, true) => Some(ResizeDirection::SouthEast),
969 (true, _, _, _) => Some(ResizeDirection::West),
970 (_, true, _, _) => Some(ResizeDirection::East),
971 (_, _, true, _) => Some(ResizeDirection::North),
972 (_, _, _, true) => Some(ResizeDirection::South),
973 _ => None,
974 }
975 }
976
977 fn cursor_icon_for_resize(direction: ResizeDirection) -> winit::window::CursorIcon {
978 match direction {
979 ResizeDirection::East | ResizeDirection::West => winit::window::CursorIcon::EwResize,
980 ResizeDirection::North | ResizeDirection::South => winit::window::CursorIcon::NsResize,
981 ResizeDirection::NorthEast | ResizeDirection::SouthWest => {
982 winit::window::CursorIcon::NeswResize
983 }
984 ResizeDirection::NorthWest | ResizeDirection::SouthEast => {
985 winit::window::CursorIcon::NwseResize
986 }
987 }
988 }
989
990 fn log_frame_stats(
1031 build_tree_cost: std::time::Duration,
1032 draw_cost: std::time::Duration,
1033 render_cost: std::time::Duration,
1034 render_breakdown: Option<RenderTimingBreakdown>,
1035 ) {
1036 let total = build_tree_cost + draw_cost + render_cost;
1037 let fps = 1.0 / total.as_secs_f32();
1038 if fps < 30.0 {
1039 if let Some(breakdown) = render_breakdown {
1040 warn!(
1041 "Jank detected! Frame statistics:
1042Build tree cost: {:?}
1043Draw commands cost: {:?}
1044Render cost: {:?}
1045Total frame cost: {:?}
1046Fps: {:.2}
1047Render breakdown:
1048Acquire: {:?}
1049Build passes: {:?}
1050Encode: {:?}
1051Submit: {:?}
1052Present: {:?}
1053Render total (core): {:?}
1054",
1055 build_tree_cost,
1056 draw_cost,
1057 render_cost,
1058 total,
1059 1.0 / total.as_secs_f32(),
1060 breakdown.acquire,
1061 breakdown.build_passes,
1062 breakdown.encode,
1063 breakdown.submit,
1064 breakdown.present,
1065 breakdown.total,
1066 );
1067 } else {
1068 warn!(
1069 "Jank detected! Frame statistics:
1070Build tree cost: {:?}
1071Draw commands cost: {:?}
1072Render cost: {:?}
1073Total frame cost: {:?}
1074Fps: {:.2}
1075",
1076 build_tree_cost,
1077 draw_cost,
1078 render_cost,
1079 total,
1080 1.0 / total.as_secs_f32()
1081 );
1082 }
1083 }
1084 }
1085
1086 #[instrument(level = "debug", skip(args))]
1087 fn compute_draw_commands<'a>(
1088 args: &mut RenderFrameArgs<'a>,
1089 screen_size: PxSize,
1090 frame_idx: u64,
1091 retry_focus_move: Option<FocusDirection>,
1092 retry_focus_reveal: bool,
1093 ) -> RenderComputationOutput {
1094 let draw_timer = Instant::now();
1095 debug!("Computing draw commands...");
1096 let cursor_position = args.cursor_state.position();
1097 let is_retry = retry_focus_move.is_some() || retry_focus_reveal;
1098 let pointer_changes = if is_retry {
1099 Vec::new()
1100 } else {
1101 args.cursor_state.take_events()
1102 };
1103 let keyboard_events = if is_retry {
1104 Vec::new()
1105 } else {
1106 args.keyboard_state.take_events()
1107 };
1108 let ime_events = if is_retry {
1109 Vec::new()
1110 } else {
1111 args.ime_state.take_events()
1112 };
1113
1114 args.app.compute_resource_manager_mut().clear();
1116 let layout_dirty_nodes = take_layout_dirty_nodes();
1117
1118 let (
1119 graph,
1120 window_requests,
1121 layout_diagnostics,
1122 record_cost,
1123 pending_focus_move_retry,
1124 pending_focus_reveal_retry,
1125 ) = TesseraRuntime::with_mut(|rt| {
1126 let component_tree = &mut rt.component_tree;
1127 let (gpu, compute_resource_manager) = args.app.record_resources();
1128 component_tree.compute(
1129 crate::component_tree::ComputeParams {
1130 screen_size,
1131 cursor_position,
1132 pointer_changes,
1133 keyboard_events,
1134 ime_events,
1135 retry_focus_move,
1136 retry_focus_reveal,
1137 modifiers: args.keyboard_state.modifiers(),
1138 layout_dirty_nodes: &layout_dirty_nodes,
1139 },
1140 crate::component_tree::ComputeMode::Full {
1141 compute_resource_manager,
1142 gpu,
1143 },
1144 )
1145 });
1146 flush_pending_focus_callbacks();
1147
1148 let draw_cost = draw_timer.elapsed();
1149 debug!("Draw commands computed in {draw_cost:?}");
1150 (
1151 graph,
1152 window_requests,
1153 draw_cost,
1154 layout_diagnostics,
1155 record_cost,
1156 pending_focus_move_retry,
1157 pending_focus_reveal_retry,
1158 )
1159 }
1160
1161 #[cfg(feature = "debug-dirty-overlay")]
1162 fn layout_node_overlay_rect(
1163 metadatas: &crate::component_tree::ComponentNodeMetaDatas,
1164 node_id: crate::NodeId,
1165 ) -> Option<PxRect> {
1166 let metadata = metadatas.get(&node_id)?;
1167 let abs_position = metadata.abs_position?;
1168 let computed_data = metadata.computed_data?;
1169 if computed_data.width.0 <= 0 || computed_data.height.0 <= 0 {
1170 return None;
1171 }
1172 let node_rect = PxRect::from_position_size(
1173 abs_position,
1174 PxSize::new(computed_data.width, computed_data.height),
1175 );
1176 Some(
1177 metadata
1178 .event_clip_rect
1179 .and_then(|clip_rect| clip_rect.intersection(&node_rect))
1180 .unwrap_or(node_rect),
1181 )
1182 }
1183
1184 #[cfg(feature = "debug-dirty-overlay")]
1185 fn collect_dirty_overlay_rects(
1186 screen_size: PxSize,
1187 build_tree_result: &BuildTreeResult,
1188 ) -> Vec<PxRect> {
1189 use crate::component_tree::{NodeRole, direct_layout_children};
1190
1191 if matches!(build_tree_result.mode(), BuildTreeMode::SkipNoInvalidation) {
1192 return Vec::new();
1193 }
1194 if matches!(build_tree_result.mode(), BuildTreeMode::RootRecompose) {
1195 if build_tree_result.had_invalidations() {
1196 return vec![PxRect::from_position_size(PxPosition::ZERO, screen_size)];
1197 }
1198 return Vec::new();
1199 }
1200 TesseraRuntime::with(|rt| {
1201 let tree = rt.component_tree.tree();
1202 let metadatas = rt.component_tree.metadatas();
1203 let mut rects = Vec::new();
1204 for instance_key in build_tree_result.dirty_replay_roots() {
1205 let Some(node_id) = rt
1206 .component_tree
1207 .find_node_id_by_instance_key(*instance_key)
1208 else {
1209 continue;
1210 };
1211 let layout_nodes = match tree.get(node_id) {
1212 Some(node_ref) if node_ref.get().role == NodeRole::Layout => vec![node_id],
1213 Some(_) => direct_layout_children(node_id, tree),
1214 None => continue,
1215 };
1216 for layout_node_id in layout_nodes {
1217 if let Some(rect) = Self::layout_node_overlay_rect(metadatas, layout_node_id) {
1218 rects.push(rect);
1219 }
1220 }
1221 }
1222 rects
1223 })
1224 }
1225
1226 #[cfg(not(feature = "debug-dirty-overlay"))]
1229 #[instrument(level = "debug", skip(args, execution))]
1230 fn perform_render<'a>(
1231 args: &mut RenderFrameArgs<'a>,
1232 execution: RenderGraphExecution,
1233 ) -> std::time::Duration {
1234 #[cfg(feature = "profiling")]
1235 let _profiler_guard =
1236 ProfilerScopeGuard::new(ProfilerPhase::RenderFrame, None, None, Some("render_frame"));
1237 let render_timer = Instant::now();
1238
1239 if TesseraRuntime::with(|rt| rt.window_minimized) {
1241 return render_timer.elapsed();
1242 }
1243
1244 debug!("Rendering draw commands...");
1245 args.app.render(execution);
1246 let render_cost = render_timer.elapsed();
1247 debug!("Rendered to surface in {render_cost:?}");
1248 render_cost
1249 }
1250
1251 #[cfg(feature = "debug-dirty-overlay")]
1252 #[instrument(level = "debug", skip(args, execution, dirty_overlay_rects))]
1253 fn perform_render<'a>(
1254 args: &mut RenderFrameArgs<'a>,
1255 execution: RenderGraphExecution,
1256 dirty_overlay_rects: &[PxRect],
1257 ) -> std::time::Duration {
1258 #[cfg(feature = "profiling")]
1259 let _profiler_guard =
1260 ProfilerScopeGuard::new(ProfilerPhase::RenderFrame, None, None, Some("render_frame"));
1261 let render_timer = Instant::now();
1262
1263 if TesseraRuntime::with(|rt| rt.window_minimized) {
1265 return render_timer.elapsed();
1266 }
1267
1268 debug!("Rendering draw commands...");
1269 args.app.render(execution, dirty_overlay_rects);
1270 let render_cost = render_timer.elapsed();
1271 debug!("Rendered to surface in {render_cost:?}");
1272 render_cost
1273 }
1274
1275 #[instrument(level = "debug", skip(context))]
1276 fn execute_render_frame(context: RenderFrameContext<'_, F>) -> RenderFrameOutcome {
1277 let RenderFrameContext {
1278 entry_point,
1279 args,
1280 accessibility_enabled,
1281 decorations,
1282 window_label,
1283 frame_idx,
1284 #[cfg(feature = "profiling")]
1285 redraw_reasons,
1286 } = context;
1287 #[cfg(feature = "profiling")]
1288 let frame_timer = Instant::now();
1289 #[cfg(feature = "profiling")]
1290 profiler_begin_frame(frame_idx);
1291 begin_frame_clock(Instant::now());
1292 tick_frame_nanos_receivers();
1295 args.app.window().pre_present_notify();
1299 TesseraRuntime::with_mut(|rt: &mut TesseraRuntime| rt.window_size = args.app.size().into());
1301 let mut build_tree_result = build_component_tree(entry_point);
1303 debug!("Component tree build mode: {:?}", build_tree_result.mode());
1304
1305 let screen_size: PxSize = args.app.size().into();
1307 let (
1308 mut new_graph,
1309 mut window_requests,
1310 mut draw_cost,
1311 mut layout_diagnostics,
1312 mut record_cost,
1313 mut pending_focus_move_retry,
1314 mut pending_focus_reveal_retry,
1315 ) = Self::compute_draw_commands(args, screen_size, frame_idx, None, false);
1316 let mut beyond_bounds_retry_count = 0usize;
1317 while pending_focus_move_retry.is_some() || pending_focus_reveal_retry {
1318 if beyond_bounds_retry_count >= Self::MAX_FOCUS_BEYOND_BOUNDS_RETRIES
1319 || !has_pending_build_invalidations()
1320 {
1321 break;
1322 }
1323
1324 beyond_bounds_retry_count += 1;
1325 let retry_build = build_component_tree(entry_point);
1326 build_tree_result.absorb_retry(retry_build);
1327
1328 let (
1329 retry_graph,
1330 retry_window_requests,
1331 retry_draw_cost,
1332 retry_layout_diagnostics,
1333 retry_record_cost,
1334 next_focus_move_retry,
1335 next_focus_reveal_retry,
1336 ) = Self::compute_draw_commands(
1337 args,
1338 screen_size,
1339 frame_idx,
1340 pending_focus_move_retry,
1341 pending_focus_reveal_retry,
1342 );
1343 new_graph = retry_graph;
1344 window_requests = retry_window_requests;
1345 draw_cost += retry_draw_cost;
1346 layout_diagnostics = retry_layout_diagnostics;
1347 record_cost += retry_record_cost;
1348 pending_focus_move_retry = next_focus_move_retry;
1349 pending_focus_reveal_retry = next_focus_reveal_retry;
1350 }
1351 let final_frame_instance_keys =
1352 TesseraRuntime::with(|runtime| runtime.component_tree.live_instance_keys());
1353 let removed_focus_handles = retain_persistent_focus_handles(&final_frame_instance_keys);
1354 if !removed_focus_handles.handle_ids.is_empty()
1355 || !removed_focus_handles.requester_ids.is_empty()
1356 {
1357 TesseraRuntime::with_mut(|runtime| {
1358 runtime.component_tree.focus_owner_mut().remove_handles(
1359 &removed_focus_handles.handle_ids,
1360 &removed_focus_handles.requester_ids,
1361 );
1362 });
1363 }
1364 #[cfg(not(feature = "profiling"))]
1365 let _ = (layout_diagnostics, record_cost);
1366 #[cfg(feature = "debug-dirty-overlay")]
1367 let dirty_overlay_rects =
1368 Self::collect_dirty_overlay_rects(screen_size, &build_tree_result);
1369 #[cfg(feature = "debug-dirty-overlay")]
1370 let overlay_clear_pending = !dirty_overlay_rects.is_empty();
1371 let (composite_context, composite_registry) =
1372 args.app.composite_context_parts(screen_size, frame_idx);
1373 let new_graph =
1374 composite::expand_composites(new_graph, composite_context, composite_registry);
1375 let RenderGraphExecution {
1376 ops,
1377 resources,
1378 external_resources,
1379 } = new_graph.into_execution();
1380 let render_cost = Self::perform_render(
1382 args,
1383 RenderGraphExecution {
1384 ops,
1385 resources,
1386 external_resources,
1387 },
1388 #[cfg(feature = "debug-dirty-overlay")]
1389 &dirty_overlay_rects,
1390 );
1391 let render_breakdown = args.app.last_render_breakdown();
1393 Self::log_frame_stats(
1394 build_tree_result.duration(),
1395 draw_cost,
1396 render_cost,
1397 render_breakdown,
1398 );
1399
1400 #[cfg(feature = "profiling")]
1401 {
1402 let render_duration_ns = Some(render_cost.as_nanos());
1403 let render_acquire_ns = render_breakdown.map(|breakdown| breakdown.acquire.as_nanos());
1404 let render_build_passes_ns =
1405 render_breakdown.map(|breakdown| breakdown.build_passes.as_nanos());
1406 let render_encode_ns = render_breakdown.map(|breakdown| breakdown.encode.as_nanos());
1407 let render_submit_ns = render_breakdown.map(|breakdown| breakdown.submit.as_nanos());
1408 let render_present_ns = render_breakdown.map(|breakdown| breakdown.present.as_nanos());
1409 let frame_total_ns = frame_timer.elapsed().as_nanos();
1410 let inter_frame_wait_ns = (frame_idx > 0).then(|| frame_delta().as_nanos());
1411 let nodes = TesseraRuntime::with(|rt| rt.component_tree.profiler_nodes());
1412 submit_frame_meta(FrameMeta {
1413 frame_idx,
1414 build_mode: build_tree_result.profiler_build_mode(),
1415 redraw_reasons,
1416 inter_frame_wait_ns,
1417 partial_replay_nodes: build_tree_result.partial_replay_nodes(),
1418 total_nodes_before_build: build_tree_result.total_nodes_before_build(),
1419 render_time_ns: render_duration_ns,
1420 render_acquire_ns,
1421 render_build_passes_ns,
1422 render_encode_ns,
1423 render_submit_ns,
1424 render_present_ns,
1425 build_tree_time_ns: Some(build_tree_result.duration().as_nanos()),
1426 draw_time_ns: Some(draw_cost.as_nanos()),
1427 record_time_ns: Some(record_cost.as_nanos()),
1428 frame_total_ns: Some(frame_total_ns),
1429 layout_diagnostics: Some(layout_diagnostics),
1430 nodes,
1431 });
1432 }
1433
1434 let accessibility_update = if accessibility_enabled {
1437 Self::build_accessibility_update(window_label)
1438 } else {
1439 None
1440 };
1441
1442 #[cfg(feature = "profiling")]
1443 profiler_end_frame();
1444
1445 let cursor_position = args.cursor_state.position();
1449 let window_size = args.app.size();
1450 let resize_direction = (Self::supports_native_window_frame_controls() && !decorations)
1451 .then(|| {
1452 Self::cursor_resize_direction(
1453 cursor_position,
1454 window_size,
1455 Self::RESIZE_EDGE_THRESHOLD,
1456 )
1457 });
1458
1459 if let Some(direction) = resize_direction.flatten() {
1460 let icon = Self::cursor_icon_for_resize(direction);
1461 args.app
1462 .window()
1463 .set_cursor(winit::window::Cursor::Icon(icon));
1464 } else {
1465 let should_set_cursor = Self::should_set_cursor_pos(
1466 cursor_position,
1467 window_size.width as f64,
1468 window_size.height as f64,
1469 Self::RESIZE_EDGE_THRESHOLD,
1470 );
1471
1472 if should_set_cursor {
1473 args.app
1474 .window()
1475 .set_cursor(winit::window::Cursor::Icon(window_requests.cursor_icon));
1476 }
1477 }
1478
1479 let request_window_drag =
1480 Self::supports_native_window_frame_controls() && window_requests.request_window_drag;
1481
1482 let ime_bridge_update = args
1483 .ime_bridge_state
1484 .update_request(window_requests.ime_request);
1485 if let Some(allowed) = ime_bridge_update.allowed {
1486 #[cfg(not(target_os = "android"))]
1487 args.app.window().set_ime_allowed(allowed);
1488 #[cfg(target_os = "android")]
1489 {
1490 if allowed {
1491 if !*args.android_ime_opened {
1492 args.app.window().set_ime_allowed(true);
1493 show_soft_input(true, args.event_loop.android_app());
1494 *args.android_ime_opened = true;
1495 }
1496 } else if *args.android_ime_opened {
1497 args.app.window().set_ime_allowed(false);
1498 hide_soft_input(args.event_loop.android_app());
1499 *args.android_ime_opened = false;
1500 }
1501 }
1502 }
1503 if let Some((position, size)) = ime_bridge_update.cursor_area {
1504 args.app
1505 .window()
1506 .set_ime_cursor_area::<PxPosition, PxSize>(position, size);
1507 } else if ime_bridge_update.snapshot_changed
1508 && let Some(ime_request) = ime_bridge_update.request
1509 && ime_request.position.is_none()
1510 {
1511 warn!("IME request missing position; skipping IME cursor area update");
1512 }
1513
1514 args.cursor_state.frame_cleanup();
1516
1517 let runtime_pending_work = RuntimePendingWork {
1518 invalidation_pending: has_pending_build_invalidations(),
1519 frame_receiver_pending: has_pending_frame_nanos_receivers(),
1520 };
1521
1522 RenderFrameOutcome {
1523 accessibility_update,
1524 request_window_drag,
1525 runtime_pending_work,
1526 #[cfg(feature = "debug-dirty-overlay")]
1527 overlay_clear_pending,
1528 }
1529 }
1530}
1531
1532impl<F: Fn()> Renderer<F> {
1533 fn desktop_platform_context(&self) -> Option<DesktopPlatformContext> {
1534 let app = self.app.as_ref()?;
1535 let redraw_pending = self.redraw_request_pending.clone();
1536 let window = app.window_arc();
1537 let wake_handler = Arc::new(move || {
1538 Self::try_request_redraw(window.as_ref(), redraw_pending.as_ref());
1539 });
1540 Some(DesktopPlatformContext::new(
1541 app.window_arc(),
1542 self.pending_desktop_window_action.clone(),
1543 wake_handler,
1544 ))
1545 }
1546
1547 fn take_pending_desktop_window_action(&self) -> Option<DesktopWindowAction> {
1548 self.pending_desktop_window_action.write().take()
1549 }
1550
1551 #[cfg(target_os = "android")]
1552 fn plugin_context(&self, event_loop: &ActiveEventLoop) -> Option<PluginContext> {
1553 let desktop = self.desktop_platform_context()?;
1554 Some(PluginContext::new(
1555 desktop,
1556 event_loop.android_app().clone(),
1557 ))
1558 }
1559
1560 #[cfg(not(target_os = "android"))]
1561 fn plugin_context(&self, _event_loop: &ActiveEventLoop) -> Option<PluginContext> {
1562 let desktop = self.desktop_platform_context()?;
1563 Some(PluginContext::new(desktop))
1564 }
1565
1566 fn try_request_redraw(window: &Window, redraw_pending: &AtomicBool) {
1567 if redraw_pending
1568 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
1569 .is_ok()
1570 {
1571 window.request_redraw();
1572 }
1573 }
1574
1575 fn request_redraw_now(&self) {
1576 if let Some(app) = self.app.as_ref() {
1577 Self::try_request_redraw(app.window(), self.redraw_request_pending.as_ref());
1578 }
1579 }
1580
1581 fn install_runtime_redraw_waker(&self) {
1582 let Some(proxy) = self.event_loop_proxy.clone() else {
1583 clear_redraw_waker();
1584 return;
1585 };
1586 install_redraw_waker(Arc::new(move || {
1587 let _ = proxy.send_event(RendererUserEvent::RuntimeRedrawWake);
1588 }));
1589 }
1590
1591 #[cfg(target_family = "wasm")]
1592 fn begin_web_initialization(&mut self, window: Arc<Window>) {
1593 let Some(proxy) = self.event_loop_proxy.clone() else {
1594 error!("Missing event loop proxy for web renderer initialization");
1595 return;
1596 };
1597
1598 self.web_init_in_progress = true;
1599 let epoch = self.web_init_epoch;
1600 let pending_web_inits = self.pending_web_inits.clone();
1601 let sample_count = self.config.sample_count;
1602 let transparent = self.config.window.transparent;
1603 spawn_local(async move {
1604 let render_core = RenderCore::new(window, sample_count, transparent).await;
1605 pending_web_inits.borrow_mut().push((epoch, render_core));
1606 let _ = proxy.send_event(RendererUserEvent::WebInitReady(epoch));
1607 });
1608 }
1609
1610 #[cfg(target_family = "wasm")]
1611 fn finish_web_initialization(&mut self, event_loop: &ActiveEventLoop, epoch: u64) -> bool {
1612 let pending_render_core = {
1613 let mut pending_web_inits = self.pending_web_inits.borrow_mut();
1614 pending_web_inits
1615 .iter()
1616 .position(|(ready_epoch, _)| *ready_epoch == epoch)
1617 .map(|position| pending_web_inits.remove(position).1)
1618 };
1619 let Some(mut render_core) = pending_render_core else {
1620 return false;
1621 };
1622
1623 if epoch != self.web_init_epoch {
1624 return false;
1625 }
1626
1627 let mut context = PipelineContext::new(&mut render_core);
1628 for module in &self.modules {
1629 module.register_pipelines(&mut context);
1630 }
1631
1632 self.app = Some(render_core);
1633 self.web_init_in_progress = false;
1634 self.install_runtime_redraw_waker();
1635 #[cfg(feature = "profiling")]
1636 self.request_redraw_with_reasons(WakeSource::Lifecycle, vec![RedrawReason::Startup]);
1637 #[cfg(not(feature = "profiling"))]
1638 self.request_redraw_now();
1639
1640 if let Some(context) = self.plugin_context(event_loop) {
1641 self.plugins.resumed(&context);
1642 }
1643 true
1644 }
1645
1646 #[cfg(feature = "profiling")]
1647 fn request_redraw_with_reasons(&mut self, source: WakeSource, mut reasons: Vec<RedrawReason>) {
1648 reasons.sort_unstable();
1649 reasons.dedup();
1650 if reasons.is_empty() {
1651 return;
1652 }
1653
1654 self.pending_redraw_reasons.extend(reasons.iter().copied());
1655
1656 submit_wake_meta(WakeMeta {
1657 frame_idx: self.frame_index,
1658 source,
1659 reasons: reasons.clone(),
1660 });
1661
1662 self.request_redraw_now();
1663 }
1664
1665 #[cfg(feature = "profiling")]
1666 fn take_pending_redraw_reasons(&mut self) -> Vec<RedrawReason> {
1667 std::mem::take(&mut self.pending_redraw_reasons)
1668 .into_iter()
1669 .collect()
1670 }
1671
1672 fn handle_close_requested(&mut self, event_loop: &ActiveEventLoop) {
1674 if let Some(context) = self.plugin_context(event_loop) {
1675 self.plugins.shutdown(&context);
1676 }
1677 if let Some(ref app) = self.app
1678 && let Err(e) = app.save_pipeline_cache()
1679 {
1680 warn!("Failed to save pipeline cache: {}", e);
1681 }
1682 event_loop.exit();
1683 }
1684
1685 fn apply_window_drag(&mut self, window: &Window) {
1686 if !Self::supports_native_window_frame_controls() {
1687 return;
1688 }
1689 if let Err(err) = window.drag_window() {
1690 warn!("Failed to start window drag: {}", err);
1691 }
1692 self.update_native_window_shape(window);
1693 }
1694
1695 fn apply_desktop_window_action(&mut self, window: &Window, action: DesktopWindowAction) {
1696 match action {
1697 DesktopWindowAction::Minimize => {
1698 window.set_minimized(true);
1699 }
1700 DesktopWindowAction::Maximize => {
1701 window.set_maximized(true);
1702 }
1703 DesktopWindowAction::ToggleMaximize => {
1704 window.set_maximized(!window.is_maximized());
1705 }
1706 DesktopWindowAction::Close => {
1707 self.pending_close_requested = true;
1708 }
1709 }
1710 self.update_native_window_shape(window);
1711 }
1712
1713 fn handle_resized(&mut self, size: winit::dpi::PhysicalSize<u32>) {
1714 let app = match self.app.as_mut() {
1717 Some(app) => app,
1718 None => return,
1719 };
1720 let window = app.window_arc();
1721
1722 if size.width == 0 || size.height == 0 {
1723 TesseraRuntime::with_mut(|rt| {
1724 if !rt.window_minimized {
1725 rt.window_minimized = true;
1726 }
1727 });
1728 } else {
1729 TesseraRuntime::with_mut(|rt| {
1730 if rt.window_minimized {
1731 rt.window_minimized = false;
1732 }
1733 });
1734 app.resize(size);
1735 }
1736 self.update_native_window_shape(&window);
1737 }
1738
1739 fn handle_cursor_moved(&mut self, position: winit::dpi::PhysicalPosition<f64>) {
1740 if self.resize_in_progress {
1741 return;
1742 }
1743 let px_position = PxPosition::from_f64_arr2([position.x, position.y]);
1744 self.cursor_state.update_position(px_position);
1746 self.cursor_state.push_event(PointerChange {
1747 timestamp: Instant::now(),
1748 pointer_id: MOUSE_POINTER_ID,
1749 content: CursorEventContent::Moved(px_position),
1750 gesture_state: GestureState::TapCandidate,
1751 consumed: false,
1752 });
1753 debug!("Cursor moved to: {}, {}", position.x, position.y);
1754 }
1755
1756 fn handle_cursor_left(&mut self) {
1757 self.cursor_state.clear();
1760 debug!("Cursor left the window");
1761 }
1762
1763 fn push_accessibility_update(&mut self, tree_update: TreeUpdate) {
1764 if let Some(adapter) = self.accessibility_adapter.as_mut() {
1765 adapter.update_if_active(|| tree_update);
1766 }
1767 }
1768
1769 fn send_accessibility_update(&mut self) {
1770 if let Some(tree_update) = Self::build_accessibility_update(&self.config.window_title) {
1771 self.push_accessibility_update(tree_update);
1772 }
1773 }
1774
1775 fn build_accessibility_update(window_label: &str) -> Option<TreeUpdate> {
1776 TesseraRuntime::with(|runtime| {
1777 let tree = runtime.component_tree.tree();
1778 let metadatas = runtime.component_tree.metadatas();
1779 let root_node_id = tree.get_node_id_at(
1780 std::num::NonZero::new(1).expect("root node index must be non-zero"),
1781 )?;
1782 crate::accessibility::build_tree_update(
1783 tree,
1784 metadatas,
1785 root_node_id,
1786 Some(window_label),
1787 )
1788 })
1789 }
1790
1791 fn handle_mouse_input(
1792 &mut self,
1793 state: winit::event::ElementState,
1794 button: winit::event::MouseButton,
1795 ) {
1796 let Some(event_content) = CursorEventContent::from_press_event(state, button) else {
1797 return; };
1799
1800 if self.resize_in_progress {
1801 if matches!(
1802 event_content,
1803 CursorEventContent::Released(PressKeyEventType::Left)
1804 ) {
1805 self.resize_in_progress = false;
1806 }
1807 return;
1808 }
1809
1810 if matches!(
1811 event_content,
1812 CursorEventContent::Pressed(PressKeyEventType::Left)
1813 ) && Self::supports_native_window_frame_controls()
1814 && !self.config.window.decorations
1815 && let Some(app) = self.app.as_ref()
1816 {
1817 let window_size = app.size();
1818 let direction = Self::cursor_resize_direction(
1819 self.cursor_state.position(),
1820 window_size,
1821 Self::RESIZE_EDGE_THRESHOLD,
1822 );
1823 if let Some(direction) = direction {
1824 if let Err(err) = app.window().drag_resize_window(direction) {
1825 warn!("Failed to start window resize: {}", err);
1826 } else {
1827 self.resize_in_progress = true;
1828 self.cursor_state.clear();
1829 debug!("Started native border resize; suppressing cursor input");
1830 }
1831 return;
1832 }
1833 }
1834 let event = PointerChange {
1835 timestamp: Instant::now(),
1836 pointer_id: MOUSE_POINTER_ID,
1837 content: event_content,
1838 gesture_state: GestureState::TapCandidate,
1839 consumed: false,
1840 };
1841 self.cursor_state.push_event(event);
1842 debug!("Mouse input: {state:?} button {button:?}");
1843 }
1844
1845 fn handle_mouse_wheel(&mut self, delta: winit::event::MouseScrollDelta) {
1846 if self.resize_in_progress {
1847 return;
1848 }
1849 let event_content = CursorEventContent::from_scroll_event(delta);
1850 let event = PointerChange {
1851 timestamp: Instant::now(),
1852 pointer_id: MOUSE_POINTER_ID,
1853 content: event_content,
1854 gesture_state: GestureState::Dragged,
1855 consumed: false,
1856 };
1857 self.cursor_state.push_event(event);
1858 debug!("Mouse scroll: {delta:?}");
1859 }
1860
1861 fn handle_touch(&mut self, touch_event: winit::event::Touch) {
1862 if self.resize_in_progress {
1863 return;
1864 }
1865 let pos = PxPosition::from_f64_arr2([touch_event.location.x, touch_event.location.y]);
1866 debug!(
1867 "Touch event: id {}, phase {:?}, position {:?}",
1868 touch_event.id, touch_event.phase, pos
1869 );
1870 match touch_event.phase {
1871 winit::event::TouchPhase::Started => {
1872 self.cursor_state.handle_touch_start(touch_event.id, pos);
1874 }
1875 winit::event::TouchPhase::Moved => {
1876 if let Some(scroll_event) = self.cursor_state.handle_touch_move(touch_event.id, pos)
1878 {
1879 self.cursor_state.push_event(scroll_event);
1881 }
1882 }
1883 winit::event::TouchPhase::Ended | winit::event::TouchPhase::Cancelled => {
1884 self.cursor_state.handle_touch_end(touch_event.id);
1886 }
1887 }
1888 }
1889
1890 fn handle_keyboard_input(&mut self, event: winit::event::KeyEvent) {
1891 debug!("Keyboard input: {event:?}");
1892 self.keyboard_state.push_event(event);
1893 }
1894
1895 fn handle_redraw_requested(
1896 &mut self,
1897 #[cfg(target_os = "android")] event_loop: &ActiveEventLoop,
1898 ) {
1899 self.redraw_request_pending.store(false, Ordering::Release);
1900 let mut app = match self.app.take() {
1901 Some(app) => app,
1902 None => return,
1903 };
1904
1905 app.resize_if_needed();
1906 let accessibility_enabled = self.accessibility_adapter.is_some();
1907 let frame_idx = self.frame_index;
1908 #[cfg(feature = "profiling")]
1909 let redraw_reasons = self.take_pending_redraw_reasons();
1910 let window_label = &self.config.window_title;
1911
1912 let RenderFrameOutcome {
1913 accessibility_update,
1914 request_window_drag,
1915 runtime_pending_work,
1916 #[cfg(feature = "debug-dirty-overlay")]
1917 overlay_clear_pending,
1918 } = {
1919 let mut args = RenderFrameArgs {
1920 cursor_state: &mut self.cursor_state,
1921 keyboard_state: &mut self.keyboard_state,
1922 ime_state: &mut self.ime_state,
1923 ime_bridge_state: &mut self.ime_bridge_state,
1924 #[cfg(target_os = "android")]
1925 android_ime_opened: &mut self.android_ime_opened,
1926 app: &mut app,
1927 #[cfg(target_os = "android")]
1928 event_loop,
1929 };
1930 Self::execute_render_frame(RenderFrameContext {
1931 entry_point: &self.entry_point,
1932 args: &mut args,
1933 accessibility_enabled,
1934 decorations: self.config.window.decorations,
1935 window_label,
1936 frame_idx,
1937 #[cfg(feature = "profiling")]
1938 redraw_reasons,
1939 })
1940 };
1941
1942 if request_window_drag {
1943 self.apply_window_drag(app.window());
1944 }
1945 if let Some(action) = self.take_pending_desktop_window_action() {
1946 self.apply_desktop_window_action(app.window(), action);
1947 }
1948
1949 self.frame_index = self.frame_index.wrapping_add(1);
1950
1951 if let Some(tree_update) = accessibility_update {
1952 self.push_accessibility_update(tree_update);
1953 }
1954
1955 self.app = Some(app);
1956
1957 if runtime_pending_work.requires_redraw() {
1958 #[cfg(feature = "profiling")]
1959 self.request_redraw_with_reasons(
1960 WakeSource::Runtime,
1961 runtime_pending_work.redraw_reasons(),
1962 );
1963 #[cfg(not(feature = "profiling"))]
1964 self.request_redraw_now();
1965 }
1966 #[cfg(feature = "debug-dirty-overlay")]
1967 if overlay_clear_pending {
1968 self.request_redraw_now();
1969 }
1970 }
1971}
1972
1973impl<F: Fn()> ApplicationHandler<RendererUserEvent> for Renderer<F> {
1981 #[tracing::instrument(level = "debug", skip(self, event_loop))]
2006 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
2007 event_loop.set_control_flow(ControlFlow::Wait);
2008 #[cfg(feature = "profiling")]
2009 submit_runtime_meta(RuntimeMeta {
2010 kind: RuntimeEventKind::Resumed,
2011 });
2012 #[cfg(target_family = "wasm")]
2013 self.finish_web_initialization(event_loop, self.web_init_epoch);
2014 if self.app.is_some() {
2016 self.install_runtime_redraw_waker();
2017 #[cfg(feature = "profiling")]
2018 self.request_redraw_with_reasons(WakeSource::Lifecycle, vec![RedrawReason::Startup]);
2019 #[cfg(not(feature = "profiling"))]
2020 self.request_redraw_now();
2021 return;
2022 }
2023 #[cfg(target_family = "wasm")]
2024 if self.web_init_in_progress {
2025 return;
2026 }
2027
2028 let window_attributes = Window::default_attributes()
2030 .with_title(&self.config.window_title)
2031 .with_decorations(self.config.window.decorations)
2032 .with_resizable(self.config.window.resizable)
2033 .with_transparent(self.config.window.transparent)
2034 .with_visible(false); #[cfg(target_family = "wasm")]
2036 let window_attributes = {
2037 use winit::platform::web::WindowAttributesExtWebSys;
2038
2039 if let Some(canvas) = Self::resolve_web_canvas(&self.config) {
2040 window_attributes
2041 .with_canvas(Some(canvas))
2042 .with_append(false)
2043 } else {
2044 window_attributes.with_append(true)
2045 }
2046 };
2047 let window = match event_loop.create_window(window_attributes) {
2048 Ok(window) => Arc::new(window),
2049 Err(err) => {
2050 error!("Failed to create window: {err}");
2051 return;
2052 }
2053 };
2054
2055 if let Some(proxy) = self.event_loop_proxy.clone() {
2057 self.accessibility_adapter = Some(AccessKitAdapter::with_event_loop_proxy(
2058 event_loop, &window, proxy,
2059 ));
2060 }
2061
2062 window.set_visible(true);
2064 self.update_native_window_shape(&window);
2065
2066 #[cfg(target_family = "wasm")]
2067 {
2068 self.begin_web_initialization(window);
2069 return;
2070 }
2071
2072 #[cfg(not(target_family = "wasm"))]
2073 {
2074 let mut render_core = pollster::block_on(RenderCore::new(
2075 window.clone(),
2076 self.config.sample_count,
2077 self.config.window.transparent,
2078 ));
2079
2080 let mut context = PipelineContext::new(&mut render_core);
2082 for module in &self.modules {
2083 module.register_pipelines(&mut context);
2084 }
2085
2086 self.app = Some(render_core);
2087 self.install_runtime_redraw_waker();
2088 #[cfg(feature = "profiling")]
2089 self.request_redraw_with_reasons(WakeSource::Lifecycle, vec![RedrawReason::Startup]);
2090 #[cfg(not(feature = "profiling"))]
2091 self.request_redraw_now();
2092
2093 if let Some(context) = self.plugin_context(event_loop) {
2094 self.plugins.resumed(&context);
2095 }
2096 }
2097 }
2098
2099 fn suspended(&mut self, event_loop: &ActiveEventLoop) {
2111 debug!("Suspending renderer; tearing down WGPU resources.");
2112 #[cfg(feature = "profiling")]
2113 submit_runtime_meta(RuntimeMeta {
2114 kind: RuntimeEventKind::Suspended,
2115 });
2116
2117 if let Some(context) = self.plugin_context(event_loop) {
2118 self.plugins.suspended(&context);
2119 }
2120
2121 if let Some(mut app) = self.app.take() {
2122 app.compute_resource_manager_mut().clear();
2123 }
2124
2125 self.accessibility_adapter = None;
2127
2128 self.cursor_state = CursorState::default();
2129 self.keyboard_state = KeyboardState::default();
2130 self.ime_state = ImeState::default();
2131 self.ime_bridge_state.reset();
2132 self.resize_in_progress = false;
2133 self.redraw_request_pending.store(false, Ordering::Release);
2134 #[cfg(target_family = "wasm")]
2135 {
2136 self.web_init_epoch = self.web_init_epoch.wrapping_add(1);
2137 self.web_init_in_progress = false;
2138 self.pending_web_inits.borrow_mut().clear();
2139 }
2140 #[cfg(target_os = "android")]
2141 {
2142 self.android_ime_opened = false;
2143 }
2144
2145 TesseraRuntime::with_mut(|runtime| {
2146 runtime.component_tree.reset();
2147 runtime.cursor_icon_request = None;
2148 runtime.window_minimized = false;
2149 runtime.window_size = [0, 0];
2150 });
2151 clear_layout_snapshots();
2152 reset_layout_dirty_tracking();
2153 reset_component_replay_tracking();
2154 reset_focus_read_dependencies();
2155 reset_render_slot_read_dependencies();
2156 reset_state_read_dependencies();
2157 reset_component_context_tracking();
2158 reset_context_read_dependencies();
2159 reset_build_invalidations();
2160 reset_frame_clock();
2161 clear_redraw_waker();
2162 clear_persistent_focus_handles();
2163 crate::runtime::reset_slots();
2164 #[cfg(feature = "profiling")]
2165 self.pending_redraw_reasons.clear();
2166 }
2167
2168 #[tracing::instrument(level = "debug", skip(self, event_loop))]
2169 fn window_event(
2170 &mut self,
2171 event_loop: &ActiveEventLoop,
2172 _window_id: WindowId,
2173 event: WindowEvent,
2174 ) {
2175 if self.pending_close_requested {
2176 self.pending_close_requested = false;
2177 self.handle_close_requested(event_loop);
2178 return;
2179 }
2180
2181 if let (Some(adapter), Some(app)) = (&mut self.accessibility_adapter, &self.app) {
2183 adapter.process_event(app.window(), &event);
2184 }
2185
2186 let mut request_redraw = false;
2188 #[cfg(feature = "profiling")]
2189 let mut redraw_reasons = Vec::new();
2190 match event {
2191 WindowEvent::CloseRequested => {
2192 self.handle_close_requested(event_loop);
2193 }
2194 WindowEvent::Resized(size) => {
2195 self.handle_resized(size);
2196 request_redraw = true;
2197 #[cfg(feature = "profiling")]
2198 redraw_reasons.push(RedrawReason::WindowResized);
2199 }
2200 WindowEvent::CursorMoved {
2201 device_id: _,
2202 position,
2203 } => {
2204 self.handle_cursor_moved(position);
2205 request_redraw = true;
2206 #[cfg(feature = "profiling")]
2207 redraw_reasons.push(RedrawReason::CursorMoved);
2208 }
2209 WindowEvent::CursorLeft { device_id: _ } => {
2210 self.handle_cursor_left();
2211 request_redraw = true;
2212 #[cfg(feature = "profiling")]
2213 redraw_reasons.push(RedrawReason::CursorLeft);
2214 }
2215 WindowEvent::MouseInput {
2216 device_id: _,
2217 state,
2218 button,
2219 } => {
2220 self.handle_mouse_input(state, button);
2221 request_redraw = true;
2222 #[cfg(feature = "profiling")]
2223 redraw_reasons.push(RedrawReason::MouseInput);
2224 }
2225 WindowEvent::MouseWheel {
2226 device_id: _,
2227 delta,
2228 phase: _,
2229 } => {
2230 self.handle_mouse_wheel(delta);
2231 request_redraw = true;
2232 #[cfg(feature = "profiling")]
2233 redraw_reasons.push(RedrawReason::MouseWheel);
2234 }
2235 WindowEvent::Touch(touch_event) => {
2236 self.handle_touch(touch_event);
2237 request_redraw = true;
2238 #[cfg(feature = "profiling")]
2239 redraw_reasons.push(RedrawReason::TouchInput);
2240 }
2241 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
2242 if let Some(scale_factor_lock) = SCALE_FACTOR.get() {
2243 *scale_factor_lock.write() = scale_factor;
2244 } else {
2245 let _ = SCALE_FACTOR.set(RwLock::new(scale_factor));
2246 }
2247 if let Some(app) = self.app.as_ref() {
2248 self.update_native_window_shape(app.window());
2249 }
2250 request_redraw = true;
2251 #[cfg(feature = "profiling")]
2252 redraw_reasons.push(RedrawReason::ScaleFactorChanged);
2253 }
2254 WindowEvent::KeyboardInput { event, .. } => {
2255 self.handle_keyboard_input(event);
2256 request_redraw = true;
2257 #[cfg(feature = "profiling")]
2258 redraw_reasons.push(RedrawReason::KeyboardInput);
2259 }
2260 WindowEvent::ModifiersChanged(modifiers) => {
2261 debug!("Modifiers changed: {modifiers:?}");
2262 self.keyboard_state.update_modifiers(modifiers.state());
2263 request_redraw = true;
2264 #[cfg(feature = "profiling")]
2265 redraw_reasons.push(RedrawReason::ModifiersChanged);
2266 }
2267 WindowEvent::Ime(ime_event) => {
2268 debug!("IME event: {ime_event:?}");
2269 self.ime_state.push_event(ime_event);
2270 request_redraw = true;
2271 #[cfg(feature = "profiling")]
2272 redraw_reasons.push(RedrawReason::ImeEvent);
2273 }
2274 WindowEvent::Focused(focused) => {
2275 TesseraRuntime::with_mut(|runtime| {
2276 runtime
2277 .component_tree
2278 .focus_owner_mut()
2279 .set_owner_focused(focused);
2280 });
2281 flush_pending_focus_callbacks();
2282 if self.resize_in_progress {
2283 self.resize_in_progress = false;
2284 self.cursor_state.clear();
2285 }
2286 request_redraw = true;
2287 #[cfg(feature = "profiling")]
2288 redraw_reasons.push(RedrawReason::FocusChanged);
2289 }
2290 WindowEvent::RedrawRequested => {
2291 #[cfg(target_os = "android")]
2292 self.handle_redraw_requested(event_loop);
2293 #[cfg(not(target_os = "android"))]
2294 self.handle_redraw_requested();
2295 }
2296 _ => (),
2297 }
2298
2299 if request_redraw {
2300 #[cfg(feature = "profiling")]
2301 self.request_redraw_with_reasons(WakeSource::WindowEvent, redraw_reasons);
2302 #[cfg(not(feature = "profiling"))]
2303 self.request_redraw_now();
2304 }
2305 }
2306
2307 fn user_event(&mut self, event_loop: &ActiveEventLoop, event: RendererUserEvent) {
2308 use accesskit_winit::WindowEvent as AccessKitWindowEvent;
2309 #[cfg(not(target_family = "wasm"))]
2310 let _ = event_loop;
2311
2312 match event {
2313 RendererUserEvent::RuntimeRedrawWake => {
2314 #[cfg(feature = "profiling")]
2315 self.request_redraw_with_reasons(
2316 WakeSource::Runtime,
2317 vec![RedrawReason::RuntimeInvalidation],
2318 );
2319 #[cfg(not(feature = "profiling"))]
2320 self.request_redraw_now();
2321 }
2322 #[cfg(target_family = "wasm")]
2323 RendererUserEvent::WebInitReady(epoch) => {
2324 self.finish_web_initialization(event_loop, epoch);
2325 }
2326 RendererUserEvent::AccessKit(event) => {
2327 if self.accessibility_adapter.is_none() {
2328 return;
2329 }
2330 match event.window_event {
2331 AccessKitWindowEvent::InitialTreeRequested => {
2332 self.send_accessibility_update();
2333 }
2334 AccessKitWindowEvent::ActionRequested(action_request) => {
2335 let handled = TesseraRuntime::with_mut(|runtime| {
2337 let (tree, metadatas, focus_owner) =
2338 runtime.component_tree.accessibility_dispatch_context();
2339 crate::accessibility::dispatch_action(
2340 tree,
2341 metadatas,
2342 focus_owner,
2343 action_request,
2344 )
2345 });
2346 flush_pending_focus_callbacks();
2347
2348 if !handled {
2349 debug!("Action was not handled by any component");
2350 }
2351 }
2352 AccessKitWindowEvent::AccessibilityDeactivated => {
2353 debug!("AccessKit deactivated");
2354 }
2355 }
2356 }
2357 }
2358 }
2359}
2360
2361#[cfg(test)]
2362mod tests {
2363 use super::{RendererImeBridgeState, RendererImeBridgeUpdate};
2364 use crate::{ImeRequest, Px, PxPosition, px::PxSize};
2365
2366 fn positioned_request(
2367 position: PxPosition,
2368 size: PxSize,
2369 selection_range: Option<std::ops::Range<usize>>,
2370 composition_range: Option<std::ops::Range<usize>>,
2371 ) -> ImeRequest {
2372 let mut request = ImeRequest::new(size)
2373 .with_local_position(position)
2374 .with_selection_range(selection_range)
2375 .with_composition_range(composition_range);
2376 request.position = Some(position);
2377 request
2378 }
2379
2380 #[test]
2381 fn ime_bridge_enables_and_updates_cursor_area_for_first_request() {
2382 let mut bridge = RendererImeBridgeState::default();
2383 let request = positioned_request(
2384 PxPosition::new(Px(12), Px(18)),
2385 PxSize::new(Px(7), Px(13)),
2386 Some(2..6),
2387 Some(3..5),
2388 );
2389
2390 let update = bridge.update_request(Some(request.clone()));
2391
2392 assert_eq!(
2393 update,
2394 RendererImeBridgeUpdate {
2395 allowed: Some(true),
2396 cursor_area: Some((PxPosition::new(Px(12), Px(18)), PxSize::new(Px(7), Px(13)))),
2397 request: Some(request.clone()),
2398 snapshot_changed: true,
2399 }
2400 );
2401 assert_eq!(bridge.request(), Some(&request));
2402 }
2403
2404 #[test]
2405 fn ime_bridge_reuses_identical_request_without_window_updates() {
2406 let request = positioned_request(
2407 PxPosition::new(Px(12), Px(18)),
2408 PxSize::new(Px(7), Px(13)),
2409 Some(2..6),
2410 Some(3..5),
2411 );
2412 let mut bridge = RendererImeBridgeState::default();
2413 bridge.update_request(Some(request.clone()));
2414
2415 let update = bridge.update_request(Some(request.clone()));
2416
2417 assert_eq!(
2418 update,
2419 RendererImeBridgeUpdate {
2420 allowed: None,
2421 cursor_area: None,
2422 request: Some(request.clone()),
2423 snapshot_changed: false,
2424 }
2425 );
2426 assert_eq!(bridge.request(), Some(&request));
2427 }
2428
2429 #[test]
2430 fn ime_bridge_preserves_selection_and_composition_changes_without_cursor_reapply() {
2431 let size = PxSize::new(Px(7), Px(13));
2432 let mut bridge = RendererImeBridgeState::default();
2433 bridge.update_request(Some(positioned_request(
2434 PxPosition::new(Px(12), Px(18)),
2435 size,
2436 Some(2..6),
2437 Some(3..5),
2438 )));
2439 let next_request = positioned_request(
2440 PxPosition::new(Px(12), Px(18)),
2441 size,
2442 Some(4..8),
2443 Some(5..7),
2444 );
2445
2446 let update = bridge.update_request(Some(next_request.clone()));
2447
2448 assert_eq!(update.allowed, None);
2449 assert_eq!(update.cursor_area, None);
2450 assert!(update.snapshot_changed);
2451 assert_eq!(bridge.request(), Some(&next_request));
2452 }
2453
2454 #[test]
2455 fn ime_bridge_disables_and_clears_snapshot_when_request_is_removed() {
2456 let request = positioned_request(
2457 PxPosition::new(Px(12), Px(18)),
2458 PxSize::new(Px(7), Px(13)),
2459 Some(2..6),
2460 Some(3..5),
2461 );
2462 let mut bridge = RendererImeBridgeState::default();
2463 bridge.update_request(Some(request));
2464
2465 let update = bridge.update_request(None);
2466
2467 assert_eq!(
2468 update,
2469 RendererImeBridgeUpdate {
2470 allowed: Some(false),
2471 cursor_area: None,
2472 request: None,
2473 snapshot_changed: true,
2474 }
2475 );
2476 assert_eq!(bridge.request(), None);
2477 }
2478}
2479
2480#[cfg(target_os = "android")]
2522pub fn show_soft_input(show_implicit: bool, android_app: &AndroidApp) {
2523 let ctx = android_app;
2524
2525 let jvm = unsafe { jni::JavaVM::from_raw(ctx.vm_as_ptr().cast()) }.unwrap();
2526 let na = unsafe { jni::objects::JObject::from_raw(ctx.activity_as_ptr().cast()) };
2527
2528 let mut env = jvm.attach_current_thread().unwrap();
2529 if env.exception_check().unwrap() {
2530 return;
2531 }
2532 let class_ctxt = env.find_class("android/content/Context").unwrap();
2533 if env.exception_check().unwrap() {
2534 return;
2535 }
2536 let ims = env
2537 .get_static_field(class_ctxt, "INPUT_METHOD_SERVICE", "Ljava/lang/String;")
2538 .unwrap();
2539 if env.exception_check().unwrap() {
2540 return;
2541 }
2542
2543 let im_manager = env
2544 .call_method(
2545 &na,
2546 "getSystemService",
2547 "(Ljava/lang/String;)Ljava/lang/Object;",
2548 &[(&ims).into()],
2549 )
2550 .unwrap()
2551 .l()
2552 .unwrap();
2553 if env.exception_check().unwrap() {
2554 return;
2555 }
2556
2557 let jni_window = env
2558 .call_method(&na, "getWindow", "()Landroid/view/Window;", &[])
2559 .unwrap()
2560 .l()
2561 .unwrap();
2562 if env.exception_check().unwrap() {
2563 return;
2564 }
2565 let view = env
2566 .call_method(&jni_window, "getDecorView", "()Landroid/view/View;", &[])
2567 .unwrap()
2568 .l()
2569 .unwrap();
2570 if env.exception_check().unwrap() {
2571 return;
2572 }
2573
2574 let _ = env.call_method(
2575 im_manager,
2576 "showSoftInput",
2577 "(Landroid/view/View;I)Z",
2578 &[
2579 jni::objects::JValue::Object(&view),
2580 if show_implicit {
2581 (ndk_sys::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT as i32).into()
2582 } else {
2583 0i32.into()
2584 },
2585 ],
2586 );
2587 if env.exception_check().unwrap() {
2590 let _ = env.exception_clear();
2591 }
2592}
2593
2594#[cfg(target_os = "android")]
2636pub fn hide_soft_input(android_app: &AndroidApp) {
2637 use jni::objects::JValue;
2638
2639 let ctx = android_app;
2640 let jvm = match unsafe { jni::JavaVM::from_raw(ctx.vm_as_ptr().cast()) } {
2641 Ok(jvm) => jvm,
2642 Err(_) => return, };
2644 let activity = unsafe { jni::objects::JObject::from_raw(ctx.activity_as_ptr().cast()) };
2645
2646 let mut env = match jvm.attach_current_thread() {
2647 Ok(env) => env,
2648 Err(_) => return,
2649 };
2650
2651 let class_ctxt = match env.find_class("android/content/Context") {
2654 Ok(c) => c,
2655 Err(_) => return,
2656 };
2657 let ims_field =
2658 match env.get_static_field(class_ctxt, "INPUT_METHOD_SERVICE", "Ljava/lang/String;") {
2659 Ok(f) => f,
2660 Err(_) => return,
2661 };
2662 let ims = match ims_field.l() {
2663 Ok(s) => s,
2664 Err(_) => return,
2665 };
2666
2667 let im_manager = match env.call_method(
2668 &activity,
2669 "getSystemService",
2670 "(Ljava/lang/String;)Ljava/lang/Object;",
2671 &[(&ims).into()],
2672 ) {
2673 Ok(m) => match m.l() {
2674 Ok(im) => im,
2675 Err(_) => return,
2676 },
2677 Err(_) => return,
2678 };
2679
2680 let window = match env.call_method(&activity, "getWindow", "()Landroid/view/Window;", &[]) {
2683 Ok(w) => match w.l() {
2684 Ok(win) => win,
2685 Err(_) => return,
2686 },
2687 Err(_) => return,
2688 };
2689
2690 let decor_view = match env.call_method(&window, "getDecorView", "()Landroid/view/View;", &[]) {
2691 Ok(v) => match v.l() {
2692 Ok(view) => view,
2693 Err(_) => return,
2694 },
2695 Err(_) => return,
2696 };
2697
2698 let window_token =
2699 match env.call_method(&decor_view, "getWindowToken", "()Landroid/os/IBinder;", &[]) {
2700 Ok(t) => match t.l() {
2701 Ok(token) => token,
2702 Err(_) => return,
2703 },
2704 Err(_) => return,
2705 };
2706
2707 let _ = env.call_method(
2709 &im_manager,
2710 "hideSoftInputFromWindow",
2711 "(Landroid/os/IBinder;I)Z",
2712 &[
2713 JValue::Object(&window_token),
2714 JValue::Int(0), ],
2716 );
2717
2718 if env.exception_check().unwrap_or(false) {
2720 let _ = env.exception_clear();
2721 }
2722}