use crate::{
layout::MeasuredNode,
modifier::{
Modifier, ModifierChainHandle, ModifierLocalSource, ModifierLocalToken,
ModifierLocalsHandle, ModifierNodeSlices, Point, ResolvedModifierLocal, ResolvedModifiers,
Size,
},
};
use cranpose_core::{Node, NodeId};
use cranpose_foundation::{
InvalidationKind, ModifierInvalidation, NodeCapabilities, SemanticsConfiguration,
};
use cranpose_ui_layout::{Constraints, MeasurePolicy};
use std::any::TypeId;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::rc::Rc;
#[derive(Clone, Debug)]
pub struct LayoutState {
pub size: Size,
pub position: Point,
pub is_placed: bool,
pub measurement_constraints: Constraints,
pub content_offset: Point,
}
impl Default for LayoutState {
fn default() -> Self {
Self {
size: Size::default(),
position: Point::default(),
is_placed: false,
measurement_constraints: Constraints {
min_width: 0.0,
max_width: f32::INFINITY,
min_height: 0.0,
max_height: f32::INFINITY,
},
content_offset: Point::default(),
}
}
}
#[derive(Clone)]
struct MeasurementCacheEntry {
constraints: Constraints,
measured: Rc<MeasuredNode>,
}
#[derive(Clone, Copy, Debug)]
pub enum IntrinsicKind {
MinWidth(f32),
MaxWidth(f32),
MinHeight(f32),
MaxHeight(f32),
}
impl IntrinsicKind {
fn discriminant(&self) -> u8 {
match self {
IntrinsicKind::MinWidth(_) => 0,
IntrinsicKind::MaxWidth(_) => 1,
IntrinsicKind::MinHeight(_) => 2,
IntrinsicKind::MaxHeight(_) => 3,
}
}
fn value_bits(&self) -> u32 {
match self {
IntrinsicKind::MinWidth(value)
| IntrinsicKind::MaxWidth(value)
| IntrinsicKind::MinHeight(value)
| IntrinsicKind::MaxHeight(value) => value.to_bits(),
}
}
}
impl PartialEq for IntrinsicKind {
fn eq(&self, other: &Self) -> bool {
self.discriminant() == other.discriminant() && self.value_bits() == other.value_bits()
}
}
impl Eq for IntrinsicKind {}
impl Hash for IntrinsicKind {
fn hash<H: Hasher>(&self, state: &mut H) {
self.discriminant().hash(state);
self.value_bits().hash(state);
}
}
#[derive(Default)]
struct NodeCacheState {
epoch: u64,
measurements: Vec<MeasurementCacheEntry>,
intrinsics: Vec<(IntrinsicKind, f32)>,
}
#[derive(Clone, Default)]
pub(crate) struct LayoutNodeCacheHandles {
state: Rc<RefCell<NodeCacheState>>,
}
impl LayoutNodeCacheHandles {
pub(crate) fn clear(&self) {
let mut state = self.state.borrow_mut();
state.measurements.clear();
state.intrinsics.clear();
state.epoch = 0;
}
pub(crate) fn activate(&self, epoch: u64) {
let mut state = self.state.borrow_mut();
if state.epoch != epoch {
state.measurements.clear();
state.intrinsics.clear();
state.epoch = epoch;
}
}
pub(crate) fn epoch(&self) -> u64 {
self.state.borrow().epoch
}
pub(crate) fn get_measurement(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
let state = self.state.borrow();
state
.measurements
.iter()
.find(|entry| entry.constraints == constraints)
.map(|entry| Rc::clone(&entry.measured))
}
pub(crate) fn store_measurement(&self, constraints: Constraints, measured: Rc<MeasuredNode>) {
let mut state = self.state.borrow_mut();
if let Some(entry) = state
.measurements
.iter_mut()
.find(|entry| entry.constraints == constraints)
{
entry.measured = measured;
} else {
state.measurements.push(MeasurementCacheEntry {
constraints,
measured,
});
}
}
pub(crate) fn get_intrinsic(&self, kind: &IntrinsicKind) -> Option<f32> {
let state = self.state.borrow();
state
.intrinsics
.iter()
.find(|(stored_kind, _)| stored_kind == kind)
.map(|(_, value)| *value)
}
pub(crate) fn store_intrinsic(&self, kind: IntrinsicKind, value: f32) {
let mut state = self.state.borrow_mut();
if let Some((_, existing)) = state
.intrinsics
.iter_mut()
.find(|(stored_kind, _)| stored_kind == &kind)
{
*existing = value;
} else {
state.intrinsics.push((kind, value));
}
}
}
pub struct LayoutNode {
pub modifier: Modifier,
modifier_chain: ModifierChainHandle,
resolved_modifiers: ResolvedModifiers,
modifier_capabilities: NodeCapabilities,
modifier_child_capabilities: NodeCapabilities,
pub measure_policy: Rc<dyn MeasurePolicy>,
pub children: Vec<NodeId>,
cache: LayoutNodeCacheHandles,
needs_measure: Cell<bool>,
needs_layout: Cell<bool>,
needs_semantics: Cell<bool>,
needs_redraw: Cell<bool>,
needs_pointer_pass: Cell<bool>,
needs_focus_sync: Cell<bool>,
parent: Cell<Option<NodeId>>,
folded_parent: Cell<Option<NodeId>>,
id: Cell<Option<NodeId>>,
debug_modifiers: Cell<bool>,
is_virtual: bool,
virtual_children_count: Cell<usize>,
modifier_slices_snapshot: RefCell<Rc<ModifierNodeSlices>>,
modifier_slices_dirty: Cell<bool>,
layout_state: Rc<RefCell<LayoutState>>,
}
pub(crate) const RECYCLED_LAYOUT_NODE_POOL_LIMIT: usize = 128;
thread_local! {
static EMPTY_MEASURE_POLICY: Rc<dyn MeasurePolicy> =
Rc::new(crate::layout::policies::EmptyMeasurePolicy);
}
fn empty_measure_policy() -> Rc<dyn MeasurePolicy> {
EMPTY_MEASURE_POLICY.with(Rc::clone)
}
impl LayoutNode {
pub fn new(modifier: Modifier, measure_policy: Rc<dyn MeasurePolicy>) -> Self {
Self::new_with_virtual(modifier, measure_policy, false)
}
pub fn new_virtual() -> Self {
Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), true)
}
fn new_recycled_shell(is_virtual: bool) -> Self {
let mut shell =
Self::new_with_virtual(Modifier::empty(), empty_measure_policy(), is_virtual);
shell.needs_measure.set(false);
shell.needs_layout.set(false);
shell.needs_semantics.set(false);
shell.needs_redraw.set(false);
shell.needs_pointer_pass.set(false);
shell.needs_focus_sync.set(false);
shell.parent.set(None);
shell.folded_parent.set(None);
shell.id.set(None);
shell.debug_modifiers.set(false);
shell.virtual_children_count.set(0);
shell.cache = LayoutNodeCacheHandles::default();
shell.modifier_slices_snapshot = RefCell::new(Rc::default());
shell.modifier_slices_dirty = Cell::new(true);
shell.layout_state = Rc::new(RefCell::new(LayoutState::default()));
shell
}
fn new_with_virtual(
modifier: Modifier,
measure_policy: Rc<dyn MeasurePolicy>,
is_virtual: bool,
) -> Self {
let mut node = Self {
modifier,
modifier_chain: ModifierChainHandle::new(),
resolved_modifiers: ResolvedModifiers::default(),
modifier_capabilities: NodeCapabilities::default(),
modifier_child_capabilities: NodeCapabilities::default(),
measure_policy,
children: Vec::new(),
cache: LayoutNodeCacheHandles::default(),
needs_measure: Cell::new(true), needs_layout: Cell::new(true), needs_semantics: Cell::new(true), needs_redraw: Cell::new(true), needs_pointer_pass: Cell::new(false),
needs_focus_sync: Cell::new(false),
parent: Cell::new(None), folded_parent: Cell::new(None), id: Cell::new(None), debug_modifiers: Cell::new(false),
is_virtual,
virtual_children_count: Cell::new(0),
modifier_slices_snapshot: RefCell::new(Rc::default()),
modifier_slices_dirty: Cell::new(true),
layout_state: Rc::new(RefCell::new(LayoutState::default())),
};
node.sync_modifier_chain();
node
}
pub fn set_modifier(&mut self, modifier: Modifier) {
let modifier_changed = !self.modifier.structural_eq(&modifier);
self.modifier = modifier;
self.sync_modifier_chain();
if modifier_changed {
self.cache.clear();
self.request_semantics_update();
}
}
fn sync_modifier_chain(&mut self) {
let prev_caps = self.modifier_capabilities;
let start_parent = self.parent();
let mut resolver = move |token: ModifierLocalToken| {
resolve_modifier_local_from_parent_chain(start_parent, token)
};
self.modifier_chain
.set_debug_logging(self.debug_modifiers.get());
self.modifier_chain.set_node_id(self.id.get());
let modifier_local_invalidations = self
.modifier_chain
.update_with_resolver(&self.modifier, &mut resolver);
self.resolved_modifiers = self.modifier_chain.resolved_modifiers();
self.modifier_capabilities = self.modifier_chain.capabilities();
self.modifier_child_capabilities = self.modifier_chain.aggregate_child_capabilities();
self.update_modifier_slices_cache();
let mut invalidations = self.modifier_chain.take_invalidations();
invalidations.extend(modifier_local_invalidations);
self.dispatch_modifier_invalidations_with_prev(&invalidations, prev_caps);
self.refresh_registry_state();
}
fn update_modifier_slices_cache(&self) {
use crate::modifier::collect_modifier_slices_into;
let mut snapshot = self.modifier_slices_snapshot.borrow_mut();
collect_modifier_slices_into(self.modifier_chain.chain(), Rc::make_mut(&mut snapshot));
self.modifier_slices_dirty.set(false);
}
#[cfg(test)]
fn dispatch_modifier_invalidations(&self, invalidations: &[ModifierInvalidation]) {
self.dispatch_modifier_invalidations_with_prev(invalidations, NodeCapabilities::empty());
}
fn dispatch_modifier_invalidations_with_prev(
&self,
invalidations: &[ModifierInvalidation],
prev_caps: NodeCapabilities,
) {
let curr_caps = self.modifier_capabilities;
for invalidation in invalidations {
self.modifier_slices_dirty.set(true);
match invalidation.kind() {
InvalidationKind::Layout => {
if curr_caps.contains(NodeCapabilities::LAYOUT)
|| prev_caps.contains(NodeCapabilities::LAYOUT)
{
self.mark_needs_measure();
if let Some(id) = self.id.get() {
let inside_composition =
cranpose_core::composer_context::try_with_composer(|_| ())
.is_some();
if !inside_composition {
crate::schedule_layout_repass(id);
}
}
}
}
InvalidationKind::Draw => {
if curr_caps.contains(NodeCapabilities::DRAW)
|| prev_caps.contains(NodeCapabilities::DRAW)
{
self.mark_needs_redraw();
}
}
InvalidationKind::PointerInput => {
if curr_caps.contains(NodeCapabilities::POINTER_INPUT)
|| prev_caps.contains(NodeCapabilities::POINTER_INPUT)
{
self.mark_needs_pointer_pass();
crate::request_pointer_invalidation();
if let Some(id) = self.id.get() {
crate::schedule_pointer_repass(id);
}
}
}
InvalidationKind::Semantics => {
self.request_semantics_update();
}
InvalidationKind::Focus => {
if curr_caps.contains(NodeCapabilities::FOCUS)
|| prev_caps.contains(NodeCapabilities::FOCUS)
{
self.mark_needs_focus_sync();
crate::request_focus_invalidation();
if let Some(id) = self.id.get() {
crate::schedule_focus_invalidation(id);
}
}
}
}
}
}
pub fn set_measure_policy(&mut self, policy: Rc<dyn MeasurePolicy>) {
if !Rc::ptr_eq(&self.measure_policy, &policy) {
self.measure_policy = policy;
self.cache.clear();
self.mark_needs_measure();
if let Some(id) = self.id.get() {
cranpose_core::bubble_measure_dirty_in_composer(id);
}
}
}
pub fn mark_needs_measure(&self) {
self.needs_measure.set(true);
self.needs_layout.set(true);
}
pub fn mark_needs_layout(&self) {
self.needs_layout.set(true);
}
pub fn mark_needs_redraw(&self) {
self.needs_redraw.set(true);
if let Some(id) = self.id.get() {
crate::schedule_draw_repass(id);
}
crate::request_render_invalidation();
}
pub fn needs_measure(&self) -> bool {
self.needs_measure.get()
}
pub fn needs_layout(&self) -> bool {
self.needs_layout.get()
}
pub fn mark_needs_semantics(&self) {
self.needs_semantics.set(true);
}
pub(crate) fn clear_needs_semantics(&self) {
self.needs_semantics.set(false);
}
pub fn needs_semantics(&self) -> bool {
self.needs_semantics.get()
}
pub fn needs_redraw(&self) -> bool {
self.needs_redraw.get()
}
pub fn clear_needs_redraw(&self) {
self.needs_redraw.set(false);
}
fn request_semantics_update(&self) {
let already_dirty = self.needs_semantics.replace(true);
if already_dirty {
return;
}
if let Some(id) = self.id.get() {
cranpose_core::queue_semantics_invalidation(id);
}
}
pub(crate) fn clear_needs_measure(&self) {
self.needs_measure.set(false);
}
pub(crate) fn clear_needs_layout(&self) {
self.needs_layout.set(false);
}
pub fn mark_needs_pointer_pass(&self) {
self.needs_pointer_pass.set(true);
}
pub fn needs_pointer_pass(&self) -> bool {
self.needs_pointer_pass.get()
}
pub fn clear_needs_pointer_pass(&self) {
self.needs_pointer_pass.set(false);
}
pub fn mark_needs_focus_sync(&self) {
self.needs_focus_sync.set(true);
}
pub fn needs_focus_sync(&self) -> bool {
self.needs_focus_sync.get()
}
pub fn clear_needs_focus_sync(&self) {
self.needs_focus_sync.set(false);
}
pub fn set_node_id(&mut self, id: NodeId) {
if let Some(existing) = self.id.replace(Some(id)) {
unregister_layout_node(existing);
}
register_layout_node(id, self);
self.refresh_registry_state();
self.modifier_chain.set_node_id(Some(id));
}
pub fn node_id(&self) -> Option<NodeId> {
self.id.get()
}
pub fn set_parent(&self, parent: NodeId) {
self.folded_parent.set(Some(parent));
self.parent.set(Some(parent));
self.refresh_registry_state();
}
pub fn clear_parent(&self) {
self.folded_parent.set(None);
self.parent.set(None);
self.refresh_registry_state();
}
pub fn parent(&self) -> Option<NodeId> {
self.parent.get()
}
pub fn folded_parent(&self) -> Option<NodeId> {
self.folded_parent.get()
}
pub fn is_virtual(&self) -> bool {
self.is_virtual
}
pub(crate) fn cache_handles(&self) -> LayoutNodeCacheHandles {
self.cache.clone()
}
pub fn resolved_modifiers(&self) -> ResolvedModifiers {
self.resolved_modifiers
}
pub fn modifier_capabilities(&self) -> NodeCapabilities {
self.modifier_capabilities
}
pub fn modifier_child_capabilities(&self) -> NodeCapabilities {
self.modifier_child_capabilities
}
pub fn set_debug_modifiers(&mut self, enabled: bool) {
self.debug_modifiers.set(enabled);
self.modifier_chain.set_debug_logging(enabled);
}
pub fn debug_modifiers_enabled(&self) -> bool {
self.debug_modifiers.get()
}
pub fn modifier_locals_handle(&self) -> ModifierLocalsHandle {
self.modifier_chain.modifier_locals_handle()
}
pub fn has_layout_modifier_nodes(&self) -> bool {
self.modifier_capabilities
.contains(NodeCapabilities::LAYOUT)
}
pub fn has_draw_modifier_nodes(&self) -> bool {
self.modifier_capabilities.contains(NodeCapabilities::DRAW)
}
pub fn has_pointer_input_modifier_nodes(&self) -> bool {
self.modifier_capabilities
.contains(NodeCapabilities::POINTER_INPUT)
}
pub fn has_semantics_modifier_nodes(&self) -> bool {
self.modifier_capabilities
.contains(NodeCapabilities::SEMANTICS)
}
pub fn has_focus_modifier_nodes(&self) -> bool {
self.modifier_capabilities.contains(NodeCapabilities::FOCUS)
}
fn refresh_registry_state(&self) {
if let Some(id) = self.id.get() {
let parent = self.parent();
let capabilities = self.modifier_child_capabilities();
let modifier_locals = self.modifier_locals_handle();
LAYOUT_NODE_REGISTRY.with(|registry| {
if let Some(entry) = registry.borrow_mut().get_mut(&id) {
entry.parent = parent;
entry.modifier_child_capabilities = capabilities;
entry.modifier_locals = modifier_locals;
}
});
}
}
pub fn modifier_slices_snapshot(&self) -> Rc<ModifierNodeSlices> {
if self.modifier_slices_dirty.get() {
self.update_modifier_slices_cache();
}
self.modifier_slices_snapshot.borrow().clone()
}
pub fn layout_state(&self) -> LayoutState {
self.layout_state.borrow().clone()
}
pub fn measured_size(&self) -> Size {
self.layout_state.borrow().size
}
pub fn position(&self) -> Point {
self.layout_state.borrow().position
}
pub fn is_placed(&self) -> bool {
self.layout_state.borrow().is_placed
}
pub fn set_measured_size(&self, size: Size) {
let mut state = self.layout_state.borrow_mut();
state.size = size;
}
pub fn set_position(&self, position: Point) {
let mut state = self.layout_state.borrow_mut();
state.position = position;
state.is_placed = true;
}
pub fn set_measurement_constraints(&self, constraints: Constraints) {
self.layout_state.borrow_mut().measurement_constraints = constraints;
}
pub fn set_content_offset(&self, offset: Point) {
self.layout_state.borrow_mut().content_offset = offset;
}
pub fn clear_placed(&self) {
self.layout_state.borrow_mut().is_placed = false;
}
pub fn semantics_configuration(&self) -> Option<SemanticsConfiguration> {
crate::modifier::collect_semantics_from_chain(self.modifier_chain.chain())
}
pub(crate) fn modifier_chain(&self) -> &ModifierChainHandle {
&self.modifier_chain
}
pub fn with_text_field_modifier_mut<R>(
&mut self,
f: impl FnMut(&mut crate::TextFieldModifierNode) -> R,
) -> Option<R> {
self.modifier_chain.with_text_field_modifier_mut(f)
}
pub fn layout_state_handle(&self) -> Rc<RefCell<LayoutState>> {
self.layout_state.clone()
}
}
impl Clone for LayoutNode {
fn clone(&self) -> Self {
let mut node = Self {
modifier: self.modifier.clone(),
modifier_chain: ModifierChainHandle::new(),
resolved_modifiers: ResolvedModifiers::default(),
modifier_capabilities: self.modifier_capabilities,
modifier_child_capabilities: self.modifier_child_capabilities,
measure_policy: self.measure_policy.clone(),
children: self.children.clone(),
cache: self.cache.clone(),
needs_measure: Cell::new(self.needs_measure.get()),
needs_layout: Cell::new(self.needs_layout.get()),
needs_semantics: Cell::new(self.needs_semantics.get()),
needs_redraw: Cell::new(self.needs_redraw.get()),
needs_pointer_pass: Cell::new(self.needs_pointer_pass.get()),
needs_focus_sync: Cell::new(self.needs_focus_sync.get()),
parent: Cell::new(self.parent.get()),
folded_parent: Cell::new(self.folded_parent.get()),
id: Cell::new(None),
debug_modifiers: Cell::new(self.debug_modifiers.get()),
is_virtual: self.is_virtual,
virtual_children_count: Cell::new(self.virtual_children_count.get()),
modifier_slices_snapshot: RefCell::new(Rc::default()),
modifier_slices_dirty: Cell::new(true),
layout_state: self.layout_state.clone(),
};
node.sync_modifier_chain();
node
}
}
impl Node for LayoutNode {
fn mount(&mut self) {
let (chain, mut context) = self.modifier_chain.chain_and_context_mut();
chain.repair_chain();
chain.attach_nodes(&mut *context);
}
fn unmount(&mut self) {
self.modifier_chain.chain_mut().detach_nodes();
}
fn set_node_id(&mut self, id: NodeId) {
LayoutNode::set_node_id(self, id);
}
fn insert_child(&mut self, child: NodeId) {
if self.children.contains(&child) {
return;
}
if is_virtual_node(child) {
let count = self.virtual_children_count.get();
self.virtual_children_count.set(count + 1);
}
self.children.push(child);
self.cache.clear();
self.mark_needs_measure();
}
fn remove_child(&mut self, child: NodeId) {
let before = self.children.len();
self.children.retain(|&id| id != child);
if self.children.len() < before {
if is_virtual_node(child) {
let count = self.virtual_children_count.get();
if count > 0 {
self.virtual_children_count.set(count - 1);
}
}
self.cache.clear();
self.mark_needs_measure();
}
}
fn move_child(&mut self, from: usize, to: usize) {
if from == to || from >= self.children.len() {
return;
}
let child = self.children.remove(from);
let target = to.min(self.children.len());
self.children.insert(target, child);
self.cache.clear();
self.mark_needs_measure();
}
fn update_children(&mut self, children: &[NodeId]) {
self.children.clear();
self.children.extend_from_slice(children);
self.cache.clear();
self.mark_needs_measure();
}
fn children(&self) -> Vec<NodeId> {
self.children.clone()
}
fn collect_children_into(&self, out: &mut smallvec::SmallVec<[NodeId; 8]>) {
out.clear();
out.extend(self.children.iter().copied());
}
fn on_attached_to_parent(&mut self, parent: NodeId) {
self.set_parent(parent);
}
fn on_removed_from_parent(&mut self) {
self.clear_parent();
}
fn parent(&self) -> Option<NodeId> {
self.parent.get()
}
fn mark_needs_layout(&self) {
self.needs_layout.set(true);
}
fn needs_layout(&self) -> bool {
self.needs_layout.get()
}
fn mark_needs_measure(&self) {
self.needs_measure.set(true);
self.needs_layout.set(true);
}
fn needs_measure(&self) -> bool {
self.needs_measure.get()
}
fn mark_needs_semantics(&self) {
self.needs_semantics.set(true);
}
fn needs_semantics(&self) -> bool {
self.needs_semantics.get()
}
fn set_parent_for_bubbling(&mut self, parent: NodeId) {
self.parent.set(Some(parent));
}
fn recycle_key(&self) -> Option<TypeId> {
Some(TypeId::of::<Self>())
}
fn recycle_pool_limit(&self) -> Option<usize> {
Some(RECYCLED_LAYOUT_NODE_POOL_LIMIT)
}
fn prepare_for_recycle(&mut self) {
*self = Self::new_recycled_shell(self.is_virtual);
}
fn rehouse_for_recycle(&self) -> Option<Box<dyn cranpose_core::Node>> {
Some(Box::new(Self::new_recycled_shell(self.is_virtual)))
}
fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn cranpose_core::Node>> {
let mut previous = std::mem::replace(self, Self::new_recycled_shell(self.is_virtual));
let node_id = previous.id.replace(None);
let parent = previous.parent.get();
let folded_parent = previous.folded_parent.get();
let debug_modifiers = previous.debug_modifiers.get();
let needs_measure = previous.needs_measure.get();
let needs_layout = previous.needs_layout.get();
let needs_semantics = previous.needs_semantics.get();
let needs_redraw = previous.needs_redraw.get();
let needs_pointer_pass = previous.needs_pointer_pass.get();
let needs_focus_sync = previous.needs_focus_sync.get();
let virtual_children_count = previous.virtual_children_count.get();
let children = previous.children.to_vec();
let modifier = previous.modifier.rehouse_for_live_compaction();
let measure_policy = previous.measure_policy.clone();
let layout_state = previous.layout_state.clone();
previous.modifier_chain.chain_mut().detach_nodes();
let mut compact = Self::new_with_virtual(modifier, measure_policy, previous.is_virtual);
compact.children = children;
compact.parent.set(parent);
compact.folded_parent.set(folded_parent);
compact.id.set(node_id);
compact.debug_modifiers.set(debug_modifiers);
compact.needs_measure.set(needs_measure);
compact.needs_layout.set(needs_layout);
compact.needs_semantics.set(needs_semantics);
compact.needs_redraw.set(needs_redraw);
compact.needs_pointer_pass.set(needs_pointer_pass);
compact.needs_focus_sync.set(needs_focus_sync);
compact.virtual_children_count.set(virtual_children_count);
compact.layout_state = layout_state;
compact.sync_modifier_chain();
if let Some(id) = node_id {
register_layout_node(id, &compact);
}
Some(Box::new(compact))
}
}
impl Drop for LayoutNode {
fn drop(&mut self) {
if let Some(id) = self.id.get() {
unregister_layout_node(id);
}
}
}
thread_local! {
static LAYOUT_NODE_REGISTRY: RefCell<HashMap<NodeId, LayoutNodeRegistryEntry>> =
RefCell::new(HashMap::new());
static VIRTUAL_NODE_ID_COUNTER: std::sync::atomic::AtomicUsize = const { std::sync::atomic::AtomicUsize::new(0xC0000000) };
}
const MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY: usize = 128;
#[cfg(test)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
struct LayoutNodeRegistryDebugStats {
len: usize,
capacity: usize,
}
struct LayoutNodeRegistryEntry {
parent: Option<NodeId>,
modifier_child_capabilities: NodeCapabilities,
modifier_locals: ModifierLocalsHandle,
is_virtual: bool,
}
pub(crate) fn register_layout_node(id: NodeId, node: &LayoutNode) {
LAYOUT_NODE_REGISTRY.with(|registry| {
registry.borrow_mut().insert(
id,
LayoutNodeRegistryEntry {
parent: node.parent(),
modifier_child_capabilities: node.modifier_child_capabilities(),
modifier_locals: node.modifier_locals_handle(),
is_virtual: node.is_virtual(),
},
);
});
}
pub(crate) fn unregister_layout_node(id: NodeId) {
LAYOUT_NODE_REGISTRY.with(|registry| {
let mut registry = registry.borrow_mut();
registry.remove(&id);
let should_shrink = (registry.len() <= MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY
&& registry.capacity() > MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
|| registry.capacity()
> registry
.len()
.max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY)
.saturating_mul(4);
if should_shrink {
let retained = registry
.len()
.max(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY);
let mut rebuilt = HashMap::new();
rebuilt.reserve(retained);
rebuilt.extend(registry.drain());
*registry = rebuilt;
}
});
}
#[cfg(test)]
fn layout_node_registry_stats() -> LayoutNodeRegistryDebugStats {
LAYOUT_NODE_REGISTRY.with(|registry| {
let registry = registry.borrow();
LayoutNodeRegistryDebugStats {
len: registry.len(),
capacity: registry.capacity(),
}
})
}
pub(crate) fn is_virtual_node(id: NodeId) -> bool {
LAYOUT_NODE_REGISTRY.with(|registry| {
registry
.borrow()
.get(&id)
.map(|entry| entry.is_virtual)
.unwrap_or(false)
})
}
pub(crate) fn allocate_virtual_node_id() -> NodeId {
use std::sync::atomic::Ordering;
VIRTUAL_NODE_ID_COUNTER.with(|counter| counter.fetch_add(1, Ordering::Relaxed))
}
fn resolve_modifier_local_from_parent_chain(
start: Option<NodeId>,
token: ModifierLocalToken,
) -> Option<ResolvedModifierLocal> {
let mut current = start;
while let Some(parent_id) = current {
let (next_parent, resolved) = LAYOUT_NODE_REGISTRY.with(|registry| {
let registry = registry.borrow();
if let Some(entry) = registry.get(&parent_id) {
let resolved = if entry
.modifier_child_capabilities
.contains(NodeCapabilities::MODIFIER_LOCALS)
{
entry
.modifier_locals
.borrow()
.resolve(token)
.map(|value| value.with_source(ModifierLocalSource::Ancestor))
} else {
None
};
(entry.parent, resolved)
} else {
(None, None)
}
});
if let Some(value) = resolved {
return Some(value);
}
current = next_parent;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use cranpose_ui_graphics::Size as GeometrySize;
use cranpose_ui_layout::{Measurable, MeasureResult};
use std::rc::Rc;
#[derive(Default)]
struct TestMeasurePolicy;
impl MeasurePolicy for TestMeasurePolicy {
fn measure(
&self,
_measurables: &[Box<dyn Measurable>],
_constraints: Constraints,
) -> MeasureResult {
MeasureResult::new(
GeometrySize {
width: 0.0,
height: 0.0,
},
Vec::new(),
)
}
fn min_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
0.0
}
fn max_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
0.0
}
fn min_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
0.0
}
fn max_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
0.0
}
}
fn fresh_node() -> LayoutNode {
LayoutNode::new(Modifier::empty(), Rc::new(TestMeasurePolicy))
}
#[test]
fn modifier_slices_cache_reuses_unique_snapshot_allocation() {
let mut node = fresh_node();
let snapshot = node.modifier_slices_snapshot();
let snapshot_ptr = Rc::as_ptr(&snapshot);
drop(snapshot);
node.set_modifier(Modifier::empty().padding(4.0));
let updated = node.modifier_slices_snapshot();
assert_eq!(Rc::as_ptr(&updated), snapshot_ptr);
}
#[test]
fn modifier_slices_cache_preserves_live_snapshot_isolation() {
let mut node = fresh_node();
let old_snapshot = node.modifier_slices_snapshot();
let old_snapshot_ptr = Rc::as_ptr(&old_snapshot);
node.set_modifier(Modifier::empty().padding(4.0));
let updated = node.modifier_slices_snapshot();
assert_ne!(Rc::as_ptr(&updated), old_snapshot_ptr);
assert_eq!(old_snapshot.draw_commands().len(), 0);
}
#[test]
fn layout_node_registry_retains_warm_capacity_after_large_cleanup() {
let nodes: Vec<_> = (0..2048)
.map(|_| {
let id = allocate_virtual_node_id();
let node = fresh_node();
register_layout_node(id, &node);
(id, node)
})
.collect();
for (id, _) in &nodes {
unregister_layout_node(*id);
}
let stats = layout_node_registry_stats();
assert_eq!(stats.len, 0);
assert!(
(MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY
..=MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY.saturating_mul(2))
.contains(&stats.capacity),
"registry warm capacity {} fell outside expected retained range {}..={}",
stats.capacity,
MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY,
MIN_RETAINED_LAYOUT_NODE_REGISTRY_CAPACITY.saturating_mul(2),
);
}
fn invalidation(kind: InvalidationKind) -> ModifierInvalidation {
ModifierInvalidation::new(kind, NodeCapabilities::for_invalidation(kind))
}
#[test]
fn layout_invalidation_requires_layout_capability() {
let mut node = fresh_node();
node.clear_needs_measure();
node.clear_needs_layout();
node.modifier_capabilities = NodeCapabilities::DRAW;
node.modifier_child_capabilities = node.modifier_capabilities;
node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Layout)]);
assert!(!node.needs_measure());
assert!(!node.needs_layout());
}
#[test]
fn semantics_configuration_reflects_modifier_state() {
let mut node = fresh_node();
node.set_modifier(Modifier::empty().semantics(|config| {
config.content_description = Some("greeting".into());
config.is_clickable = true;
}));
let config = node
.semantics_configuration()
.expect("expected semantics configuration");
assert_eq!(config.content_description.as_deref(), Some("greeting"));
assert!(config.is_clickable);
}
#[test]
fn layout_invalidation_marks_flags_when_capability_present() {
let _guard = crate::render_state::render_state_test_guard();
crate::reset_render_state_for_tests();
let mut node = fresh_node();
node.id.set(Some(11));
node.clear_needs_measure();
node.clear_needs_layout();
node.modifier_capabilities = NodeCapabilities::LAYOUT;
node.modifier_child_capabilities = node.modifier_capabilities;
node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Layout)]);
assert!(node.needs_measure());
assert!(node.needs_layout());
assert_eq!(crate::take_layout_repass_nodes(), vec![11]);
assert!(crate::take_layout_invalidation());
}
#[test]
fn layout_invalidation_skips_repass_while_composing() {
let _guard = crate::render_state::render_state_test_guard();
crate::reset_render_state_for_tests();
let node = Rc::new(RefCell::new(fresh_node()));
{
let mut node = node.borrow_mut();
node.id.set(Some(17));
node.clear_needs_measure();
node.clear_needs_layout();
node.modifier_capabilities = NodeCapabilities::LAYOUT;
node.modifier_child_capabilities = node.modifier_capabilities;
}
let node_for_composition = Rc::clone(&node);
let _composition = crate::run_test_composition(move || {
node_for_composition
.borrow()
.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Layout)]);
});
let node = node.borrow();
assert!(node.needs_measure());
assert!(node.needs_layout());
assert!(crate::take_layout_repass_nodes().is_empty());
assert!(!crate::take_layout_invalidation());
}
#[test]
fn draw_invalidation_marks_redraw_flag_when_capable() {
let mut node = fresh_node();
node.clear_needs_measure();
node.clear_needs_layout();
node.modifier_capabilities = NodeCapabilities::DRAW;
node.modifier_child_capabilities = node.modifier_capabilities;
node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Draw)]);
assert!(node.needs_redraw());
assert!(!node.needs_layout());
}
#[test]
fn semantics_invalidation_sets_semantics_flag_only() {
let mut node = fresh_node();
node.clear_needs_measure();
node.clear_needs_layout();
node.clear_needs_semantics();
node.modifier_capabilities = NodeCapabilities::SEMANTICS;
node.modifier_child_capabilities = node.modifier_capabilities;
node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Semantics)]);
assert!(node.needs_semantics());
assert!(!node.needs_measure());
assert!(!node.needs_layout());
}
#[test]
fn pointer_invalidation_requires_pointer_capability() {
let mut node = fresh_node();
node.clear_needs_pointer_pass();
node.modifier_capabilities = NodeCapabilities::DRAW;
node.modifier_child_capabilities = node.modifier_capabilities;
node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::PointerInput)]);
assert!(!node.needs_pointer_pass());
}
#[test]
fn pointer_invalidation_marks_flag_and_requests_queue() {
let mut node = fresh_node();
node.clear_needs_pointer_pass();
node.modifier_capabilities = NodeCapabilities::POINTER_INPUT;
node.modifier_child_capabilities = node.modifier_capabilities;
node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::PointerInput)]);
assert!(node.needs_pointer_pass());
}
#[test]
fn focus_invalidation_requires_focus_capability() {
let mut node = fresh_node();
node.clear_needs_focus_sync();
node.modifier_capabilities = NodeCapabilities::DRAW;
node.modifier_child_capabilities = node.modifier_capabilities;
crate::take_focus_invalidation();
node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Focus)]);
assert!(!node.needs_focus_sync());
assert!(!crate::take_focus_invalidation());
}
#[test]
fn focus_invalidation_marks_flag_and_requests_queue() {
let mut node = fresh_node();
node.clear_needs_focus_sync();
node.modifier_capabilities = NodeCapabilities::FOCUS;
node.modifier_child_capabilities = node.modifier_capabilities;
crate::take_focus_invalidation();
node.dispatch_modifier_invalidations(&[invalidation(InvalidationKind::Focus)]);
assert!(node.needs_focus_sync());
assert!(crate::take_focus_invalidation());
}
#[test]
fn set_modifier_marks_semantics_dirty() {
let mut node = fresh_node();
node.clear_needs_semantics();
node.set_modifier(Modifier::empty().semantics(|config| {
config.is_clickable = true;
}));
assert!(node.needs_semantics());
}
#[test]
fn modifier_child_capabilities_reflect_chain_head() {
let mut node = fresh_node();
node.set_modifier(Modifier::empty().padding(4.0));
assert!(
node.modifier_child_capabilities()
.contains(NodeCapabilities::LAYOUT),
"padding should introduce layout capability"
);
}
}