use std::{
any::TypeId,
cell::{Cell, RefCell},
rc::Rc,
};
use cranpose_core::{Node, NodeId, NodeIdCell};
use cranpose_foundation::{
InvalidationKind, ModifierInvalidation, ModifierInvalidations, NodeCapabilities,
SemanticsConfiguration,
};
use cranpose_ui_layout::MeasurePolicy;
use super::layout_invalidation_diag::log_layout_invalidation_dispatch;
#[cfg(test)]
use crate::layout::LayoutRuntimeDebugStats;
use crate::{
layout::LayoutRuntimeState,
modifier::{
InPlaceUpdate, Modifier, ModifierChainHandle, ModifierLocalToken, ModifierLocalsHandle,
ModifierNodeSlices, Point, ResolvedModifiers, Size,
},
render_state::AppContext,
widgets::nodes::{
ChildPlacement, LayoutDirt, LayoutParts, LayoutState, NodeCache,
layout_node_registry::{
register_layout_node, resolve_modifier_local_from_parent_chain, unregister_layout_node,
},
},
};
#[repr(C)]
pub struct LayoutNode {
parts: Rc<LayoutParts>,
pub(crate) children: Vec<NodeId>,
pub(crate) measure_policy: Rc<dyn MeasurePolicy>,
density: crate::density::Density,
is_virtual: bool,
needs_semantics: Cell<bool>,
descendant_needs_semantics: Cell<bool>,
needs_redraw: Cell<bool>,
needs_pointer_pass: Cell<bool>,
needs_focus_sync: Cell<bool>,
modifier_slices_dirty: Cell<bool>,
debug_modifiers: Cell<bool>,
id: NodeIdCell,
parent: NodeIdCell,
folded_parent: NodeIdCell,
modifier_slices_snapshot: crate::modifier::SlicesSnapshot,
semantics_reach: Cell<Option<cranpose_foundation::SemanticsReach>>,
owner_context_id: Cell<Option<crate::render_state::AppContextId>>,
modifier_chain: ModifierChainHandle,
pub(crate) modifier: Modifier,
#[cfg(feature = "inspection")]
pub source_trace: Rc<[cranpose_core::source_trace::SourceLocation]>,
}
pub(crate) fn empty_measure_policy() -> Rc<dyn MeasurePolicy> {
crate::thread_state::with(|state| {
Rc::clone(
state
.empty_measure_policy
.get_or_init(|| Rc::new(crate::layout::policies::EmptyMeasurePolicy)),
)
})
}
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.parts.cache().set_dirt(LayoutDirt::CLEAN);
shell.needs_semantics.set(false);
shell.descendant_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.owner_context_id.set(None);
shell.debug_modifiers.set(false);
shell.modifier_slices_dirty = Cell::new(true);
shell
}
fn new_with_virtual(
modifier: Modifier,
measure_policy: Rc<dyn MeasurePolicy>,
is_virtual: bool,
) -> Self {
let parts = Rc::new(LayoutParts::new(LayoutRuntimeState::new(Rc::clone(
&measure_policy,
))));
let mut node = Self {
#[cfg(feature = "inspection")]
source_trace: cranpose_core::source_trace::current_source_trace(),
modifier,
modifier_chain: ModifierChainHandle::detached(),
measure_policy,
density: crate::density::Density::default(),
children: Vec::new(),
needs_semantics: Cell::new(true),
descendant_needs_semantics: Cell::new(false),
semantics_reach: Cell::new(None),
needs_redraw: Cell::new(true),
needs_pointer_pass: Cell::new(false),
needs_focus_sync: Cell::new(false),
parent: NodeIdCell::new(None),
folded_parent: NodeIdCell::new(None),
id: NodeIdCell::new(None),
owner_context_id: Cell::new(None),
debug_modifiers: Cell::new(false),
is_virtual,
modifier_slices_snapshot: crate::modifier::SlicesSnapshot::default(),
modifier_slices_dirty: Cell::new(true),
parts,
};
node.sync_modifier_chain(None);
node
}
pub fn children(&self) -> &[NodeId] {
&self.children
}
pub fn modifier(&self) -> Modifier {
self.modifier.clone()
}
pub fn set_modifier(&mut self, modifier: Modifier) {
#[cfg(feature = "inspection")]
{
let trace = cranpose_core::source_trace::current_source_trace();
if !trace.is_empty() {
self.source_trace = trace;
}
}
if self.modifier == modifier
&& !self
.modifier_capabilities()
.contains(NodeCapabilities::MODIFIER_LOCALS)
{
return;
}
let update = self.modifier.in_place_update(&modifier);
let modifier_changed = update.is_some() || !self.modifier.structural_eq(&modifier);
self.modifier = modifier;
self.sync_modifier_chain(update);
if modifier_changed {
self.parts.cache().clear();
self.request_semantics_update();
}
}
fn sync_modifier_chain(&mut self, in_place: Option<InPlaceUpdate>) {
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());
let mut context = self.modifier_chain.context(self.id.get());
let in_place = in_place.is_some_and(|update| {
self.modifier_chain
.update_elements_in_place(&self.modifier, update, &mut context)
});
let modifier_local_invalidations = if in_place {
ModifierInvalidations::new()
} else {
self.modifier_chain
.update_with_resolver(&self.modifier, &mut resolver, &mut context)
};
if prev_caps.contains(NodeCapabilities::WINDOW_ROOT)
!= self
.modifier_capabilities()
.contains(NodeCapabilities::WINDOW_ROOT)
{
self.note_semantics_layout_change();
}
if !in_place {
self.forget_semantics_reach();
}
let keep_slices = in_place && !self.modifier_slices_dirty.get();
if !keep_slices {
self.modifier_slices_dirty.set(true);
}
let mut invalidations = context.take_invalidations();
invalidations.extend(modifier_local_invalidations);
self.dispatch_modifier_invalidations_with_prev(&invalidations, prev_caps, keep_slices);
if !in_place {
self.refresh_registry_state();
}
}
fn update_modifier_slices_cache(&self) {
self.modifier_slices_snapshot.collect(
self.modifier_chain.chain(),
Some(&self.parts),
self.density.density(),
);
self.modifier_slices_dirty.set(false);
}
#[cfg(test)]
pub(crate) fn modifier_slices_ready(&self) -> bool {
!self.modifier_slices_dirty.get()
}
pub(crate) fn mark_modifier_slices_dirty(&self) {
self.modifier_slices_dirty.set(true);
}
#[cfg(test)]
fn dispatch_modifier_invalidations(&self, invalidations: &[ModifierInvalidation]) {
self.dispatch_modifier_invalidations_with_prev(
invalidations,
NodeCapabilities::empty(),
false,
);
}
fn dispatch_modifier_invalidations_with_prev(
&self,
invalidations: &[ModifierInvalidation],
prev_caps: NodeCapabilities,
keep_slices: bool,
) {
let curr_caps = self.modifier_capabilities();
for invalidation in invalidations {
if !keep_slices {
self.modifier_slices_dirty.set(true);
}
let has_capability =
|capability| curr_caps.contains(capability) || prev_caps.contains(capability);
match invalidation.kind() {
InvalidationKind::Layout => {
if has_capability(NodeCapabilities::LAYOUT) {
self.mark_needs_measure();
if let Some(id) = self.id.get() {
log_layout_invalidation_dispatch(
id,
invalidation,
curr_caps,
prev_caps,
&self.modifier,
);
let inside_composition =
cranpose_core::composer_context::try_with_composer(|_| ())
.is_some();
if inside_composition {
cranpose_core::bubble_measure_dirty_in_composer(id);
} else {
crate::schedule_layout_repass(id);
}
}
}
}
InvalidationKind::Draw => {
if has_capability(NodeCapabilities::DRAW)
|| invalidation.capabilities().contains(NodeCapabilities::DRAW)
{
self.mark_needs_redraw();
}
}
InvalidationKind::PointerInput => {
if has_capability(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 has_capability(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 density(&self) -> crate::density::Density {
self.density
}
pub fn set_density(&mut self, density: crate::density::Density) {
if self.density != density {
self.density = density;
self.parts.cache().clear();
self.modifier_slices_dirty.set(true);
self.mark_needs_measure();
}
}
pub fn set_layout_direction(&mut self, direction: crate::LayoutDirection) {
if self.modifier_chain.set_layout_direction(direction) {
self.parts.cache().clear();
self.modifier_slices_dirty.set(true);
self.mark_needs_measure();
}
}
pub fn set_measure_policy(&mut self, policy: Rc<dyn MeasurePolicy>) {
if !Rc::ptr_eq(&self.measure_policy, &policy) {
self.measure_policy = policy;
self.parts.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.parts.cache().mark_needs_measure();
}
pub fn mark_needs_layout(&self) {
self.parts.cache().mark_needs_layout();
}
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.parts.cache().dirt().measure
}
pub fn needs_layout(&self) -> bool {
self.parts.cache().dirt().layout
}
pub fn mark_needs_semantics(&self) {
self.needs_semantics.set(true);
self.forget_semantics_reach();
}
pub(crate) fn clear_needs_semantics(&self) {
self.needs_semantics.set(false);
self.descendant_needs_semantics.set(false);
}
pub fn needs_semantics(&self) -> bool {
self.needs_semantics.get() || self.descendant_needs_semantics.get()
}
pub(crate) fn semantics_changed(&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.parts.cache().clear_needs_measure();
}
pub(crate) fn clear_needs_layout(&self) {
self.parts.cache().clear_layout_dirt();
}
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 self.needs_redraw.get() {
crate::render_state::note_retained_redraw(id);
}
let previous = self.id.replace(Some(id));
if let Some(existing) = previous
&& let Some(owner_context_id) = self.owner_context_id.take()
{
unregister_layout_node(owner_context_id, existing);
}
self.parts.layout_state().borrow_mut().set_node_id(id);
let owner_context_id = register_layout_node(id, self);
self.owner_context_id.set(Some(owner_context_id));
let mut context = self.modifier_chain.context(Some(id));
self.modifier_chain.move_to_node(previous, &mut context);
let invalidations = context.take_invalidations();
self.dispatch_modifier_invalidations_with_prev(
&invalidations,
NodeCapabilities::empty(),
false,
);
self.modifier_slices_dirty.set(true);
}
pub fn node_id(&self) -> Option<NodeId> {
self.id.get()
}
pub fn set_parent(&self, parent: NodeId) {
if self.parent.get() == Some(parent) && self.folded_parent.get() == Some(parent) {
return;
}
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(crate) fn cache(&self) -> &NodeCache {
self.parts.cache()
}
pub fn resolved_modifiers(&self) -> ResolvedModifiers {
self.modifier_chain.resolved_modifiers()
}
pub(crate) fn parent_data(&self) -> cranpose_ui_layout::ParentData {
self.modifier_chain.parent_data()
}
pub fn modifier_capabilities(&self) -> NodeCapabilities {
self.modifier_chain.capabilities()
}
pub fn is_window_root(&self) -> bool {
self.modifier_capabilities()
.contains(NodeCapabilities::WINDOW_ROOT)
}
pub fn modifier_child_capabilities(&self) -> NodeCapabilities {
self.modifier_chain.aggregate_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) -> Option<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), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
let parent = self.parent();
let capabilities = self.modifier_child_capabilities();
let modifier_locals = self.modifier_locals_handle();
let _ = crate::render_state::with_layout_node_registry_by_app_context(
owner_context_id,
|registry| {
registry.update_entry(id, parent, capabilities, 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.get()
}
pub fn layout_state(&self) -> LayoutState {
*self.parts.layout_state().borrow()
}
pub fn measured_size(&self) -> Size {
self.parts.layout_state().borrow().size()
}
pub fn position(&self) -> Point {
self.parts.layout_state().borrow().position()
}
pub fn is_placed(&self) -> bool {
self.parts.layout_state().borrow().is_placed()
}
pub fn set_measured_size(&self, size: Size, context: &AppContext) {
self.parts
.layout_state()
.borrow_mut()
.set_size(size, context);
}
pub(crate) fn place(&self, at: ChildPlacement, placer: Option<NodeId>, context: &AppContext) {
self.parts
.layout_state()
.borrow_mut()
.place_child(at, placer, context);
}
pub fn set_content_offset(&self, offset: Point) {
self.parts
.layout_state()
.borrow_mut()
.set_content_offset(offset);
}
pub fn clear_placed(&self, context: &AppContext) {
self.parts.layout_state().borrow_mut().clear_placed(context);
}
fn note_semantics_layout_change(&self) {
if let Ok(mut state) = self.parts.layout_state().try_borrow_mut() {
state.note_semantics_change();
}
}
pub fn semantics_configuration(&self) -> Option<SemanticsConfiguration> {
crate::modifier::collect_semantics_from_chain(self.modifier_chain.chain())
}
fn forget_semantics_reach(&self) {
self.semantics_reach.set(None);
crate::modal_nodes::reach_changed(self.id.get());
}
pub fn semantics_reach(&self) -> cranpose_foundation::SemanticsReach {
if !self
.modifier_capabilities()
.contains(NodeCapabilities::SEMANTICS)
{
return cranpose_foundation::SemanticsReach::default();
}
if let Some(reach) = self.semantics_reach.get() {
return reach;
}
let reach = crate::modifier::semantics_reach_of_chain(self.modifier_chain.chain());
self.semantics_reach.set(Some(reach));
reach
}
pub(crate) fn modifier_chain(&self) -> &ModifierChainHandle {
&self.modifier_chain
}
pub fn with_layout_state<R>(&self, read: impl FnOnce(&LayoutState) -> R) -> R {
read(&self.parts.layout_state().borrow())
}
pub(crate) fn layout_parts(&self) -> &Rc<LayoutParts> {
&self.parts
}
pub(crate) fn layout_runtime_state(&self) -> &RefCell<LayoutRuntimeState> {
self.parts.runtime_state()
}
#[cfg(test)]
pub(crate) fn layout_runtime_debug_stats(&self) -> LayoutRuntimeDebugStats {
self.parts.runtime_state().borrow().debug_stats()
}
}
impl Clone for LayoutNode {
fn clone(&self) -> Self {
let mut node = Self {
#[cfg(feature = "inspection")]
source_trace: Rc::clone(&self.source_trace),
modifier: self.modifier.clone(),
modifier_chain: ModifierChainHandle::for_layout_direction(
self.modifier_chain.layout_direction(),
),
measure_policy: Rc::clone(&self.measure_policy),
density: self.density,
children: self.children.clone(),
needs_semantics: Cell::new(self.needs_semantics.get()),
descendant_needs_semantics: Cell::new(self.descendant_needs_semantics.get()),
semantics_reach: Cell::new(None),
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: NodeIdCell::new(self.parent.get()),
folded_parent: NodeIdCell::new(self.folded_parent.get()),
id: NodeIdCell::new(None),
owner_context_id: Cell::new(None),
debug_modifiers: Cell::new(self.debug_modifiers.get()),
is_virtual: self.is_virtual,
modifier_slices_snapshot: crate::modifier::SlicesSnapshot::default(),
modifier_slices_dirty: Cell::new(true),
parts: Rc::clone(&self.parts),
};
node.sync_modifier_chain(None);
node
}
}
impl Node for LayoutNode {
fn mount(&mut self) {
self.modifier_chain.remount(self.id.get());
crate::modal_nodes::reach_changed(self.id.get());
}
fn unmount(&mut self) {
self.modifier_chain.chain_mut().detach_nodes();
}
fn set_node_id(&mut self, id: NodeId) {
Self::set_node_id(self, id);
}
fn insert_child(&mut self, child: NodeId) -> bool {
if self.children.contains(&child) {
return false;
}
self.children.push(child);
self.parts.cache().clear();
self.mark_needs_measure();
self.note_semantics_layout_change();
true
}
fn remove_child(&mut self, child: NodeId) -> bool {
let before = self.children.len();
self.children.retain(|&id| id != child);
let removed = self.children.len() < before;
if removed {
self.parts.cache().clear();
self.mark_needs_measure();
self.note_semantics_layout_change();
}
removed
}
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.parts.cache().clear();
self.mark_needs_measure();
self.note_semantics_layout_change();
}
fn update_children(&mut self, children: &[NodeId]) {
self.children.clear();
self.children.extend_from_slice(children);
self.parts.cache().clear();
self.mark_needs_measure();
self.note_semantics_layout_change();
}
fn collect_children_into(&self, out: &mut smallvec::SmallVec<[NodeId; 8]>) {
out.clear();
out.extend_from_slice(&self.children);
}
fn for_each_child(&self, f: &mut dyn FnMut(NodeId)) {
for &child in &self.children {
f(child);
}
}
fn owned_child_index(&self, child: NodeId) -> Option<usize> {
self.children.iter().position(|&id| id == child)
}
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::mark_needs_layout(self);
}
fn needs_layout(&self) -> bool {
Self::needs_layout(self)
}
fn mark_needs_measure(&self) {
Self::mark_needs_measure(self);
}
fn needs_measure(&self) -> bool {
Self::needs_measure(self)
}
fn mark_descendant_needs_layout(&self, measure: bool) {
self.parts.cache().mark_descendant_needs_layout(measure);
}
fn descendant_needs_layout(&self) -> bool {
self.parts.cache().dirt().descendant.layout
}
fn is_virtual(&self) -> bool {
self.is_virtual
}
fn descendant_needs_measure(&self) -> bool {
self.parts.cache().dirt().descendant.measure
}
fn mark_needs_semantics(&self) {
self.needs_semantics.set(true);
self.forget_semantics_reach();
}
fn mark_descendant_needs_semantics(&self) {
self.descendant_needs_semantics.set(true);
}
fn needs_semantics(&self) -> bool {
Self::needs_semantics(self)
}
fn set_parent_for_bubbling(&mut self, parent: NodeId) {
if self.parent.get().is_none() {
self.parent.set(Some(parent));
}
}
fn recycle_key(&self) -> Option<TypeId> {
Some(TypeId::of::<Self>())
}
fn recycle_pool_limit(&self) -> Option<usize> {
Some(super::node_pool_limit::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 Node>> {
Some(Box::new(Self::new_recycled_shell(self.is_virtual)))
}
fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn Node>> {
let mut node = std::mem::replace(self, Self::new_recycled_shell(self.is_virtual));
node.children.shrink_to_fit();
Some(Box::new(node))
}
}
impl Drop for LayoutNode {
fn drop(&mut self) {
self.parts.cache().advance_revision();
if let (Some(id), Some(owner_context_id)) = (self.id.get(), self.owner_context_id.get()) {
unregister_layout_node(owner_context_id, id);
}
}
}
#[cfg(test)]
#[path = "tests/layout_node_tests.rs"]
mod tests;