#[cfg(test)]
use std::sync::{Mutex, OnceLock};
use std::{
cell::{Cell, RefCell},
rc::{Rc, Weak},
sync::Arc,
};
use cranpose_core::{MemoryApplier, NodeId, PackedU64, RuntimeHandle, SnapshotStateObserver};
pub(crate) type ModifierChainTraceCallback =
dyn Fn(&[crate::modifier::ModifierChainInspectorNode]) + Send + Sync + 'static;
mod dirty_node_set;
mod display_scale;
mod draw_observation;
mod geometry_scene_nodes;
pub(crate) mod measure_reads;
mod noted_scene_nodes;
use dirty_node_set::DirtyNodeSet;
use display_scale::normalize_density;
pub use display_scale::{
MAX_FONT_SCALE, MIN_FONT_SCALE, current_density, current_font_scale, current_font_scale_curve,
scale_sp, set_density, set_font_scale, set_font_scale_curve,
};
pub(crate) use display_scale::{current_host_density, text_layout_stamp};
use draw_observation::new_draw_observer;
pub(crate) use draw_observation::{
DrawObservationScope, clear_draw_observations_for_node, observe_draw_reads,
};
pub use draw_observation::{prune_draw_observations_to_nodes, request_current_draw_redraw};
pub use geometry_scene_nodes::{GeometrySceneNodes, MovedSceneNode};
use noted_scene_nodes::NotedSceneNodes;
struct RenderState {
layout_repasses: RefCell<DirtyNodeSet>,
measure_repasses: RefCell<DirtyNodeSet>,
draw_repasses: RefCell<DirtyNodeSet>,
layer_property_repasses: RefCell<DirtyNodeSet>,
modifier_slice_repasses: RefCell<DirtyNodeSet>,
geometry_scene_nodes: RefCell<NotedSceneNodes<NodeId>>,
moved_scene_nodes: RefCell<NotedSceneNodes<MovedSceneNode>>,
z_order_changes: RefCell<DirtyNodeSet>,
retained_redraws: RefCell<Vec<NodeId>>,
render_invalidated: Cell<bool>,
pointer_invalidated: Cell<bool>,
focus_invalidated: Cell<bool>,
layout_invalidated: Cell<bool>,
density_bits: Cell<u32>,
font_scale: Cell<crate::font_scale::FontScaleCurve>,
font_scale_identity: Cell<bool>,
font_scale_fingerprint: Cell<u32>,
}
#[doc(hidden)]
pub struct AppContext {
id: AppContextId,
self_weak: RefCell<Weak<Self>>,
state: RenderState,
draw_observer: SnapshotStateObserver,
pub(crate) measure_reads: Rc<cranpose_core::internal::NodeReads>,
text: crate::text::measure::TextService,
layout_frame_arena: RefCell<crate::layout::FrameLayoutArena>,
layout_host: RefCell<Option<crate::layout::LayoutHost>>,
layout_cache_epoch: Cell<u64>,
layout_cache_floor: Cell<u64>,
last_fling_velocity_bits: Cell<u32>,
scroll_motion_contexts: crate::scroll::ScrollMotionContextStore,
lazy_prefetch: crate::lazy_prefetch::LazyPrefetchState,
layout_node_registry: crate::widgets::nodes::LayoutNodeRegistryState,
pointer_dispatch: crate::dirty_node_queue::DirtyNodeQueue,
mouse_input: crate::mouse_input::MouseInputQueue,
pointer_claim: Cell<Option<NodeId>>,
focus_dispatch: crate::focus_dispatch::FocusInvalidationState,
modal: crate::modal::ModalState,
modal_nodes: crate::modal_nodes::ModalNodes,
semantics_layout: crate::semantics_layout_log::SemanticsLayoutLog,
hosted_popups: crate::widgets::popup::HostedPopupRegistries,
pub(crate) semantics_dispatch: crate::dirty_node_queue::DirtyNodeQueue,
pub(crate) cursor_animation: crate::cursor_animation::CursorAnimationState,
pub(crate) text_field_focus: crate::text_field_focus::TextFieldFocusState,
pub(crate) text_input_session: crate::text_input_session::PlatformTextInputState,
clipboard_session: crate::clipboard_session::ClipboardSessionState,
pointer_icon: crate::pointer_icon_session::PointerIconState,
pointer_input_tasks: crate::modifier::pointer_input::PointerInputTaskRegistry,
modifier_chain_trace: RefCell<Option<Arc<ModifierChainTraceCallback>>>,
window_roots: crate::modifier::WindowRootRegistry,
drag_and_drop: crate::modifier::DragAndDropState,
placement_pass: crate::widgets::nodes::PlacementPass,
memory_headroom: Cell<crate::MemoryHeadroom>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct AppContextId(std::num::NonZeroU32);
impl RenderState {
fn new_with_density(density: f32) -> Self {
Self {
layout_repasses: RefCell::new(DirtyNodeSet::new()),
measure_repasses: RefCell::new(DirtyNodeSet::new()),
draw_repasses: RefCell::new(DirtyNodeSet::new()),
layer_property_repasses: RefCell::new(DirtyNodeSet::new()),
modifier_slice_repasses: RefCell::new(DirtyNodeSet::new()),
geometry_scene_nodes: RefCell::new(NotedSceneNodes::new()),
moved_scene_nodes: RefCell::new(NotedSceneNodes::new()),
z_order_changes: RefCell::new(DirtyNodeSet::new()),
retained_redraws: RefCell::new(Vec::new()),
render_invalidated: Cell::new(false),
pointer_invalidated: Cell::new(false),
focus_invalidated: Cell::new(false),
layout_invalidated: Cell::new(false),
density_bits: Cell::new(normalize_density(density).to_bits()),
font_scale: Cell::new(crate::font_scale::FontScaleCurve::linear(1.0)),
font_scale_identity: Cell::new(true),
font_scale_fingerprint: Cell::new(
crate::font_scale::FontScaleCurve::linear(1.0).fingerprint(),
),
}
}
}
fn next_app_context_id() -> AppContextId {
crate::thread_state::cells(|cells| {
let next = &cells.next_app_context_id;
let id = next.get();
next.set(id.checked_add(1).unwrap_or(std::num::NonZeroU32::MIN));
AppContextId(id)
})
}
#[doc(hidden)]
pub struct AppContextScope;
impl Drop for AppContextScope {
fn drop(&mut self) {
crate::thread_state::try_with(|state| {
state.app_context_stack.borrow_mut().pop();
});
}
}
impl AppContext {
pub fn new() -> Rc<Self> {
Self::new_with_density(1.0, None)
}
pub fn new_with_density(density: f32, runtime: Option<RuntimeHandle>) -> Rc<Self> {
let id = next_app_context_id();
let context = Rc::new(Self {
id,
self_weak: RefCell::new(Weak::new()),
state: RenderState::new_with_density(density),
draw_observer: new_draw_observer(runtime),
measure_reads: measure_reads::new(id),
text: crate::text::measure::TextService::new(),
layout_frame_arena: RefCell::new(crate::layout::FrameLayoutArena::default()),
layout_host: RefCell::new(None),
layout_cache_epoch: Cell::new(1),
layout_cache_floor: Cell::new(0),
last_fling_velocity_bits: Cell::new(0.0f32.to_bits()),
scroll_motion_contexts: crate::scroll::ScrollMotionContextStore::new(),
lazy_prefetch: crate::lazy_prefetch::LazyPrefetchState::new(),
layout_node_registry: crate::widgets::nodes::LayoutNodeRegistryState::new(),
pointer_dispatch: crate::dirty_node_queue::DirtyNodeQueue::new(),
mouse_input: crate::mouse_input::MouseInputQueue::default(),
pointer_claim: Cell::new(None),
focus_dispatch: crate::focus_dispatch::FocusInvalidationState::new(),
modal: crate::modal::ModalState::new(),
modal_nodes: crate::modal_nodes::ModalNodes::default(),
semantics_layout: crate::semantics_layout_log::SemanticsLayoutLog::default(),
hosted_popups: crate::widgets::popup::HostedPopupRegistries::default(),
semantics_dispatch: crate::dirty_node_queue::DirtyNodeQueue::new(),
cursor_animation: crate::cursor_animation::CursorAnimationState::new(),
text_field_focus: crate::text_field_focus::TextFieldFocusState::new(),
text_input_session: crate::text_input_session::PlatformTextInputState::new(),
clipboard_session: crate::clipboard_session::ClipboardSessionState::new(),
pointer_icon: crate::pointer_icon_session::PointerIconState::new(),
pointer_input_tasks: crate::modifier::pointer_input::PointerInputTaskRegistry::new(),
modifier_chain_trace: RefCell::new(None),
window_roots: crate::modifier::WindowRootRegistry::default(),
drag_and_drop: crate::modifier::DragAndDropState::default(),
placement_pass: crate::widgets::nodes::PlacementPass::new(),
memory_headroom: Cell::new(crate::MemoryHeadroom::Ample),
});
*context.self_weak.borrow_mut() = Rc::downgrade(&context);
crate::thread_state::with(|state| {
state
.app_contexts
.borrow_mut()
.insert(context.id, Rc::downgrade(&context));
});
context
}
pub fn enter<R>(self: &Rc<Self>, block: impl FnOnce() -> R) -> R {
let _scope = self.enter_scope();
block()
}
pub(crate) fn id(&self) -> AppContextId {
self.id
}
pub(crate) fn mouse_input(&self) -> &crate::mouse_input::MouseInputQueue {
&self.mouse_input
}
#[doc(hidden)]
pub fn pending_mouse_moves(&self) -> usize {
self.mouse_input.len()
}
#[doc(hidden)]
pub fn take_mouse_move(&self) -> Option<crate::mouse_input::MouseMoveRequest> {
self.mouse_input.take()
}
pub(crate) fn claim_pointer(&self, node: NodeId) {
self.pointer_claim.set(Some(node));
}
#[doc(hidden)]
pub fn take_pointer_claim(&self) -> Option<NodeId> {
self.pointer_claim.take()
}
pub fn window_roots(&self) -> &crate::modifier::WindowRootRegistry {
&self.window_roots
}
pub fn drag_and_drop(&self) -> &crate::modifier::DragAndDropState {
&self.drag_and_drop
}
#[doc(hidden)]
pub fn enter_scope(self: &Rc<Self>) -> AppContextScope {
crate::thread_state::with(|state| {
state
.app_context_stack
.borrow_mut()
.push(Rc::downgrade(self));
});
AppContextScope
}
pub fn set_text_fonts(&self, fonts: cranpose_text::FontFamilyResolver) {
self.text.set_fonts(fonts);
self.invalidate_layout_caches();
self.state.layout_invalidated.set(true);
self.state.render_invalidated.set(true);
}
fn invalidate_layout_caches(&self) {
let floor = self.layout_cache_epoch.get() + 1;
self.layout_cache_epoch.set(floor);
self.layout_cache_floor.set(floor);
}
#[doc(hidden)]
pub fn downgrade(&self) -> Weak<Self> {
self.self_weak.borrow().clone()
}
pub(crate) fn begin_text_layout_pass(&self) {
self.text.begin_layout_pass();
}
pub(crate) fn take_layout_frame_arena(&self) -> crate::layout::FrameLayoutArena {
std::mem::take(&mut *self.layout_frame_arena.borrow_mut())
}
pub(crate) fn replace_layout_frame_arena(&self, arena: crate::layout::FrameLayoutArena) {
*self.layout_frame_arena.borrow_mut() = arena;
}
pub(crate) fn take_layout_host(&self) -> crate::layout::LayoutHost {
self.layout_host.borrow_mut().take().unwrap_or_default()
}
pub(crate) fn replace_layout_host(&self, host: crate::layout::LayoutHost) {
*self.layout_host.borrow_mut() = Some(host);
}
pub(crate) fn next_layout_cache_epoch(&self) -> u64 {
let epoch = self.layout_cache_epoch.get();
self.layout_cache_epoch.set(epoch + 1);
epoch
}
pub(crate) fn layout_cache_floor(&self) -> u64 {
self.layout_cache_floor.get()
}
pub(crate) fn current_layout_cache_epoch(&self) -> u64 {
self.layout_cache_epoch.get()
}
pub(crate) fn take_modifier_slice_repass_nodes(&self) -> Vec<NodeId> {
self.state
.modifier_slice_repasses
.borrow_mut()
.take_dirty_nodes()
}
pub(crate) fn take_measure_repass_nodes(&self) -> Vec<NodeId> {
self.state.measure_repasses.borrow_mut().take_dirty_nodes()
}
pub(crate) fn take_layout_repass_nodes(&self) -> Vec<NodeId> {
self.state.layout_repasses.borrow_mut().take_dirty_nodes()
}
pub(crate) fn lazy_prefetch(&self) -> &crate::lazy_prefetch::LazyPrefetchState {
&self.lazy_prefetch
}
pub(crate) fn memory_headroom(&self) -> crate::MemoryHeadroom {
self.memory_headroom.get()
}
pub(crate) fn set_memory_headroom(&self, headroom: crate::MemoryHeadroom) {
self.memory_headroom.set(headroom);
}
pub fn has_pending_frame_work(&self) -> bool {
let state = &self.state;
state.render_invalidated.get()
|| state.pointer_invalidated.get()
|| state.focus_invalidated.get()
|| state.layout_invalidated.get()
|| state.layout_repasses.borrow_mut().has_pending_repass()
|| state.measure_repasses.borrow_mut().has_pending_repass()
|| state
.layer_property_repasses
.borrow_mut()
.has_pending_repass()
}
pub fn has_stale_work(&self) -> bool {
self.has_pending_frame_work()
|| self.state.draw_repasses.borrow_mut().has_pending_repass()
|| self.pointer_dispatch.has_pending()
|| self.focus_dispatch.invalidations.has_pending()
}
pub fn modal_node_may_be_live(&self, applier: &mut MemoryApplier, count: bool) -> bool {
self.modal_nodes.may_be_live(applier, count)
}
pub fn dismissable_popup_open(&self) -> bool {
self.hosted_popups.dismissable_open(false)
}
pub fn take_draw_repass_nodes_into(&self, output: &mut Vec<NodeId>) {
self.state
.draw_repasses
.borrow_mut()
.take_dirty_nodes_into(output);
}
pub fn take_retained_redraw_nodes_into(&self, output: &mut Vec<NodeId>) {
output.append(&mut self.state.retained_redraws.borrow_mut());
}
pub fn keep_retained_redraw_node(&self, node_id: NodeId) {
self.state.retained_redraws.borrow_mut().push(node_id);
}
pub fn append_z_order_changes(&self, output: &mut Vec<NodeId>) {
let mut changes = self.state.z_order_changes.borrow_mut();
if !changes.dirty_nodes.is_empty() {
output.extend(changes.dirty_nodes.drain());
}
}
pub fn take_layer_property_repass_nodes_into(&self, output: &mut Vec<NodeId>) {
self.state
.layer_property_repasses
.borrow_mut()
.take_dirty_nodes_into(output);
}
pub fn has_pending_layout_repasses(&self) -> bool {
self.state.layout_repasses.borrow_mut().has_pending_repass()
}
pub fn pending_layout_repass_nodes_snapshot(&self) -> Vec<NodeId> {
self.state
.layout_repasses
.borrow_mut()
.dirty_nodes_snapshot()
}
pub fn has_pending_measure_repasses(&self) -> bool {
self.state
.measure_repasses
.borrow_mut()
.has_pending_repass()
}
pub fn pending_measure_repass_nodes_snapshot(&self) -> Vec<NodeId> {
self.state
.measure_repasses
.borrow_mut()
.dirty_nodes_snapshot()
}
pub fn take_geometry_scene_nodes_into(&self, output: &mut GeometrySceneNodes) {
self.state
.geometry_scene_nodes
.borrow_mut()
.take_into(&mut output.reshaped);
self.state
.moved_scene_nodes
.borrow_mut()
.take_into(&mut output.moved);
}
pub(crate) fn record_geometry_scene_node(&self, node_id: NodeId) {
self.state.geometry_scene_nodes.borrow_mut().push(node_id);
}
pub(crate) fn note_geometry_scene_node(&self, node_id: NodeId, noted_in: &mut PackedU64) {
self.state
.geometry_scene_nodes
.borrow_mut()
.note(node_id, noted_in);
}
pub(crate) fn record_moved_scene_node(
&self,
node_id: NodeId,
placer: Option<NodeId>,
noted_in: &mut PackedU64,
) {
self.state.moved_scene_nodes.borrow_mut().note(
MovedSceneNode {
node: node_id,
placer,
},
noted_in,
);
}
pub(crate) fn placement_pass(&self) -> &crate::widgets::nodes::PlacementPass {
&self.placement_pass
}
pub fn take_layout_invalidation(&self) -> bool {
self.state.layout_invalidated.replace(false)
}
pub fn start_text_frame(&self) {
self.text.owe_pass();
}
pub fn take_render_invalidation(&self) -> bool {
self.state.render_invalidated.replace(false)
}
pub fn take_pointer_invalidation(&self) -> bool {
self.state.pointer_invalidated.replace(false)
}
pub fn take_focus_invalidation(&self) -> bool {
self.state.focus_invalidated.replace(false)
}
pub fn clear_transient_scroll_motion_contexts(&self) {
self.scroll_motion_contexts.clear_transient_after_frame();
}
pub fn has_pending_pointer_repasses(&self) -> bool {
self.pointer_dispatch.has_pending()
}
pub fn has_pending_focus_invalidations(&self) -> bool {
self.focus_dispatch.invalidations.has_pending()
}
pub fn active_focus_target(&self) -> Option<NodeId> {
self.focus_dispatch.active_focus_target()
}
pub fn has_pending_semantics_invalidations(&self) -> bool {
self.semantics_dispatch.has_pending()
}
pub fn has_focused_field(&self) -> bool {
let has_focus = self.text_field_focus.has_focused_field();
if !has_focus {
self.text_input_session.notify_focus_lost();
}
has_focus
}
pub fn tick_cursor_blink(&self) -> bool {
self.cursor_animation.next_blink_time().is_some()
&& self.cursor_animation.tick_blink(web_time::Instant::now())
}
}
impl Drop for AppContext {
fn drop(&mut self) {
let id = self.id;
crate::thread_state::try_with(|state| {
state.app_contexts.borrow_mut().remove(&id);
});
}
}
fn app_context_by_id(id: AppContextId) -> Option<Rc<AppContext>> {
crate::thread_state::try_with(|state| {
let contexts = &state.app_contexts;
let context = contexts.borrow().get(&id).cloned()?;
let Some(context) = context.upgrade() else {
contexts.borrow_mut().remove(&id);
return None;
};
Some(context)
})
.flatten()
}
#[cfg(test)]
fn app_context_registry_entry_count() -> usize {
crate::thread_state::try_with(|state| state.app_contexts.borrow().len()).unwrap_or_default()
}
pub(crate) fn with_app_context_by_id<R>(
id: AppContextId,
f: impl FnOnce(&Rc<AppContext>) -> R,
) -> Option<R> {
app_context_by_id(id).map(|context| f(&context))
}
pub(crate) fn current_app_context_id() -> AppContextId {
require_current_app_context("app context identity access").id
}
pub(crate) fn with_layout_node_registry_by_app_context<R>(
id: AppContextId,
f: impl FnOnce(&crate::widgets::nodes::LayoutNodeRegistryState) -> R,
) -> Option<R> {
with_app_context_by_id(id, |context| f(&context.layout_node_registry))
}
pub(crate) fn enter_app_context_by_id<R>(id: AppContextId, f: impl FnOnce() -> R) -> Option<R> {
with_app_context_by_id(id, |context| context.enter(f))
}
pub(crate) fn current_app_context() -> Option<Rc<AppContext>> {
crate::thread_state::try_with(|state| {
let mut stack = state.app_context_stack.borrow_mut();
loop {
let context = stack.last()?;
if let Some(context) = context.upgrade() {
return Some(context);
}
stack.pop();
}
})
.flatten()
}
#[doc(hidden)]
pub fn has_current_app_context() -> bool {
current_app_context().is_some()
}
pub(crate) fn require_current_app_context(operation: &str) -> Rc<AppContext> {
if let Some(context) = current_app_context() {
return context;
}
require_current_app_context_without_scope(operation)
}
fn require_current_app_context_without_scope(operation: &str) -> Rc<AppContext> {
panic!("{operation} requires an active AppContext")
}
fn with_render_state<R>(f: impl FnOnce(&RenderState) -> R) -> R {
let context = require_current_app_context("render state access");
f(&context.state)
}
pub(crate) fn with_text_service<R>(f: impl FnOnce(&crate::text::measure::TextService) -> R) -> R {
let context = require_current_app_context("text service access");
context.text.pay_owed_pass();
f(&context.text)
}
pub(crate) fn set_modifier_chain_trace(callback: Arc<ModifierChainTraceCallback>) -> AppContextId {
let context = require_current_app_context("modifier chain trace installation");
*context.modifier_chain_trace.borrow_mut() = Some(callback);
context.id
}
pub(crate) fn clear_modifier_chain_trace(context_id: AppContextId) {
let _ = with_app_context_by_id(context_id, |context| {
*context.modifier_chain_trace.borrow_mut() = None;
});
}
pub(crate) fn emit_modifier_chain_trace(nodes: &[crate::modifier::ModifierChainInspectorNode]) {
let Some(context) = current_app_context() else {
return;
};
let callback = context.modifier_chain_trace.borrow().clone();
if let Some(callback) = callback {
callback(nodes);
}
}
pub(crate) fn invalidate_layout_cache_epoch() {
require_current_app_context("layout cache epoch access").invalidate_layout_caches();
}
pub(crate) fn record_last_fling_velocity(velocity: f32) {
if let Some(context) = current_app_context() {
context.last_fling_velocity_bits.set(velocity.to_bits());
}
}
#[doc(hidden)]
pub fn debug_last_fling_velocity() -> f32 {
let context = require_current_app_context("fling velocity diagnostics access");
f32::from_bits(context.last_fling_velocity_bits.get())
}
#[doc(hidden)]
pub fn debug_reset_last_fling_velocity() {
let context = require_current_app_context("fling velocity diagnostics access");
context.last_fling_velocity_bits.set(0.0f32.to_bits());
}
pub(crate) fn with_scroll_motion_context_store<R>(
f: impl FnOnce(&crate::scroll::ScrollMotionContextStore) -> R,
) -> R {
let context = require_current_app_context("scroll motion context access");
f(&context.scroll_motion_contexts)
}
#[doc(hidden)]
pub fn clear_transient_scroll_motion_contexts() {
if let Some(context) = current_app_context() {
context.clear_transient_scroll_motion_contexts();
}
}
#[cfg(test)]
pub(crate) fn layout_frame_arena_placement_scratch_count() -> usize {
let context = require_current_app_context("layout frame arena access");
context
.layout_frame_arena
.borrow()
.available_placement_scratch_count()
}
pub(crate) fn with_current_layout_node_registry(
f: impl FnOnce(&crate::widgets::nodes::LayoutNodeRegistryState),
) -> AppContextId {
let context = require_current_app_context("layout node registry access");
f(&context.layout_node_registry);
context.id
}
pub(crate) fn with_layout_node_registry<R>(
f: impl FnOnce(&crate::widgets::nodes::LayoutNodeRegistryState) -> R,
) -> R {
let context = require_current_app_context("layout node registry access");
f(&context.layout_node_registry)
}
pub(crate) fn with_pointer_dispatch<R>(
f: impl FnOnce(&crate::dirty_node_queue::DirtyNodeQueue) -> R,
) -> R {
let context = require_current_app_context("pointer dispatch access");
f(&context.pointer_dispatch)
}
pub(crate) fn with_focus_dispatch<R>(
f: impl FnOnce(&crate::focus_dispatch::FocusInvalidationState) -> R,
) -> R {
let context = require_current_app_context("focus dispatch access");
f(&context.focus_dispatch)
}
pub(crate) fn with_focus_dispatch_by_app_context<R>(
id: AppContextId,
f: impl FnOnce(&crate::focus_dispatch::FocusInvalidationState) -> R,
) -> Option<R> {
with_app_context_by_id(id, |context| context.enter(|| f(&context.focus_dispatch)))
}
pub(crate) fn with_modal_state<R>(f: impl FnOnce(&crate::modal::ModalState) -> R) -> R {
let context = require_current_app_context("modal state access");
f(&context.modal)
}
pub(crate) fn with_current_modal_nodes<R>(
f: impl FnOnce(&crate::modal_nodes::ModalNodes) -> R,
) -> Option<R> {
current_app_context().map(|context| f(&context.modal_nodes))
}
pub(crate) fn with_current_semantics_layout_log<R>(
f: impl FnOnce(&crate::semantics_layout_log::SemanticsLayoutLog) -> R,
) -> Option<R> {
current_app_context().map(|context| f(&context.semantics_layout))
}
pub(crate) fn with_hosted_popup_registries<R>(
f: impl FnOnce(&crate::widgets::popup::HostedPopupRegistries) -> R,
) -> R {
let context = require_current_app_context("hosted popup registries access");
f(&context.hosted_popups)
}
pub(crate) fn with_semantics_dispatch<R>(
f: impl FnOnce(&crate::dirty_node_queue::DirtyNodeQueue) -> R,
) -> R {
let context = require_current_app_context("semantics dispatch access");
f(&context.semantics_dispatch)
}
pub(crate) fn with_semantics_dispatch_by_app_context(
id: AppContextId,
f: impl FnOnce(&crate::dirty_node_queue::DirtyNodeQueue),
) {
with_app_context_by_id(id, |context| f(&context.semantics_dispatch));
}
pub(crate) fn current_app_context_id_opt() -> Option<AppContextId> {
current_app_context().map(|context| context.id)
}
pub(crate) fn with_cursor_animation<R>(
f: impl FnOnce(&crate::cursor_animation::CursorAnimationState) -> R,
) -> R {
let context = require_current_app_context("cursor animation access");
f(&context.cursor_animation)
}
pub(crate) fn with_text_field_focus<R>(
f: impl FnOnce(&crate::text_field_focus::TextFieldFocusState) -> R,
) -> R {
let context = require_current_app_context("text field focus access");
f(&context.text_field_focus)
}
pub(crate) fn with_text_input_session<R>(
f: impl FnOnce(&crate::text_input_session::PlatformTextInputState) -> R,
) -> R {
let context = require_current_app_context("platform text input session access");
f(&context.text_input_session)
}
pub(crate) fn with_clipboard_session<R>(
f: impl FnOnce(&crate::clipboard_session::ClipboardSessionState) -> R,
) -> R {
let context = require_current_app_context("clipboard session access");
f(&context.clipboard_session)
}
pub(crate) fn with_pointer_icon_session<R>(
f: impl FnOnce(&crate::pointer_icon_session::PointerIconState) -> R,
) -> R {
let context = require_current_app_context("pointer icon session access");
f(&context.pointer_icon)
}
pub(crate) fn register_pointer_input_task(
task_id: u64,
task: Rc<crate::modifier::pointer_input::PointerInputTaskInner>,
) -> crate::modifier::pointer_input::PointerInputTaskOwner {
let context = require_current_app_context("pointer input task registration");
context.pointer_input_tasks.insert(task_id, task);
crate::modifier::pointer_input::PointerInputTaskOwner::App(context.id)
}
pub(crate) fn remove_pointer_input_task(
owner: crate::modifier::pointer_input::PointerInputTaskOwner,
task_id: u64,
) {
match owner {
crate::modifier::pointer_input::PointerInputTaskOwner::App(context_id) => {
let _ = with_app_context_by_id(context_id, |context| {
context.pointer_input_tasks.remove(task_id);
});
}
}
}
pub(crate) fn request_pointer_input_task_poll(
owner: crate::modifier::pointer_input::PointerInputTaskOwner,
task_id: u64,
) {
match owner {
crate::modifier::pointer_input::PointerInputTaskOwner::App(context_id) => {
let _ = with_app_context_by_id(context_id, |context| {
context.enter(|| {
context.pointer_input_tasks.request_poll(task_id, owner);
});
});
}
}
}
fn with_draw_observer<R>(f: impl FnOnce(&SnapshotStateObserver) -> R) -> R {
let context = require_current_app_context("draw observer access");
f(&context.draw_observer)
}
#[track_caller]
pub fn schedule_layout_repass(node_id: NodeId) {
if layout_repass_schedule_diagnostics_enabled_for(node_id) {
let caller = std::panic::Location::caller();
log::warn!(
"[layout-repass-schedule] node={} caller={}:{}:{}",
node_id,
caller.file(),
caller.line(),
caller.column()
);
}
with_render_state(|state| {
state.layout_repasses.borrow_mut().schedule_repass(node_id);
});
request_render_invalidation();
}
#[derive(Clone, Copy)]
enum LayoutRepassScheduleDiag {
Disabled,
All,
Node(NodeId),
}
fn layout_repass_schedule_diagnostics_enabled_for(node_id: NodeId) -> bool {
let mode = cranpose_core::DIAGNOSTICS
.layout_repass_schedule_diag
.with(|value| match value {
None => LayoutRepassScheduleDiag::Disabled,
Some("all") => LayoutRepassScheduleDiag::All,
Some(value) => value.parse::<NodeId>().map_or(
LayoutRepassScheduleDiag::Disabled,
LayoutRepassScheduleDiag::Node,
),
});
match mode {
LayoutRepassScheduleDiag::Disabled => false,
LayoutRepassScheduleDiag::All => true,
LayoutRepassScheduleDiag::Node(target) => target == node_id,
}
}
pub(crate) fn schedule_modifier_slices_repass(node_id: NodeId) {
with_render_state(|state| {
state
.modifier_slice_repasses
.borrow_mut()
.schedule_repass(node_id);
});
schedule_draw_repass(node_id);
}
pub fn schedule_draw_repass(node_id: NodeId) {
let context = require_current_app_context("render state access");
schedule_draw_repass_in_context(&context, node_id);
}
fn schedule_draw_repass_for_app_context(context_id: AppContextId, node_id: NodeId) {
let _ = with_app_context_by_id(context_id, |context| {
schedule_draw_repass_in_context(context, node_id);
});
}
fn schedule_draw_repass_in_context(context: &AppContext, node_id: NodeId) {
context
.state
.draw_repasses
.borrow_mut()
.schedule_repass(node_id);
context.state.render_invalidated.set(true);
}
fn schedule_layer_property_repass_for_app_context(context_id: AppContextId, node_id: NodeId) {
let _ = with_app_context_by_id(context_id, |context| {
schedule_layer_property_repass_in_context(context, node_id);
});
}
fn schedule_layer_property_repass_in_context(context: &AppContext, node_id: NodeId) {
context
.state
.layer_property_repasses
.borrow_mut()
.schedule_repass(node_id);
context.state.render_invalidated.set(true);
}
pub(crate) fn note_retained_redraw(node_id: NodeId) {
if let Some(context) = current_app_context() {
context.keep_retained_redraw_node(node_id);
}
}
pub fn has_pending_draw_repasses() -> bool {
with_render_state(|state| state.draw_repasses.borrow_mut().has_pending_repass())
}
pub fn take_draw_repass_nodes() -> Vec<NodeId> {
with_render_state(|state| state.draw_repasses.borrow_mut().take_dirty_nodes())
}
pub fn take_draw_repass_nodes_into(output: &mut Vec<NodeId>) {
require_current_app_context("render state access").take_draw_repass_nodes_into(output);
}
pub fn schedule_z_order_change(node_id: NodeId) {
with_render_state(|state| {
state.z_order_changes.borrow_mut().schedule_repass(node_id);
state.render_invalidated.set(true);
});
}
pub fn append_z_order_changes(output: &mut Vec<NodeId>) {
require_current_app_context("render state access").append_z_order_changes(output);
}
#[doc(hidden)]
pub fn has_pending_layer_property_repasses() -> bool {
with_render_state(|state| {
state
.layer_property_repasses
.borrow_mut()
.has_pending_repass()
})
}
#[doc(hidden)]
pub fn take_layer_property_repass_nodes_into(output: &mut Vec<NodeId>) {
require_current_app_context("render state access")
.take_layer_property_repass_nodes_into(output);
}
pub fn has_pending_layout_repasses() -> bool {
require_current_app_context("render state access").has_pending_layout_repasses()
}
pub fn pending_layout_repass_nodes_snapshot() -> Vec<NodeId> {
require_current_app_context("render state access").pending_layout_repass_nodes_snapshot()
}
pub fn take_layout_repass_nodes() -> Vec<NodeId> {
require_current_app_context("render state access").take_layout_repass_nodes()
}
pub fn schedule_measure_repass(node_id: NodeId) {
with_render_state(|state| {
state.measure_repasses.borrow_mut().schedule_repass(node_id);
});
request_render_invalidation();
}
pub fn has_pending_measure_repasses() -> bool {
require_current_app_context("render state access").has_pending_measure_repasses()
}
pub fn pending_measure_repass_nodes_snapshot() -> Vec<NodeId> {
require_current_app_context("render state access").pending_measure_repass_nodes_snapshot()
}
pub fn take_measure_repass_nodes() -> Vec<NodeId> {
require_current_app_context("render state access").take_measure_repass_nodes()
}
#[cfg(any(test, feature = "test-helpers"))]
pub(crate) fn take_modifier_slice_repass_nodes() -> Vec<NodeId> {
require_current_app_context("render state access").take_modifier_slice_repass_nodes()
}
pub(crate) fn record_geometry_scene_node(node_id: NodeId) {
require_current_app_context("render state access").record_geometry_scene_node(node_id);
}
pub fn take_geometry_scene_nodes() -> GeometrySceneNodes {
let mut nodes = GeometrySceneNodes::default();
take_geometry_scene_nodes_into(&mut nodes);
nodes
}
pub fn take_geometry_scene_nodes_into(output: &mut GeometrySceneNodes) {
require_current_app_context("render state access").take_geometry_scene_nodes_into(output);
}
pub fn request_render_invalidation() {
with_render_state(|state| state.render_invalidated.set(true));
}
pub fn take_render_invalidation() -> bool {
require_current_app_context("render state access").take_render_invalidation()
}
pub fn peek_render_invalidation() -> bool {
with_render_state(|state| state.render_invalidated.get())
}
pub fn request_pointer_invalidation() {
with_render_state(|state| state.pointer_invalidated.set(true));
}
pub fn take_pointer_invalidation() -> bool {
require_current_app_context("render state access").take_pointer_invalidation()
}
pub fn peek_pointer_invalidation() -> bool {
with_render_state(|state| state.pointer_invalidated.get())
}
pub fn request_focus_invalidation() {
with_render_state(|state| state.focus_invalidated.set(true));
}
pub fn take_focus_invalidation() -> bool {
require_current_app_context("render state access").take_focus_invalidation()
}
pub fn peek_focus_invalidation() -> bool {
with_render_state(|state| state.focus_invalidated.get())
}
pub fn request_layout_invalidation() {
with_render_state(|state| state.layout_invalidated.set(true));
}
pub fn take_layout_invalidation() -> bool {
require_current_app_context("render state access").take_layout_invalidation()
}
pub fn peek_layout_invalidation() -> bool {
with_render_state(|state| state.layout_invalidated.get())
}
#[cfg(any(test, feature = "test-helpers"))]
#[doc(hidden)]
pub fn reset_render_state_for_tests() {
let _ = take_draw_repass_nodes();
let mut layer_nodes = Vec::new();
take_layer_property_repass_nodes_into(&mut layer_nodes);
let _ = take_layout_repass_nodes();
let _ = take_modifier_slice_repass_nodes();
append_z_order_changes(&mut layer_nodes);
let _ = take_render_invalidation();
let _ = take_pointer_invalidation();
let _ = take_focus_invalidation();
let _ = take_layout_invalidation();
debug_reset_last_fling_velocity();
set_density(1.0);
set_font_scale(1.0);
let _ = take_layout_invalidation();
}
#[cfg(test)]
pub(crate) struct TestAppContextScope {
_scope: AppContextScope,
_context: Rc<AppContext>,
}
#[cfg(test)]
pub(crate) fn app_context_test_scope() -> TestAppContextScope {
let context = AppContext::new();
let scope = context.enter_scope();
context.enter(reset_render_state_for_tests);
TestAppContextScope {
_scope: scope,
_context: context,
}
}
#[cfg(test)]
pub(crate) struct RenderStateTestGuard {
_app_scope: TestAppContextScope,
_lock: std::sync::MutexGuard<'static, ()>,
}
#[cfg(test)]
pub(crate) fn render_state_test_guard() -> RenderStateTestGuard {
static TEST_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
let lock = match TEST_LOCK.get_or_init(|| Mutex::new(())).lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
RenderStateTestGuard {
_app_scope: app_context_test_scope(),
_lock: lock,
}
}
#[cfg(test)]
#[path = "tests/render_state_tests.rs"]
mod tests;