#![allow(private_interfaces)]
#[allow(unused_imports)] use cranpose_foundation::InvalidationKind;
use cranpose_core::NodeId;
use cranpose_foundation::{
BasicModifierNodeContext, ModifierInvalidation, ModifierNodeChain, ModifierNodeContext,
NodeCapabilities,
};
use super::{
local::ModifierLocalManager, modifier_debug_enabled, DimensionConstraint, EdgeInsets,
LayoutProperties, Modifier, ModifierInspectorRecord, ModifierLocalAncestorResolver,
ModifierLocalToken, Point, ResolvedModifierLocal, ResolvedModifiers,
};
use crate::modifier_nodes::{
AlignmentNode, FillDirection, FillNode, IntrinsicAxis, IntrinsicSizeNode, OffsetNode,
PaddingNode, SizeNode, WeightNode,
};
use std::any::type_name_of_val;
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone, Debug, PartialEq)]
pub struct ModifierChainInspectorNode {
pub depth: usize,
pub entry_index: Option<usize>,
pub type_name: &'static str,
pub capabilities: NodeCapabilities,
pub aggregate_child_capabilities: NodeCapabilities,
pub inspector: Option<ModifierInspectorRecord>,
}
pub type ModifierLocalsHandle = Rc<RefCell<ModifierLocalManager>>;
pub struct ModifierChainHandle {
chain: ModifierNodeChain,
context: RefCell<BasicModifierNodeContext>,
resolved: ResolvedModifiers,
capabilities: NodeCapabilities,
aggregate_child_capabilities: NodeCapabilities,
modifier_locals: ModifierLocalsHandle,
inspector_snapshot: Vec<ModifierChainInspectorNode>,
inspector_entry_scratch: Vec<Option<ModifierInspectorRecord>>,
debug_logging: bool,
}
impl Default for ModifierChainHandle {
fn default() -> Self {
Self {
chain: ModifierNodeChain::new(),
context: RefCell::new(BasicModifierNodeContext::new()),
resolved: ResolvedModifiers::default(),
capabilities: NodeCapabilities::default(),
aggregate_child_capabilities: NodeCapabilities::default(),
modifier_locals: Rc::new(RefCell::new(ModifierLocalManager::new())),
inspector_snapshot: Vec::new(),
inspector_entry_scratch: Vec::new(),
debug_logging: false,
}
}
}
impl ModifierChainHandle {
pub fn new() -> Self {
Self::default()
}
pub fn update(&mut self, modifier: &Modifier) -> Vec<ModifierInvalidation> {
let mut resolver = |_: ModifierLocalToken| None;
self.update_with_resolver(modifier, &mut resolver)
}
pub fn update_with_resolver(
&mut self,
modifier: &Modifier,
resolver: &mut ModifierLocalAncestorResolver<'_>,
) -> Vec<ModifierInvalidation> {
self.chain
.update_from_ref_iter(modifier.iter_elements(), &mut *self.context.borrow_mut());
self.capabilities = self.chain.capabilities();
self.aggregate_child_capabilities = self.chain.head().aggregate_child_capabilities();
let modifier_local_invalidations = self
.modifier_locals
.borrow_mut()
.sync(&self.chain, resolver);
let needs_resolved_update = {
let ctx = self.context.borrow();
ctx.invalidations()
.iter()
.any(|inv| inv.kind() == InvalidationKind::Layout)
};
if needs_resolved_update {
self.resolved = self.compute_resolved();
}
let should_log = self.debug_logging || modifier_debug_enabled();
if should_log {
self.collect_inspector_snapshot(modifier);
crate::debug::log_modifier_chain(self.chain(), self.inspector_snapshot());
crate::debug::emit_modifier_chain_trace(self.inspector_snapshot());
}
modifier_local_invalidations
}
pub fn set_debug_logging(&mut self, enabled: bool) {
self.debug_logging = enabled;
}
pub fn set_node_id(&mut self, id: Option<NodeId>) {
let old_id = self.context.borrow().node_id();
if old_id == id {
return;
}
self.context.borrow_mut().set_node_id(id);
if id.is_some() {
self.chain.detach_nodes();
self.chain.repair_chain();
self.chain.attach_nodes(&mut *self.context.borrow_mut());
}
}
pub fn chain(&self) -> &ModifierNodeChain {
&self.chain
}
pub fn chain_mut(&mut self) -> &mut ModifierNodeChain {
&mut self.chain
}
pub fn context_mut(&self) -> std::cell::RefMut<'_, BasicModifierNodeContext> {
self.context.borrow_mut()
}
pub fn chain_and_context_mut(
&mut self,
) -> (
&mut ModifierNodeChain,
std::cell::RefMut<'_, BasicModifierNodeContext>,
) {
(&mut self.chain, self.context.borrow_mut())
}
pub fn capabilities(&self) -> NodeCapabilities {
self.capabilities
}
pub fn aggregate_child_capabilities(&self) -> NodeCapabilities {
self.aggregate_child_capabilities
}
pub fn has_layout_nodes(&self) -> bool {
self.capabilities.contains(NodeCapabilities::LAYOUT)
}
pub fn has_draw_nodes(&self) -> bool {
self.capabilities.contains(NodeCapabilities::DRAW)
}
pub fn has_pointer_input_nodes(&self) -> bool {
self.capabilities.contains(NodeCapabilities::POINTER_INPUT)
}
pub fn has_semantics_nodes(&self) -> bool {
self.capabilities.contains(NodeCapabilities::SEMANTICS)
}
pub fn take_invalidations(&self) -> Vec<ModifierInvalidation> {
self.context.borrow_mut().take_invalidations()
}
pub fn resolved_modifiers(&self) -> ResolvedModifiers {
self.resolved
}
pub fn resolve_modifier_local(
&self,
token: ModifierLocalToken,
) -> Option<ResolvedModifierLocal> {
self.modifier_locals.borrow().resolve(token)
}
pub fn modifier_locals_handle(&self) -> ModifierLocalsHandle {
Rc::clone(&self.modifier_locals)
}
pub fn inspector_snapshot(&self) -> &[ModifierChainInspectorNode] {
&self.inspector_snapshot
}
#[cfg(test)]
pub fn refresh_inspector_snapshot(&mut self, modifier: &Modifier) {
self.collect_inspector_snapshot(modifier);
}
fn compute_resolved(&self) -> ResolvedModifiers {
let mut resolved = ResolvedModifiers::default();
let mut layout = LayoutProperties::default();
let mut padding = EdgeInsets::default();
let mut offset = Point::default();
self.chain.for_each_forward(|node_ref| {
node_ref.with_node(|node| {
let any = node.as_any();
if let Some(padding_node) = any.downcast_ref::<PaddingNode>() {
padding += padding_node.padding();
} else if let Some(size_node) = any.downcast_ref::<SizeNode>() {
apply_size_node(&mut layout, size_node);
} else if let Some(fill_node) = any.downcast_ref::<FillNode>() {
apply_fill_node(&mut layout, fill_node);
} else if let Some(intrinsic_node) = any.downcast_ref::<IntrinsicSizeNode>() {
apply_intrinsic_size_node(&mut layout, intrinsic_node);
} else if let Some(weight_node) = any.downcast_ref::<WeightNode>() {
layout.weight = Some(weight_node.layout_weight());
} else if let Some(alignment_node) = any.downcast_ref::<AlignmentNode>() {
if let Some(alignment) = alignment_node.box_alignment() {
layout.box_alignment = Some(alignment);
}
if let Some(alignment) = alignment_node.column_alignment() {
layout.column_alignment = Some(alignment);
}
if let Some(alignment) = alignment_node.row_alignment() {
layout.row_alignment = Some(alignment);
}
} else if let Some(offset_node) = any.downcast_ref::<OffsetNode>() {
let delta = offset_node.offset();
offset.x += delta.x;
offset.y += delta.y;
}
});
});
resolved.set_padding(padding);
resolved.set_layout_properties(layout);
resolved.set_offset(offset);
resolved
}
fn collect_inspector_snapshot(&mut self, modifier: &Modifier) {
if self.chain.is_empty() {
self.inspector_snapshot.clear();
return;
}
let chain_len = self.chain.len();
self.inspector_entry_scratch.clear();
self.inspector_entry_scratch.resize_with(chain_len, || None);
for (index, metadata) in modifier.iter_inspector_metadata().enumerate() {
if index >= chain_len {
break;
}
self.inspector_entry_scratch[index] = Some(metadata.to_record());
}
self.inspector_snapshot.clear();
self.inspector_snapshot.reserve(chain_len);
let chain = &self.chain;
let inspector_entry_scratch = &mut self.inspector_entry_scratch;
let inspector_snapshot = &mut self.inspector_snapshot;
chain.for_each_forward(|node_ref| {
node_ref.with_node(|node| {
let depth = node_ref.delegate_depth();
let entry_index = node_ref.entry_index();
let inspector = if depth == 0 {
entry_index
.and_then(|idx| inspector_entry_scratch.get_mut(idx))
.and_then(|slot| slot.take())
} else {
None
};
inspector_snapshot.push(ModifierChainInspectorNode {
depth,
entry_index,
type_name: type_name_of_val(node),
capabilities: node_ref.kind_set(),
aggregate_child_capabilities: node_ref.aggregate_child_capabilities(),
inspector,
});
});
});
}
pub fn with_text_field_modifier_mut<R>(
&mut self,
mut f: impl FnMut(&mut crate::TextFieldModifierNode) -> R,
) -> Option<R> {
let mut result = None;
self.chain.visit_nodes_mut(|node, capabilities| {
if capabilities.contains(NodeCapabilities::LAYOUT) {
let any = node.as_any_mut();
if let Some(text_field) = any.downcast_mut::<crate::TextFieldModifierNode>() {
if result.is_none() {
result = Some(f(text_field));
}
}
}
});
result
}
}
fn apply_size_node(layout: &mut LayoutProperties, node: &SizeNode) {
if let Some(width) = node.max_width().or(node.min_width()) {
layout.width = DimensionConstraint::Points(width);
}
if let Some(height) = node.max_height().or(node.min_height()) {
layout.height = DimensionConstraint::Points(height);
}
if !node.enforce_incoming() {
if let Some(min_width) = node.min_width() {
layout.min_width = Some(min_width);
}
if let Some(max_width) = node.max_width() {
layout.max_width = Some(max_width);
}
if let Some(min_height) = node.min_height() {
layout.min_height = Some(min_height);
}
if let Some(max_height) = node.max_height() {
layout.max_height = Some(max_height);
}
}
}
fn apply_fill_node(layout: &mut LayoutProperties, node: &FillNode) {
let fraction = node.fraction();
match node.direction() {
FillDirection::Horizontal => {
layout.width = DimensionConstraint::Fraction(fraction);
}
FillDirection::Vertical => {
layout.height = DimensionConstraint::Fraction(fraction);
}
FillDirection::Both => {
layout.width = DimensionConstraint::Fraction(fraction);
layout.height = DimensionConstraint::Fraction(fraction);
}
}
}
fn apply_intrinsic_size_node(layout: &mut LayoutProperties, node: &IntrinsicSizeNode) {
let constraint = DimensionConstraint::Intrinsic(node.intrinsic_size());
match node.axis() {
IntrinsicAxis::Width => {
layout.width = constraint;
}
IntrinsicAxis::Height => {
layout.height = constraint;
}
}
}
#[cfg(test)]
mod tests {
use cranpose_foundation::{
InvalidationKind, ModifierInvalidation, ModifierNode, NodeCapabilities,
};
use super::*;
use crate::modifier::Color;
use crate::modifier_nodes::PaddingNode;
#[test]
fn attaches_padding_node_and_invalidates_layout() {
let mut handle = ModifierChainHandle::new();
let _ = handle.update(&Modifier::empty().padding(8.0));
assert_eq!(handle.chain().len(), 1);
let invalidations = handle.take_invalidations();
assert_eq!(
invalidations,
vec![ModifierInvalidation::new(
InvalidationKind::Layout,
NodeCapabilities::LAYOUT
)]
);
}
#[test]
fn reuses_nodes_between_updates() {
let mut handle = ModifierChainHandle::new();
let _ = handle.update(&Modifier::empty().padding(12.0));
let first_ptr = node_ptr::<PaddingNode>(&handle);
handle.take_invalidations();
let _ = handle.update(&Modifier::empty().padding(12.0));
let second_ptr = node_ptr::<PaddingNode>(&handle);
assert_eq!(first_ptr, second_ptr, "expected the node to be reused");
assert!(
handle.take_invalidations().is_empty(),
"no additional invalidations should be issued for a pure update"
);
}
#[test]
fn modifier_slices_capture_background_and_shape() {
use crate::modifier::slices::collect_modifier_slices;
let mut handle = ModifierChainHandle::new();
let _ = handle.update(
&Modifier::empty()
.background(Color(0.2, 0.3, 0.4, 1.0))
.then(Modifier::empty().rounded_corners(8.0)),
);
let slices = collect_modifier_slices(handle.chain());
assert!(
!slices.draw_commands().is_empty(),
"Expected draw commands for background"
);
let _ = handle.update(
&Modifier::empty()
.rounded_corners(4.0)
.then(Modifier::empty().background(Color(0.9, 0.1, 0.1, 1.0))),
);
let slices = collect_modifier_slices(handle.chain());
assert!(
!slices.draw_commands().is_empty(),
"Expected draw commands after update"
);
let _ = handle.update(&Modifier::empty());
let slices = collect_modifier_slices(handle.chain());
assert!(
slices.draw_commands().is_empty(),
"Expected no draw commands with empty modifier"
);
}
#[test]
fn capability_mask_updates_with_chain() {
let mut handle = ModifierChainHandle::new();
let _ = handle.update(&Modifier::empty().padding(4.0));
assert_eq!(handle.capabilities(), NodeCapabilities::LAYOUT);
assert!(handle.has_layout_nodes());
assert!(!handle.has_draw_nodes());
handle.take_invalidations();
let color = Color(0.5, 0.6, 0.7, 1.0);
let _ = handle.update(&Modifier::empty().background(color));
assert_eq!(handle.capabilities(), NodeCapabilities::DRAW);
assert!(handle.has_draw_nodes());
assert!(!handle.has_layout_nodes());
}
#[test]
fn offset_update_invalidates_layout() {
let mut handle = ModifierChainHandle::new();
let _ = handle.update(&Modifier::empty().offset(0.0, 0.0));
handle.take_invalidations();
let _ = handle.update(&Modifier::empty().offset(12.0, 0.0));
let invalidations = handle.take_invalidations();
assert!(
invalidations
.iter()
.any(|invalidation| invalidation.kind() == InvalidationKind::Layout),
"expected offset changes to invalidate layout"
);
}
fn node_ptr<N: ModifierNode + 'static>(handle: &ModifierChainHandle) -> *const N {
handle
.chain()
.node::<N>(0)
.map(|node| &*node as *const N)
.expect("expected node to exist")
}
}