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