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