use std::rc::Rc;
use cranpose_core::{MemoryApplier, Node, NodeError, NodeId};
use cranpose_ui_layout::Constraints;
use smallvec::SmallVec;
use super::{
ForParent, LayoutBuilderState, MeasuredNode,
live_layout_node::{LiveLayoutNode, read_live_layout_node},
};
use crate::widgets::nodes::{LayoutDirt, LayoutNode, NodeCache};
type Cleared = Vec<(NodeId, LayoutDirt)>;
#[derive(Clone, Copy)]
struct Start {
node: NodeId,
parent: NodeId,
constraints: Constraints,
changed_itself: bool,
dirty: NodeId,
}
struct DirtyNode {
node: NodeId,
dirt: LayoutDirt,
window_root: bool,
parent: Option<NodeId>,
own_constraints: Option<Constraints>,
}
struct PlanWalk {
dirty: NodeId,
start: NodeId,
changed_itself: bool,
own: Option<Constraints>,
first_parent: Option<NodeId>,
constraints: Option<Constraints>,
child: NodeId,
}
impl PlanWalk {
fn new(dirty: &DirtyNode) -> Self {
Self {
dirty: dirty.node,
start: dirty.node,
changed_itself: dirty.dirt.measure || dirty.dirt.layout,
own: dirty.own_constraints,
first_parent: None,
constraints: None,
child: dirty.node,
}
}
fn visit(&mut self, id: NodeId, ancestor: &LiveLayoutNode<'_>, cleared: &mut Cleared) -> Step {
if ancestor.is_virtual() {
return Step::SlotHost(ancestor.parent());
}
if ancestor.is_window_root() || needs_layout_itself(ancestor.cache()) {
return Step::WalkDown;
}
if self.child == self.start {
self.constraints = match ancestor {
LiveLayoutNode::Layout(node) => given_constraints(node, self.child),
LiveLayoutNode::Subcompose(_) => match self.own.filter(|_| !self.changed_itself) {
Some(constraints) => Some(constraints),
None => {
return Step::StartHere(
ancestor.parent(),
own_constraints(ancestor.cache()),
);
}
},
};
}
cleared.push((id, ancestor.cache().take_layout_dirt()));
Step::Up(ancestor.parent())
}
fn passed(&mut self, id: NodeId) {
self.first_parent.get_or_insert(id);
self.child = id;
}
fn start_at(&mut self, id: NodeId, own: Option<Constraints>) {
self.start = id;
self.changed_itself = false;
self.own = own;
self.first_parent = None;
self.child = id;
}
fn into_plan(self) -> Plan {
match (self.first_parent, self.constraints) {
(Some(parent), Some(constraints)) => Plan::Start(Start {
node: self.start,
parent,
constraints,
changed_itself: self.changed_itself,
dirty: self.dirty,
}),
_ => Plan::WalkDown,
}
}
}
enum Step {
Up(Option<NodeId>),
SlotHost(Option<NodeId>),
StartHere(Option<NodeId>, Option<Constraints>),
WalkDown,
}
enum Plan {
Clean,
Start(Start),
WalkDown,
}
enum Climb {
Stopped { measured_ancestor: bool },
Root(Rc<MeasuredNode>),
WalkDown,
}
impl LayoutBuilderState {
pub(super) fn measure_root(
self: &Rc<Self>,
root: NodeId,
constraints: Constraints,
) -> Result<Rc<MeasuredNode>, NodeError> {
match self.measure_dirty_nodes_first(root, constraints)? {
Some(measured) => Ok(measured),
None => self.measure_node(root, constraints),
}
}
fn measure_dirty_nodes_first(
self: &Rc<Self>,
root: NodeId,
constraints: Constraints,
) -> Result<Option<Rc<MeasuredNode>>, NodeError> {
let mut dirty = self.frame_arena.borrow_mut().tmp_child_ids.acquire();
let complete = match self.applier.try_borrow_typed() {
Ok(mut applier) => applier.take_layout_dirt_roots(&mut dirty),
Err(_) => false,
};
let measured = if complete {
self.measure_from_dirty_nodes(root, constraints, &dirty)
} else {
Ok(None)
};
self.frame_arena.borrow_mut().tmp_child_ids.release(dirty);
measured
}
fn measure_from_dirty_nodes(
self: &Rc<Self>,
root: NodeId,
constraints: Constraints,
dirty: &[NodeId],
) -> Result<Option<Rc<MeasuredNode>>, NodeError> {
if dirty.is_empty() {
return Ok(None);
}
let Some(root_measured) = self.held_root_measurement(root, constraints) else {
return Ok(None);
};
let mut cleared = std::mem::take(&mut self.frame_arena.borrow_mut().cleared_marks);
cleared.clear();
let measured = self.measure_from_dirty_nodes_clearing(
root,
constraints,
dirty,
root_measured,
&mut cleared,
);
self.frame_arena.borrow_mut().cleared_marks = cleared;
measured
}
fn measure_from_dirty_nodes_clearing(
self: &Rc<Self>,
root: NodeId,
constraints: Constraints,
dirty: &[NodeId],
mut root_measured: Rc<MeasuredNode>,
cleared: &mut Cleared,
) -> Result<Option<Rc<MeasuredNode>>, NodeError> {
let mut starts: SmallVec<[Start; 4]> = SmallVec::new();
for &node in dirty {
match self.plan(node, root, cleared) {
Plan::Clean => {}
Plan::Start(start) => starts.push(start),
Plan::WalkDown => {
self.restore_marks(cleared);
return Ok(None);
}
}
}
if starts.is_empty() {
return Ok(None);
}
let mut measured_ancestors = false;
for start in &starts {
match self.measure_up(start, root, constraints, measured_ancestors)? {
Climb::Stopped { measured_ancestor } => measured_ancestors |= measured_ancestor,
Climb::Root(measured) => {
root_measured = measured;
measured_ancestors = true;
}
Climb::WalkDown => {
self.restore_marks(cleared);
return Ok(None);
}
}
}
if self
.applier
.try_borrow_typed()
.is_ok_and(|applier| applier.has_layout_dirt_roots())
{
self.clear_marks(cleared);
}
Ok(Some(root_measured))
}
fn held_root_measurement(
&self,
root: NodeId,
constraints: Constraints,
) -> Option<Rc<MeasuredNode>> {
let mut applier = self.applier.try_borrow_typed().ok()?;
applier
.with_node::<LayoutNode, _>(root, |node| {
if needs_layout_itself(node.cache())
|| Node::is_virtual(node)
|| node.cache().epoch() < self.cache_floor
{
return None;
}
node.cache().get_measurement(constraints)
})
.ok()
.flatten()
}
fn plan(&self, node: NodeId, root: NodeId, cleared: &mut Cleared) -> Plan {
let Ok(mut applier) = self.applier.try_borrow_typed() else {
return Plan::WalkDown;
};
let dirty = match read_dirty_node(&mut applier, node) {
Some(dirty) if !dirty.dirt.any() => return Plan::Clean,
Some(dirty) if !dirty.window_root && dirty.node != root => dirty,
_ => return Plan::WalkDown,
};
let mut walk = PlanWalk::new(&dirty);
let mut next = dirty.parent;
while let Some(id) = next {
let step = read_live_layout_node(&mut applier, id, |ancestor| {
walk.visit(id, &ancestor, cleared)
});
next = match step {
Ok(Some(Step::Up(parent))) => {
walk.passed(id);
if id == root {
return walk.into_plan();
}
parent
}
Ok(Some(Step::StartHere(parent, own))) if id != root => {
walk.start_at(id, own);
parent
}
Ok(Some(Step::SlotHost(parent))) => {
if walk.child != walk.start
&& let Some(&(content, dirt)) = cleared.last()
{
let restored = read_live_layout_node(&mut applier, content, |content| {
content.cache().restore_dirt(dirt);
});
if !matches!(restored, Ok(Some(()))) {
return Plan::WalkDown;
}
cleared.pop();
}
parent
}
_ => return Plan::WalkDown,
};
}
Plan::WalkDown
}
fn clear_marks(&self, cleared: &Cleared) {
let Ok(mut applier) = self.applier.try_borrow_typed() else {
return;
};
for &(id, _) in cleared {
let read = read_live_layout_node(&mut applier, id, |node| {
node.cache().clear_layout_dirt();
});
if read.is_err() {
return;
}
}
}
fn restore_marks(&self, cleared: &Cleared) {
let Ok(mut applier) = self.applier.try_borrow_typed() else {
return;
};
for &(id, dirt) in cleared.iter().rev() {
let _ = read_live_layout_node(&mut applier, id, |node| {
node.cache().restore_dirt(dirt);
});
}
}
fn measure_up(
self: &Rc<Self>,
start: &Start,
root: NodeId,
root_constraints: Constraints,
measured_ancestors: bool,
) -> Result<Climb, NodeError> {
if measured_ancestors
&& !self
.read_node(start.dirty, |node| node.cache().dirt().any())
.unwrap_or(false)
{
return Ok(Climb::Stopped {
measured_ancestor: false,
});
}
let mut child = start.node;
let mut parent = start.parent;
let mut measured = self.measure_node(child, start.constraints)?;
let mut changed_itself = start.changed_itself;
loop {
let (unaffected, grandparent) =
self.parent_take(child, parent, &measured, changed_itself)?;
if unaffected {
return Ok(Climb::Stopped {
measured_ancestor: child != start.dirty,
});
}
if parent == root {
return Ok(Climb::Root(self.measure_node(root, root_constraints)?));
}
let Some((grandparent, constraints)) = grandparent
.and_then(|grandparent| self.layout_parent_constraints(grandparent, parent))
else {
return Ok(Climb::WalkDown);
};
measured = self.measure_node(parent, constraints)?;
child = parent;
parent = grandparent;
changed_itself = false;
}
}
fn read_node<R>(&self, node: NodeId, read: impl FnOnce(LiveLayoutNode<'_>) -> R) -> Option<R> {
let mut applier = self.applier.try_borrow_typed().ok()?;
read_live_layout_node(&mut applier, node, read)
.ok()
.flatten()
}
fn layout_parent_constraints(
&self,
parent: NodeId,
child: NodeId,
) -> Option<(NodeId, Constraints)> {
let mut applier = self.applier.try_borrow_typed().ok()?;
let recorded = read_live_layout_node(&mut applier, parent, |node| match node {
LiveLayoutNode::Layout(node) if !Node::is_virtual(node) => {
Ok(given_constraints(node, child))
}
LiveLayoutNode::Layout(host) => Err(Node::parent(host)),
LiveLayoutNode::Subcompose(_) => Err(Some(parent)),
})
.ok()??;
match recorded {
Ok(constraints) => Some((parent, constraints?)),
Err(layout_parent) => {
let constraints = read_live_layout_node(&mut applier, child, |child| {
own_constraints(child.cache())
})
.ok()??;
Some((layout_parent?, constraints?))
}
}
}
fn parent_take(
&self,
child: NodeId,
parent: NodeId,
measured: &Rc<MeasuredNode>,
changed_itself: bool,
) -> Result<(bool, Option<NodeId>), NodeError> {
let mut applier =
self.applier
.try_borrow_typed()
.map_err(|_| NodeError::MissingContext {
id: parent,
reason: "layout applier borrowed",
})?;
let parent_data = read_live_layout_node(&mut applier, child, |child| {
child.stay_placed(parent, &self.context);
changed_itself.then(|| child.parent_data())
})?
.flatten();
let taken = read_live_layout_node(&mut applier, parent, |node| {
let unaffected = match &node {
LiveLayoutNode::Layout(node) => {
Self::lays_out_as_before(node, child, measured, parent_data.as_ref())
}
LiveLayoutNode::Subcompose(node) => {
keeps_measurement(node.cache(), child, measured)
}
};
if !unaffected {
node.cache().mark_needs_measure();
}
let subcompose = matches!(node, LiveLayoutNode::Subcompose(_));
(unaffected, subcompose, node.parent())
})?;
drop(applier);
let Some((unaffected, subcompose, grandparent)) = taken else {
return Ok((false, None));
};
if subcompose {
self.mark_measured_without_policy(child);
}
Ok((unaffected, grandparent))
}
fn lays_out_as_before(
parent: &LayoutNode,
child: NodeId,
measured: &MeasuredNode,
parent_data: Option<&cranpose_ui_layout::ParentData>,
) -> bool {
{
let runtime_state = parent.layout_runtime_state().borrow();
let Some(record) = runtime_state
.child_states()
.iter()
.find(|state| state.node_id == child)
else {
return false;
};
if record.read_intrinsics.get() {
return false;
}
if let Some(data) = parent_data
&& super::read_cell(&record.parent_data, |bound| {
bound.as_ref().is_none_or(|bound| {
runtime_state
.measure_policy
.reads_parent_data_change(bound, data)
})
})
{
return false;
}
}
keeps_measurement(parent.cache(), child, measured)
}
}
fn read_dirty_node(applier: &mut MemoryApplier, mut node: NodeId) -> Option<DirtyNode> {
loop {
let read = read_live_layout_node(applier, node, |live| {
if live.is_virtual() {
return Err(live.parent());
}
Ok(DirtyNode {
node,
dirt: live.cache().dirt(),
window_root: live.is_window_root(),
parent: live.parent(),
own_constraints: own_constraints(live.cache()),
})
});
match read.ok()?? {
Ok(dirty) => return Some(dirty),
Err(parent) => node = parent?,
}
}
}
fn given_constraints(parent: &LayoutNode, child: NodeId) -> Option<Constraints> {
parent
.layout_runtime_state()
.borrow()
.child_states()
.iter()
.find(|state| state.node_id == child)
.and_then(|state| state.measured_constraints.get())
}
fn own_constraints(cache: &NodeCache) -> Option<Constraints> {
(!cache.has_intrinsics())
.then(|| cache.measured_constraints())
.flatten()
}
fn keeps_measurement(cache: &NodeCache, child: NodeId, measured: &MeasuredNode) -> bool {
let now = ForParent::of(measured);
let resized = cache.read_measurement(|held, ignores_child_sizes| {
let previous = held.children.iter().find(|entry| entry.node_id == child)?;
if previous.for_parent.same_as(&now) {
return Some(false);
}
(ignores_child_sizes && previous.for_parent.same_but_size(&now)).then_some(true)
});
match resized.flatten() {
Some(false) => true,
Some(true) => {
if let (Some(held), Some(constraints)) =
(cache.held_measurement(), cache.measured_constraints())
{
cache.store_resized_measurement(constraints, held);
}
true
}
None => false,
}
}
fn needs_layout_itself(cache: &NodeCache) -> bool {
let dirt = cache.dirt();
dirt.measure || dirt.layout
}