use std::{
cell::{Cell, RefCell},
rc::{Rc, Weak},
};
use cranpose_core::{MemoryApplier, Node, NodeError, NodeId, PackedU64};
use cranpose_ui_layout::{AlignmentLines, Constraints, MeasurePolicy, ParentData, Placement};
use super::{
LayoutBuilderState, LayoutRuntimeState, MeasuredChild, MeasuredNode, core::Measurable,
layout_child_measurable::LayoutChildMeasurable,
};
use crate::{
modifier::Point,
render_state::AppContext,
subcompose_layout::SubcomposeLayoutNode,
widgets::nodes::{IntrinsicKind, LayoutNode, LayoutParts, LayoutState, NodeCache},
};
#[expect(
clippy::multiple_inherent_impl,
reason = "the bind of a parent's child records in their file"
)]
impl LayoutBuilderState {
pub(super) fn bind_layout_children(
self: &Rc<Self>,
applier: &mut MemoryApplier,
(node_id, parts): (NodeId, &Rc<LayoutParts>),
child_ids: &[NodeId],
pass: ChildPass,
) -> Result<(), NodeError> {
let mut runtime_state = parts.runtime_state().borrow_mut();
let Some(children) =
runtime_state.bind_children(self, (node_id, parts), pass, child_ids.len())
else {
return Ok(());
};
let mut bound = 0;
for &child_id in child_ids {
if self.bind_layout_child(applier, children, bound, child_id)? {
bound += 1;
}
}
children.truncate(bound);
Ok(())
}
fn bind_layout_child(
&self,
applier: &mut MemoryApplier,
children: &mut LayoutChildren,
position: usize,
child_id: NodeId,
) -> Result<bool, NodeError> {
let unchanged = children.ids.get(position) == Some(&child_id)
&& children.states[position].rebind_unchanged(self);
if unchanged {
return Ok(true);
}
let bound = applier.with_node::<LayoutNode, _>(child_id, |child| {
children.state_at(position, child_id).bind(
LayoutChildBinding {
parts: child.layout_parts(),
layout_node: true,
parent_data: Some(child.parent_data()),
dirty: child.needs_layout() || child.needs_measure(),
descendant_dirty: Node::descendant_needs_layout(child),
},
self,
);
});
match bound {
Ok(()) => Ok(true),
Err(NodeError::TypeMismatch { .. }) => {
match applier.with_node::<SubcomposeLayoutNode, _>(child_id, |child| {
children.state_at(position, child_id).bind(
LayoutChildBinding {
parts: child.layout_parts(),
layout_node: false,
parent_data: Some(child.parent_data()),
dirty: child.needs_layout() || child.needs_measure(),
descendant_dirty: Node::descendant_needs_layout(child),
},
self,
);
}) {
Ok(()) => Ok(true),
Err(NodeError::TypeMismatch { .. } | NodeError::Missing { .. }) => Ok(false),
Err(err) => Err(err),
}
}
Err(NodeError::Missing { .. }) => Ok(false),
Err(err) => Err(err),
}
}
}
#[derive(Default)]
pub(super) struct LayoutChildFrame {
pub(super) builder: RefCell<Option<Rc<LayoutBuilderState>>>,
pub(super) error: RefCell<Option<NodeError>>,
pass: Cell<ChildPass>,
parent: Cell<Option<NodeId>>,
placed_with_z_index: Cell<u32>,
parent_parts: Weak<LayoutParts>,
}
#[derive(Clone, Copy, Default, PartialEq, Eq)]
pub(super) enum ChildPass {
#[default]
Measure,
Intrinsics,
}
impl LayoutChildFrame {
pub(super) fn bind(&self, builder: &Rc<LayoutBuilderState>, parent: NodeId, pass: ChildPass) {
self.error.borrow_mut().take();
*self.builder.borrow_mut() = Some(Rc::clone(builder));
self.pass.set(pass);
self.parent.set(Some(parent));
}
fn count_placed_z_index(&self, before: f32, after: f32) {
let count = match (before != 0.0, after != 0.0) {
(false, true) => self.placed_with_z_index.get() + 1,
(true, false) => self.placed_with_z_index.get() - 1,
_ => return,
};
self.placed_with_z_index.set(count);
if count <= 1
&& let Some(parent) = self.parent_parts.upgrade()
{
parent
.layout_state()
.borrow_mut()
.set_places_children_with_z_index(count > 0);
}
}
pub(super) fn measures(&self) -> bool {
self.pass.get() == ChildPass::Measure
}
pub(super) fn unbind(&self) {
self.builder.borrow_mut().take();
}
fn record_error(&self, err: NodeError) {
if self.builder.borrow().is_none() {
return;
}
let mut slot = self.error.borrow_mut();
if slot.is_none() {
*slot = Some(err);
}
}
}
pub(super) struct LayoutChildBinding<'a> {
pub(super) parts: &'a Rc<LayoutParts>,
pub(super) layout_node: bool,
pub(super) parent_data: Option<ParentData>,
pub(super) dirty: bool,
pub(super) descendant_dirty: bool,
}
pub(super) struct LayoutChildMeasureState {
pub(super) node_id: NodeId,
pub(super) frame: Rc<LayoutChildFrame>,
pub(super) parts: Cell<Option<Rc<LayoutParts>>>,
pub(super) layout_node: Cell<bool>,
revision: Cell<u32>,
pub(super) cache_epoch: Cell<PackedU64>,
pub(super) force_remeasure: Cell<bool>,
pub(super) parent_data: RefCell<Option<ParentData>>,
pub(super) measured_constraints: Cell<Option<Constraints>>,
pub(super) read_intrinsics: Cell<bool>,
pub(super) measured: Cell<Option<Rc<MeasuredNode>>>,
pub(super) last_position: Cell<Option<Point>>,
placed_z_index: Cell<f32>,
}
pub(super) fn read_cell<T: Default, R>(cell: &Cell<T>, read: impl FnOnce(&T) -> R) -> R {
let value = cell.take();
let result = read(&value);
cell.set(value);
result
}
impl LayoutChildMeasureState {
pub(super) fn placement_position(&self, placement: Option<&Placement>) -> Point {
placement
.map(|placement| Point {
x: placement.x,
y: placement.y,
})
.or_else(|| self.last_position.get())
.unwrap_or_default()
}
pub(super) fn new(node_id: NodeId, frame: Rc<LayoutChildFrame>) -> Rc<Self> {
Rc::new(Self {
node_id,
frame,
parts: Cell::new(None),
layout_node: Cell::new(false),
revision: Cell::new(0),
cache_epoch: Cell::default(),
force_remeasure: Cell::new(true),
parent_data: RefCell::new(None),
measured_constraints: Cell::new(None),
read_intrinsics: Cell::new(false),
measured: Cell::new(None),
last_position: Cell::new(None),
placed_z_index: Cell::new(0.0),
})
}
pub(super) fn bind(&self, binding: LayoutChildBinding<'_>, pass: &LayoutBuilderState) {
let cache = binding.parts.cache();
self.bind_cache(cache, binding.dirty, binding.descendant_dirty, pass);
self.revision.set(cache.revision());
self.note_z_index(binding.parts.layout_state().borrow().z_index());
let parts = self.parts.take();
let shared = parts
.as_ref()
.is_some_and(|current| Rc::ptr_eq(current, binding.parts));
self.parts.set(if shared {
parts
} else {
Some(Rc::clone(binding.parts))
});
self.layout_node.set(binding.layout_node);
*self.parent_data.borrow_mut() = binding.parent_data;
}
pub(super) fn reads_parent_data_change(
&self,
policy: &dyn MeasurePolicy,
data: &ParentData,
) -> bool {
self.parent_data
.borrow()
.as_ref()
.is_none_or(|bound| policy.reads_parent_data_change(bound, data))
}
fn rebind_unchanged(&self, pass: &LayoutBuilderState) -> bool {
self.layout_node.get()
&& read_cell(&self.parts, |parts| {
parts.as_deref().is_some_and(|parts| {
let cache = parts.cache();
let unchanged = cache.revision() == self.revision.get();
if unchanged {
let dirt = cache.dirt();
let dirty = dirt.measure || dirt.layout;
self.bind_cache(cache, dirty, dirt.descendant.layout, pass);
}
unchanged
})
})
}
fn bind_cache(
&self,
cache: &NodeCache,
dirty: bool,
descendant_dirty: bool,
pass: &LayoutBuilderState,
) {
let child_epoch = cache.epoch();
let stale = dirty || child_epoch < pass.cache_floor;
let cache_epoch = if stale { pass.cache_epoch } else { child_epoch };
cache.activate(cache_epoch);
self.cache_epoch.set(PackedU64::new(cache_epoch));
self.force_remeasure.set(stale || descendant_dirty);
if self.frame.measures() {
self.measured.take();
self.last_position.set(None);
self.measured_constraints.set(None);
self.read_intrinsics.set(false);
}
}
pub(super) fn with_pass_layout_state(&self, f: impl FnOnce(&mut LayoutState, &AppContext)) {
if !self.layout_node.get() {
return;
}
let builder = self.frame.builder.borrow();
let Some(builder) = builder.as_ref() else {
return;
};
read_cell(&self.parts, |parts| {
if let Some(parts) = parts {
f(&mut parts.layout_state().borrow_mut(), &builder.context);
}
});
}
pub(super) fn place_at(&self, position: Point, z_index: f32) {
if self.last_position.get() != Some(position) {
self.place_retained(position);
}
if self.placed_z_index.get().to_bits() != z_index.to_bits() {
self.note_z_index(z_index);
read_cell(&self.parts, |parts| {
if let Some(parts) = parts {
parts.layout_state().borrow_mut().set_z_index(z_index);
}
});
}
}
fn note_z_index(&self, z_index: f32) {
let before = self.placed_z_index.replace(z_index);
self.frame.count_placed_z_index(before, z_index);
}
pub(super) fn place_retained(&self, position: Point) {
self.last_position.set(Some(position));
let builder = self.frame.builder.borrow();
let Some(builder) = builder.as_ref() else {
return;
};
read_cell(&self.parts, |parts| {
if let Some(parts) = parts {
parts.layout_state().borrow_mut().place(
position,
self.frame.parent.get(),
&builder.context,
);
}
});
}
pub(super) fn layout_intrinsic(&self, kind: IntrinsicKind) -> Option<f32> {
let builder = self.frame.builder.borrow();
let builder = builder.as_ref()?;
match builder.layout_node_intrinsic(self.node_id, kind) {
Ok(value) => value,
Err(err) => {
self.frame.record_error(err);
Some(0.0)
}
}
}
fn perform_measure(&self, constraints: Constraints) -> Result<Rc<MeasuredNode>, NodeError> {
let builder = self.frame.builder.borrow();
let builder = builder.as_ref().ok_or(NodeError::MissingContext {
id: self.node_id,
reason: "layout child applier not configured",
})?;
builder.measure_node(self.node_id, constraints)
}
pub(super) fn measure_cached(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
if self.frame.measures() {
self.measured_constraints.set(Some(constraints));
}
let parts = read_cell(&self.parts, Clone::clone);
let cache = parts.as_deref().map(LayoutParts::cache);
if let Some(cache) = cache {
cache.activate(self.cache_epoch.get().get());
}
if !self.force_remeasure.get()
&& let Some(cached) = cache.and_then(|cache| cache.get_measurement(constraints))
{
if self.frame.measures() {
self.with_pass_layout_state(LayoutState::clear_placed);
}
return Some(cached);
}
match self.perform_measure(constraints) {
Ok(measured) => {
self.force_remeasure.set(false);
if let Some(cache) = cache {
cache.store_measurement(constraints, Rc::clone(&measured));
}
Some(measured)
}
Err(err) => {
self.frame.record_error(err);
None
}
}
}
}
impl Drop for LayoutChildMeasureState {
fn drop(&mut self) {
self.frame
.count_placed_z_index(self.placed_z_index.get(), 0.0);
}
}
#[expect(
clippy::multiple_inherent_impl,
reason = "a node's children records in their file"
)]
impl LayoutRuntimeState {
pub(super) fn child_states(&self) -> &[Rc<LayoutChildMeasureState>] {
self.children
.as_deref()
.map_or(&[], |children| children.states.as_slice())
}
pub(super) fn child_measurables(&self) -> &[Box<dyn Measurable>] {
self.children
.as_deref()
.map_or(&[], |children| children.measurables.as_slice())
}
pub(super) fn bind_children(
&mut self,
builder: &Rc<LayoutBuilderState>,
(parent, parts): (NodeId, &Rc<LayoutParts>),
pass: ChildPass,
count: usize,
) -> Option<&mut LayoutChildren> {
if count == 0 && self.children.is_none() {
return None;
}
let children = self
.children
.get_or_insert_with(|| Box::new(LayoutChildren::with_capacity(count, parts)));
children.frame.bind(builder, parent, pass);
Some(children)
}
pub(super) fn unbind_children(&self) {
if let Some(children) = &self.children {
children.frame.unbind();
}
}
pub(super) fn take_child_error(&self) -> Option<NodeError> {
self.children
.as_ref()
.and_then(|children| children.frame.error.borrow_mut().take())
}
pub(super) fn measured_children(
&self,
placements: &[Placement],
placement_indices: &[usize],
content_offset: Point,
measured_children: &mut Vec<MeasuredChild>,
) {
let child_states = self.child_states();
measured_children.reserve_exact(child_states.len());
for (index, child_state) in child_states.iter().enumerate() {
let Some(measured) = child_state.measured.take() else {
continue;
};
let placement =
placement_for_child(placements, placement_indices, index, child_state.node_id);
let base_position = child_state.placement_position(placement);
let position = Point {
x: base_position.x + measured.offset.x,
y: base_position.y + measured.offset.y,
};
if let Some(placement) = placement {
child_state.place_at(position, placement.z_index);
}
measured_children.push(MeasuredChild::new(
&measured,
Point {
x: content_offset.x + base_position.x,
y: content_offset.y + base_position.y,
},
));
}
}
}
pub(super) fn inherited_alignment_lines(
child_states: &[Rc<LayoutChildMeasureState>],
placements: &[Placement],
placement_indices: &mut Vec<usize>,
) -> AlignmentLines {
placement_indices.clear();
let in_child_order = placements.len() == child_states.len()
&& placements
.iter()
.zip(child_states)
.all(|(placement, child)| placement.node_id == child.node_id);
if !in_child_order {
placement_indices.extend(0..placements.len());
placement_indices.sort_unstable_by_key(|&index| (placements[index].node_id, index));
}
let mut lines = AlignmentLines::default();
for (index, child) in child_states.iter().enumerate() {
let placement = placement_for_child(placements, placement_indices, index, child.node_id);
read_cell(&child.measured, |measured| {
if let Some(measured) = measured {
lines.merge(
measured
.alignment_lines_for_parent()
.translated(child.placement_position(placement).y),
);
}
});
}
lines
}
pub(super) fn placement_for_child<'a>(
placements: &'a [Placement],
indices: &[usize],
child_index: usize,
node_id: NodeId,
) -> Option<&'a Placement> {
let index = if indices.is_empty() {
child_index
} else {
let first = indices.partition_point(|&index| placements[index].node_id < node_id);
*indices.get(first)?
};
placements
.get(index)
.filter(|placement| placement.node_id == node_id)
}
pub(super) struct LayoutChildren {
pub(super) ids: Vec<NodeId>,
states: Vec<Rc<LayoutChildMeasureState>>,
measurables: Vec<Box<dyn Measurable>>,
frame: Rc<LayoutChildFrame>,
}
impl LayoutChildren {
fn with_capacity(count: usize, parent_parts: &Rc<LayoutParts>) -> Self {
Self {
ids: Vec::with_capacity(count),
states: Vec::with_capacity(count),
measurables: Vec::with_capacity(count),
frame: Rc::new(LayoutChildFrame {
parent_parts: Rc::downgrade(parent_parts),
..LayoutChildFrame::default()
}),
}
}
#[inline]
fn state_at(&mut self, position: usize, child_id: NodeId) -> &LayoutChildMeasureState {
if self.ids.get(position) != Some(&child_id) {
self.bind_at(position, child_id);
}
&self.states[position]
}
fn truncate(&mut self, len: usize) {
self.ids.truncate(len);
self.states.truncate(len);
self.measurables.truncate(len);
}
#[inline(never)]
fn bind_at(&mut self, position: usize, child_id: NodeId) {
let from = match self.ids[position..].iter().position(|&id| id == child_id) {
Some(offset) => position + offset,
None => {
let state = LayoutChildMeasureState::new(child_id, Rc::clone(&self.frame));
self.ids.push(child_id);
self.states.push(Rc::clone(&state));
self.measurables
.push(Box::new(LayoutChildMeasurable::new(state)));
self.ids.len() - 1
}
};
self.ids.swap(position, from);
self.states.swap(position, from);
self.measurables.swap(position, from);
}
}