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 MemoryApplier, 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<NotedSceneNodes<NodeId>>,
23 moved_scene_nodes: RefCell<NotedSceneNodes<MovedSceneNode>>,
24 z_order_changes: RefCell<DirtyNodeSet>,
26 retained_redraws: RefCell<Vec<NodeId>>,
29 render_invalidated: Cell<bool>,
30 pointer_invalidated: Cell<bool>,
31 focus_invalidated: Cell<bool>,
32 layout_invalidated: Cell<bool>,
33 density_bits: Cell<u32>,
34 font_scale: Cell<crate::font_scale::FontScaleCurve>,
35 font_scale_identity: Cell<bool>,
38 font_scale_fingerprint: Cell<u32>,
40}
41
42#[doc(hidden)]
43pub struct AppContext {
44 id: AppContextId,
45 self_weak: RefCell<Weak<AppContext>>,
46 state: RenderState,
47 draw_observer: SnapshotStateObserver,
48 text: crate::text::measure::TextService,
49 layout_frame_arena: RefCell<crate::layout::FrameLayoutArena>,
50 layout_host: RefCell<Option<crate::layout::LayoutHost>>,
53 layout_cache_epoch: Cell<u64>,
54 layout_cache_floor: Cell<u64>,
55 last_fling_velocity_bits: Cell<u32>,
56 scroll_motion_contexts: crate::scroll::ScrollMotionContextStore,
57 lazy_prefetch: crate::lazy_prefetch::LazyPrefetchState,
58 layout_node_registry: crate::widgets::nodes::layout_node::LayoutNodeRegistryState,
59 pointer_dispatch: crate::pointer_dispatch::PointerDispatchState,
60 mouse_input: crate::mouse_input::MouseInputQueue,
61 pointer_claim: Cell<Option<cranpose_core::NodeId>>,
62 focus_dispatch: crate::focus_dispatch::FocusInvalidationState,
63 modal: crate::modal::ModalState,
64 modal_nodes: crate::modal_nodes::ModalNodes,
65 semantics_layout: crate::semantics_layout_log::SemanticsLayoutLog,
66 hosted_popups: crate::widgets::popup::HostedPopupRegistries,
67 pub(crate) semantics_dispatch: crate::semantics_dispatch::SemanticsInvalidationState,
68 pub(crate) cursor_animation: crate::cursor_animation::CursorAnimationState,
69 pub(crate) text_field_focus: crate::text_field_focus::TextFieldFocusState,
70 pub(crate) text_input_session: crate::text_input_session::PlatformTextInputState,
71 clipboard_session: crate::clipboard_session::ClipboardSessionState,
72 pointer_icon: crate::pointer_icon_session::PointerIconState,
73 pointer_input_tasks: crate::modifier::pointer_input::PointerInputTaskRegistry,
74 modifier_chain_trace: RefCell<Option<Arc<ModifierChainTraceCallback>>>,
75 window_roots: crate::modifier::WindowRootRegistry,
76 drag_and_drop: crate::modifier::DragAndDropState,
77 placement_pass: crate::widgets::nodes::layout_node::PlacementPass,
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
83pub(crate) struct AppContextId(std::num::NonZeroU64);
84
85#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
86pub(crate) struct DrawObservationScope {
87 node_id: NodeId,
88 modifier_index: usize,
89 command_index: usize,
90 kind: DrawObservationKind,
91}
92
93#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
94enum DrawObservationKind {
95 Content,
96 LayerProperties,
97}
98
99impl DrawObservationScope {
100 pub(crate) fn new(node_id: NodeId, modifier_index: usize, command_index: usize) -> Self {
101 Self {
102 node_id,
103 modifier_index,
104 command_index,
105 kind: DrawObservationKind::Content,
106 }
107 }
108
109 pub(crate) fn layer_properties(node_id: NodeId, modifier_index: usize) -> Self {
110 Self {
111 node_id,
112 modifier_index,
113 command_index: 0,
114 kind: DrawObservationKind::LayerProperties,
115 }
116 }
117}
118
119fn new_draw_observer() -> SnapshotStateObserver {
120 let observer = SnapshotStateObserver::new(|callback| {
121 if let Some(runtime) = current_runtime_handle() {
122 runtime.enqueue_ui_task(callback);
123 } else {
124 callback();
125 }
126 });
127 observer.start();
128 observer
129}
130
131struct CurrentDrawNodeGuard {
132 previous: Option<NodeId>,
133}
134
135impl Drop for CurrentDrawNodeGuard {
136 fn drop(&mut self) {
137 crate::thread_state::cells(|cells| cells.current_draw_node.set(self.previous));
138 }
139}
140
141pub(crate) fn observe_draw_reads<R>(scope: DrawObservationScope, block: impl FnOnce() -> R) -> R {
142 let context = require_current_app_context("draw observer access");
143 let context_id = context.id;
144 let _guard = CurrentDrawNodeGuard {
145 previous: crate::thread_state::cells(|cells| {
146 cells.current_draw_node.replace(Some(scope.node_id))
147 }),
148 };
149 context.draw_observer.observe_reads(
150 scope,
151 move |scope| match scope.kind {
152 DrawObservationKind::Content => {
153 schedule_draw_repass_for_app_context(context_id, scope.node_id);
154 }
155 DrawObservationKind::LayerProperties => {
156 schedule_layer_property_repass_for_app_context(context_id, scope.node_id);
157 }
158 },
159 block,
160 )
161}
162
163pub fn request_current_draw_redraw() {
170 if let Some(node_id) = crate::thread_state::cells(|cells| cells.current_draw_node.get()) {
171 schedule_draw_repass(node_id);
172 }
173 request_render_invalidation();
174}
175
176pub(crate) fn clear_draw_observations_for_node(node_id: NodeId) {
177 with_draw_observer(|observer| {
178 observer.clear_if(|scope| {
179 scope
180 .downcast_ref::<DrawObservationScope>()
181 .is_some_and(|scope| scope.node_id == node_id)
182 });
183 });
184}
185
186pub fn prune_draw_observations_to_nodes(retained: &HashSet<NodeId>) {
188 with_draw_observer(|observer| {
189 observer.clear_if(|scope| {
190 scope
191 .downcast_ref::<DrawObservationScope>()
192 .is_some_and(|scope| !retained.contains(&scope.node_id))
193 });
194 });
195}
196
197impl RenderState {
198 fn new_with_density(density: f32) -> Self {
199 Self {
200 layout_repasses: RefCell::new(DirtyNodeSet::new()),
201 measure_repasses: RefCell::new(DirtyNodeSet::new()),
202 draw_repasses: RefCell::new(DirtyNodeSet::new()),
203 layer_property_repasses: RefCell::new(DirtyNodeSet::new()),
204 modifier_slice_repasses: RefCell::new(DirtyNodeSet::new()),
205 geometry_scene_nodes: RefCell::new(NotedSceneNodes::new()),
206 moved_scene_nodes: RefCell::new(NotedSceneNodes::new()),
207 z_order_changes: RefCell::new(DirtyNodeSet::new()),
208 retained_redraws: RefCell::new(Vec::new()),
209 render_invalidated: Cell::new(false),
210 pointer_invalidated: Cell::new(false),
211 focus_invalidated: Cell::new(false),
212 layout_invalidated: Cell::new(false),
213 density_bits: Cell::new(normalize_density(density).to_bits()),
214 font_scale: Cell::new(crate::font_scale::FontScaleCurve::linear(1.0)),
215 font_scale_identity: Cell::new(true),
216 font_scale_fingerprint: Cell::new(
217 crate::font_scale::FontScaleCurve::linear(1.0).fingerprint(),
218 ),
219 }
220 }
221}
222
223fn next_app_context_id() -> AppContextId {
224 crate::thread_state::cells(|cells| {
225 let next = &cells.next_app_context_id;
226 let id = next.get();
227 next.set(id.checked_add(1).unwrap_or(std::num::NonZeroU64::MIN));
228 AppContextId(id)
229 })
230}
231
232#[doc(hidden)]
233pub struct AppContextScope;
234
235impl Drop for AppContextScope {
236 fn drop(&mut self) {
237 crate::thread_state::try_with(|state| {
238 state.app_context_stack.borrow_mut().pop();
239 });
240 }
241}
242
243impl AppContext {
244 pub fn new() -> Rc<Self> {
245 Self::new_with_density(1.0)
246 }
247
248 pub fn new_with_density(density: f32) -> Rc<Self> {
249 let context = Rc::new(Self {
250 id: next_app_context_id(),
251 self_weak: RefCell::new(Weak::new()),
252 state: RenderState::new_with_density(density),
253 draw_observer: new_draw_observer(),
254 text: crate::text::measure::TextService::new(),
255 layout_frame_arena: RefCell::new(crate::layout::FrameLayoutArena::default()),
256 layout_host: RefCell::new(None),
257 layout_cache_epoch: Cell::new(1),
258 layout_cache_floor: Cell::new(0),
259 last_fling_velocity_bits: Cell::new(0.0f32.to_bits()),
260 scroll_motion_contexts: crate::scroll::ScrollMotionContextStore::new(),
261 lazy_prefetch: crate::lazy_prefetch::LazyPrefetchState::new(),
262 layout_node_registry: crate::widgets::nodes::layout_node::LayoutNodeRegistryState::new(
263 ),
264 pointer_dispatch: crate::pointer_dispatch::PointerDispatchState::new(),
265 mouse_input: crate::mouse_input::MouseInputQueue::default(),
266 pointer_claim: Cell::new(None),
267 focus_dispatch: crate::focus_dispatch::FocusInvalidationState::new(),
268 modal: crate::modal::ModalState::new(),
269 modal_nodes: crate::modal_nodes::ModalNodes::default(),
270 semantics_layout: crate::semantics_layout_log::SemanticsLayoutLog::default(),
271 hosted_popups: crate::widgets::popup::HostedPopupRegistries::default(),
272 semantics_dispatch: crate::semantics_dispatch::SemanticsInvalidationState::new(),
273 cursor_animation: crate::cursor_animation::CursorAnimationState::new(),
274 text_field_focus: crate::text_field_focus::TextFieldFocusState::new(),
275 text_input_session: crate::text_input_session::PlatformTextInputState::new(),
276 clipboard_session: crate::clipboard_session::ClipboardSessionState::new(),
277 pointer_icon: crate::pointer_icon_session::PointerIconState::new(),
278 pointer_input_tasks: crate::modifier::pointer_input::PointerInputTaskRegistry::new(),
279 modifier_chain_trace: RefCell::new(None),
280 window_roots: crate::modifier::WindowRootRegistry::default(),
281 drag_and_drop: crate::modifier::DragAndDropState::default(),
282 placement_pass: crate::widgets::nodes::layout_node::PlacementPass::new(),
283 });
284 *context.self_weak.borrow_mut() = Rc::downgrade(&context);
285 crate::thread_state::with(|state| {
286 state
287 .app_contexts
288 .borrow_mut()
289 .insert(context.id, Rc::downgrade(&context));
290 });
291 context
292 }
293
294 pub fn enter<R>(self: &Rc<Self>, block: impl FnOnce() -> R) -> R {
295 let _scope = self.enter_scope();
296 block()
297 }
298
299 pub(crate) fn id(&self) -> AppContextId {
300 self.id
301 }
302
303 pub(crate) fn mouse_input(&self) -> &crate::mouse_input::MouseInputQueue {
304 &self.mouse_input
305 }
306
307 #[doc(hidden)]
309 pub fn pending_mouse_moves(&self) -> usize {
310 self.mouse_input.len()
311 }
312
313 #[doc(hidden)]
315 pub fn take_mouse_move(&self) -> Option<crate::mouse_input::MouseMoveRequest> {
316 self.mouse_input.take()
317 }
318
319 pub(crate) fn claim_pointer(&self, node: cranpose_core::NodeId) {
322 self.pointer_claim.set(Some(node));
323 }
324
325 #[doc(hidden)]
327 pub fn take_pointer_claim(&self) -> Option<cranpose_core::NodeId> {
328 self.pointer_claim.take()
329 }
330
331 pub fn window_roots(&self) -> &crate::modifier::WindowRootRegistry {
333 &self.window_roots
334 }
335
336 pub fn drag_and_drop(&self) -> &crate::modifier::DragAndDropState {
338 &self.drag_and_drop
339 }
340
341 #[doc(hidden)]
342 pub fn enter_scope(self: &Rc<Self>) -> AppContextScope {
343 crate::thread_state::with(|state| {
344 state
345 .app_context_stack
346 .borrow_mut()
347 .push(Rc::downgrade(self));
348 });
349 AppContextScope
350 }
351
352 pub fn set_text_measurer<M: crate::text::TextMeasurer>(&self, measurer: M) {
353 self.set_text_measurer_rc(Rc::new(measurer));
354 }
355
356 pub fn set_text_measurer_rc(&self, measurer: Rc<dyn crate::text::TextMeasurer>) {
357 self.text.set_measurer(measurer);
358 self.invalidate_layout_caches();
359 self.state.layout_invalidated.set(true);
360 self.state.render_invalidated.set(true);
361 }
362
363 fn invalidate_layout_caches(&self) {
364 let floor = self.layout_cache_epoch.get() + 1;
365 self.layout_cache_epoch.set(floor);
366 self.layout_cache_floor.set(floor);
367 }
368
369 #[doc(hidden)]
370 pub fn downgrade(&self) -> Weak<Self> {
371 self.self_weak.borrow().clone()
372 }
373}
374
375impl Drop for AppContext {
376 fn drop(&mut self) {
377 let id = self.id;
378 crate::thread_state::try_with(|state| {
379 state.app_contexts.borrow_mut().remove(&id);
380 });
381 }
382}
383
384fn app_context_by_id(id: AppContextId) -> Option<Rc<AppContext>> {
385 crate::thread_state::try_with(|state| {
386 let contexts = &state.app_contexts;
387 let context = contexts.borrow().get(&id).cloned()?;
388 let Some(context) = context.upgrade() else {
389 contexts.borrow_mut().remove(&id);
390 return None;
391 };
392 Some(context)
393 })
394 .flatten()
395}
396
397#[cfg(test)]
398fn app_context_registry_entry_count() -> usize {
399 crate::thread_state::try_with(|state| state.app_contexts.borrow().len()).unwrap_or_default()
400}
401
402pub(crate) fn with_app_context_by_id<R>(
403 id: AppContextId,
404 f: impl FnOnce(&Rc<AppContext>) -> R,
405) -> Option<R> {
406 app_context_by_id(id).map(|context| f(&context))
407}
408
409pub(crate) fn current_app_context_id() -> AppContextId {
410 require_current_app_context("app context identity access").id
411}
412
413pub(crate) fn with_layout_node_registry_by_app_context<R>(
414 id: AppContextId,
415 f: impl FnOnce(&crate::widgets::nodes::layout_node::LayoutNodeRegistryState) -> R,
416) -> Option<R> {
417 with_app_context_by_id(id, |context| f(&context.layout_node_registry))
418}
419
420pub(crate) fn enter_app_context_by_id<R>(id: AppContextId, f: impl FnOnce() -> R) -> Option<R> {
421 with_app_context_by_id(id, |context| context.enter(f))
422}
423
424pub(crate) fn current_app_context() -> Option<Rc<AppContext>> {
425 crate::thread_state::try_with(|state| {
426 let mut stack = state.app_context_stack.borrow_mut();
427 loop {
428 let context = stack.last()?;
429 if let Some(context) = context.upgrade() {
430 return Some(context);
431 }
432 stack.pop();
433 }
434 })
435 .flatten()
436}
437
438#[doc(hidden)]
439pub fn has_current_app_context() -> bool {
440 current_app_context().is_some()
441}
442
443pub(crate) fn require_current_app_context(operation: &str) -> Rc<AppContext> {
444 if let Some(context) = current_app_context() {
445 return context;
446 }
447 require_current_app_context_without_scope(operation)
448}
449
450fn require_current_app_context_without_scope(operation: &str) -> Rc<AppContext> {
451 panic!("{operation} requires an active AppContext")
452}
453
454fn with_render_state<R>(f: impl FnOnce(&RenderState) -> R) -> R {
455 let context = require_current_app_context("render state access");
456 f(&context.state)
457}
458
459fn normalize_density(density: f32) -> f32 {
460 if density.is_finite() && density > 0.0 {
461 density
462 } else {
463 1.0
464 }
465}
466
467fn normalize_font_scale(scale: f32) -> f32 {
468 if scale.is_finite() && scale > 0.0 {
469 scale.clamp(MIN_FONT_SCALE, MAX_FONT_SCALE)
470 } else {
471 1.0
472 }
473}
474
475pub const MIN_FONT_SCALE: f32 = 0.5;
478pub const MAX_FONT_SCALE: f32 = 3.0;
481
482pub(crate) fn with_text_measurer<R>(f: impl FnOnce(&dyn crate::text::TextMeasurer) -> R) -> R {
483 let context = require_current_app_context("text measurer access");
484 context.text.with_measurer(f)
485}
486
487#[cfg(test)]
490pub(crate) fn begin_text_layout_pass() {
491 if let Some(context) = current_app_context() {
492 context.begin_text_layout_pass();
493 }
494}
495
496pub(crate) fn with_text_service<R>(f: impl FnOnce(&crate::text::measure::TextService) -> R) -> R {
497 let context = require_current_app_context("text service access");
498 f(&context.text)
499}
500
501pub(crate) fn set_current_text_measurer(measurer: Rc<dyn crate::text::TextMeasurer>) {
502 let Some(context) = current_app_context() else {
503 panic!("set_text_measurer requires an active AppContext");
504 };
505 context.set_text_measurer_rc(measurer);
506}
507
508pub(crate) fn set_modifier_chain_trace(callback: Arc<ModifierChainTraceCallback>) -> AppContextId {
509 let context = require_current_app_context("modifier chain trace installation");
510 *context.modifier_chain_trace.borrow_mut() = Some(callback);
511 context.id
512}
513
514pub(crate) fn clear_modifier_chain_trace(context_id: AppContextId) {
515 let _ = with_app_context_by_id(context_id, |context| {
516 *context.modifier_chain_trace.borrow_mut() = None;
517 });
518}
519
520pub(crate) fn emit_modifier_chain_trace(nodes: &[crate::modifier::ModifierChainInspectorNode]) {
521 let Some(context) = current_app_context() else {
522 return;
523 };
524 let callback = context.modifier_chain_trace.borrow().clone();
525 if let Some(callback) = callback {
526 callback(nodes);
527 }
528}
529
530impl AppContext {
534 pub(crate) fn begin_text_layout_pass(&self) {
536 self.text.begin_layout_pass();
537 }
538
539 pub(crate) fn take_layout_frame_arena(&self) -> crate::layout::FrameLayoutArena {
540 std::mem::take(&mut *self.layout_frame_arena.borrow_mut())
541 }
542
543 pub(crate) fn replace_layout_frame_arena(&self, arena: crate::layout::FrameLayoutArena) {
544 *self.layout_frame_arena.borrow_mut() = arena;
545 }
546
547 pub(crate) fn take_layout_host(&self) -> crate::layout::LayoutHost {
550 self.layout_host.borrow_mut().take().unwrap_or_default()
551 }
552
553 pub(crate) fn replace_layout_host(&self, host: crate::layout::LayoutHost) {
554 *self.layout_host.borrow_mut() = Some(host);
555 }
556
557 pub(crate) fn next_layout_cache_epoch(&self) -> u64 {
558 let epoch = self.layout_cache_epoch.get();
559 self.layout_cache_epoch.set(epoch + 1);
560 epoch
561 }
562
563 pub(crate) fn layout_cache_floor(&self) -> u64 {
564 self.layout_cache_floor.get()
565 }
566
567 pub(crate) fn current_layout_cache_epoch(&self) -> u64 {
568 self.layout_cache_epoch.get()
569 }
570
571 pub(crate) fn take_modifier_slice_repass_nodes(&self) -> Vec<NodeId> {
572 self.state
573 .modifier_slice_repasses
574 .borrow_mut()
575 .take_dirty_nodes()
576 }
577
578 pub(crate) fn take_measure_repass_nodes(&self) -> Vec<NodeId> {
579 self.state.measure_repasses.borrow_mut().take_dirty_nodes()
580 }
581
582 pub(crate) fn take_layout_repass_nodes(&self) -> Vec<NodeId> {
583 self.state.layout_repasses.borrow_mut().take_dirty_nodes()
584 }
585}
586
587pub(crate) fn invalidate_layout_cache_epoch() {
588 require_current_app_context("layout cache epoch access").invalidate_layout_caches();
589}
590
591pub(crate) fn record_last_fling_velocity(velocity: f32) {
592 if let Some(context) = current_app_context() {
593 context.last_fling_velocity_bits.set(velocity.to_bits());
594 }
595}
596
597#[doc(hidden)]
598pub fn debug_last_fling_velocity() -> f32 {
599 let context = require_current_app_context("fling velocity diagnostics access");
600 f32::from_bits(context.last_fling_velocity_bits.get())
601}
602
603#[doc(hidden)]
604pub fn debug_reset_last_fling_velocity() {
605 let context = require_current_app_context("fling velocity diagnostics access");
606 context.last_fling_velocity_bits.set(0.0f32.to_bits());
607}
608
609impl AppContext {
610 pub(crate) fn lazy_prefetch(&self) -> &crate::lazy_prefetch::LazyPrefetchState {
611 &self.lazy_prefetch
612 }
613
614 pub fn has_pending_frame_work(&self) -> bool {
619 let state = &self.state;
620 state.render_invalidated.get()
621 || state.pointer_invalidated.get()
622 || state.focus_invalidated.get()
623 || state.layout_invalidated.get()
624 || state.layout_repasses.borrow_mut().has_pending_repass()
625 || state.measure_repasses.borrow_mut().has_pending_repass()
626 || state
627 .layer_property_repasses
628 .borrow_mut()
629 .has_pending_repass()
630 }
631
632 pub fn has_stale_work(&self) -> bool {
635 self.has_pending_frame_work()
636 || self.state.draw_repasses.borrow_mut().has_pending_repass()
637 || self.pointer_dispatch.has_pending_repass()
638 || self.focus_dispatch.has_pending_invalidation()
639 }
640
641 pub fn modal_node_may_be_live(&self, applier: &mut MemoryApplier, count: bool) -> bool {
648 self.modal_nodes.may_be_live(applier, count)
649 }
650
651 pub fn dismissable_popup_open(&self) -> bool {
655 self.hosted_popups.dismissable_open(false)
656 }
657
658 pub fn take_draw_repass_nodes_into(&self, output: &mut Vec<NodeId>) {
661 self.state
662 .draw_repasses
663 .borrow_mut()
664 .take_dirty_nodes_into(output);
665 }
666
667 pub fn take_retained_redraw_nodes_into(&self, output: &mut Vec<NodeId>) {
670 output.append(&mut self.state.retained_redraws.borrow_mut());
671 }
672
673 pub fn keep_retained_redraw_node(&self, node_id: NodeId) {
677 self.state.retained_redraws.borrow_mut().push(node_id);
678 }
679
680 pub fn append_z_order_changes(&self, output: &mut Vec<NodeId>) {
683 let mut changes = self.state.z_order_changes.borrow_mut();
684 if !changes.dirty_nodes.is_empty() {
685 output.extend(changes.dirty_nodes.drain());
686 }
687 }
688
689 pub fn take_layer_property_repass_nodes_into(&self, output: &mut Vec<NodeId>) {
693 self.state
694 .layer_property_repasses
695 .borrow_mut()
696 .take_dirty_nodes_into(output);
697 }
698
699 pub fn has_pending_layout_repasses(&self) -> bool {
702 self.state.layout_repasses.borrow_mut().has_pending_repass()
703 }
704
705 pub fn pending_layout_repass_nodes_snapshot(&self) -> Vec<NodeId> {
707 self.state
708 .layout_repasses
709 .borrow_mut()
710 .dirty_nodes_snapshot()
711 }
712
713 pub fn has_pending_measure_repasses(&self) -> bool {
716 self.state
717 .measure_repasses
718 .borrow_mut()
719 .has_pending_repass()
720 }
721
722 pub fn pending_measure_repass_nodes_snapshot(&self) -> Vec<NodeId> {
724 self.state
725 .measure_repasses
726 .borrow_mut()
727 .dirty_nodes_snapshot()
728 }
729
730 pub fn take_geometry_scene_nodes_into(&self, output: &mut GeometrySceneNodes) {
734 self.state
735 .geometry_scene_nodes
736 .borrow_mut()
737 .take_into(&mut output.reshaped);
738 self.state
739 .moved_scene_nodes
740 .borrow_mut()
741 .take_into(&mut output.moved);
742 }
743
744 pub(crate) fn record_geometry_scene_node(&self, node_id: NodeId) {
748 self.state.geometry_scene_nodes.borrow_mut().push(node_id);
749 }
750
751 pub(crate) fn note_geometry_scene_node(&self, node_id: NodeId, noted_in: &mut u64) {
754 self.state
755 .geometry_scene_nodes
756 .borrow_mut()
757 .note(node_id, noted_in);
758 }
759
760 pub(crate) fn record_moved_scene_node(
765 &self,
766 node_id: NodeId,
767 placer: Option<NodeId>,
768 noted_in: &mut u64,
769 ) {
770 self.state.moved_scene_nodes.borrow_mut().note(
771 MovedSceneNode {
772 node: node_id,
773 placer,
774 },
775 noted_in,
776 );
777 }
778
779 pub(crate) fn placement_pass(&self) -> &crate::widgets::nodes::layout_node::PlacementPass {
781 &self.placement_pass
782 }
783
784 pub fn take_layout_invalidation(&self) -> bool {
787 self.state.layout_invalidated.replace(false)
788 }
789
790 pub fn take_render_invalidation(&self) -> bool {
793 self.state.render_invalidated.replace(false)
794 }
795
796 pub fn take_pointer_invalidation(&self) -> bool {
799 self.state.pointer_invalidated.replace(false)
800 }
801
802 pub fn take_focus_invalidation(&self) -> bool {
805 self.state.focus_invalidated.replace(false)
806 }
807
808 pub fn clear_transient_scroll_motion_contexts(&self) {
811 self.scroll_motion_contexts.clear_transient_after_frame();
812 }
813
814 pub fn has_pending_pointer_repasses(&self) -> bool {
817 self.pointer_dispatch.has_pending_repass()
818 }
819
820 pub fn has_pending_focus_invalidations(&self) -> bool {
823 self.focus_dispatch.has_pending_invalidation()
824 }
825
826 pub fn active_focus_target(&self) -> Option<NodeId> {
829 self.focus_dispatch.active_focus_target()
830 }
831}
832
833pub(crate) fn with_scroll_motion_context_store<R>(
834 f: impl FnOnce(&crate::scroll::ScrollMotionContextStore) -> R,
835) -> R {
836 let context = require_current_app_context("scroll motion context access");
837 f(&context.scroll_motion_contexts)
838}
839
840#[doc(hidden)]
841pub fn clear_transient_scroll_motion_contexts() {
842 if let Some(context) = current_app_context() {
843 context.clear_transient_scroll_motion_contexts();
844 }
845}
846
847#[cfg(test)]
848pub(crate) fn layout_frame_arena_placement_scratch_count() -> usize {
849 let context = require_current_app_context("layout frame arena access");
850
851 context
852 .layout_frame_arena
853 .borrow()
854 .available_placement_scratch_count()
855}
856
857pub(crate) fn with_current_layout_node_registry(
860 f: impl FnOnce(&crate::widgets::nodes::layout_node::LayoutNodeRegistryState),
861) -> AppContextId {
862 let context = require_current_app_context("layout node registry access");
863 f(&context.layout_node_registry);
864 context.id
865}
866
867pub(crate) fn with_layout_node_registry<R>(
868 f: impl FnOnce(&crate::widgets::nodes::layout_node::LayoutNodeRegistryState) -> R,
869) -> R {
870 let context = require_current_app_context("layout node registry access");
871 f(&context.layout_node_registry)
872}
873
874pub(crate) fn with_pointer_dispatch<R>(
875 f: impl FnOnce(&crate::pointer_dispatch::PointerDispatchState) -> R,
876) -> R {
877 let context = require_current_app_context("pointer dispatch access");
878 f(&context.pointer_dispatch)
879}
880
881pub(crate) fn with_focus_dispatch<R>(
882 f: impl FnOnce(&crate::focus_dispatch::FocusInvalidationState) -> R,
883) -> R {
884 let context = require_current_app_context("focus dispatch access");
885 f(&context.focus_dispatch)
886}
887
888pub(crate) fn with_focus_dispatch_by_app_context<R>(
889 id: AppContextId,
890 f: impl FnOnce(&crate::focus_dispatch::FocusInvalidationState) -> R,
891) -> Option<R> {
892 with_app_context_by_id(id, |context| context.enter(|| f(&context.focus_dispatch)))
893}
894
895pub(crate) fn with_modal_state<R>(f: impl FnOnce(&crate::modal::ModalState) -> R) -> R {
896 let context = require_current_app_context("modal state access");
897 f(&context.modal)
898}
899
900pub(crate) fn with_current_modal_nodes<R>(
901 f: impl FnOnce(&crate::modal_nodes::ModalNodes) -> R,
902) -> Option<R> {
903 current_app_context().map(|context| f(&context.modal_nodes))
904}
905
906pub(crate) fn with_current_semantics_layout_log<R>(
907 f: impl FnOnce(&crate::semantics_layout_log::SemanticsLayoutLog) -> R,
908) -> Option<R> {
909 current_app_context().map(|context| f(&context.semantics_layout))
910}
911
912pub(crate) fn with_hosted_popup_registries<R>(
913 f: impl FnOnce(&crate::widgets::popup::HostedPopupRegistries) -> R,
914) -> R {
915 let context = require_current_app_context("hosted popup registries access");
916 f(&context.hosted_popups)
917}
918
919pub(crate) fn with_semantics_dispatch<R>(
920 f: impl FnOnce(&crate::semantics_dispatch::SemanticsInvalidationState) -> R,
921) -> R {
922 let context = require_current_app_context("semantics dispatch access");
923 f(&context.semantics_dispatch)
924}
925
926pub(crate) fn with_semantics_dispatch_by_app_context(
927 id: AppContextId,
928 f: impl FnOnce(&crate::semantics_dispatch::SemanticsInvalidationState),
929) {
930 with_app_context_by_id(id, |context| f(&context.semantics_dispatch));
931}
932
933pub(crate) fn current_app_context_id_opt() -> Option<AppContextId> {
934 current_app_context().map(|context| context.id)
935}
936
937pub(crate) fn with_cursor_animation<R>(
938 f: impl FnOnce(&crate::cursor_animation::CursorAnimationState) -> R,
939) -> R {
940 let context = require_current_app_context("cursor animation access");
941 f(&context.cursor_animation)
942}
943
944pub(crate) fn with_text_field_focus<R>(
945 f: impl FnOnce(&crate::text_field_focus::TextFieldFocusState) -> R,
946) -> R {
947 let context = require_current_app_context("text field focus access");
948 f(&context.text_field_focus)
949}
950
951pub(crate) fn with_text_input_session<R>(
952 f: impl FnOnce(&crate::text_input_session::PlatformTextInputState) -> R,
953) -> R {
954 let context = require_current_app_context("platform text input session access");
955 f(&context.text_input_session)
956}
957
958pub(crate) fn with_clipboard_session<R>(
959 f: impl FnOnce(&crate::clipboard_session::ClipboardSessionState) -> R,
960) -> R {
961 let context = require_current_app_context("clipboard session access");
962 f(&context.clipboard_session)
963}
964
965pub(crate) fn with_pointer_icon_session<R>(
966 f: impl FnOnce(&crate::pointer_icon_session::PointerIconState) -> R,
967) -> R {
968 let context = require_current_app_context("pointer icon session access");
969 f(&context.pointer_icon)
970}
971
972pub(crate) fn register_pointer_input_task(
973 task_id: u64,
974 task: Rc<crate::modifier::pointer_input::PointerInputTaskInner>,
975) -> crate::modifier::pointer_input::PointerInputTaskOwner {
976 let context = require_current_app_context("pointer input task registration");
977 context.pointer_input_tasks.insert(task_id, task);
978 crate::modifier::pointer_input::PointerInputTaskOwner::App(context.id)
979}
980
981pub(crate) fn remove_pointer_input_task(
982 owner: crate::modifier::pointer_input::PointerInputTaskOwner,
983 task_id: u64,
984) {
985 match owner {
986 crate::modifier::pointer_input::PointerInputTaskOwner::App(context_id) => {
987 let _ = with_app_context_by_id(context_id, |context| {
988 context.pointer_input_tasks.remove(task_id);
989 });
990 }
991 }
992}
993
994pub(crate) fn request_pointer_input_task_poll(
995 owner: crate::modifier::pointer_input::PointerInputTaskOwner,
996 task_id: u64,
997) {
998 match owner {
999 crate::modifier::pointer_input::PointerInputTaskOwner::App(context_id) => {
1000 let _ = with_app_context_by_id(context_id, |context| {
1001 context.enter(|| {
1002 context.pointer_input_tasks.request_poll(task_id, owner);
1003 });
1004 });
1005 }
1006 }
1007}
1008
1009fn with_draw_observer<R>(f: impl FnOnce(&SnapshotStateObserver) -> R) -> R {
1010 let context = require_current_app_context("draw observer access");
1011 f(&context.draw_observer)
1012}
1013
1014struct DirtyNodeSet {
1015 dirty_nodes: HashSet<NodeId>,
1016}
1017
1018struct NotedSceneNodes<T> {
1023 nodes: Vec<T>,
1024 take: u64,
1026}
1027
1028impl<T> NotedSceneNodes<T> {
1029 fn new() -> Self {
1030 Self {
1031 nodes: Vec::new(),
1032 take: next_moved_scene_node_take(),
1033 }
1034 }
1035
1036 fn note(&mut self, node: T, noted_in: &mut u64) {
1037 if *noted_in != self.take {
1038 *noted_in = self.take;
1039 self.nodes.push(node);
1040 }
1041 }
1042
1043 fn push(&mut self, node: T) {
1044 self.nodes.push(node);
1045 }
1046
1047 fn take_into(&mut self, output: &mut Vec<T>) {
1050 output.clear();
1051 if !self.nodes.is_empty() {
1052 std::mem::swap(&mut self.nodes, output);
1053 self.take = next_moved_scene_node_take();
1054 }
1055 }
1056}
1057
1058fn next_moved_scene_node_take() -> u64 {
1059 crate::thread_state::cells(|cells| {
1060 let take = cells.moved_scene_node_takes.get() + 1;
1061 cells.moved_scene_node_takes.set(take);
1062 take
1063 })
1064}
1065
1066impl DirtyNodeSet {
1067 fn new() -> Self {
1068 Self {
1069 dirty_nodes: HashSet::default(),
1070 }
1071 }
1072
1073 fn schedule_repass(&mut self, node_id: NodeId) {
1074 self.dirty_nodes.insert(node_id);
1075 }
1076
1077 fn has_pending_repass(&self) -> bool {
1078 !self.dirty_nodes.is_empty()
1079 }
1080
1081 fn take_dirty_nodes(&mut self) -> Vec<NodeId> {
1084 if self.dirty_nodes.is_empty() {
1085 return Vec::new();
1086 }
1087 self.dirty_nodes.drain().collect()
1088 }
1089
1090 fn take_dirty_nodes_into(&mut self, output: &mut Vec<NodeId>) {
1091 output.clear();
1092 if self.dirty_nodes.is_empty() {
1093 return;
1094 }
1095 output.extend(self.dirty_nodes.drain());
1096 }
1097
1098 fn dirty_nodes_snapshot(&self) -> Vec<NodeId> {
1099 let mut nodes = self.dirty_nodes.iter().copied().collect::<Vec<_>>();
1100 nodes.sort_unstable();
1101 nodes
1102 }
1103}
1104
1105#[track_caller]
1120pub fn schedule_layout_repass(node_id: NodeId) {
1121 if layout_repass_schedule_diagnostics_enabled_for(node_id) {
1122 let caller = std::panic::Location::caller();
1123 log::warn!(
1124 "[layout-repass-schedule] node={} caller={}:{}:{}",
1125 node_id,
1126 caller.file(),
1127 caller.line(),
1128 caller.column()
1129 );
1130 }
1131 with_render_state(|state| {
1132 state.layout_repasses.borrow_mut().schedule_repass(node_id);
1133 });
1134 request_render_invalidation();
1135}
1136
1137#[derive(Clone, Copy)]
1138enum LayoutRepassScheduleDiag {
1139 Disabled,
1140 All,
1141 Node(NodeId),
1142}
1143
1144fn layout_repass_schedule_diagnostics_enabled_for(node_id: NodeId) -> bool {
1145 static MODE: std::sync::OnceLock<LayoutRepassScheduleDiag> = std::sync::OnceLock::new();
1146 match *MODE.get_or_init(|| {
1147 let Some(value) = std::env::var_os("CRANPOSE_LAYOUT_REPASS_SCHEDULE_DIAG") else {
1148 return LayoutRepassScheduleDiag::Disabled;
1149 };
1150 if value == "all" {
1151 return LayoutRepassScheduleDiag::All;
1152 }
1153 value.to_string_lossy().parse::<NodeId>().map_or(
1154 LayoutRepassScheduleDiag::Disabled,
1155 LayoutRepassScheduleDiag::Node,
1156 )
1157 }) {
1158 LayoutRepassScheduleDiag::Disabled => false,
1159 LayoutRepassScheduleDiag::All => true,
1160 LayoutRepassScheduleDiag::Node(target) => target == node_id,
1161 }
1162}
1163
1164pub(crate) fn schedule_modifier_slices_repass(node_id: NodeId) {
1165 with_render_state(|state| {
1166 state
1167 .modifier_slice_repasses
1168 .borrow_mut()
1169 .schedule_repass(node_id);
1170 });
1171 schedule_draw_repass(node_id);
1172}
1173
1174pub fn schedule_draw_repass(node_id: NodeId) {
1179 let context = require_current_app_context("render state access");
1180 schedule_draw_repass_in_context(&context, node_id);
1181}
1182
1183fn schedule_draw_repass_for_app_context(context_id: AppContextId, node_id: NodeId) {
1184 let _ = with_app_context_by_id(context_id, |context| {
1185 schedule_draw_repass_in_context(context, node_id);
1186 });
1187}
1188
1189fn schedule_draw_repass_in_context(context: &AppContext, node_id: NodeId) {
1190 context
1191 .state
1192 .draw_repasses
1193 .borrow_mut()
1194 .schedule_repass(node_id);
1195 context.state.render_invalidated.set(true);
1196}
1197
1198fn schedule_layer_property_repass_for_app_context(context_id: AppContextId, node_id: NodeId) {
1199 let _ = with_app_context_by_id(context_id, |context| {
1200 schedule_layer_property_repass_in_context(context, node_id);
1201 });
1202}
1203
1204fn schedule_layer_property_repass_in_context(context: &AppContext, node_id: NodeId) {
1205 context
1206 .state
1207 .layer_property_repasses
1208 .borrow_mut()
1209 .schedule_repass(node_id);
1210 context.state.render_invalidated.set(true);
1211}
1212
1213pub(crate) fn note_retained_redraw(node_id: NodeId) {
1216 if let Some(context) = current_app_context() {
1217 context.keep_retained_redraw_node(node_id);
1218 }
1219}
1220
1221pub fn has_pending_draw_repasses() -> bool {
1223 with_render_state(|state| state.draw_repasses.borrow_mut().has_pending_repass())
1224}
1225
1226pub fn take_draw_repass_nodes() -> Vec<NodeId> {
1228 with_render_state(|state| state.draw_repasses.borrow_mut().take_dirty_nodes())
1229}
1230
1231pub fn take_draw_repass_nodes_into(output: &mut Vec<NodeId>) {
1242 require_current_app_context("render state access").take_draw_repass_nodes_into(output);
1243}
1244
1245pub fn schedule_z_order_change(node_id: NodeId) {
1249 with_render_state(|state| {
1250 state.z_order_changes.borrow_mut().schedule_repass(node_id);
1251 state.render_invalidated.set(true);
1252 });
1253}
1254
1255pub fn append_z_order_changes(output: &mut Vec<NodeId>) {
1258 require_current_app_context("render state access").append_z_order_changes(output);
1259}
1260
1261#[doc(hidden)]
1263pub fn has_pending_layer_property_repasses() -> bool {
1264 with_render_state(|state| {
1265 state
1266 .layer_property_repasses
1267 .borrow_mut()
1268 .has_pending_repass()
1269 })
1270}
1271
1272#[doc(hidden)]
1274pub fn take_layer_property_repass_nodes_into(output: &mut Vec<NodeId>) {
1275 require_current_app_context("render state access")
1276 .take_layer_property_repass_nodes_into(output);
1277}
1278
1279pub fn has_pending_layout_repasses() -> bool {
1281 require_current_app_context("render state access").has_pending_layout_repasses()
1282}
1283
1284pub fn pending_layout_repass_nodes_snapshot() -> Vec<NodeId> {
1286 require_current_app_context("render state access").pending_layout_repass_nodes_snapshot()
1287}
1288
1289pub fn take_layout_repass_nodes() -> Vec<NodeId> {
1293 require_current_app_context("render state access").take_layout_repass_nodes()
1294}
1295
1296pub fn schedule_measure_repass(node_id: NodeId) {
1305 with_render_state(|state| {
1306 state.measure_repasses.borrow_mut().schedule_repass(node_id);
1307 });
1308 request_render_invalidation();
1309}
1310
1311pub fn has_pending_measure_repasses() -> bool {
1313 require_current_app_context("render state access").has_pending_measure_repasses()
1314}
1315
1316pub fn pending_measure_repass_nodes_snapshot() -> Vec<NodeId> {
1325 require_current_app_context("render state access").pending_measure_repass_nodes_snapshot()
1326}
1327
1328pub fn take_measure_repass_nodes() -> Vec<NodeId> {
1332 require_current_app_context("render state access").take_measure_repass_nodes()
1333}
1334
1335#[cfg(any(test, feature = "test-helpers"))]
1336pub(crate) fn take_modifier_slice_repass_nodes() -> Vec<NodeId> {
1337 require_current_app_context("render state access").take_modifier_slice_repass_nodes()
1338}
1339
1340pub(crate) fn record_geometry_scene_node(node_id: NodeId) {
1341 require_current_app_context("render state access").record_geometry_scene_node(node_id);
1342}
1343
1344#[derive(Clone, Debug, Default, PartialEq, Eq)]
1347pub struct GeometrySceneNodes {
1348 pub reshaped: Vec<NodeId>,
1351 pub moved: Vec<MovedSceneNode>,
1354}
1355
1356#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1359pub struct MovedSceneNode {
1360 pub node: NodeId,
1362 pub placer: Option<NodeId>,
1364}
1365
1366impl GeometrySceneNodes {
1367 pub fn is_empty(&self) -> bool {
1369 self.reshaped.is_empty() && self.moved.is_empty()
1370 }
1371}
1372
1373pub fn take_geometry_scene_nodes() -> GeometrySceneNodes {
1379 let mut nodes = GeometrySceneNodes::default();
1380 take_geometry_scene_nodes_into(&mut nodes);
1381 nodes
1382}
1383
1384pub fn take_geometry_scene_nodes_into(output: &mut GeometrySceneNodes) {
1395 require_current_app_context("render state access").take_geometry_scene_nodes_into(output);
1396}
1397
1398pub fn current_density() -> f32 {
1400 with_render_state(|state| f32::from_bits(state.density_bits.get()))
1401}
1402
1403pub fn set_density(density: f32) {
1408 let normalized = normalize_density(density);
1409 let new_bits = normalized.to_bits();
1410 with_render_state(|state| {
1411 let old_bits = state.density_bits.replace(new_bits);
1412 if old_bits != new_bits {
1413 state.layout_invalidated.set(true);
1414 }
1415 });
1416}
1417
1418pub fn current_font_scale() -> f32 {
1429 current_font_scale_curve().scale()
1430}
1431
1432pub fn current_font_scale_curve() -> crate::font_scale::FontScaleCurve {
1438 with_render_state(|state| state.font_scale.get())
1439}
1440
1441pub(crate) fn current_host_density() -> Option<(f32, crate::font_scale::FontScaleCurve)> {
1444 current_app_context().map(|context| {
1445 (
1446 f32::from_bits(context.state.density_bits.get()),
1447 context.state.font_scale.get(),
1448 )
1449 })
1450}
1451
1452pub(crate) fn current_scaling_font_scale_curve() -> Option<crate::font_scale::FontScaleCurve> {
1455 with_render_state(|state| (!state.font_scale_identity.get()).then(|| state.font_scale.get()))
1456}
1457
1458pub(crate) fn text_layout_stamp() -> (u64, u32) {
1461 let context = require_current_app_context("text layout stamp access");
1462 (
1463 context.text.generation(),
1464 context.state.font_scale_fingerprint.get(),
1465 )
1466}
1467
1468pub fn scale_sp(sp: f32) -> f32 {
1470 current_font_scale_curve().sp_to_dp(sp)
1471}
1472
1473pub fn set_font_scale(scale: f32) {
1483 set_font_scale_curve(crate::font_scale::FontScaleCurve::linear(scale));
1484}
1485
1486pub fn set_font_scale_curve(curve: crate::font_scale::FontScaleCurve) {
1494 let normalized = normalize_font_scale(curve.scale());
1495 let curve = if (normalized - curve.scale()).abs() <= f32::EPSILON {
1496 curve
1497 } else {
1498 crate::font_scale::FontScaleCurve::linear(normalized)
1499 };
1500 with_render_state(|state| {
1501 if state.font_scale.get() != curve {
1502 state.font_scale.set(curve);
1503 state.font_scale_identity.set(curve.is_identity());
1504 state.font_scale_fingerprint.set(curve.fingerprint());
1505 state.layout_invalidated.set(true);
1506 }
1507 });
1508}
1509
1510pub fn request_render_invalidation() {
1512 with_render_state(|state| state.render_invalidated.set(true));
1513}
1514
1515pub fn take_render_invalidation() -> bool {
1517 require_current_app_context("render state access").take_render_invalidation()
1518}
1519
1520pub fn peek_render_invalidation() -> bool {
1522 with_render_state(|state| state.render_invalidated.get())
1523}
1524
1525pub fn request_pointer_invalidation() {
1527 with_render_state(|state| state.pointer_invalidated.set(true));
1528}
1529
1530pub fn take_pointer_invalidation() -> bool {
1532 require_current_app_context("render state access").take_pointer_invalidation()
1533}
1534
1535pub fn peek_pointer_invalidation() -> bool {
1537 with_render_state(|state| state.pointer_invalidated.get())
1538}
1539
1540pub fn request_focus_invalidation() {
1542 with_render_state(|state| state.focus_invalidated.set(true));
1543}
1544
1545pub fn take_focus_invalidation() -> bool {
1547 require_current_app_context("render state access").take_focus_invalidation()
1548}
1549
1550pub fn peek_focus_invalidation() -> bool {
1552 with_render_state(|state| state.focus_invalidated.get())
1553}
1554
1555pub fn request_layout_invalidation() {
1582 with_render_state(|state| state.layout_invalidated.set(true));
1583}
1584
1585pub fn take_layout_invalidation() -> bool {
1587 require_current_app_context("render state access").take_layout_invalidation()
1588}
1589
1590pub fn peek_layout_invalidation() -> bool {
1592 with_render_state(|state| state.layout_invalidated.get())
1593}
1594
1595#[cfg(any(test, feature = "test-helpers"))]
1596#[doc(hidden)]
1597pub fn reset_render_state_for_tests() {
1598 let _ = take_draw_repass_nodes();
1599 let mut layer_nodes = Vec::new();
1600 take_layer_property_repass_nodes_into(&mut layer_nodes);
1601 let _ = take_layout_repass_nodes();
1602 let _ = take_modifier_slice_repass_nodes();
1603 append_z_order_changes(&mut layer_nodes);
1604 let _ = take_render_invalidation();
1605 let _ = take_pointer_invalidation();
1606 let _ = take_focus_invalidation();
1607 let _ = take_layout_invalidation();
1608 debug_reset_last_fling_velocity();
1609 set_density(1.0);
1610 set_font_scale(1.0);
1611 let _ = take_layout_invalidation();
1612}
1613
1614#[cfg(test)]
1615pub(crate) struct TestAppContextScope {
1616 _scope: AppContextScope,
1617 _context: Rc<AppContext>,
1618}
1619
1620#[cfg(test)]
1621pub(crate) fn app_context_test_scope() -> TestAppContextScope {
1622 let context = AppContext::new();
1623 let scope = context.enter_scope();
1624 context.enter(reset_render_state_for_tests);
1625 TestAppContextScope {
1626 _scope: scope,
1627 _context: context,
1628 }
1629}
1630
1631#[cfg(test)]
1632pub(crate) struct RenderStateTestGuard {
1633 _app_scope: TestAppContextScope,
1634 _lock: std::sync::MutexGuard<'static, ()>,
1635}
1636
1637#[cfg(test)]
1638pub(crate) fn render_state_test_guard() -> RenderStateTestGuard {
1639 static TEST_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
1640 let lock = match TEST_LOCK.get_or_init(|| Mutex::new(())).lock() {
1641 Ok(guard) => guard,
1642 Err(poisoned) => poisoned.into_inner(),
1643 };
1644 RenderStateTestGuard {
1645 _app_scope: app_context_test_scope(),
1646 _lock: lock,
1647 }
1648}
1649
1650#[cfg(test)]
1651#[path = "tests/render_state_tests.rs"]
1652mod tests;