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