1#[cfg(test)]
2use std::sync::{Mutex, OnceLock};
3use std::{
4 cell::{Cell, RefCell},
5 collections::HashMap,
6 rc::{Rc, Weak},
7 sync::Arc,
8};
9
10use cranpose_core::{
11 NodeId, SnapshotStateObserver, collections::map::HashSet, current_runtime_handle,
12};
13
14pub(crate) type ModifierChainTraceCallback =
15 dyn Fn(&[crate::modifier::ModifierChainInspectorNode]) + Send + Sync + 'static;
16
17struct RenderState {
18 layout_repasses: RefCell<DirtyNodeSet>,
19 measure_repasses: RefCell<DirtyNodeSet>,
20 draw_repasses: RefCell<DirtyNodeSet>,
21 layer_property_repasses: RefCell<DirtyNodeSet>,
22 modifier_slice_repasses: RefCell<DirtyNodeSet>,
23 geometry_scene_nodes: RefCell<DirtyNodeSet>,
24 moved_scene_nodes: RefCell<DirtyNodeSet>,
25 render_invalidated: Cell<bool>,
26 pointer_invalidated: Cell<bool>,
27 focus_invalidated: Cell<bool>,
28 layout_invalidated: Cell<bool>,
29 density_bits: Cell<u32>,
30 font_scale: Cell<crate::font_scale::FontScaleCurve>,
31 font_scale_identity: Cell<bool>,
34 font_scale_fingerprint: Cell<u32>,
36}
37
38#[doc(hidden)]
39pub struct AppContext {
40 id: AppContextId,
41 self_weak: RefCell<Weak<AppContext>>,
42 state: RenderState,
43 draw_observer: SnapshotStateObserver,
44 text: crate::text::measure::TextService,
45 layout_frame_arena: RefCell<crate::layout::FrameLayoutArena>,
46 layout_cache_epoch: Cell<u64>,
47 layout_cache_floor: Cell<u64>,
48 last_fling_velocity_bits: Cell<u32>,
49 scroll_motion_contexts: crate::scroll::ScrollMotionContextStore,
50 lazy_prefetch: crate::lazy_prefetch::LazyPrefetchState,
51 layout_node_registry: crate::widgets::nodes::layout_node::LayoutNodeRegistryState,
52 pointer_dispatch: crate::pointer_dispatch::PointerDispatchState,
53 mouse_input: crate::mouse_input::MouseInputQueue,
54 focus_dispatch: crate::focus_dispatch::FocusInvalidationState,
55 modal: crate::modal::ModalState,
56 modal_nodes: crate::modal_nodes::ModalNodes,
57 semantics_layout: crate::semantics_layout_log::SemanticsLayoutLog,
58 hosted_popups: crate::widgets::popup::HostedPopupRegistries,
59 semantics_dispatch: crate::semantics_dispatch::SemanticsInvalidationState,
60 cursor_animation: crate::cursor_animation::CursorAnimationState,
61 text_field_focus: crate::text_field_focus::TextFieldFocusState,
62 text_input_session: crate::text_input_session::PlatformTextInputState,
63 clipboard_session: crate::clipboard_session::ClipboardSessionState,
64 pointer_icon: crate::pointer_icon_session::PointerIconState,
65 pointer_input_tasks: crate::modifier::pointer_input::PointerInputTaskRegistry,
66 modifier_chain_trace: RefCell<Option<Arc<ModifierChainTraceCallback>>>,
67 window_roots: crate::modifier::WindowRootRegistry,
68 drag_and_drop: crate::modifier::DragAndDropState,
69}
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
72pub(crate) struct AppContextId(u64);
73
74#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
75pub(crate) struct DrawObservationScope {
76 node_id: NodeId,
77 modifier_index: usize,
78 command_index: usize,
79 kind: DrawObservationKind,
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
83enum DrawObservationKind {
84 Content,
85 LayerProperties,
86}
87
88impl DrawObservationScope {
89 pub(crate) fn new(node_id: NodeId, modifier_index: usize, command_index: usize) -> Self {
90 Self {
91 node_id,
92 modifier_index,
93 command_index,
94 kind: DrawObservationKind::Content,
95 }
96 }
97
98 pub(crate) fn layer_properties(node_id: NodeId, modifier_index: usize) -> Self {
99 Self {
100 node_id,
101 modifier_index,
102 command_index: 0,
103 kind: DrawObservationKind::LayerProperties,
104 }
105 }
106}
107
108fn new_draw_observer() -> SnapshotStateObserver {
109 let observer = SnapshotStateObserver::new(|callback| {
110 if let Some(runtime) = current_runtime_handle() {
111 runtime.enqueue_ui_task(callback);
112 } else {
113 callback();
114 }
115 });
116 observer.start();
117 observer
118}
119
120thread_local! {
121 static CURRENT_DRAW_NODE: std::cell::Cell<Option<NodeId>> = const { std::cell::Cell::new(None) };
122}
123
124struct CurrentDrawNodeGuard {
125 previous: Option<NodeId>,
126}
127
128impl Drop for CurrentDrawNodeGuard {
129 fn drop(&mut self) {
130 CURRENT_DRAW_NODE.with(|current| current.set(self.previous));
131 }
132}
133
134pub(crate) fn observe_draw_reads<R>(scope: DrawObservationScope, block: impl FnOnce() -> R) -> R {
135 let context = require_current_app_context("draw observer access");
136 let context_id = context.id;
137 let _guard = CurrentDrawNodeGuard {
138 previous: CURRENT_DRAW_NODE.with(|current| current.replace(Some(scope.node_id))),
139 };
140 context.draw_observer.observe_reads(
141 scope,
142 move |scope| match scope.kind {
143 DrawObservationKind::Content => {
144 schedule_draw_repass_for_app_context(context_id, scope.node_id);
145 }
146 DrawObservationKind::LayerProperties => {
147 schedule_layer_property_repass_for_app_context(context_id, scope.node_id);
148 }
149 },
150 block,
151 )
152}
153
154pub fn request_current_draw_redraw() {
161 if let Some(node_id) = CURRENT_DRAW_NODE.with(Cell::get) {
162 schedule_draw_repass(node_id);
163 }
164 request_render_invalidation();
165}
166
167pub(crate) fn clear_draw_observations_for_node(node_id: NodeId) {
168 with_draw_observer(|observer| {
169 observer.clear_if(|scope| {
170 scope
171 .downcast_ref::<DrawObservationScope>()
172 .is_some_and(|scope| scope.node_id == node_id)
173 });
174 });
175}
176
177pub fn prune_draw_observations_to_nodes(retained: &HashSet<NodeId>) {
179 with_draw_observer(|observer| {
180 observer.clear_if(|scope| {
181 scope
182 .downcast_ref::<DrawObservationScope>()
183 .is_some_and(|scope| !retained.contains(&scope.node_id))
184 });
185 });
186}
187
188impl RenderState {
189 fn new_with_density(density: f32) -> Self {
190 Self {
191 layout_repasses: RefCell::new(DirtyNodeSet::new()),
192 measure_repasses: RefCell::new(DirtyNodeSet::new()),
193 draw_repasses: RefCell::new(DirtyNodeSet::new()),
194 layer_property_repasses: RefCell::new(DirtyNodeSet::new()),
195 modifier_slice_repasses: RefCell::new(DirtyNodeSet::new()),
196 geometry_scene_nodes: RefCell::new(DirtyNodeSet::new()),
197 moved_scene_nodes: RefCell::new(DirtyNodeSet::new()),
198 render_invalidated: Cell::new(false),
199 pointer_invalidated: Cell::new(false),
200 focus_invalidated: Cell::new(false),
201 layout_invalidated: Cell::new(false),
202 density_bits: Cell::new(normalize_density(density).to_bits()),
203 font_scale: Cell::new(crate::font_scale::FontScaleCurve::linear(1.0)),
204 font_scale_identity: Cell::new(true),
205 font_scale_fingerprint: Cell::new(
206 crate::font_scale::FontScaleCurve::linear(1.0).fingerprint(),
207 ),
208 }
209 }
210}
211
212std::thread_local! {
213 static NEXT_APP_CONTEXT_ID: Cell<u64> = const { Cell::new(1) };
214 static CURRENT_APP_CONTEXT: RefCell<Vec<Weak<AppContext>>> = const { RefCell::new(Vec::new()) };
215 static APP_CONTEXTS: RefCell<HashMap<AppContextId, Weak<AppContext>>> = RefCell::new(HashMap::new());
216}
217
218fn next_app_context_id() -> AppContextId {
219 NEXT_APP_CONTEXT_ID.with(|next| {
220 let id = next.get();
221 next.set(id.wrapping_add(1));
222 AppContextId(id)
223 })
224}
225
226#[doc(hidden)]
227pub struct AppContextScope;
228
229impl Drop for AppContextScope {
230 fn drop(&mut self) {
231 CURRENT_APP_CONTEXT.with(|stack| {
232 stack.borrow_mut().pop();
233 });
234 }
235}
236
237impl AppContext {
238 pub fn new() -> Rc<Self> {
239 Self::new_with_density(1.0)
240 }
241
242 pub fn new_with_density(density: f32) -> Rc<Self> {
243 let context = Rc::new(Self {
244 id: next_app_context_id(),
245 self_weak: RefCell::new(Weak::new()),
246 state: RenderState::new_with_density(density),
247 draw_observer: new_draw_observer(),
248 text: crate::text::measure::TextService::new(),
249 layout_frame_arena: RefCell::new(crate::layout::FrameLayoutArena::default()),
250 layout_cache_epoch: Cell::new(1),
251 layout_cache_floor: Cell::new(0),
252 last_fling_velocity_bits: Cell::new(0.0f32.to_bits()),
253 scroll_motion_contexts: crate::scroll::ScrollMotionContextStore::new(),
254 lazy_prefetch: crate::lazy_prefetch::LazyPrefetchState::new(),
255 layout_node_registry: crate::widgets::nodes::layout_node::LayoutNodeRegistryState::new(
256 ),
257 pointer_dispatch: crate::pointer_dispatch::PointerDispatchState::new(),
258 mouse_input: crate::mouse_input::MouseInputQueue::default(),
259 focus_dispatch: crate::focus_dispatch::FocusInvalidationState::new(),
260 modal: crate::modal::ModalState::new(),
261 modal_nodes: crate::modal_nodes::ModalNodes::default(),
262 semantics_layout: crate::semantics_layout_log::SemanticsLayoutLog::default(),
263 hosted_popups: crate::widgets::popup::HostedPopupRegistries::default(),
264 semantics_dispatch: crate::semantics_dispatch::SemanticsInvalidationState::new(),
265 cursor_animation: crate::cursor_animation::CursorAnimationState::new(),
266 text_field_focus: crate::text_field_focus::TextFieldFocusState::new(),
267 text_input_session: crate::text_input_session::PlatformTextInputState::new(),
268 clipboard_session: crate::clipboard_session::ClipboardSessionState::new(),
269 pointer_icon: crate::pointer_icon_session::PointerIconState::new(),
270 pointer_input_tasks: crate::modifier::pointer_input::PointerInputTaskRegistry::new(),
271 modifier_chain_trace: RefCell::new(None),
272 window_roots: crate::modifier::WindowRootRegistry::default(),
273 drag_and_drop: crate::modifier::DragAndDropState::default(),
274 });
275 *context.self_weak.borrow_mut() = Rc::downgrade(&context);
276 APP_CONTEXTS.with(|contexts| {
277 contexts
278 .borrow_mut()
279 .insert(context.id, Rc::downgrade(&context));
280 });
281 context
282 }
283
284 pub fn enter<R>(self: &Rc<Self>, block: impl FnOnce() -> R) -> R {
285 let _scope = self.enter_scope();
286 block()
287 }
288
289 pub(crate) fn id(&self) -> AppContextId {
290 self.id
291 }
292
293 pub(crate) fn mouse_input(&self) -> &crate::mouse_input::MouseInputQueue {
294 &self.mouse_input
295 }
296
297 #[doc(hidden)]
299 pub fn pending_mouse_moves(&self) -> usize {
300 self.mouse_input.len()
301 }
302
303 #[doc(hidden)]
305 pub fn take_mouse_move(&self) -> Option<crate::mouse_input::MouseMoveRequest> {
306 self.mouse_input.take()
307 }
308
309 pub fn window_roots(&self) -> &crate::modifier::WindowRootRegistry {
311 &self.window_roots
312 }
313
314 pub fn drag_and_drop(&self) -> &crate::modifier::DragAndDropState {
316 &self.drag_and_drop
317 }
318
319 #[doc(hidden)]
320 pub fn enter_scope(self: &Rc<Self>) -> AppContextScope {
321 CURRENT_APP_CONTEXT.with(|stack| {
322 stack.borrow_mut().push(Rc::downgrade(self));
323 });
324 AppContextScope
325 }
326
327 pub fn set_text_measurer<M: crate::text::TextMeasurer>(&self, measurer: M) {
328 self.set_text_measurer_rc(Rc::new(measurer));
329 }
330
331 pub fn set_text_measurer_rc(&self, measurer: Rc<dyn crate::text::TextMeasurer>) {
332 self.text.set_measurer(measurer);
333 self.invalidate_layout_caches();
334 self.state.layout_invalidated.set(true);
335 self.state.render_invalidated.set(true);
336 }
337
338 fn invalidate_layout_caches(&self) {
339 let floor = self.layout_cache_epoch.get() + 1;
340 self.layout_cache_epoch.set(floor);
341 self.layout_cache_floor.set(floor);
342 }
343
344 #[doc(hidden)]
345 pub fn downgrade(&self) -> Weak<Self> {
346 self.self_weak.borrow().clone()
347 }
348}
349
350impl Drop for AppContext {
351 fn drop(&mut self) {
352 let id = self.id;
353 let _ = APP_CONTEXTS.try_with(|contexts| {
354 contexts.borrow_mut().remove(&id);
355 });
356 }
357}
358
359fn app_context_by_id(id: AppContextId) -> Option<Rc<AppContext>> {
360 APP_CONTEXTS
361 .try_with(|contexts| {
362 let context = contexts.borrow().get(&id).cloned()?;
363 let Some(context) = context.upgrade() else {
364 contexts.borrow_mut().remove(&id);
365 return None;
366 };
367 Some(context)
368 })
369 .ok()
370 .flatten()
371}
372
373#[cfg(test)]
374fn app_context_registry_entry_count() -> usize {
375 APP_CONTEXTS
376 .try_with(|contexts| contexts.borrow().len())
377 .unwrap_or_default()
378}
379
380pub(crate) fn with_app_context_by_id<R>(
381 id: AppContextId,
382 f: impl FnOnce(&Rc<AppContext>) -> R,
383) -> Option<R> {
384 app_context_by_id(id).map(|context| f(&context))
385}
386
387pub(crate) fn current_app_context_id() -> AppContextId {
388 require_current_app_context("app context identity access").id
389}
390
391pub(crate) fn with_layout_node_registry_by_app_context<R>(
392 id: AppContextId,
393 f: impl FnOnce(&crate::widgets::nodes::layout_node::LayoutNodeRegistryState) -> R,
394) -> Option<R> {
395 with_app_context_by_id(id, |context| f(&context.layout_node_registry))
396}
397
398pub(crate) fn enter_app_context_by_id<R>(id: AppContextId, f: impl FnOnce() -> R) -> Option<R> {
399 with_app_context_by_id(id, |context| context.enter(f))
400}
401
402pub(crate) fn current_app_context() -> Option<Rc<AppContext>> {
403 CURRENT_APP_CONTEXT
404 .try_with(|stack| {
405 let mut stack = stack.borrow_mut();
406 loop {
407 let context = stack.last()?;
408 if let Some(context) = context.upgrade() {
409 return Some(context);
410 }
411 stack.pop();
412 }
413 })
414 .ok()
415 .flatten()
416}
417
418#[doc(hidden)]
419pub fn has_current_app_context() -> bool {
420 current_app_context().is_some()
421}
422
423pub(crate) fn require_current_app_context(operation: &str) -> Rc<AppContext> {
424 if let Some(context) = current_app_context() {
425 return context;
426 }
427 require_current_app_context_without_scope(operation)
428}
429
430fn require_current_app_context_without_scope(operation: &str) -> Rc<AppContext> {
431 panic!("{operation} requires an active AppContext")
432}
433
434fn with_render_state<R>(f: impl FnOnce(&RenderState) -> R) -> R {
435 let context = require_current_app_context("render state access");
436 f(&context.state)
437}
438
439fn normalize_density(density: f32) -> f32 {
440 if density.is_finite() && density > 0.0 {
441 density
442 } else {
443 1.0
444 }
445}
446
447fn normalize_font_scale(scale: f32) -> f32 {
448 if scale.is_finite() && scale > 0.0 {
449 scale.clamp(MIN_FONT_SCALE, MAX_FONT_SCALE)
450 } else {
451 1.0
452 }
453}
454
455pub const MIN_FONT_SCALE: f32 = 0.5;
458pub const MAX_FONT_SCALE: f32 = 3.0;
461
462pub(crate) fn with_text_measurer<R>(f: impl FnOnce(&dyn crate::text::TextMeasurer) -> R) -> R {
463 let context = require_current_app_context("text measurer access");
464 context.text.with_measurer(f)
465}
466
467pub(crate) fn begin_text_layout_pass() {
470 if let Some(context) = current_app_context() {
471 context.text.begin_layout_pass();
472 }
473}
474
475pub(crate) fn with_text_service<R>(f: impl FnOnce(&crate::text::measure::TextService) -> R) -> R {
476 let context = require_current_app_context("text service access");
477 f(&context.text)
478}
479
480pub(crate) fn set_current_text_measurer(measurer: Rc<dyn crate::text::TextMeasurer>) {
481 let Some(context) = current_app_context() else {
482 panic!("set_text_measurer requires an active AppContext");
483 };
484 context.set_text_measurer_rc(measurer);
485}
486
487pub(crate) fn set_modifier_chain_trace(callback: Arc<ModifierChainTraceCallback>) -> AppContextId {
488 let context = require_current_app_context("modifier chain trace installation");
489 *context.modifier_chain_trace.borrow_mut() = Some(callback);
490 context.id
491}
492
493pub(crate) fn clear_modifier_chain_trace(context_id: AppContextId) {
494 let _ = with_app_context_by_id(context_id, |context| {
495 *context.modifier_chain_trace.borrow_mut() = None;
496 });
497}
498
499pub(crate) fn emit_modifier_chain_trace(nodes: &[crate::modifier::ModifierChainInspectorNode]) {
500 let Some(context) = current_app_context() else {
501 return;
502 };
503 let callback = context.modifier_chain_trace.borrow().clone();
504 if let Some(callback) = callback {
505 callback(nodes);
506 }
507}
508
509pub(crate) fn take_layout_frame_arena() -> crate::layout::FrameLayoutArena {
510 let context = require_current_app_context("layout frame arena access");
511
512 std::mem::take(&mut *context.layout_frame_arena.borrow_mut())
513}
514
515pub(crate) fn replace_layout_frame_arena(arena: crate::layout::FrameLayoutArena) {
516 let context = require_current_app_context("layout frame arena access");
517 *context.layout_frame_arena.borrow_mut() = arena;
518}
519
520pub(crate) fn invalidate_layout_cache_epoch() {
521 require_current_app_context("layout cache epoch access").invalidate_layout_caches();
522}
523
524pub(crate) fn next_layout_cache_epoch() -> u64 {
525 let context = require_current_app_context("layout cache epoch access");
526 let epoch = context.layout_cache_epoch.get();
527 context.layout_cache_epoch.set(epoch + 1);
528 epoch
529}
530
531pub(crate) fn current_layout_cache_epoch() -> u64 {
532 let context = require_current_app_context("layout cache epoch access");
533 context.layout_cache_epoch.get()
534}
535
536pub(crate) fn layout_cache_floor() -> u64 {
537 let context = require_current_app_context("layout cache epoch access");
538 context.layout_cache_floor.get()
539}
540
541pub(crate) fn record_last_fling_velocity(velocity: f32) {
542 if let Some(context) = current_app_context() {
543 context.last_fling_velocity_bits.set(velocity.to_bits());
544 }
545}
546
547#[doc(hidden)]
548pub fn debug_last_fling_velocity() -> f32 {
549 let context = require_current_app_context("fling velocity diagnostics access");
550 f32::from_bits(context.last_fling_velocity_bits.get())
551}
552
553#[doc(hidden)]
554pub fn debug_reset_last_fling_velocity() {
555 let context = require_current_app_context("fling velocity diagnostics access");
556 context.last_fling_velocity_bits.set(0.0f32.to_bits());
557}
558
559impl AppContext {
560 pub(crate) fn lazy_prefetch(&self) -> &crate::lazy_prefetch::LazyPrefetchState {
561 &self.lazy_prefetch
562 }
563}
564
565pub(crate) fn with_scroll_motion_context_store<R>(
566 f: impl FnOnce(&crate::scroll::ScrollMotionContextStore) -> R,
567) -> R {
568 let context = require_current_app_context("scroll motion context access");
569 f(&context.scroll_motion_contexts)
570}
571
572#[doc(hidden)]
573pub fn clear_transient_scroll_motion_contexts() {
574 let Some(context) = current_app_context() else {
575 return;
576 };
577 context.scroll_motion_contexts.clear_transient_after_frame();
578}
579
580#[cfg(test)]
581pub(crate) fn layout_frame_arena_placement_scratch_count() -> usize {
582 let context = require_current_app_context("layout frame arena access");
583
584 context
585 .layout_frame_arena
586 .borrow()
587 .available_placement_scratch_count()
588}
589
590pub(crate) fn with_layout_node_registry<R>(
591 f: impl FnOnce(&crate::widgets::nodes::layout_node::LayoutNodeRegistryState) -> R,
592) -> R {
593 let context = require_current_app_context("layout node registry access");
594 f(&context.layout_node_registry)
595}
596
597pub(crate) fn with_pointer_dispatch<R>(
598 f: impl FnOnce(&crate::pointer_dispatch::PointerDispatchState) -> R,
599) -> R {
600 let context = require_current_app_context("pointer dispatch access");
601 f(&context.pointer_dispatch)
602}
603
604pub(crate) fn with_focus_dispatch<R>(
605 f: impl FnOnce(&crate::focus_dispatch::FocusInvalidationState) -> R,
606) -> R {
607 let context = require_current_app_context("focus dispatch access");
608 f(&context.focus_dispatch)
609}
610
611pub(crate) fn with_focus_dispatch_by_app_context<R>(
612 id: AppContextId,
613 f: impl FnOnce(&crate::focus_dispatch::FocusInvalidationState) -> R,
614) -> Option<R> {
615 with_app_context_by_id(id, |context| context.enter(|| f(&context.focus_dispatch)))
616}
617
618pub(crate) fn with_modal_state<R>(f: impl FnOnce(&crate::modal::ModalState) -> R) -> R {
619 let context = require_current_app_context("modal state access");
620 f(&context.modal)
621}
622
623pub(crate) fn with_current_modal_nodes<R>(
624 f: impl FnOnce(&crate::modal_nodes::ModalNodes) -> R,
625) -> Option<R> {
626 current_app_context().map(|context| f(&context.modal_nodes))
627}
628
629pub(crate) fn with_current_semantics_layout_log<R>(
630 f: impl FnOnce(&crate::semantics_layout_log::SemanticsLayoutLog) -> R,
631) -> Option<R> {
632 current_app_context().map(|context| f(&context.semantics_layout))
633}
634
635pub(crate) fn with_hosted_popup_registries<R>(
636 f: impl FnOnce(&crate::widgets::popup::HostedPopupRegistries) -> R,
637) -> R {
638 let context = require_current_app_context("hosted popup registries access");
639 f(&context.hosted_popups)
640}
641
642pub(crate) fn with_semantics_dispatch<R>(
643 f: impl FnOnce(&crate::semantics_dispatch::SemanticsInvalidationState) -> R,
644) -> R {
645 let context = require_current_app_context("semantics dispatch access");
646 f(&context.semantics_dispatch)
647}
648
649pub(crate) fn with_semantics_dispatch_by_app_context(
650 id: AppContextId,
651 f: impl FnOnce(&crate::semantics_dispatch::SemanticsInvalidationState),
652) {
653 with_app_context_by_id(id, |context| f(&context.semantics_dispatch));
654}
655
656pub(crate) fn current_app_context_id_opt() -> Option<AppContextId> {
657 current_app_context().map(|context| context.id)
658}
659
660pub(crate) fn with_cursor_animation<R>(
661 f: impl FnOnce(&crate::cursor_animation::CursorAnimationState) -> R,
662) -> R {
663 let context = require_current_app_context("cursor animation access");
664 f(&context.cursor_animation)
665}
666
667pub(crate) fn with_text_field_focus<R>(
668 f: impl FnOnce(&crate::text_field_focus::TextFieldFocusState) -> R,
669) -> R {
670 let context = require_current_app_context("text field focus access");
671 f(&context.text_field_focus)
672}
673
674pub(crate) fn with_text_input_session<R>(
675 f: impl FnOnce(&crate::text_input_session::PlatformTextInputState) -> R,
676) -> R {
677 let context = require_current_app_context("platform text input session access");
678 f(&context.text_input_session)
679}
680
681pub(crate) fn with_clipboard_session<R>(
682 f: impl FnOnce(&crate::clipboard_session::ClipboardSessionState) -> R,
683) -> R {
684 let context = require_current_app_context("clipboard session access");
685 f(&context.clipboard_session)
686}
687
688pub(crate) fn with_pointer_icon_session<R>(
689 f: impl FnOnce(&crate::pointer_icon_session::PointerIconState) -> R,
690) -> R {
691 let context = require_current_app_context("pointer icon session access");
692 f(&context.pointer_icon)
693}
694
695pub(crate) fn register_pointer_input_task(
696 task_id: u64,
697 task: Rc<crate::modifier::pointer_input::PointerInputTaskInner>,
698) -> crate::modifier::pointer_input::PointerInputTaskOwner {
699 let context = require_current_app_context("pointer input task registration");
700 context.pointer_input_tasks.insert(task_id, task);
701 crate::modifier::pointer_input::PointerInputTaskOwner::App(context.id)
702}
703
704pub(crate) fn remove_pointer_input_task(
705 owner: crate::modifier::pointer_input::PointerInputTaskOwner,
706 task_id: u64,
707) {
708 match owner {
709 crate::modifier::pointer_input::PointerInputTaskOwner::App(context_id) => {
710 let _ = with_app_context_by_id(context_id, |context| {
711 context.pointer_input_tasks.remove(task_id);
712 });
713 }
714 }
715}
716
717pub(crate) fn request_pointer_input_task_poll(
718 owner: crate::modifier::pointer_input::PointerInputTaskOwner,
719 task_id: u64,
720) {
721 match owner {
722 crate::modifier::pointer_input::PointerInputTaskOwner::App(context_id) => {
723 let _ = with_app_context_by_id(context_id, |context| {
724 context.enter(|| {
725 context.pointer_input_tasks.request_poll(task_id, owner);
726 });
727 });
728 }
729 }
730}
731
732fn with_draw_observer<R>(f: impl FnOnce(&SnapshotStateObserver) -> R) -> R {
733 let context = require_current_app_context("draw observer access");
734 f(&context.draw_observer)
735}
736
737struct DirtyNodeSet {
738 dirty_nodes: HashSet<NodeId>,
739}
740
741impl DirtyNodeSet {
742 fn new() -> Self {
743 Self {
744 dirty_nodes: HashSet::default(),
745 }
746 }
747
748 fn schedule_repass(&mut self, node_id: NodeId) {
749 self.dirty_nodes.insert(node_id);
750 }
751
752 fn has_pending_repass(&self) -> bool {
753 !self.dirty_nodes.is_empty()
754 }
755
756 fn take_dirty_nodes(&mut self) -> Vec<NodeId> {
757 self.dirty_nodes.drain().collect()
758 }
759
760 fn take_dirty_nodes_into(&mut self, output: &mut Vec<NodeId>) {
761 output.clear();
762 output.reserve(self.dirty_nodes.len());
763 output.extend(self.dirty_nodes.drain());
764 }
765
766 fn dirty_nodes_snapshot(&self) -> Vec<NodeId> {
767 let mut nodes = self.dirty_nodes.iter().copied().collect::<Vec<_>>();
768 nodes.sort_unstable();
769 nodes
770 }
771}
772
773#[track_caller]
788pub fn schedule_layout_repass(node_id: NodeId) {
789 if layout_repass_schedule_diagnostics_enabled_for(node_id) {
790 let caller = std::panic::Location::caller();
791 log::warn!(
792 "[layout-repass-schedule] node={} caller={}:{}:{}",
793 node_id,
794 caller.file(),
795 caller.line(),
796 caller.column()
797 );
798 }
799 with_render_state(|state| {
800 state.layout_repasses.borrow_mut().schedule_repass(node_id);
801 });
802 request_render_invalidation();
803}
804
805#[derive(Clone, Copy)]
806enum LayoutRepassScheduleDiag {
807 Disabled,
808 All,
809 Node(NodeId),
810}
811
812fn layout_repass_schedule_diagnostics_enabled_for(node_id: NodeId) -> bool {
813 static MODE: std::sync::OnceLock<LayoutRepassScheduleDiag> = std::sync::OnceLock::new();
814 match *MODE.get_or_init(|| {
815 let Some(value) = std::env::var_os("CRANPOSE_LAYOUT_REPASS_SCHEDULE_DIAG") else {
816 return LayoutRepassScheduleDiag::Disabled;
817 };
818 if value == "all" {
819 return LayoutRepassScheduleDiag::All;
820 }
821 value.to_string_lossy().parse::<NodeId>().map_or(
822 LayoutRepassScheduleDiag::Disabled,
823 LayoutRepassScheduleDiag::Node,
824 )
825 }) {
826 LayoutRepassScheduleDiag::Disabled => false,
827 LayoutRepassScheduleDiag::All => true,
828 LayoutRepassScheduleDiag::Node(target) => target == node_id,
829 }
830}
831
832pub(crate) fn schedule_modifier_slices_repass(node_id: NodeId) {
833 with_render_state(|state| {
834 state
835 .modifier_slice_repasses
836 .borrow_mut()
837 .schedule_repass(node_id);
838 });
839 schedule_draw_repass(node_id);
840}
841
842pub fn schedule_draw_repass(node_id: NodeId) {
847 let context = require_current_app_context("render state access");
848 schedule_draw_repass_in_context(&context, node_id);
849}
850
851fn schedule_draw_repass_for_app_context(context_id: AppContextId, node_id: NodeId) {
852 let _ = with_app_context_by_id(context_id, |context| {
853 schedule_draw_repass_in_context(context, node_id);
854 });
855}
856
857fn schedule_draw_repass_in_context(context: &AppContext, node_id: NodeId) {
858 context
859 .state
860 .draw_repasses
861 .borrow_mut()
862 .schedule_repass(node_id);
863 context.state.render_invalidated.set(true);
864}
865
866fn schedule_layer_property_repass_for_app_context(context_id: AppContextId, node_id: NodeId) {
867 let _ = with_app_context_by_id(context_id, |context| {
868 schedule_layer_property_repass_in_context(context, node_id);
869 });
870}
871
872fn schedule_layer_property_repass_in_context(context: &AppContext, node_id: NodeId) {
873 context
874 .state
875 .layer_property_repasses
876 .borrow_mut()
877 .schedule_repass(node_id);
878 context.state.render_invalidated.set(true);
879}
880
881pub fn has_pending_draw_repasses() -> bool {
883 with_render_state(|state| state.draw_repasses.borrow_mut().has_pending_repass())
884}
885
886pub fn take_draw_repass_nodes() -> Vec<NodeId> {
888 with_render_state(|state| state.draw_repasses.borrow_mut().take_dirty_nodes())
889}
890
891pub fn take_draw_repass_nodes_into(output: &mut Vec<NodeId>) {
902 with_render_state(|state| {
903 state
904 .draw_repasses
905 .borrow_mut()
906 .take_dirty_nodes_into(output);
907 });
908}
909
910#[doc(hidden)]
912pub fn has_pending_layer_property_repasses() -> bool {
913 with_render_state(|state| {
914 state
915 .layer_property_repasses
916 .borrow_mut()
917 .has_pending_repass()
918 })
919}
920
921#[doc(hidden)]
923pub fn take_layer_property_repass_nodes_into(output: &mut Vec<NodeId>) {
924 with_render_state(|state| {
925 state
926 .layer_property_repasses
927 .borrow_mut()
928 .take_dirty_nodes_into(output);
929 });
930}
931
932pub fn has_pending_layout_repasses() -> bool {
934 with_render_state(|state| state.layout_repasses.borrow_mut().has_pending_repass())
935}
936
937pub fn pending_layout_repass_nodes_snapshot() -> Vec<NodeId> {
939 with_render_state(|state| state.layout_repasses.borrow_mut().dirty_nodes_snapshot())
940}
941
942pub fn take_layout_repass_nodes() -> Vec<NodeId> {
946 with_render_state(|state| state.layout_repasses.borrow_mut().take_dirty_nodes())
947}
948
949pub fn schedule_measure_repass(node_id: NodeId) {
958 with_render_state(|state| {
959 state.measure_repasses.borrow_mut().schedule_repass(node_id);
960 });
961 request_render_invalidation();
962}
963
964pub fn has_pending_measure_repasses() -> bool {
966 with_render_state(|state| state.measure_repasses.borrow_mut().has_pending_repass())
967}
968
969pub fn pending_measure_repass_nodes_snapshot() -> Vec<NodeId> {
978 with_render_state(|state| state.measure_repasses.borrow_mut().dirty_nodes_snapshot())
979}
980
981pub fn take_measure_repass_nodes() -> Vec<NodeId> {
985 with_render_state(|state| state.measure_repasses.borrow_mut().take_dirty_nodes())
986}
987
988pub(crate) fn take_modifier_slice_repass_nodes() -> Vec<NodeId> {
989 with_render_state(|state| {
990 state
991 .modifier_slice_repasses
992 .borrow_mut()
993 .take_dirty_nodes()
994 })
995}
996
997pub(crate) fn record_geometry_scene_node(node_id: NodeId) {
998 with_render_state(|state| {
999 state
1000 .geometry_scene_nodes
1001 .borrow_mut()
1002 .schedule_repass(node_id);
1003 });
1004}
1005
1006pub(crate) fn record_moved_scene_node(node_id: NodeId) {
1007 with_render_state(|state| {
1008 state
1009 .moved_scene_nodes
1010 .borrow_mut()
1011 .schedule_repass(node_id);
1012 });
1013}
1014
1015#[derive(Clone, Debug, Default, PartialEq, Eq)]
1018pub struct GeometrySceneNodes {
1019 pub reshaped: Vec<NodeId>,
1022 pub moved: Vec<NodeId>,
1025}
1026
1027impl GeometrySceneNodes {
1028 pub fn is_empty(&self) -> bool {
1030 self.reshaped.is_empty() && self.moved.is_empty()
1031 }
1032}
1033
1034pub fn take_geometry_scene_nodes() -> GeometrySceneNodes {
1040 let mut nodes = GeometrySceneNodes::default();
1041 take_geometry_scene_nodes_into(&mut nodes);
1042 nodes
1043}
1044
1045pub fn take_geometry_scene_nodes_into(output: &mut GeometrySceneNodes) {
1056 with_render_state(|state| {
1057 state
1058 .geometry_scene_nodes
1059 .borrow_mut()
1060 .take_dirty_nodes_into(&mut output.reshaped);
1061 state
1062 .moved_scene_nodes
1063 .borrow_mut()
1064 .take_dirty_nodes_into(&mut output.moved);
1065 });
1066}
1067
1068pub fn current_density() -> f32 {
1070 with_render_state(|state| f32::from_bits(state.density_bits.get()))
1071}
1072
1073pub fn set_density(density: f32) {
1078 let normalized = normalize_density(density);
1079 let new_bits = normalized.to_bits();
1080 with_render_state(|state| {
1081 let old_bits = state.density_bits.replace(new_bits);
1082 if old_bits != new_bits {
1083 state.layout_invalidated.set(true);
1084 }
1085 });
1086}
1087
1088pub fn current_font_scale() -> f32 {
1099 current_font_scale_curve().scale()
1100}
1101
1102pub fn current_font_scale_curve() -> crate::font_scale::FontScaleCurve {
1108 with_render_state(|state| state.font_scale.get())
1109}
1110
1111pub(crate) fn current_scaling_font_scale_curve() -> Option<crate::font_scale::FontScaleCurve> {
1114 with_render_state(|state| (!state.font_scale_identity.get()).then(|| state.font_scale.get()))
1115}
1116
1117pub(crate) fn text_layout_stamp() -> (u64, u32) {
1120 let context = require_current_app_context("text layout stamp access");
1121 (
1122 context.text.generation(),
1123 context.state.font_scale_fingerprint.get(),
1124 )
1125}
1126
1127pub fn scale_sp(sp: f32) -> f32 {
1129 current_font_scale_curve().sp_to_dp(sp)
1130}
1131
1132pub fn set_font_scale(scale: f32) {
1142 set_font_scale_curve(crate::font_scale::FontScaleCurve::linear(scale));
1143}
1144
1145pub fn set_font_scale_curve(curve: crate::font_scale::FontScaleCurve) {
1153 let normalized = normalize_font_scale(curve.scale());
1154 let curve = if (normalized - curve.scale()).abs() <= f32::EPSILON {
1155 curve
1156 } else {
1157 crate::font_scale::FontScaleCurve::linear(normalized)
1158 };
1159 with_render_state(|state| {
1160 if state.font_scale.get() != curve {
1161 state.font_scale.set(curve);
1162 state.font_scale_identity.set(curve.is_identity());
1163 state.font_scale_fingerprint.set(curve.fingerprint());
1164 state.layout_invalidated.set(true);
1165 }
1166 });
1167}
1168
1169pub fn request_render_invalidation() {
1171 with_render_state(|state| state.render_invalidated.set(true));
1172}
1173
1174pub fn take_render_invalidation() -> bool {
1176 with_render_state(|state| state.render_invalidated.replace(false))
1177}
1178
1179pub fn peek_render_invalidation() -> bool {
1181 with_render_state(|state| state.render_invalidated.get())
1182}
1183
1184pub fn request_pointer_invalidation() {
1186 with_render_state(|state| state.pointer_invalidated.set(true));
1187}
1188
1189pub fn take_pointer_invalidation() -> bool {
1191 with_render_state(|state| state.pointer_invalidated.replace(false))
1192}
1193
1194pub fn peek_pointer_invalidation() -> bool {
1196 with_render_state(|state| state.pointer_invalidated.get())
1197}
1198
1199pub fn request_focus_invalidation() {
1201 with_render_state(|state| state.focus_invalidated.set(true));
1202}
1203
1204pub fn take_focus_invalidation() -> bool {
1206 with_render_state(|state| state.focus_invalidated.replace(false))
1207}
1208
1209pub fn peek_focus_invalidation() -> bool {
1211 with_render_state(|state| state.focus_invalidated.get())
1212}
1213
1214pub fn request_layout_invalidation() {
1241 with_render_state(|state| state.layout_invalidated.set(true));
1242}
1243
1244pub fn take_layout_invalidation() -> bool {
1246 with_render_state(|state| state.layout_invalidated.replace(false))
1247}
1248
1249pub fn peek_layout_invalidation() -> bool {
1251 with_render_state(|state| state.layout_invalidated.get())
1252}
1253
1254#[cfg(any(test, feature = "test-helpers"))]
1255#[doc(hidden)]
1256pub fn reset_render_state_for_tests() {
1257 let _ = take_draw_repass_nodes();
1258 let mut layer_nodes = Vec::new();
1259 take_layer_property_repass_nodes_into(&mut layer_nodes);
1260 let _ = take_layout_repass_nodes();
1261 let _ = take_modifier_slice_repass_nodes();
1262 let _ = take_render_invalidation();
1263 let _ = take_pointer_invalidation();
1264 let _ = take_focus_invalidation();
1265 let _ = take_layout_invalidation();
1266 debug_reset_last_fling_velocity();
1267 set_density(1.0);
1268 set_font_scale(1.0);
1269 let _ = take_layout_invalidation();
1270}
1271
1272#[cfg(test)]
1273pub(crate) struct TestAppContextScope {
1274 _scope: AppContextScope,
1275 _context: Rc<AppContext>,
1276}
1277
1278#[cfg(test)]
1279pub(crate) fn app_context_test_scope() -> TestAppContextScope {
1280 let context = AppContext::new();
1281 let scope = context.enter_scope();
1282 context.enter(reset_render_state_for_tests);
1283 TestAppContextScope {
1284 _scope: scope,
1285 _context: context,
1286 }
1287}
1288
1289#[cfg(test)]
1290pub(crate) struct RenderStateTestGuard {
1291 _app_scope: TestAppContextScope,
1292 _lock: std::sync::MutexGuard<'static, ()>,
1293}
1294
1295#[cfg(test)]
1296pub(crate) fn render_state_test_guard() -> RenderStateTestGuard {
1297 static TEST_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
1298 let lock = match TEST_LOCK.get_or_init(|| Mutex::new(())).lock() {
1299 Ok(guard) => guard,
1300 Err(poisoned) => poisoned.into_inner(),
1301 };
1302 RenderStateTestGuard {
1303 _app_scope: app_context_test_scope(),
1304 _lock: lock,
1305 }
1306}
1307
1308#[cfg(test)]
1309#[path = "tests/render_state_tests.rs"]
1310mod tests;