use super::*;
use crate::modifier::{collect_slices_from_modifier, Modifier, PointerInputScope};
use cranpose_foundation::{
modifier_element, BasicModifierNodeContext, ModifierNodeChain, NodeCapabilities, PointerButton,
PointerButtons, PointerEvent, PointerEventKind,
};
use cranpose_ui_layout::Placeable;
use std::cell::{Cell, RefCell};
use std::future::pending;
use std::rc::Rc;
struct TestMeasurable {
intrinsic_width: f32,
intrinsic_height: f32,
}
impl Measurable for TestMeasurable {
fn measure(&self, constraints: Constraints) -> Placeable {
Placeable::value(
constraints.max_width.min(self.intrinsic_width),
constraints.max_height.min(self.intrinsic_height),
0,
)
}
fn min_intrinsic_width(&self, _height: f32) -> f32 {
self.intrinsic_width
}
fn max_intrinsic_width(&self, _height: f32) -> f32 {
self.intrinsic_width
}
fn min_intrinsic_height(&self, _width: f32) -> f32 {
self.intrinsic_height
}
fn max_intrinsic_height(&self, _width: f32) -> f32 {
self.intrinsic_height
}
}
#[test]
fn padding_node_adds_space_to_content() {
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let padding = EdgeInsets::uniform(10.0);
let elements = vec![modifier_element(PaddingElement::new(padding))];
chain.update_from_slice(&elements, &mut context);
assert_eq!(chain.len(), 1);
assert!(chain.has_nodes_for_invalidation(cranpose_foundation::InvalidationKind::Layout));
let node = chain.node_mut::<PaddingNode>(0).unwrap();
let measurable = TestMeasurable {
intrinsic_width: 50.0,
intrinsic_height: 50.0,
};
let constraints = Constraints {
min_width: 0.0,
max_width: 200.0,
min_height: 0.0,
max_height: 200.0,
};
let result = node.measure(&mut context, &measurable, constraints);
assert_eq!(result.size.width, 70.0);
assert_eq!(result.size.height, 70.0);
}
#[test]
fn padding_node_clamps_to_constraints() {
let mut context = BasicModifierNodeContext::new();
let node = PaddingNode::new(EdgeInsets::uniform(12.0));
let measurable = TestMeasurable {
intrinsic_width: 100.0,
intrinsic_height: 100.0,
};
let constraints = Constraints {
min_width: 0.0,
max_width: 16.0,
min_height: 0.0,
max_height: 16.0,
};
let result = node.measure(&mut context, &measurable, constraints);
assert_eq!(result.size.width, 16.0);
assert_eq!(result.size.height, 16.0);
}
#[test]
fn padding_node_respects_intrinsics() {
let padding = EdgeInsets::uniform(10.0);
let node = PaddingNode::new(padding);
let measurable = TestMeasurable {
intrinsic_width: 50.0,
intrinsic_height: 30.0,
};
assert_eq!(node.min_intrinsic_width(&measurable, 100.0), 70.0); assert_eq!(node.max_intrinsic_width(&measurable, 100.0), 70.0);
assert_eq!(node.min_intrinsic_height(&measurable, 100.0), 50.0); assert_eq!(node.max_intrinsic_height(&measurable, 100.0), 50.0);
}
#[test]
fn background_node_is_draw_only() {
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let color = Color(1.0, 0.0, 0.0, 1.0);
let elements = vec![modifier_element(BackgroundElement::new(color))];
chain.update_from_slice(&elements, &mut context);
assert_eq!(chain.len(), 1);
assert!(chain.has_nodes_for_invalidation(cranpose_foundation::InvalidationKind::Draw));
assert!(!chain.has_nodes_for_invalidation(cranpose_foundation::InvalidationKind::Layout));
}
#[test]
fn corner_shape_node_is_draw_only() {
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let elements = vec![modifier_element(CornerShapeElement::new(
RoundedCornerShape::uniform(6.0),
))];
chain.update_from_slice(&elements, &mut context);
assert!(chain.has_nodes_for_invalidation(cranpose_foundation::InvalidationKind::Draw));
assert!(!chain.has_nodes_for_invalidation(cranpose_foundation::InvalidationKind::Layout));
}
#[test]
fn modifier_chain_reuses_padding_nodes() {
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let elements = vec![modifier_element(PaddingElement::new(EdgeInsets::uniform(
10.0,
)))];
chain.update_from_slice(&elements, &mut context);
let initial_node = {
let node_ref = chain.node::<PaddingNode>(0).unwrap();
&*node_ref as *const _
};
context.clear_invalidations();
let elements = vec![modifier_element(PaddingElement::new(EdgeInsets::uniform(
20.0,
)))];
chain.update_from_slice(&elements, &mut context);
let updated_node = {
let node_ref = chain.node::<PaddingNode>(0).unwrap();
&*node_ref as *const _
};
assert_eq!(initial_node, updated_node);
{
let node_ref = chain.node::<PaddingNode>(0).unwrap();
assert_eq!(node_ref.padding.left, 20.0);
}
}
#[test]
fn size_node_enforces_dimensions() {
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let elements = vec![modifier_element(SizeElement::new(Some(100.0), Some(200.0)))];
chain.update_from_slice(&elements, &mut context);
let node = chain.node_mut::<SizeNode>(0).unwrap();
let measurable = TestMeasurable {
intrinsic_width: 50.0,
intrinsic_height: 50.0,
};
let constraints = Constraints {
min_width: 0.0,
max_width: 500.0,
min_height: 0.0,
max_height: 500.0,
};
let result = node.measure(&mut context, &measurable, constraints);
assert_eq!(result.size.width, 100.0);
assert_eq!(result.size.height, 200.0);
}
#[test]
fn clickable_node_handles_pointer_events() {
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let clicked = Rc::new(Cell::new(false));
let clicked_clone = clicked.clone();
let elements = vec![modifier_element(ClickableElement::new(move |_point| {
clicked_clone.set(true);
}))];
chain.update_from_slice(&elements, &mut context);
assert!(chain.has_nodes_for_invalidation(cranpose_foundation::InvalidationKind::PointerInput));
let mut node = chain.node_mut::<ClickableNode>(0).unwrap();
let mut down_event = PointerEvent::new(
PointerEventKind::Down,
Point { x: 10.0, y: 20.0 },
Point { x: 10.0, y: 20.0 },
);
down_event.buttons = PointerButtons::new().with(PointerButton::Primary);
let consumed = node.on_pointer_event(&mut context, &down_event);
assert!(!consumed); assert!(!clicked.get());
let mut up_event = PointerEvent::new(
PointerEventKind::Up,
Point { x: 10.0, y: 20.0 },
Point { x: 10.0, y: 20.0 },
);
up_event.buttons = PointerButtons::new().with(PointerButton::Primary);
let consumed = node.on_pointer_event(&mut context, &up_event);
assert!(consumed); assert!(clicked.get()); }
#[test]
fn clickable_node_cancels_click_on_drag() {
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let clicked = Rc::new(Cell::new(false));
let clicked_clone = clicked.clone();
let elements = vec![modifier_element(ClickableElement::new(move |_point| {
clicked_clone.set(true);
}))];
chain.update_from_slice(&elements, &mut context);
let mut node = chain.node_mut::<ClickableNode>(0).unwrap();
let mut down_event = PointerEvent::new(
PointerEventKind::Down,
Point { x: 10.0, y: 20.0 },
Point { x: 10.0, y: 20.0 },
);
down_event.buttons = PointerButtons::new().with(PointerButton::Primary);
node.on_pointer_event(&mut context, &down_event);
assert!(!clicked.get());
let mut move_event = PointerEvent::new(
PointerEventKind::Move,
Point { x: 20.0, y: 20.0 }, Point { x: 20.0, y: 20.0 },
);
move_event.buttons = PointerButtons::new().with(PointerButton::Primary);
node.on_pointer_event(&mut context, &move_event);
assert!(!clicked.get());
let mut up_event = PointerEvent::new(
PointerEventKind::Up,
Point { x: 20.0, y: 20.0 },
Point { x: 20.0, y: 20.0 },
);
up_event.buttons = PointerButtons::new().with(PointerButton::Primary);
let consumed = node.on_pointer_event(&mut context, &up_event);
assert!(!consumed); assert!(!clicked.get()); }
#[test]
fn alpha_node_clamps_values() {
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let elements = vec![modifier_element(AlphaElement::new(1.5))]; chain.update_from_slice(&elements, &mut context);
{
let node = chain.node::<AlphaNode>(0).unwrap();
assert_eq!(node.alpha, 1.0);
}
context.clear_invalidations();
let elements = vec![modifier_element(AlphaElement::new(-0.5))];
chain.update_from_slice(&elements, &mut context);
{
let node = chain.node::<AlphaNode>(0).unwrap();
assert_eq!(node.alpha, 0.0);
}
}
#[test]
fn alpha_node_is_draw_only() {
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let elements = vec![modifier_element(AlphaElement::new(0.5))];
chain.update_from_slice(&elements, &mut context);
assert!(chain.has_nodes_for_invalidation(cranpose_foundation::InvalidationKind::Draw));
assert!(!chain.has_nodes_for_invalidation(cranpose_foundation::InvalidationKind::Layout));
}
#[test]
fn mixed_modifier_chain_tracks_all_capabilities() {
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let clicked = Rc::new(Cell::new(false));
let clicked_clone = clicked.clone();
let elements = vec![
modifier_element(PaddingElement::new(EdgeInsets::uniform(10.0))),
modifier_element(AlphaElement::new(0.8)),
modifier_element(ClickableElement::new(move |_| {
clicked_clone.set(true);
})),
modifier_element(BackgroundElement::new(Color(1.0, 0.0, 0.0, 1.0))),
];
chain.update_from_slice(&elements, &mut context);
assert_eq!(chain.len(), 4);
assert!(chain.has_nodes_for_invalidation(cranpose_foundation::InvalidationKind::Layout));
assert!(chain.has_nodes_for_invalidation(cranpose_foundation::InvalidationKind::Draw));
assert!(chain.has_nodes_for_invalidation(cranpose_foundation::InvalidationKind::PointerInput));
let mut layout_nodes = 0;
chain.for_each_forward_matching(NodeCapabilities::LAYOUT, |_| {
layout_nodes += 1;
});
assert_eq!(layout_nodes, 1, "expected a single layout node");
let mut draw_nodes = 0;
chain.for_each_forward_matching(NodeCapabilities::DRAW, |_| {
draw_nodes += 1;
});
assert_eq!(draw_nodes, 2, "expected alpha + background draw nodes");
let mut pointer_nodes = 0;
chain.for_each_forward_matching(NodeCapabilities::POINTER_INPUT, |_| {
pointer_nodes += 1;
});
assert_eq!(pointer_nodes, 1, "expected exactly one pointer node");
}
#[test]
fn toggling_background_color_reuses_node() {
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let red = Color(1.0, 0.0, 0.0, 1.0);
let elements = vec![modifier_element(BackgroundElement::new(red))];
chain.update_from_slice(&elements, &mut context);
let initial_node_ptr = {
let node_ref = chain.node::<BackgroundNode>(0).unwrap();
&*node_ref as *const _
};
let blue = Color(0.0, 0.0, 1.0, 1.0);
let elements = vec![modifier_element(BackgroundElement::new(blue))];
chain.update_from_slice(&elements, &mut context);
let updated_node_ptr = {
let node_ref = chain.node::<BackgroundNode>(0).unwrap();
&*node_ref as *const _
};
assert_eq!(initial_node_ptr, updated_node_ptr, "Node should be reused");
{
let node_ref = chain.node::<BackgroundNode>(0).unwrap();
assert_eq!(node_ref.color, blue);
}
}
#[test]
fn reordering_modifiers_with_stable_reuse() {
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let padding = EdgeInsets::uniform(10.0);
let color = Color(1.0, 0.0, 0.0, 1.0);
let elements = vec![
modifier_element(PaddingElement::new(padding)),
modifier_element(BackgroundElement::new(color)),
];
chain.update_from_slice(&elements, &mut context);
let (padding_ptr, background_ptr) = {
let padding_ref = chain.node::<PaddingNode>(0).unwrap();
let background_ref = chain.node::<BackgroundNode>(1).unwrap();
(&*padding_ref as *const _, &*background_ref as *const _)
};
let elements = vec![
modifier_element(BackgroundElement::new(color)),
modifier_element(PaddingElement::new(padding)),
];
chain.update_from_slice(&elements, &mut context);
let (new_background_ptr, new_padding_ptr) = {
let background_ref = chain.node::<BackgroundNode>(0).unwrap();
let padding_ref = chain.node::<PaddingNode>(1).unwrap();
(&*background_ref as *const _, &*padding_ref as *const _)
};
assert_eq!(
background_ptr, new_background_ptr,
"Background node should be reused"
);
assert_eq!(
padding_ptr, new_padding_ptr,
"Padding node should be reused"
);
}
#[test]
fn pointer_input_coroutine_receives_events() {
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let recorded = Rc::new(RefCell::new(Vec::new()));
let modifier = Modifier::empty().pointer_input((), {
let recorded = recorded.clone();
move |scope: PointerInputScope| {
let recorded = recorded.clone();
async move {
scope
.await_pointer_event_scope(|await_scope| async move {
loop {
let event = await_scope.await_pointer_event().await;
recorded.borrow_mut().push(event.kind);
}
})
.await;
}
}
});
let elements = modifier.elements();
chain.update_from_slice(&elements, &mut context);
let slices = collect_slices_from_modifier(&modifier);
assert_eq!(slices.pointer_inputs().len(), 1);
let handler = slices.pointer_inputs()[0].clone();
handler(PointerEvent::new(
PointerEventKind::Down,
Point { x: 0.0, y: 0.0 },
Point { x: 0.0, y: 0.0 },
));
handler(PointerEvent::new(
PointerEventKind::Up,
Point { x: 1.0, y: 1.0 },
Point { x: 1.0, y: 1.0 },
));
let events = recorded.borrow();
assert_eq!(*events, vec![PointerEventKind::Down, PointerEventKind::Up]);
}
#[test]
fn pointer_input_restarts_on_key_change() {
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let starts = Rc::new(Cell::new(0));
let modifier = Modifier::empty().pointer_input(0u32, {
let starts = starts.clone();
move |_scope: PointerInputScope| {
let starts = starts.clone();
async move {
starts.set(starts.get() + 1);
pending::<()>().await;
}
}
});
let elements = modifier.elements();
chain.update_from_slice(&elements, &mut context);
assert_eq!(starts.get(), 1);
let modifier_updated = Modifier::empty().pointer_input(1u32, {
let starts = starts.clone();
move |_scope: PointerInputScope| {
let starts = starts.clone();
async move {
starts.set(starts.get() + 1);
pending::<()>().await;
}
}
});
let elements_updated = modifier_updated.elements();
chain.update_from_slice(&elements_updated, &mut context);
assert_eq!(starts.get(), 2);
}
#[test]
fn pointer_input_handlers_survive_temporary_chain_drop() {
use std::cell::RefCell;
use std::rc::Rc;
let received_events = Rc::new(RefCell::new(Vec::new()));
let modifier = Modifier::empty().pointer_input(42u32, {
let events = received_events.clone();
move |scope: PointerInputScope| {
let events = events.clone();
async move {
loop {
let event = scope
.await_pointer_event_scope(|s| async move { s.await_pointer_event().await })
.await;
events.borrow_mut().push(event.kind);
}
}
}
});
let slices = collect_slices_from_modifier(&modifier);
assert_eq!(
slices.pointer_inputs().len(),
1,
"Should have extracted one pointer input handler"
);
let handler = slices.pointer_inputs()[0].clone();
handler(PointerEvent::new(
PointerEventKind::Move,
Point { x: 10.0, y: 20.0 },
Point { x: 10.0, y: 20.0 },
));
handler(PointerEvent::new(
PointerEventKind::Down,
Point { x: 10.0, y: 20.0 },
Point { x: 10.0, y: 20.0 },
));
handler(PointerEvent::new(
PointerEventKind::Up,
Point { x: 10.0, y: 20.0 },
Point { x: 10.0, y: 20.0 },
));
let events = received_events.borrow();
assert_eq!(
*events,
vec![
PointerEventKind::Move,
PointerEventKind::Down,
PointerEventKind::Up
],
"All events should be received even after temporary chain is dropped"
);
}
#[test]
fn multiple_temporary_chains_dont_interfere() {
use std::cell::RefCell;
use std::rc::Rc;
let events1 = Rc::new(RefCell::new(Vec::new()));
let events2 = Rc::new(RefCell::new(Vec::new()));
let modifier1 = Modifier::empty().pointer_input(1u32, {
let events = events1.clone();
move |scope: PointerInputScope| {
let events = events.clone();
async move {
loop {
let event = scope
.await_pointer_event_scope(|s| async move { s.await_pointer_event().await })
.await;
events.borrow_mut().push(("handler1", event.kind));
}
}
}
});
let modifier2 = Modifier::empty().pointer_input(2u32, {
let events = events2.clone();
move |scope: PointerInputScope| {
let events = events.clone();
async move {
loop {
let event = scope
.await_pointer_event_scope(|s| async move { s.await_pointer_event().await })
.await;
events.borrow_mut().push(("handler2", event.kind));
}
}
}
});
let slices1 = collect_slices_from_modifier(&modifier1);
let slices2 = collect_slices_from_modifier(&modifier2);
let handler1 = slices1.pointer_inputs()[0].clone();
let handler2 = slices2.pointer_inputs()[0].clone();
handler1(PointerEvent::new(
PointerEventKind::Move,
Point { x: 1.0, y: 1.0 },
Point { x: 1.0, y: 1.0 },
));
handler2(PointerEvent::new(
PointerEventKind::Down,
Point { x: 2.0, y: 2.0 },
Point { x: 2.0, y: 2.0 },
));
handler1(PointerEvent::new(
PointerEventKind::Up,
Point { x: 1.0, y: 1.0 },
Point { x: 1.0, y: 1.0 },
));
let ev1 = events1.borrow();
let ev2 = events2.borrow();
assert_eq!(ev1.len(), 2, "Handler 1 should receive 2 events");
assert_eq!(ev1[0], ("handler1", PointerEventKind::Move));
assert_eq!(ev1[1], ("handler1", PointerEventKind::Up));
assert_eq!(ev2.len(), 1, "Handler 2 should receive 1 event");
assert_eq!(ev2[0], ("handler2", PointerEventKind::Down));
}
#[test]
fn custom_layout_modifier_works_via_proxy() {
use cranpose_foundation::{
DelegatableNode, LayoutModifierNode, Measurable, MeasurementProxy, ModifierNode,
ModifierNodeContext, ModifierNodeElement, NodeCapabilities, NodeState,
};
use std::hash::{Hash, Hasher};
#[derive(Debug)]
struct CustomWidthNode {
extra_width: f32,
state: NodeState,
}
impl CustomWidthNode {
fn new(extra_width: f32) -> Self {
Self {
extra_width,
state: NodeState::new(),
}
}
}
impl DelegatableNode for CustomWidthNode {
fn node_state(&self) -> &NodeState {
&self.state
}
}
impl ModifierNode for CustomWidthNode {
fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
context.invalidate(cranpose_foundation::InvalidationKind::Layout);
}
fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
Some(self)
}
fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
Some(self)
}
}
impl LayoutModifierNode for CustomWidthNode {
fn measure(
&self,
_context: &mut dyn ModifierNodeContext,
measurable: &dyn Measurable,
constraints: Constraints,
) -> cranpose_ui_layout::LayoutModifierMeasureResult {
let placeable = measurable.measure(constraints);
cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size {
width: placeable.width() + self.extra_width,
height: placeable.height(),
})
}
fn min_intrinsic_width(&self, measurable: &dyn Measurable, height: f32) -> f32 {
measurable.min_intrinsic_width(height) + self.extra_width
}
fn max_intrinsic_width(&self, measurable: &dyn Measurable, height: f32) -> f32 {
measurable.max_intrinsic_width(height) + self.extra_width
}
fn min_intrinsic_height(&self, measurable: &dyn Measurable, width: f32) -> f32 {
measurable.min_intrinsic_height(width)
}
fn max_intrinsic_height(&self, measurable: &dyn Measurable, width: f32) -> f32 {
measurable.max_intrinsic_height(width)
}
fn create_measurement_proxy(&self) -> Option<Box<dyn MeasurementProxy>> {
Some(Box::new(CustomWidthProxy {
extra_width: self.extra_width,
}))
}
}
struct CustomWidthProxy {
extra_width: f32,
}
impl MeasurementProxy for CustomWidthProxy {
fn measure_proxy(
&self,
context: &mut dyn ModifierNodeContext,
wrapped: &dyn Measurable,
constraints: Constraints,
) -> cranpose_ui_layout::LayoutModifierMeasureResult {
let node = CustomWidthNode::new(self.extra_width);
node.measure(context, wrapped, constraints)
}
fn min_intrinsic_width_proxy(&self, wrapped: &dyn Measurable, height: f32) -> f32 {
let node = CustomWidthNode::new(self.extra_width);
node.min_intrinsic_width(wrapped, height)
}
fn max_intrinsic_width_proxy(&self, wrapped: &dyn Measurable, height: f32) -> f32 {
let node = CustomWidthNode::new(self.extra_width);
node.max_intrinsic_width(wrapped, height)
}
fn min_intrinsic_height_proxy(&self, wrapped: &dyn Measurable, width: f32) -> f32 {
let node = CustomWidthNode::new(self.extra_width);
node.min_intrinsic_height(wrapped, width)
}
fn max_intrinsic_height_proxy(&self, wrapped: &dyn Measurable, width: f32) -> f32 {
let node = CustomWidthNode::new(self.extra_width);
node.max_intrinsic_height(wrapped, width)
}
}
#[derive(Debug, Clone, PartialEq)]
struct CustomWidthElement {
extra_width: f32,
}
impl Hash for CustomWidthElement {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u32(self.extra_width.to_bits());
}
}
impl ModifierNodeElement for CustomWidthElement {
type Node = CustomWidthNode;
fn create(&self) -> Self::Node {
CustomWidthNode::new(self.extra_width)
}
fn update(&self, node: &mut Self::Node) {
if node.extra_width != self.extra_width {
node.extra_width = self.extra_width;
}
}
fn capabilities(&self) -> NodeCapabilities {
NodeCapabilities::LAYOUT
}
}
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let elements = vec![modifier_element(CustomWidthElement { extra_width: 20.0 })];
chain.update_from_slice(&elements, &mut context);
assert_eq!(chain.len(), 1);
assert!(chain.has_nodes_for_invalidation(cranpose_foundation::InvalidationKind::Layout));
let node = chain.node_mut::<CustomWidthNode>(0).unwrap();
let measurable = TestMeasurable {
intrinsic_width: 100.0,
intrinsic_height: 50.0,
};
let constraints = Constraints {
min_width: 0.0,
max_width: 300.0,
min_height: 0.0,
max_height: 200.0,
};
let result = node.measure(&mut context, &measurable, constraints);
assert_eq!(result.size.width, 120.0);
assert_eq!(result.size.height, 50.0);
let intrinsic_width = node.min_intrinsic_width(&measurable, 100.0);
assert_eq!(intrinsic_width, 120.0); }
#[test]
fn draw_command_updates_on_closure_change() {
use crate::draw::DrawCommand;
use cranpose_ui_graphics::{DrawPrimitive, Size};
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let executed = Rc::new(Cell::new(0));
let executed_1 = executed.clone();
let element_1 = modifier_element(DrawCommandElement::new(DrawCommand::Behind(Rc::new(
move |_size: Size| -> Vec<DrawPrimitive> {
executed_1.set(executed_1.get() + 1);
Vec::new()
},
))));
let executed_2 = executed.clone();
let element_2 = modifier_element(DrawCommandElement::new(DrawCommand::Behind(Rc::new(
move |_size: Size| -> Vec<DrawPrimitive> {
executed_2.set(executed_2.get() + 10);
Vec::new()
},
))));
chain.update_from_slice(&[element_1], &mut context);
{
let node = chain.node::<DrawCommandNode>(0).unwrap();
if let DrawCommand::Behind(ref func) = node.commands()[0] {
func(Size::ZERO);
}
}
assert_eq!(executed.get(), 1);
executed.set(0);
chain.update_from_slice(&[element_2], &mut context);
let node = chain.node::<DrawCommandNode>(0).unwrap();
if let DrawCommand::Behind(ref func) = node.commands()[0] {
func(Size::ZERO);
}
assert_eq!(
executed.get(),
10,
"Node should have updated to the new closure"
);
}
#[test]
fn stateful_measure_exposes_proxy_reconstruction_issue() {
use cranpose_foundation::{
Constraints, DelegatableNode, LayoutModifierNode, Measurable, MeasurementProxy,
ModifierNode, ModifierNodeContext, ModifierNodeElement, NodeCapabilities, NodeState, Size,
};
use std::hash::{Hash, Hasher};
#[derive(Debug)]
struct StatefulMeasureNode {
state: NodeState,
measure_count: Cell<i32>,
initial_value: i32,
}
impl StatefulMeasureNode {
fn new(initial_value: i32) -> Self {
Self {
state: NodeState::new(),
measure_count: Cell::new(0),
initial_value,
}
}
}
impl DelegatableNode for StatefulMeasureNode {
fn node_state(&self) -> &NodeState {
&self.state
}
}
impl ModifierNode for StatefulMeasureNode {
fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
Some(self)
}
fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
Some(self)
}
}
impl LayoutModifierNode for StatefulMeasureNode {
fn measure(
&self,
_context: &mut dyn ModifierNodeContext,
measurable: &dyn Measurable,
constraints: Constraints,
) -> cranpose_ui_layout::LayoutModifierMeasureResult {
let count = self.measure_count.get();
self.measure_count.set(count + 1);
let placeable = measurable.measure(constraints);
cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size {
width: placeable.width() + self.initial_value as f32,
height: placeable.height(),
})
}
fn create_measurement_proxy(&self) -> Option<Box<dyn MeasurementProxy>> {
Some(Box::new(StatefulMeasureProxy {
initial_value: self.initial_value,
}))
}
}
struct StatefulMeasureProxy {
initial_value: i32,
}
impl MeasurementProxy for StatefulMeasureProxy {
fn measure_proxy(
&self,
context: &mut dyn ModifierNodeContext,
wrapped: &dyn Measurable,
constraints: Constraints,
) -> cranpose_ui_layout::LayoutModifierMeasureResult {
let node = StatefulMeasureNode::new(self.initial_value);
node.measure(context, wrapped, constraints)
}
fn min_intrinsic_width_proxy(&self, wrapped: &dyn Measurable, height: f32) -> f32 {
wrapped.min_intrinsic_width(height) + self.initial_value as f32
}
fn max_intrinsic_width_proxy(&self, wrapped: &dyn Measurable, height: f32) -> f32 {
wrapped.max_intrinsic_width(height) + self.initial_value as f32
}
fn min_intrinsic_height_proxy(&self, wrapped: &dyn Measurable, _width: f32) -> f32 {
wrapped.min_intrinsic_height(_width)
}
fn max_intrinsic_height_proxy(&self, wrapped: &dyn Measurable, _width: f32) -> f32 {
wrapped.max_intrinsic_height(_width)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct StatefulMeasureElement {
initial_value: i32,
}
impl Hash for StatefulMeasureElement {
fn hash<H: Hasher>(&self, state: &mut H) {
self.initial_value.hash(state);
}
}
impl ModifierNodeElement for StatefulMeasureElement {
type Node = StatefulMeasureNode;
fn create(&self) -> Self::Node {
StatefulMeasureNode::new(self.initial_value)
}
fn update(&self, node: &mut Self::Node) {
node.initial_value = self.initial_value;
}
fn capabilities(&self) -> NodeCapabilities {
NodeCapabilities::LAYOUT
}
}
let mut chain = ModifierNodeChain::new();
let mut context = BasicModifierNodeContext::new();
let element = StatefulMeasureElement { initial_value: 10 };
let elements = vec![modifier_element(element)];
chain.update_from_slice(&elements, &mut context);
assert_eq!(chain.len(), 1);
let node = chain.node::<StatefulMeasureNode>(0).unwrap();
let measurable = TestMeasurable {
intrinsic_width: 100.0,
intrinsic_height: 50.0,
};
let constraints = Constraints {
min_width: 0.0,
max_width: 200.0,
min_height: 0.0,
max_height: 200.0,
};
let size1 = node.measure(&mut context, &measurable, constraints);
assert_eq!(size1.size.width, 110.0); assert_eq!(size1.size.height, 50.0);
let count_after_first = node.measure_count.get();
assert_eq!(
count_after_first, 1,
"First measure should increment count to 1"
);
let proxy = node.create_measurement_proxy().expect("Should have proxy");
let size2 = proxy.measure_proxy(&mut context, &measurable, constraints);
assert_eq!(size2.size.width, 110.0); assert_eq!(size2.size.height, 50.0);
let count_after_proxy = node.measure_count.get();
assert_eq!(
count_after_proxy, 1,
"Original node count unchanged - proxy creates fresh node"
);
}