Skip to main content

cranpose_ui/
modifier_nodes.rs

1use std::{
2    cell::Cell,
3    hash::{Hash, Hasher},
4    rc::Rc,
5};
6
7use cranpose_core::NodeId;
8use cranpose_foundation::{
9    Constraints, DelegatableNode, DrawModifierNode, DrawScope, InvalidationKind,
10    LayoutModifierNode, Measurable, ModifierNode, ModifierNodeContext, ModifierNodeElement,
11    NodeCapabilities, NodeState, PointerEvent, PointerEventKind, PointerInputNode, Size,
12};
13use cranpose_ui_layout::{Alignment, HorizontalAlignment, IntrinsicSize, VerticalAlignment};
14
15use crate::{
16    draw::DrawCommand,
17    modifier::{
18        BlendMode, Color, ColorFilter, CompositingStrategy, EdgeInsets, GraphicsLayer,
19        LayoutWeight, Point, RoundedCornerShape,
20    },
21};
22
23fn hash_f32_value<H: Hasher>(state: &mut H, value: f32) {
24    state.write_u32(value.to_bits());
25}
26
27fn hash_option_f32<H: Hasher>(state: &mut H, value: Option<f32>) {
28    match value {
29        Some(v) => {
30            state.write_u8(1);
31            hash_f32_value(state, v);
32        }
33        None => state.write_u8(0),
34    }
35}
36
37fn hash_graphics_layer<H: Hasher>(state: &mut H, layer: &GraphicsLayer) {
38    hash_f32_value(state, layer.alpha);
39    hash_f32_value(state, layer.scale);
40    hash_f32_value(state, layer.scale_x);
41    hash_f32_value(state, layer.scale_y);
42    hash_f32_value(state, layer.rotation_x);
43    hash_f32_value(state, layer.rotation_y);
44    hash_f32_value(state, layer.rotation_z);
45    hash_f32_value(state, layer.camera_distance);
46    hash_f32_value(state, layer.transform_origin.pivot_fraction_x);
47    hash_f32_value(state, layer.transform_origin.pivot_fraction_y);
48    hash_f32_value(state, layer.translation_x);
49    hash_f32_value(state, layer.translation_y);
50    hash_f32_value(state, layer.shadow_elevation);
51    hash_f32_value(state, layer.ambient_shadow_color.r());
52    hash_f32_value(state, layer.ambient_shadow_color.g());
53    hash_f32_value(state, layer.ambient_shadow_color.b());
54    hash_f32_value(state, layer.ambient_shadow_color.a());
55    hash_f32_value(state, layer.spot_shadow_color.r());
56    hash_f32_value(state, layer.spot_shadow_color.g());
57    hash_f32_value(state, layer.spot_shadow_color.b());
58    hash_f32_value(state, layer.spot_shadow_color.a());
59    match layer.shape {
60        crate::modifier::LayerShape::Rectangle => {
61            state.write_u8(0);
62        }
63        crate::modifier::LayerShape::Rounded(shape) => {
64            state.write_u8(1);
65            let radii = shape.radii();
66            hash_f32_value(state, radii.top_left);
67            hash_f32_value(state, radii.top_right);
68            hash_f32_value(state, radii.bottom_right);
69            hash_f32_value(state, radii.bottom_left);
70        }
71    }
72    state.write_u8(layer.clip as u8);
73    match layer.color_filter {
74        Some(ColorFilter::Tint(color)) => {
75            state.write_u8(1);
76            hash_f32_value(state, color.r());
77            hash_f32_value(state, color.g());
78            hash_f32_value(state, color.b());
79            hash_f32_value(state, color.a());
80        }
81        Some(ColorFilter::Modulate(color)) => {
82            state.write_u8(2);
83            hash_f32_value(state, color.r());
84            hash_f32_value(state, color.g());
85            hash_f32_value(state, color.b());
86            hash_f32_value(state, color.a());
87        }
88        Some(ColorFilter::Matrix(matrix)) => {
89            state.write_u8(3);
90            for value in matrix {
91                hash_f32_value(state, value);
92            }
93        }
94        None => state.write_u8(0),
95    }
96    state.write_u8(layer.render_effect.is_some() as u8);
97    state.write_u8(layer.backdrop_effect.is_some() as u8);
98    let compositing_tag = match layer.compositing_strategy {
99        CompositingStrategy::Auto => 0,
100        CompositingStrategy::Offscreen => 1,
101        CompositingStrategy::ModulateAlpha => 2,
102    };
103    state.write_u8(compositing_tag);
104    let blend_tag = match layer.blend_mode {
105        BlendMode::Clear => 0,
106        BlendMode::Src => 1,
107        BlendMode::Dst => 2,
108        BlendMode::SrcOver => 3,
109        BlendMode::DstOver => 4,
110        BlendMode::SrcIn => 5,
111        BlendMode::DstIn => 6,
112        BlendMode::SrcOut => 7,
113        BlendMode::DstOut => 8,
114        BlendMode::SrcAtop => 9,
115        BlendMode::DstAtop => 10,
116        BlendMode::Xor => 11,
117        BlendMode::Plus => 12,
118        BlendMode::Modulate => 13,
119        BlendMode::Screen => 14,
120        BlendMode::Overlay => 15,
121        BlendMode::Darken => 16,
122        BlendMode::Lighten => 17,
123        BlendMode::ColorDodge => 18,
124        BlendMode::ColorBurn => 19,
125        BlendMode::HardLight => 20,
126        BlendMode::SoftLight => 21,
127        BlendMode::Difference => 22,
128        BlendMode::Exclusion => 23,
129        BlendMode::Multiply => 24,
130        BlendMode::Hue => 25,
131        BlendMode::Saturation => 26,
132        BlendMode::Color => 27,
133        BlendMode::Luminosity => 28,
134    };
135    state.write_u8(blend_tag);
136}
137
138fn hash_horizontal_alignment<H: Hasher>(state: &mut H, alignment: HorizontalAlignment) {
139    let tag = match alignment {
140        HorizontalAlignment::Start => 0,
141        HorizontalAlignment::CenterHorizontally => 1,
142        HorizontalAlignment::End => 2,
143    };
144    state.write_u8(tag);
145}
146
147fn hash_vertical_alignment<H: Hasher>(state: &mut H, alignment: VerticalAlignment) {
148    let tag = match alignment {
149        VerticalAlignment::Top => 0,
150        VerticalAlignment::CenterVertically => 1,
151        VerticalAlignment::Bottom => 2,
152    };
153    state.write_u8(tag);
154}
155
156fn hash_alignment<H: Hasher>(state: &mut H, alignment: Alignment) {
157    hash_horizontal_alignment(state, alignment.horizontal);
158    hash_vertical_alignment(state, alignment.vertical);
159}
160
161macro_rules! impl_layout_modifier_node {
162    ($ty:ty) => {
163        impl ModifierNode for $ty {
164            fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
165                Some(self)
166            }
167
168            fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
169                Some(self)
170            }
171        }
172    };
173    ($ty:ty, invalidate = $invalidation:expr) => {
174        impl ModifierNode for $ty {
175            fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
176                context.invalidate($invalidation);
177            }
178
179            fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
180                Some(self)
181            }
182
183            fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
184                Some(self)
185            }
186        }
187    };
188}
189
190macro_rules! impl_draw_modifier_node {
191    ($ty:ty) => {
192        impl ModifierNode for $ty {
193            fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
194                context.invalidate(InvalidationKind::Draw);
195            }
196
197            fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
198                Some(self)
199            }
200
201            fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
202                Some(self)
203            }
204        }
205
206        impl DrawModifierNode for $ty {
207            fn draw(&self, _draw_scope: &mut dyn DrawScope) {}
208        }
209    };
210}
211
212macro_rules! forward_intrinsics_to_child {
213    () => {
214        fn min_intrinsic_width(&self, measurable: &dyn Measurable, height: f32) -> f32 {
215            measurable.min_intrinsic_width(height)
216        }
217
218        fn max_intrinsic_width(&self, measurable: &dyn Measurable, height: f32) -> f32 {
219            measurable.max_intrinsic_width(height)
220        }
221
222        fn min_intrinsic_height(&self, measurable: &dyn Measurable, width: f32) -> f32 {
223            measurable.min_intrinsic_height(width)
224        }
225
226        fn max_intrinsic_height(&self, measurable: &dyn Measurable, width: f32) -> f32 {
227            measurable.max_intrinsic_height(width)
228        }
229    };
230}
231
232macro_rules! impl_sink_reporter_element {
233    ($element:ty, $node:ty) => {
234        impl ModifierNodeElement for $element {
235            type Node = $node;
236
237            fn create(&self) -> Self::Node {
238                <$node>::new(self.sink.clone())
239            }
240
241            fn update(&self, node: &mut Self::Node) {
242                node.sink = self.sink.clone();
243            }
244
245            fn capabilities(&self) -> NodeCapabilities {
246                NodeCapabilities::LAYOUT
247            }
248        }
249    };
250}
251
252fn measure_pass_through(
253    measurable: &dyn Measurable,
254    constraints: Constraints,
255    offset: impl FnOnce(Size) -> (f32, f32),
256) -> cranpose_ui_layout::LayoutModifierMeasureResult {
257    let placeable = measurable.measure(constraints);
258    let size = Size {
259        width: placeable.width(),
260        height: placeable.height(),
261    };
262    let (x, y) = offset(size);
263    cranpose_ui_layout::LayoutModifierMeasureResult::new(size, x, y)
264}
265
266fn attach_draw_observer(
267    node_id_cell: &Cell<Option<NodeId>>,
268    context: &mut dyn ModifierNodeContext,
269) {
270    node_id_cell.set(context.node_id());
271    context.invalidate(InvalidationKind::Draw);
272}
273
274fn detach_draw_observer(node_id_cell: &Cell<Option<NodeId>>) {
275    if let Some(node_id) = node_id_cell.replace(None) {
276        crate::render_state::clear_draw_observations_for_node(node_id);
277    }
278}
279
280enum SizeAxis {
281    Width,
282    Height,
283}
284
285fn size_intrinsic(
286    target: Constraints,
287    axis: SizeAxis,
288    enforce_incoming: bool,
289    cross: f32,
290    intrinsic: impl FnOnce(f32) -> f32,
291) -> f32 {
292    let (target_min, target_max, cross_min, cross_max) = match axis {
293        SizeAxis::Width => (
294            target.min_width,
295            target.max_width,
296            target.min_height,
297            target.max_height,
298        ),
299        SizeAxis::Height => (
300            target.min_height,
301            target.max_height,
302            target.min_width,
303            target.max_width,
304        ),
305    };
306    if target_min == target_max && target_max != f32::INFINITY {
307        target_max
308    } else {
309        let child_cross = if enforce_incoming {
310            cross
311        } else {
312            cross.clamp(cross_min, cross_max)
313        };
314        intrinsic(child_cross).clamp(target_min, target_max)
315    }
316}
317
318/// Node that adds padding around its content.
319#[derive(Debug)]
320pub struct PaddingNode {
321    padding: EdgeInsets,
322    state: NodeState,
323}
324
325impl PaddingNode {
326    pub fn new(padding: EdgeInsets) -> Self {
327        Self {
328            padding,
329            state: NodeState::new(),
330        }
331    }
332
333    pub fn padding(&self) -> EdgeInsets {
334        self.padding
335    }
336}
337
338impl DelegatableNode for PaddingNode {
339    fn node_state(&self) -> &NodeState {
340        &self.state
341    }
342}
343
344impl_layout_modifier_node!(PaddingNode, invalidate = InvalidationKind::Layout);
345
346impl LayoutModifierNode for PaddingNode {
347    fn measure(
348        &self,
349        _context: &mut dyn ModifierNodeContext,
350        measurable: &dyn Measurable,
351        constraints: Constraints,
352    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
353        let horizontal_padding = self.padding.horizontal_sum();
354        let vertical_padding = self.padding.vertical_sum();
355
356        let inner_constraints = Constraints {
357            min_width: (constraints.min_width - horizontal_padding).max(0.0),
358            max_width: (constraints.max_width - horizontal_padding).max(0.0),
359            min_height: (constraints.min_height - vertical_padding).max(0.0),
360            max_height: (constraints.max_height - vertical_padding).max(0.0),
361        };
362
363        let inner_placeable = measurable.measure(inner_constraints);
364        let inner_width = inner_placeable.width();
365        let inner_height = inner_placeable.height();
366
367        let (width, height) = constraints.constrain(
368            inner_width + horizontal_padding,
369            inner_height + vertical_padding,
370        );
371
372        cranpose_ui_layout::LayoutModifierMeasureResult::new(
373            Size { width, height },
374            self.padding.left,
375            self.padding.top,
376        )
377    }
378
379    fn min_intrinsic_width(&self, measurable: &dyn Measurable, height: f32) -> f32 {
380        let vertical_padding = self.padding.vertical_sum();
381        let inner_height = (height - vertical_padding).max(0.0);
382        let inner_width = measurable.min_intrinsic_width(inner_height);
383        inner_width + self.padding.horizontal_sum()
384    }
385
386    fn max_intrinsic_width(&self, measurable: &dyn Measurable, height: f32) -> f32 {
387        let vertical_padding = self.padding.vertical_sum();
388        let inner_height = (height - vertical_padding).max(0.0);
389        let inner_width = measurable.max_intrinsic_width(inner_height);
390        inner_width + self.padding.horizontal_sum()
391    }
392
393    fn min_intrinsic_height(&self, measurable: &dyn Measurable, width: f32) -> f32 {
394        let horizontal_padding = self.padding.horizontal_sum();
395        let inner_width = (width - horizontal_padding).max(0.0);
396        let inner_height = measurable.min_intrinsic_height(inner_width);
397        inner_height + self.padding.vertical_sum()
398    }
399
400    fn max_intrinsic_height(&self, measurable: &dyn Measurable, width: f32) -> f32 {
401        let horizontal_padding = self.padding.horizontal_sum();
402        let inner_width = (width - horizontal_padding).max(0.0);
403        let inner_height = measurable.max_intrinsic_height(inner_width);
404        inner_height + self.padding.vertical_sum()
405    }
406}
407
408/// Element that creates and updates padding nodes.
409#[derive(Debug, Clone, PartialEq)]
410pub struct PaddingElement {
411    padding: EdgeInsets,
412}
413
414impl PaddingElement {
415    pub fn new(padding: EdgeInsets) -> Self {
416        Self { padding }
417    }
418}
419
420impl Hash for PaddingElement {
421    fn hash<H: Hasher>(&self, state: &mut H) {
422        hash_f32_value(state, self.padding.left);
423        hash_f32_value(state, self.padding.top);
424        hash_f32_value(state, self.padding.right);
425        hash_f32_value(state, self.padding.bottom);
426    }
427}
428
429impl ModifierNodeElement for PaddingElement {
430    type Node = PaddingNode;
431
432    fn create(&self) -> Self::Node {
433        PaddingNode::new(self.padding)
434    }
435
436    fn update(&self, node: &mut Self::Node) {
437        if node.padding != self.padding {
438            node.padding = self.padding;
439        }
440    }
441
442    fn capabilities(&self) -> NodeCapabilities {
443        NodeCapabilities::LAYOUT
444    }
445}
446
447/// Node that draws a background behind its content.
448#[derive(Debug)]
449pub struct BackgroundNode {
450    color: Color,
451    shape: Option<RoundedCornerShape>,
452    state: NodeState,
453}
454
455impl BackgroundNode {
456    pub fn new(color: Color) -> Self {
457        Self {
458            color,
459            shape: None,
460            state: NodeState::new(),
461        }
462    }
463
464    pub fn color(&self) -> Color {
465        self.color
466    }
467
468    pub fn shape(&self) -> Option<RoundedCornerShape> {
469        self.shape
470    }
471}
472
473impl DelegatableNode for BackgroundNode {
474    fn node_state(&self) -> &NodeState {
475        &self.state
476    }
477}
478
479impl_draw_modifier_node!(BackgroundNode);
480
481/// Element that creates and updates background nodes.
482#[derive(Debug, Clone, PartialEq)]
483pub struct BackgroundElement {
484    color: Color,
485}
486
487impl BackgroundElement {
488    pub fn new(color: Color) -> Self {
489        Self { color }
490    }
491}
492
493impl Hash for BackgroundElement {
494    fn hash<H: Hasher>(&self, state: &mut H) {
495        hash_f32_value(state, self.color.0);
496        hash_f32_value(state, self.color.1);
497        hash_f32_value(state, self.color.2);
498        hash_f32_value(state, self.color.3);
499    }
500}
501
502impl ModifierNodeElement for BackgroundElement {
503    type Node = BackgroundNode;
504
505    fn create(&self) -> Self::Node {
506        BackgroundNode::new(self.color)
507    }
508
509    fn update(&self, node: &mut Self::Node) {
510        if node.color != self.color {
511            node.color = self.color;
512        }
513    }
514
515    fn capabilities(&self) -> NodeCapabilities {
516        NodeCapabilities::DRAW
517    }
518}
519
520/// Node that tracks the latest rounded corner shape.
521#[derive(Debug)]
522pub struct CornerShapeNode {
523    shape: RoundedCornerShape,
524    state: NodeState,
525}
526
527impl CornerShapeNode {
528    pub fn new(shape: RoundedCornerShape) -> Self {
529        Self {
530            shape,
531            state: NodeState::new(),
532        }
533    }
534
535    pub fn shape(&self) -> RoundedCornerShape {
536        self.shape
537    }
538}
539
540impl DelegatableNode for CornerShapeNode {
541    fn node_state(&self) -> &NodeState {
542        &self.state
543    }
544}
545
546impl_draw_modifier_node!(CornerShapeNode);
547
548/// Element that creates and updates corner shape nodes.
549#[derive(Debug, Clone, PartialEq)]
550pub struct CornerShapeElement {
551    shape: RoundedCornerShape,
552}
553
554impl CornerShapeElement {
555    pub fn new(shape: RoundedCornerShape) -> Self {
556        Self { shape }
557    }
558}
559
560impl Hash for CornerShapeElement {
561    fn hash<H: Hasher>(&self, state: &mut H) {
562        let radii = self.shape.radii();
563        hash_f32_value(state, radii.top_left);
564        hash_f32_value(state, radii.top_right);
565        hash_f32_value(state, radii.bottom_right);
566        hash_f32_value(state, radii.bottom_left);
567    }
568}
569
570impl ModifierNodeElement for CornerShapeElement {
571    type Node = CornerShapeNode;
572
573    fn create(&self) -> Self::Node {
574        CornerShapeNode::new(self.shape)
575    }
576
577    fn update(&self, node: &mut Self::Node) {
578        if node.shape != self.shape {
579            node.shape = self.shape;
580        }
581    }
582
583    fn capabilities(&self) -> NodeCapabilities {
584        NodeCapabilities::DRAW
585    }
586}
587
588pub struct GraphicsLayerNode {
589    layer: GraphicsLayer,
590    layer_resolver: Option<Rc<dyn Fn() -> GraphicsLayer>>,
591    node_id: Rc<Cell<Option<NodeId>>>,
592    state: NodeState,
593}
594
595impl GraphicsLayerNode {
596    pub fn new(layer: GraphicsLayer) -> Self {
597        Self {
598            layer,
599            layer_resolver: None,
600            node_id: Rc::new(Cell::new(None)),
601            state: NodeState::new(),
602        }
603    }
604
605    pub fn new_lazy(layer_resolver: Rc<dyn Fn() -> GraphicsLayer>) -> Self {
606        Self {
607            layer: GraphicsLayer::default(),
608            layer_resolver: Some(layer_resolver),
609            node_id: Rc::new(Cell::new(None)),
610            state: NodeState::new(),
611        }
612    }
613
614    #[cfg(test)]
615    pub fn layer(&self) -> GraphicsLayer {
616        if let Some(resolve) = self.layer_resolver() {
617            resolve()
618        } else {
619            self.layer.clone()
620        }
621    }
622
623    pub fn layer_snapshot(&self) -> GraphicsLayer {
624        self.layer.clone()
625    }
626
627    pub fn layer_resolver(&self) -> Option<Rc<dyn Fn() -> GraphicsLayer>> {
628        self.layer_resolver.as_ref().map(|resolve| {
629            let resolve = resolve.clone();
630            let node_id = Rc::clone(&self.node_id);
631            Rc::new(move || {
632                if let Some(node_id) = node_id.get() {
633                    let scope = crate::render_state::DrawObservationScope::new(node_id, usize::MAX);
634                    crate::render_state::observe_draw_reads(scope, || resolve())
635                } else {
636                    resolve()
637                }
638            }) as Rc<dyn Fn() -> GraphicsLayer>
639        })
640    }
641
642    fn set_static(&mut self, layer: GraphicsLayer) {
643        let changed = self.layer != layer || self.layer_resolver.is_some();
644        self.layer = layer;
645        self.layer_resolver = None;
646        if changed && let Some(node_id) = self.node_id.get() {
647            crate::render_state::schedule_draw_repass(node_id);
648        }
649    }
650
651    fn set_lazy(&mut self, layer_resolver: Rc<dyn Fn() -> GraphicsLayer>) {
652        let changed = self
653            .layer_resolver
654            .as_ref()
655            .is_none_or(|current| !Rc::ptr_eq(current, &layer_resolver));
656        self.layer_resolver = Some(layer_resolver);
657        if changed && let Some(node_id) = self.node_id.get() {
658            crate::render_state::schedule_draw_repass(node_id);
659        }
660    }
661}
662
663impl DelegatableNode for GraphicsLayerNode {
664    fn node_state(&self) -> &NodeState {
665        &self.state
666    }
667}
668
669impl ModifierNode for GraphicsLayerNode {
670    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
671        attach_draw_observer(&self.node_id, context);
672    }
673
674    fn on_detach(&mut self) {
675        detach_draw_observer(&self.node_id);
676    }
677}
678
679impl std::fmt::Debug for GraphicsLayerNode {
680    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
681        f.debug_struct("GraphicsLayerNode")
682            .field("layer", &self.layer)
683            .field("lazy", &self.layer_resolver.is_some())
684            .finish()
685    }
686}
687
688#[derive(Debug, Clone, PartialEq)]
689pub struct GraphicsLayerElement {
690    layer: GraphicsLayer,
691}
692
693impl GraphicsLayerElement {
694    pub fn new(layer: GraphicsLayer) -> Self {
695        Self { layer }
696    }
697}
698
699impl Hash for GraphicsLayerElement {
700    fn hash<H: Hasher>(&self, state: &mut H) {
701        hash_graphics_layer(state, &self.layer);
702    }
703}
704
705impl ModifierNodeElement for GraphicsLayerElement {
706    type Node = GraphicsLayerNode;
707
708    fn create(&self) -> Self::Node {
709        GraphicsLayerNode::new(self.layer.clone())
710    }
711
712    fn update(&self, node: &mut Self::Node) {
713        node.set_static(self.layer.clone());
714    }
715
716    fn capabilities(&self) -> NodeCapabilities {
717        NodeCapabilities::DRAW
718    }
719}
720
721#[derive(Clone)]
722pub struct LazyGraphicsLayerElement {
723    layer_resolver: Rc<dyn Fn() -> GraphicsLayer>,
724}
725
726impl LazyGraphicsLayerElement {
727    pub fn new(layer_resolver: Rc<dyn Fn() -> GraphicsLayer>) -> Self {
728        Self { layer_resolver }
729    }
730}
731
732impl std::fmt::Debug for LazyGraphicsLayerElement {
733    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
734        f.debug_struct("LazyGraphicsLayerElement")
735            .field("resolver", &"<closure>")
736            .finish()
737    }
738}
739
740impl PartialEq for LazyGraphicsLayerElement {
741    fn eq(&self, other: &Self) -> bool {
742        Rc::ptr_eq(&self.layer_resolver, &other.layer_resolver)
743    }
744}
745
746impl Eq for LazyGraphicsLayerElement {}
747
748impl Hash for LazyGraphicsLayerElement {
749    fn hash<H: Hasher>(&self, state: &mut H) {
750        let ptr = Rc::as_ptr(&self.layer_resolver) as *const ();
751        ptr.hash(state);
752    }
753}
754
755impl ModifierNodeElement for LazyGraphicsLayerElement {
756    type Node = GraphicsLayerNode;
757
758    fn create(&self) -> Self::Node {
759        GraphicsLayerNode::new_lazy(self.layer_resolver.clone())
760    }
761
762    fn update(&self, node: &mut Self::Node) {
763        node.set_lazy(self.layer_resolver.clone());
764    }
765
766    fn capabilities(&self) -> NodeCapabilities {
767        NodeCapabilities::DRAW
768    }
769
770    fn always_update(&self) -> bool {
771        true
772    }
773
774    fn auto_invalidate_on_update(&self) -> bool {
775        false
776    }
777}
778
779/// Node that enforces size constraints on its content.
780///
781/// Matches Kotlin: `SizeNode` in foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Size.kt
782#[derive(Debug)]
783pub struct SizeNode {
784    min_width: Option<f32>,
785    max_width: Option<f32>,
786    min_height: Option<f32>,
787    max_height: Option<f32>,
788    enforce_incoming: bool,
789    state: NodeState,
790}
791
792impl SizeNode {
793    pub fn new(
794        min_width: Option<f32>,
795        max_width: Option<f32>,
796        min_height: Option<f32>,
797        max_height: Option<f32>,
798        enforce_incoming: bool,
799    ) -> Self {
800        Self {
801            min_width,
802            max_width,
803            min_height,
804            max_height,
805            enforce_incoming,
806            state: NodeState::new(),
807        }
808    }
809
810    fn target_constraints(&self) -> Constraints {
811        let max_width = self.max_width.map(|v| v.max(0.0)).unwrap_or(f32::INFINITY);
812        let max_height = self.max_height.map(|v| v.max(0.0)).unwrap_or(f32::INFINITY);
813
814        let min_width = self
815            .min_width
816            .map(|v| {
817                let clamped = v.clamp(0.0, max_width);
818                if clamped == f32::INFINITY {
819                    0.0
820                } else {
821                    clamped
822                }
823            })
824            .unwrap_or(0.0);
825
826        let min_height = self
827            .min_height
828            .map(|v| {
829                let clamped = v.clamp(0.0, max_height);
830                if clamped == f32::INFINITY {
831                    0.0
832                } else {
833                    clamped
834                }
835            })
836            .unwrap_or(0.0);
837
838        Constraints {
839            min_width,
840            max_width,
841            min_height,
842            max_height,
843        }
844    }
845
846    pub fn min_width(&self) -> Option<f32> {
847        self.min_width
848    }
849
850    pub fn max_width(&self) -> Option<f32> {
851        self.max_width
852    }
853
854    pub fn min_height(&self) -> Option<f32> {
855        self.min_height
856    }
857
858    pub fn max_height(&self) -> Option<f32> {
859        self.max_height
860    }
861
862    pub fn enforce_incoming(&self) -> bool {
863        self.enforce_incoming
864    }
865}
866
867impl DelegatableNode for SizeNode {
868    fn node_state(&self) -> &NodeState {
869        &self.state
870    }
871}
872
873impl_layout_modifier_node!(SizeNode, invalidate = InvalidationKind::Layout);
874
875impl LayoutModifierNode for SizeNode {
876    fn measure(
877        &self,
878        _context: &mut dyn ModifierNodeContext,
879        measurable: &dyn Measurable,
880        constraints: Constraints,
881    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
882        let target = self.target_constraints();
883
884        let wrapped_constraints = if self.enforce_incoming {
885            Constraints {
886                min_width: target
887                    .min_width
888                    .max(constraints.min_width)
889                    .min(constraints.max_width),
890                max_width: target
891                    .max_width
892                    .min(constraints.max_width)
893                    .max(constraints.min_width),
894                min_height: target
895                    .min_height
896                    .max(constraints.min_height)
897                    .min(constraints.max_height),
898                max_height: target
899                    .max_height
900                    .min(constraints.max_height)
901                    .max(constraints.min_height),
902            }
903        } else {
904            let resolved_min_width = if self.min_width.is_some() {
905                target.min_width
906            } else {
907                constraints.min_width.min(target.max_width)
908            };
909            let resolved_max_width = if self.max_width.is_some() {
910                target.max_width
911            } else {
912                constraints.max_width.max(target.min_width)
913            };
914            let resolved_min_height = if self.min_height.is_some() {
915                target.min_height
916            } else {
917                constraints.min_height.min(target.max_height)
918            };
919            let resolved_max_height = if self.max_height.is_some() {
920                target.max_height
921            } else {
922                constraints.max_height.max(target.min_height)
923            };
924
925            Constraints {
926                min_width: resolved_min_width,
927                max_width: resolved_max_width,
928                min_height: resolved_min_height,
929                max_height: resolved_max_height,
930            }
931        };
932
933        let placeable = measurable.measure(wrapped_constraints);
934        let measured_width = placeable.width();
935        let measured_height = placeable.height();
936
937        let result_width = if self.min_width.is_some()
938            && self.max_width.is_some()
939            && self.min_width == self.max_width
940            && target.min_width >= wrapped_constraints.min_width
941            && target.min_width <= wrapped_constraints.max_width
942        {
943            target.min_width
944        } else {
945            measured_width
946        };
947
948        let result_height = if self.min_height.is_some()
949            && self.max_height.is_some()
950            && self.min_height == self.max_height
951            && target.min_height >= wrapped_constraints.min_height
952            && target.min_height <= wrapped_constraints.max_height
953        {
954            target.min_height
955        } else {
956            measured_height
957        };
958
959        cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size {
960            width: result_width,
961            height: result_height,
962        })
963    }
964
965    fn min_intrinsic_width(&self, measurable: &dyn Measurable, height: f32) -> f32 {
966        size_intrinsic(
967            self.target_constraints(),
968            SizeAxis::Width,
969            self.enforce_incoming,
970            height,
971            |h| measurable.min_intrinsic_width(h),
972        )
973    }
974
975    fn max_intrinsic_width(&self, measurable: &dyn Measurable, height: f32) -> f32 {
976        size_intrinsic(
977            self.target_constraints(),
978            SizeAxis::Width,
979            self.enforce_incoming,
980            height,
981            |h| measurable.max_intrinsic_width(h),
982        )
983    }
984
985    fn min_intrinsic_height(&self, measurable: &dyn Measurable, width: f32) -> f32 {
986        size_intrinsic(
987            self.target_constraints(),
988            SizeAxis::Height,
989            self.enforce_incoming,
990            width,
991            |w| measurable.min_intrinsic_height(w),
992        )
993    }
994
995    fn max_intrinsic_height(&self, measurable: &dyn Measurable, width: f32) -> f32 {
996        size_intrinsic(
997            self.target_constraints(),
998            SizeAxis::Height,
999            self.enforce_incoming,
1000            width,
1001            |w| measurable.max_intrinsic_height(w),
1002        )
1003    }
1004}
1005
1006/// Element that creates and updates size nodes.
1007///
1008/// Matches Kotlin: `SizeElement` in foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Size.kt
1009#[derive(Debug, Clone, PartialEq)]
1010pub struct SizeElement {
1011    min_width: Option<f32>,
1012    max_width: Option<f32>,
1013    min_height: Option<f32>,
1014    max_height: Option<f32>,
1015    enforce_incoming: bool,
1016}
1017
1018impl SizeElement {
1019    pub fn new(width: Option<f32>, height: Option<f32>) -> Self {
1020        Self {
1021            min_width: width,
1022            max_width: width,
1023            min_height: height,
1024            max_height: height,
1025            enforce_incoming: true,
1026        }
1027    }
1028
1029    pub fn with_constraints(
1030        min_width: Option<f32>,
1031        max_width: Option<f32>,
1032        min_height: Option<f32>,
1033        max_height: Option<f32>,
1034        enforce_incoming: bool,
1035    ) -> Self {
1036        Self {
1037            min_width,
1038            max_width,
1039            min_height,
1040            max_height,
1041            enforce_incoming,
1042        }
1043    }
1044}
1045
1046impl Hash for SizeElement {
1047    fn hash<H: Hasher>(&self, state: &mut H) {
1048        hash_option_f32(state, self.min_width);
1049        hash_option_f32(state, self.max_width);
1050        hash_option_f32(state, self.min_height);
1051        hash_option_f32(state, self.max_height);
1052        self.enforce_incoming.hash(state);
1053    }
1054}
1055
1056impl ModifierNodeElement for SizeElement {
1057    type Node = SizeNode;
1058
1059    fn create(&self) -> Self::Node {
1060        SizeNode::new(
1061            self.min_width,
1062            self.max_width,
1063            self.min_height,
1064            self.max_height,
1065            self.enforce_incoming,
1066        )
1067    }
1068
1069    fn update(&self, node: &mut Self::Node) {
1070        if node.min_width != self.min_width
1071            || node.max_width != self.max_width
1072            || node.min_height != self.min_height
1073            || node.max_height != self.max_height
1074            || node.enforce_incoming != self.enforce_incoming
1075        {
1076            node.min_width = self.min_width;
1077            node.max_width = self.max_width;
1078            node.min_height = self.min_height;
1079            node.max_height = self.max_height;
1080            node.enforce_incoming = self.enforce_incoming;
1081        }
1082    }
1083
1084    fn capabilities(&self) -> NodeCapabilities {
1085        NodeCapabilities::LAYOUT
1086    }
1087
1088    fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
1089        Some(InvalidationKind::Layout)
1090    }
1091}
1092
1093use std::cell::RefCell;
1094
1095use cranpose_foundation::DRAG_THRESHOLD;
1096
1097pub struct ClickableNode {
1098    on_press: Option<Rc<dyn Fn(Point)>>,
1099    on_click: Rc<dyn Fn(Point)>,
1100    state: NodeState,
1101    press_position: Rc<RefCell<Option<Point>>>,
1102    cached_handler: Rc<dyn Fn(PointerEvent)>,
1103}
1104
1105impl std::fmt::Debug for ClickableNode {
1106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1107        f.debug_struct("ClickableNode").finish()
1108    }
1109}
1110
1111impl ClickableNode {
1112    pub fn new(on_click: impl Fn(Point) + 'static) -> Self {
1113        Self::with_handler(Rc::new(on_click))
1114    }
1115
1116    pub fn with_handler(on_click: Rc<dyn Fn(Point)>) -> Self {
1117        Self::with_handlers(None, on_click)
1118    }
1119
1120    pub fn with_handlers(on_press: Option<Rc<dyn Fn(Point)>>, on_click: Rc<dyn Fn(Point)>) -> Self {
1121        let press_position = Rc::new(RefCell::new(None));
1122        let cached_handler =
1123            Self::create_handler(on_press.clone(), on_click.clone(), press_position.clone());
1124        Self {
1125            on_press,
1126            on_click,
1127            state: NodeState::new(),
1128            press_position,
1129            cached_handler,
1130        }
1131    }
1132
1133    fn create_handler(
1134        on_press: Option<Rc<dyn Fn(Point)>>,
1135        on_click: Rc<dyn Fn(Point)>,
1136        press_position: Rc<RefCell<Option<Point>>>,
1137    ) -> Rc<dyn Fn(PointerEvent)> {
1138        Rc::new(move |event: PointerEvent| {
1139            if event.id != 0 {
1140                return;
1141            }
1142
1143            if event.is_consumed() {
1144                *press_position.borrow_mut() = None;
1145                return;
1146            }
1147
1148            match event.kind {
1149                PointerEventKind::Down => {
1150                    *press_position.borrow_mut() = Some(Point {
1151                        x: event.global_position.x,
1152                        y: event.global_position.y,
1153                    });
1154                    if let Some(on_press) = on_press.as_ref() {
1155                        on_press(event.position);
1156                    }
1157                }
1158                PointerEventKind::Move => {}
1159                PointerEventKind::Up => {
1160                    let press_pos_value = *press_position.borrow();
1161
1162                    let should_click = if let Some(press_pos) = press_pos_value {
1163                        let dx = event.global_position.x - press_pos.x;
1164                        let dy = event.global_position.y - press_pos.y;
1165                        let distance = (dx * dx + dy * dy).sqrt();
1166                        distance <= DRAG_THRESHOLD
1167                    } else {
1168                        true
1169                    };
1170
1171                    *press_position.borrow_mut() = None;
1172
1173                    if should_click {
1174                        on_click(Point {
1175                            x: event.position.x,
1176                            y: event.position.y,
1177                        });
1178                        event.consume();
1179                    }
1180                }
1181                PointerEventKind::Cancel => {
1182                    *press_position.borrow_mut() = None;
1183                }
1184                PointerEventKind::Scroll
1185                | PointerEventKind::Zoom
1186                | PointerEventKind::RotaryScrollPre
1187                | PointerEventKind::RotaryScroll
1188                | PointerEventKind::Enter
1189                | PointerEventKind::Exit => {}
1190            }
1191        })
1192    }
1193
1194    pub fn handler(&self) -> Rc<dyn Fn(Point)> {
1195        self.on_click.clone()
1196    }
1197}
1198
1199impl DelegatableNode for ClickableNode {
1200    fn node_state(&self) -> &NodeState {
1201        &self.state
1202    }
1203}
1204
1205impl ModifierNode for ClickableNode {
1206    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
1207        context.invalidate(cranpose_foundation::InvalidationKind::PointerInput);
1208    }
1209
1210    fn as_pointer_input_node(&self) -> Option<&dyn PointerInputNode> {
1211        Some(self)
1212    }
1213
1214    fn as_pointer_input_node_mut(&mut self) -> Option<&mut dyn PointerInputNode> {
1215        Some(self)
1216    }
1217}
1218
1219impl PointerInputNode for ClickableNode {
1220    fn on_pointer_event(
1221        &mut self,
1222        _context: &mut dyn ModifierNodeContext,
1223        event: &PointerEvent,
1224    ) -> bool {
1225        (self.cached_handler)(event.clone());
1226        event.is_consumed()
1227    }
1228
1229    fn hit_test(&self, _x: f32, _y: f32) -> bool {
1230        true
1231    }
1232
1233    fn pointer_input_handler(&self) -> Option<Rc<dyn Fn(PointerEvent)>> {
1234        Some(self.cached_handler.clone())
1235    }
1236}
1237
1238/// Element that creates and updates clickable nodes.
1239#[derive(Clone)]
1240pub struct ClickableElement {
1241    on_press: Option<Rc<dyn Fn(Point)>>,
1242    on_click: Rc<dyn Fn(Point)>,
1243}
1244
1245impl ClickableElement {
1246    pub fn new(on_click: impl Fn(Point) + 'static) -> Self {
1247        Self {
1248            on_press: None,
1249            on_click: Rc::new(on_click),
1250        }
1251    }
1252
1253    pub fn with_handler(on_click: Rc<dyn Fn(Point)>) -> Self {
1254        Self {
1255            on_press: None,
1256            on_click,
1257        }
1258    }
1259
1260    pub fn with_handlers(on_press: Rc<dyn Fn(Point)>, on_click: Rc<dyn Fn(Point)>) -> Self {
1261        Self {
1262            on_press: Some(on_press),
1263            on_click,
1264        }
1265    }
1266}
1267
1268impl std::fmt::Debug for ClickableElement {
1269    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1270        f.debug_struct("ClickableElement").finish()
1271    }
1272}
1273
1274impl PartialEq for ClickableElement {
1275    fn eq(&self, _other: &Self) -> bool {
1276        true
1277    }
1278}
1279
1280impl Eq for ClickableElement {}
1281
1282impl Hash for ClickableElement {
1283    fn hash<H: Hasher>(&self, state: &mut H) {
1284        "clickable".hash(state);
1285    }
1286}
1287
1288impl ModifierNodeElement for ClickableElement {
1289    type Node = ClickableNode;
1290
1291    fn create(&self) -> Self::Node {
1292        ClickableNode::with_handlers(self.on_press.clone(), self.on_click.clone())
1293    }
1294
1295    fn update(&self, node: &mut Self::Node) {
1296        node.on_press = self.on_press.clone();
1297        node.on_click = self.on_click.clone();
1298        node.cached_handler = ClickableNode::create_handler(
1299            node.on_press.clone(),
1300            node.on_click.clone(),
1301            node.press_position.clone(),
1302        );
1303    }
1304
1305    fn capabilities(&self) -> NodeCapabilities {
1306        NodeCapabilities::POINTER_INPUT
1307    }
1308
1309    fn always_update(&self) -> bool {
1310        true
1311    }
1312}
1313
1314/// Node that applies alpha transparency to its content.
1315#[derive(Debug)]
1316pub struct AlphaNode {
1317    alpha: f32,
1318    state: NodeState,
1319}
1320
1321impl AlphaNode {
1322    pub fn new(alpha: f32) -> Self {
1323        Self {
1324            alpha: alpha.clamp(0.0, 1.0),
1325            state: NodeState::new(),
1326        }
1327    }
1328}
1329
1330impl DelegatableNode for AlphaNode {
1331    fn node_state(&self) -> &NodeState {
1332        &self.state
1333    }
1334}
1335
1336impl_draw_modifier_node!(AlphaNode);
1337
1338/// Element that creates and updates alpha nodes.
1339#[derive(Debug, Clone, PartialEq)]
1340pub struct AlphaElement {
1341    alpha: f32,
1342}
1343
1344impl AlphaElement {
1345    pub fn new(alpha: f32) -> Self {
1346        Self {
1347            alpha: alpha.clamp(0.0, 1.0),
1348        }
1349    }
1350}
1351
1352impl Hash for AlphaElement {
1353    fn hash<H: Hasher>(&self, state: &mut H) {
1354        hash_f32_value(state, self.alpha);
1355    }
1356}
1357
1358impl ModifierNodeElement for AlphaElement {
1359    type Node = AlphaNode;
1360
1361    fn create(&self) -> Self::Node {
1362        AlphaNode::new(self.alpha)
1363    }
1364
1365    fn update(&self, node: &mut Self::Node) {
1366        let new_alpha = self.alpha.clamp(0.0, 1.0);
1367        if (node.alpha - new_alpha).abs() > f32::EPSILON {
1368            node.alpha = new_alpha;
1369        }
1370    }
1371
1372    fn capabilities(&self) -> NodeCapabilities {
1373        NodeCapabilities::DRAW
1374    }
1375}
1376
1377#[derive(Debug)]
1378pub struct ClipToBoundsNode {
1379    state: NodeState,
1380}
1381
1382impl ClipToBoundsNode {
1383    pub fn new() -> Self {
1384        Self {
1385            state: NodeState::new(),
1386        }
1387    }
1388}
1389
1390impl DelegatableNode for ClipToBoundsNode {
1391    fn node_state(&self) -> &NodeState {
1392        &self.state
1393    }
1394}
1395
1396impl_draw_modifier_node!(ClipToBoundsNode);
1397
1398#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1399pub struct ClipToBoundsElement;
1400
1401impl ClipToBoundsElement {
1402    pub fn new() -> Self {
1403        Self
1404    }
1405}
1406
1407impl ModifierNodeElement for ClipToBoundsElement {
1408    type Node = ClipToBoundsNode;
1409
1410    fn create(&self) -> Self::Node {
1411        ClipToBoundsNode::new()
1412    }
1413
1414    fn update(&self, _node: &mut Self::Node) {}
1415
1416    fn capabilities(&self) -> NodeCapabilities {
1417        NodeCapabilities::DRAW
1418    }
1419}
1420
1421pub trait WindowRectSink {
1422    fn set(&self, rect: cranpose_ui_graphics::Rect);
1423}
1424
1425impl WindowRectSink for Cell<cranpose_ui_graphics::Rect> {
1426    fn set(&self, rect: cranpose_ui_graphics::Rect) {
1427        Cell::set(self, rect);
1428    }
1429}
1430
1431struct StateWindowRectSink(cranpose_core::MutableState<cranpose_ui_graphics::Rect>);
1432
1433impl WindowRectSink for StateWindowRectSink {
1434    fn set(&self, rect: cranpose_ui_graphics::Rect) {
1435        self.0.set(rect);
1436    }
1437}
1438
1439pub struct WindowRectReporterNode {
1440    sink: Rc<dyn WindowRectSink>,
1441    state: NodeState,
1442}
1443
1444impl WindowRectReporterNode {
1445    pub(crate) fn new(sink: Rc<dyn WindowRectSink>) -> Self {
1446        Self {
1447            sink,
1448            state: NodeState::new(),
1449        }
1450    }
1451
1452    pub(crate) fn window_rect_sink(&self) -> Rc<dyn WindowRectSink> {
1453        self.sink.clone()
1454    }
1455}
1456
1457impl DelegatableNode for WindowRectReporterNode {
1458    fn node_state(&self) -> &NodeState {
1459        &self.state
1460    }
1461}
1462
1463impl_layout_modifier_node!(WindowRectReporterNode);
1464
1465impl LayoutModifierNode for WindowRectReporterNode {
1466    fn measure(
1467        &self,
1468        _context: &mut dyn ModifierNodeContext,
1469        measurable: &dyn Measurable,
1470        constraints: Constraints,
1471    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
1472        measure_pass_through(measurable, constraints, |_| (0.0, 0.0))
1473    }
1474}
1475
1476#[derive(Clone)]
1477pub struct WindowRectReporterElement {
1478    sink: Rc<dyn WindowRectSink>,
1479}
1480
1481impl WindowRectReporterElement {
1482    pub fn new(sink: Rc<Cell<cranpose_ui_graphics::Rect>>) -> Self {
1483        Self { sink }
1484    }
1485
1486    pub fn from_state(sink: cranpose_core::MutableState<cranpose_ui_graphics::Rect>) -> Self {
1487        Self {
1488            sink: Rc::new(StateWindowRectSink(sink)),
1489        }
1490    }
1491}
1492
1493impl std::fmt::Debug for WindowRectReporterElement {
1494    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1495        f.debug_struct("WindowRectReporterElement").finish()
1496    }
1497}
1498
1499impl PartialEq for WindowRectReporterElement {
1500    fn eq(&self, other: &Self) -> bool {
1501        Rc::ptr_eq(&self.sink, &other.sink)
1502    }
1503}
1504
1505impl Eq for WindowRectReporterElement {}
1506
1507impl Hash for WindowRectReporterElement {
1508    fn hash<H: Hasher>(&self, state: &mut H) {
1509        std::ptr::hash(Rc::as_ptr(&self.sink).cast::<()>(), state);
1510    }
1511}
1512
1513impl_sink_reporter_element!(WindowRectReporterElement, WindowRectReporterNode);
1514
1515pub trait SizeSink {
1516    fn set(&self, size: Size);
1517}
1518
1519impl SizeSink for Cell<Size> {
1520    fn set(&self, size: Size) {
1521        Cell::set(self, size);
1522    }
1523}
1524
1525struct StateSizeSink(cranpose_core::MutableState<Size>);
1526
1527impl SizeSink for StateSizeSink {
1528    fn set(&self, size: Size) {
1529        self.0.set(size);
1530    }
1531}
1532
1533pub struct SizeReporterNode {
1534    sink: Rc<dyn SizeSink>,
1535    state: NodeState,
1536    #[cfg(debug_assertions)]
1537    oscillation: Cell<(Size, Size, u32)>,
1538}
1539
1540impl SizeReporterNode {
1541    pub fn new(sink: Rc<dyn SizeSink>) -> Self {
1542        Self {
1543            sink,
1544            state: NodeState::new(),
1545            #[cfg(debug_assertions)]
1546            oscillation: Cell::new((Size::default(), Size::default(), 0)),
1547        }
1548    }
1549
1550    #[cfg(debug_assertions)]
1551    fn check_oscillation(&self, size: Size) {
1552        const ALTERNATION_CEILING: u32 = 64;
1553        let (last, second_last, count) = self.oscillation.get();
1554        let count = if size == second_last && size != last {
1555            count + 1
1556        } else if size == last {
1557            count
1558        } else {
1559            0
1560        };
1561        assert!(
1562            count <= ALTERNATION_CEILING,
1563            "size-reactive feedback loop: this node's measured size has \
1564             alternated between {last:?} and {size:?} for {count} passes — \
1565             its content's size depends on the size it reports (the \
1566             onSizeChanged self-reference hazard). Break the cycle by making \
1567             the reported size feed only content that does not change this \
1568             node's own measured size."
1569        );
1570        self.oscillation.set((size, last, count));
1571    }
1572}
1573
1574impl DelegatableNode for SizeReporterNode {
1575    fn node_state(&self) -> &NodeState {
1576        &self.state
1577    }
1578}
1579
1580impl_layout_modifier_node!(SizeReporterNode);
1581
1582impl LayoutModifierNode for SizeReporterNode {
1583    fn measure(
1584        &self,
1585        _context: &mut dyn ModifierNodeContext,
1586        measurable: &dyn Measurable,
1587        constraints: Constraints,
1588    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
1589        measure_pass_through(measurable, constraints, |size| {
1590            #[cfg(debug_assertions)]
1591            self.check_oscillation(size);
1592            self.sink.set(size);
1593            (0.0, 0.0)
1594        })
1595    }
1596}
1597
1598#[derive(Clone)]
1599pub struct SizeReporterElement {
1600    sink: Rc<dyn SizeSink>,
1601}
1602
1603impl SizeReporterElement {
1604    pub fn new(sink: Rc<Cell<Size>>) -> Self {
1605        Self { sink }
1606    }
1607
1608    pub fn from_state(sink: cranpose_core::MutableState<Size>) -> Self {
1609        Self {
1610            sink: Rc::new(StateSizeSink(sink)),
1611        }
1612    }
1613}
1614
1615impl std::fmt::Debug for SizeReporterElement {
1616    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1617        f.debug_struct("SizeReporterElement").finish()
1618    }
1619}
1620
1621impl PartialEq for SizeReporterElement {
1622    fn eq(&self, other: &Self) -> bool {
1623        std::ptr::addr_eq(Rc::as_ptr(&self.sink), Rc::as_ptr(&other.sink))
1624    }
1625}
1626
1627impl Hash for SizeReporterElement {
1628    fn hash<H: Hasher>(&self, state: &mut H) {
1629        (Rc::as_ptr(&self.sink) as *const () as usize).hash(state);
1630    }
1631}
1632
1633impl_sink_reporter_element!(SizeReporterElement, SizeReporterNode);
1634
1635pub struct DrawCommandNode {
1636    commands: Vec<DrawCommand>,
1637    node_id: Cell<Option<NodeId>>,
1638    state: NodeState,
1639}
1640
1641impl DrawCommandNode {
1642    pub fn new(commands: Vec<DrawCommand>) -> Self {
1643        Self {
1644            commands,
1645            node_id: Cell::new(None),
1646            state: NodeState::new(),
1647        }
1648    }
1649
1650    #[cfg(test)]
1651    pub fn commands(&self) -> &[DrawCommand] {
1652        &self.commands
1653    }
1654
1655    pub(crate) fn observed_commands(&self) -> Vec<DrawCommand> {
1656        let node_id = self.node_id.get();
1657        self.commands
1658            .iter()
1659            .cloned()
1660            .enumerate()
1661            .map(|(index, command)| observe_draw_command(command, node_id, index))
1662            .collect()
1663    }
1664}
1665
1666impl DelegatableNode for DrawCommandNode {
1667    fn node_state(&self) -> &NodeState {
1668        &self.state
1669    }
1670}
1671
1672impl ModifierNode for DrawCommandNode {
1673    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
1674        attach_draw_observer(&self.node_id, context);
1675    }
1676
1677    fn on_detach(&mut self) {
1678        detach_draw_observer(&self.node_id);
1679    }
1680
1681    fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
1682        Some(self)
1683    }
1684
1685    fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
1686        Some(self)
1687    }
1688}
1689
1690impl DrawModifierNode for DrawCommandNode {
1691    fn draw(&self, _draw_scope: &mut dyn DrawScope) {}
1692}
1693
1694fn observe_draw_command(
1695    command: DrawCommand,
1696    node_id: Option<NodeId>,
1697    command_index: usize,
1698) -> DrawCommand {
1699    let Some(node_id) = node_id else {
1700        return command;
1701    };
1702    let observation = crate::render_state::DrawObservationScope::new(node_id, command_index);
1703    match command {
1704        DrawCommand::Behind(draw) => DrawCommand::Behind(Rc::new(move |scope| {
1705            crate::render_state::observe_draw_reads(observation, || draw(scope))
1706        })),
1707        DrawCommand::WithContent(draw) => DrawCommand::WithContent(Rc::new(move |scope| {
1708            crate::render_state::observe_draw_reads(observation, || draw(scope))
1709        })),
1710        DrawCommand::Overlay(draw) => DrawCommand::Overlay(Rc::new(move |scope| {
1711            crate::render_state::observe_draw_reads(observation, || draw(scope))
1712        })),
1713    }
1714}
1715
1716fn draw_command_tag(cmd: &DrawCommand) -> u8 {
1717    match cmd {
1718        DrawCommand::Behind(_) => 0,
1719        DrawCommand::WithContent(_) => 1,
1720        DrawCommand::Overlay(_) => 2,
1721    }
1722}
1723
1724fn draw_command_closure_identity(cmd: &DrawCommand) -> *const () {
1725    match cmd {
1726        DrawCommand::Behind(f) | DrawCommand::WithContent(f) | DrawCommand::Overlay(f) => {
1727            Rc::as_ptr(f) as *const ()
1728        }
1729    }
1730}
1731
1732#[derive(Clone)]
1733pub struct DrawCommandElement {
1734    commands: Vec<DrawCommand>,
1735}
1736
1737impl DrawCommandElement {
1738    pub fn new(command: DrawCommand) -> Self {
1739        Self {
1740            commands: vec![command],
1741        }
1742    }
1743
1744    pub fn from_commands(commands: Vec<DrawCommand>) -> Self {
1745        Self { commands }
1746    }
1747}
1748
1749impl std::fmt::Debug for DrawCommandElement {
1750    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1751        f.debug_struct("DrawCommandElement")
1752            .field("commands", &self.commands.len())
1753            .finish()
1754    }
1755}
1756
1757impl PartialEq for DrawCommandElement {
1758    fn eq(&self, other: &Self) -> bool {
1759        if self.commands.len() != other.commands.len() {
1760            return false;
1761        }
1762        self.commands
1763            .iter()
1764            .zip(other.commands.iter())
1765            .all(|(a, b)| {
1766                draw_command_tag(a) == draw_command_tag(b)
1767                    && draw_command_closure_identity(a) == draw_command_closure_identity(b)
1768            })
1769    }
1770}
1771
1772impl Eq for DrawCommandElement {}
1773
1774impl std::hash::Hash for DrawCommandElement {
1775    fn hash<H: Hasher>(&self, state: &mut H) {
1776        "draw_commands".hash(state);
1777        self.commands.len().hash(state);
1778        for command in &self.commands {
1779            draw_command_tag(command).hash(state);
1780            (draw_command_closure_identity(command) as usize).hash(state);
1781        }
1782    }
1783}
1784
1785impl ModifierNodeElement for DrawCommandElement {
1786    type Node = DrawCommandNode;
1787
1788    fn create(&self) -> Self::Node {
1789        DrawCommandNode::new(self.commands.clone())
1790    }
1791
1792    fn update(&self, node: &mut Self::Node) {
1793        node.commands = self.commands.clone();
1794    }
1795
1796    fn capabilities(&self) -> NodeCapabilities {
1797        NodeCapabilities::DRAW
1798    }
1799}
1800
1801/// Node that offsets its content by a fixed (x, y) amount.
1802///
1803/// Matches Kotlin: `OffsetNode` in foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Offset.kt
1804#[derive(Debug)]
1805pub struct OffsetNode {
1806    x: f32,
1807    y: f32,
1808    rtl_aware: bool,
1809    state: NodeState,
1810}
1811
1812impl OffsetNode {
1813    pub fn new(x: f32, y: f32, rtl_aware: bool) -> Self {
1814        Self {
1815            x,
1816            y,
1817            rtl_aware,
1818            state: NodeState::new(),
1819        }
1820    }
1821
1822    pub fn offset(&self) -> Point {
1823        Point {
1824            x: self.x,
1825            y: self.y,
1826        }
1827    }
1828
1829    pub fn rtl_aware(&self) -> bool {
1830        self.rtl_aware
1831    }
1832}
1833
1834impl DelegatableNode for OffsetNode {
1835    fn node_state(&self) -> &NodeState {
1836        &self.state
1837    }
1838}
1839
1840impl_layout_modifier_node!(OffsetNode, invalidate = InvalidationKind::Layout);
1841
1842impl LayoutModifierNode for OffsetNode {
1843    fn measure(
1844        &self,
1845        _context: &mut dyn ModifierNodeContext,
1846        measurable: &dyn Measurable,
1847        constraints: Constraints,
1848    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
1849        measure_pass_through(measurable, constraints, |_| (self.x, self.y))
1850    }
1851
1852    forward_intrinsics_to_child!();
1853}
1854
1855/// Element that creates and updates offset nodes.
1856///
1857/// Matches Kotlin: `OffsetElement` in foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Offset.kt
1858#[derive(Debug, Clone, PartialEq)]
1859pub struct OffsetElement {
1860    x: f32,
1861    y: f32,
1862    rtl_aware: bool,
1863}
1864
1865impl OffsetElement {
1866    pub fn new(x: f32, y: f32, rtl_aware: bool) -> Self {
1867        Self { x, y, rtl_aware }
1868    }
1869}
1870
1871impl Hash for OffsetElement {
1872    fn hash<H: Hasher>(&self, state: &mut H) {
1873        hash_f32_value(state, self.x);
1874        hash_f32_value(state, self.y);
1875        self.rtl_aware.hash(state);
1876    }
1877}
1878
1879impl ModifierNodeElement for OffsetElement {
1880    type Node = OffsetNode;
1881
1882    fn create(&self) -> Self::Node {
1883        OffsetNode::new(self.x, self.y, self.rtl_aware)
1884    }
1885
1886    fn update(&self, node: &mut Self::Node) {
1887        if node.x != self.x || node.y != self.y || node.rtl_aware != self.rtl_aware {
1888            node.x = self.x;
1889            node.y = self.y;
1890            node.rtl_aware = self.rtl_aware;
1891        }
1892    }
1893
1894    fn capabilities(&self) -> NodeCapabilities {
1895        NodeCapabilities::LAYOUT
1896    }
1897
1898    fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
1899        Some(InvalidationKind::Layout)
1900    }
1901}
1902
1903/// Node that offsets its content by a fraction of its own measured size.
1904///
1905/// There is no direct Jetpack Compose modifier equivalent; Compose's slide
1906/// transitions receive the measured size through a lambda instead. This node
1907/// backs `slide_in_vertically` / `slide_out_vertically` in
1908/// `AnimatedVisibility`, where the offset is expressed as a fraction of the
1909/// content height.
1910#[derive(Debug)]
1911pub struct FractionalOffsetNode {
1912    x_fraction: f32,
1913    y_fraction: f32,
1914    state: NodeState,
1915}
1916
1917impl FractionalOffsetNode {
1918    pub fn new(x_fraction: f32, y_fraction: f32) -> Self {
1919        Self {
1920            x_fraction,
1921            y_fraction,
1922            state: NodeState::new(),
1923        }
1924    }
1925
1926    pub fn fractions(&self) -> Point {
1927        Point {
1928            x: self.x_fraction,
1929            y: self.y_fraction,
1930        }
1931    }
1932}
1933
1934impl DelegatableNode for FractionalOffsetNode {
1935    fn node_state(&self) -> &NodeState {
1936        &self.state
1937    }
1938}
1939
1940impl_layout_modifier_node!(FractionalOffsetNode, invalidate = InvalidationKind::Layout);
1941
1942impl LayoutModifierNode for FractionalOffsetNode {
1943    fn measure(
1944        &self,
1945        _context: &mut dyn ModifierNodeContext,
1946        measurable: &dyn Measurable,
1947        constraints: Constraints,
1948    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
1949        measure_pass_through(measurable, constraints, |size| {
1950            (self.x_fraction * size.width, self.y_fraction * size.height)
1951        })
1952    }
1953
1954    forward_intrinsics_to_child!();
1955}
1956
1957/// Element that creates and updates fractional offset nodes.
1958#[derive(Debug, Clone, PartialEq)]
1959pub struct FractionalOffsetElement {
1960    x_fraction: f32,
1961    y_fraction: f32,
1962}
1963
1964impl FractionalOffsetElement {
1965    pub fn new(x_fraction: f32, y_fraction: f32) -> Self {
1966        Self {
1967            x_fraction,
1968            y_fraction,
1969        }
1970    }
1971}
1972
1973impl Hash for FractionalOffsetElement {
1974    fn hash<H: Hasher>(&self, state: &mut H) {
1975        "fractional_offset".hash(state);
1976        hash_f32_value(state, self.x_fraction);
1977        hash_f32_value(state, self.y_fraction);
1978    }
1979}
1980
1981impl ModifierNodeElement for FractionalOffsetElement {
1982    type Node = FractionalOffsetNode;
1983
1984    fn create(&self) -> Self::Node {
1985        FractionalOffsetNode::new(self.x_fraction, self.y_fraction)
1986    }
1987
1988    fn update(&self, node: &mut Self::Node) {
1989        if node.x_fraction != self.x_fraction || node.y_fraction != self.y_fraction {
1990            node.x_fraction = self.x_fraction;
1991            node.y_fraction = self.y_fraction;
1992        }
1993    }
1994
1995    fn capabilities(&self) -> NodeCapabilities {
1996        NodeCapabilities::LAYOUT
1997    }
1998
1999    fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
2000        Some(InvalidationKind::Layout)
2001    }
2002}
2003
2004/// Direction for fill modifiers (horizontal, vertical, or both).
2005#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2006pub enum FillDirection {
2007    Horizontal,
2008    Vertical,
2009    Both,
2010}
2011
2012/// Node that fills the maximum available space in one or both dimensions.
2013///
2014/// Matches Kotlin: `FillNode` in foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Size.kt
2015#[derive(Debug)]
2016pub struct FillNode {
2017    direction: FillDirection,
2018    fraction: f32,
2019    state: NodeState,
2020}
2021
2022impl FillNode {
2023    pub fn new(direction: FillDirection, fraction: f32) -> Self {
2024        Self {
2025            direction,
2026            fraction,
2027            state: NodeState::new(),
2028        }
2029    }
2030
2031    pub fn direction(&self) -> FillDirection {
2032        self.direction
2033    }
2034
2035    pub fn fraction(&self) -> f32 {
2036        self.fraction
2037    }
2038}
2039
2040impl DelegatableNode for FillNode {
2041    fn node_state(&self) -> &NodeState {
2042        &self.state
2043    }
2044}
2045
2046impl_layout_modifier_node!(FillNode, invalidate = InvalidationKind::Layout);
2047
2048impl LayoutModifierNode for FillNode {
2049    fn measure(
2050        &self,
2051        _context: &mut dyn ModifierNodeContext,
2052        measurable: &dyn Measurable,
2053        constraints: Constraints,
2054    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
2055        let (fill_width, child_min_width, child_max_width) = if self.direction
2056            != FillDirection::Vertical
2057            && constraints.max_width != f32::INFINITY
2058        {
2059            let width = (constraints.max_width * self.fraction)
2060                .round()
2061                .clamp(constraints.min_width, constraints.max_width);
2062            (width, width, width)
2063        } else {
2064            (
2065                constraints.max_width,
2066                constraints.min_width,
2067                constraints.max_width,
2068            )
2069        };
2070
2071        let (fill_height, child_min_height, child_max_height) = if self.direction
2072            != FillDirection::Horizontal
2073            && constraints.max_height != f32::INFINITY
2074        {
2075            let height = (constraints.max_height * self.fraction)
2076                .round()
2077                .clamp(constraints.min_height, constraints.max_height);
2078            (height, height, height)
2079        } else {
2080            (
2081                constraints.max_height,
2082                constraints.min_height,
2083                constraints.max_height,
2084            )
2085        };
2086
2087        let fill_constraints = Constraints {
2088            min_width: child_min_width,
2089            max_width: child_max_width,
2090            min_height: child_min_height,
2091            max_height: child_max_height,
2092        };
2093
2094        let placeable = measurable.measure(fill_constraints);
2095
2096        let result_width = if self.direction != FillDirection::Vertical
2097            && constraints.max_width != f32::INFINITY
2098        {
2099            fill_width
2100        } else {
2101            placeable.width()
2102        };
2103
2104        let result_height = if self.direction != FillDirection::Horizontal
2105            && constraints.max_height != f32::INFINITY
2106        {
2107            fill_height
2108        } else {
2109            placeable.height()
2110        };
2111
2112        cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size {
2113            width: result_width,
2114            height: result_height,
2115        })
2116    }
2117
2118    forward_intrinsics_to_child!();
2119}
2120
2121/// Element that creates and updates fill nodes.
2122///
2123/// Matches Kotlin: `FillElement` in foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Size.kt
2124#[derive(Debug, Clone, PartialEq)]
2125pub struct FillElement {
2126    direction: FillDirection,
2127    fraction: f32,
2128}
2129
2130impl FillElement {
2131    pub fn width(fraction: f32) -> Self {
2132        Self {
2133            direction: FillDirection::Horizontal,
2134            fraction,
2135        }
2136    }
2137
2138    pub fn height(fraction: f32) -> Self {
2139        Self {
2140            direction: FillDirection::Vertical,
2141            fraction,
2142        }
2143    }
2144
2145    pub fn size(fraction: f32) -> Self {
2146        Self {
2147            direction: FillDirection::Both,
2148            fraction,
2149        }
2150    }
2151}
2152
2153impl Hash for FillElement {
2154    fn hash<H: Hasher>(&self, state: &mut H) {
2155        self.direction.hash(state);
2156        hash_f32_value(state, self.fraction);
2157    }
2158}
2159
2160impl ModifierNodeElement for FillElement {
2161    type Node = FillNode;
2162
2163    fn create(&self) -> Self::Node {
2164        FillNode::new(self.direction, self.fraction)
2165    }
2166
2167    fn update(&self, node: &mut Self::Node) {
2168        if node.direction != self.direction || node.fraction != self.fraction {
2169            node.direction = self.direction;
2170            node.fraction = self.fraction;
2171        }
2172    }
2173
2174    fn capabilities(&self) -> NodeCapabilities {
2175        NodeCapabilities::LAYOUT
2176    }
2177}
2178
2179#[derive(Debug)]
2180pub struct WeightNode {
2181    weight: f32,
2182    fill: bool,
2183    state: NodeState,
2184}
2185
2186impl WeightNode {
2187    pub fn new(weight: f32, fill: bool) -> Self {
2188        Self {
2189            weight,
2190            fill,
2191            state: NodeState::new(),
2192        }
2193    }
2194
2195    pub fn layout_weight(&self) -> LayoutWeight {
2196        LayoutWeight {
2197            weight: self.weight,
2198            fill: self.fill,
2199        }
2200    }
2201}
2202
2203impl DelegatableNode for WeightNode {
2204    fn node_state(&self) -> &NodeState {
2205        &self.state
2206    }
2207}
2208
2209impl ModifierNode for WeightNode {
2210    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
2211        context.invalidate(cranpose_foundation::InvalidationKind::Layout);
2212    }
2213}
2214
2215#[derive(Debug, Clone, PartialEq)]
2216pub struct WeightElement {
2217    weight: f32,
2218    fill: bool,
2219}
2220
2221impl WeightElement {
2222    pub fn new(weight: f32, fill: bool) -> Self {
2223        Self { weight, fill }
2224    }
2225}
2226
2227impl Hash for WeightElement {
2228    fn hash<H: Hasher>(&self, state: &mut H) {
2229        hash_f32_value(state, self.weight);
2230        self.fill.hash(state);
2231    }
2232}
2233
2234impl ModifierNodeElement for WeightElement {
2235    type Node = WeightNode;
2236
2237    fn create(&self) -> Self::Node {
2238        WeightNode::new(self.weight, self.fill)
2239    }
2240
2241    fn update(&self, node: &mut Self::Node) {
2242        if node.weight != self.weight || node.fill != self.fill {
2243            node.weight = self.weight;
2244            node.fill = self.fill;
2245        }
2246    }
2247
2248    fn capabilities(&self) -> NodeCapabilities {
2249        NodeCapabilities::LAYOUT
2250    }
2251}
2252
2253#[derive(Debug)]
2254pub struct AlignmentNode {
2255    box_alignment: Option<Alignment>,
2256    column_alignment: Option<HorizontalAlignment>,
2257    row_alignment: Option<VerticalAlignment>,
2258    state: NodeState,
2259}
2260
2261impl AlignmentNode {
2262    pub fn new(
2263        box_alignment: Option<Alignment>,
2264        column_alignment: Option<HorizontalAlignment>,
2265        row_alignment: Option<VerticalAlignment>,
2266    ) -> Self {
2267        Self {
2268            box_alignment,
2269            column_alignment,
2270            row_alignment,
2271            state: NodeState::new(),
2272        }
2273    }
2274
2275    pub fn box_alignment(&self) -> Option<Alignment> {
2276        self.box_alignment
2277    }
2278
2279    pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
2280        self.column_alignment
2281    }
2282
2283    pub fn row_alignment(&self) -> Option<VerticalAlignment> {
2284        self.row_alignment
2285    }
2286}
2287
2288impl DelegatableNode for AlignmentNode {
2289    fn node_state(&self) -> &NodeState {
2290        &self.state
2291    }
2292}
2293
2294impl ModifierNode for AlignmentNode {
2295    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
2296        context.invalidate(cranpose_foundation::InvalidationKind::Layout);
2297    }
2298}
2299
2300#[derive(Debug, Clone, PartialEq)]
2301pub struct AlignmentElement {
2302    box_alignment: Option<Alignment>,
2303    column_alignment: Option<HorizontalAlignment>,
2304    row_alignment: Option<VerticalAlignment>,
2305}
2306
2307impl AlignmentElement {
2308    pub fn box_alignment(alignment: Alignment) -> Self {
2309        Self {
2310            box_alignment: Some(alignment),
2311            column_alignment: None,
2312            row_alignment: None,
2313        }
2314    }
2315
2316    pub fn column_alignment(alignment: HorizontalAlignment) -> Self {
2317        Self {
2318            box_alignment: None,
2319            column_alignment: Some(alignment),
2320            row_alignment: None,
2321        }
2322    }
2323
2324    pub fn row_alignment(alignment: VerticalAlignment) -> Self {
2325        Self {
2326            box_alignment: None,
2327            column_alignment: None,
2328            row_alignment: Some(alignment),
2329        }
2330    }
2331}
2332
2333impl Hash for AlignmentElement {
2334    fn hash<H: Hasher>(&self, state: &mut H) {
2335        if let Some(alignment) = self.box_alignment {
2336            state.write_u8(1);
2337            hash_alignment(state, alignment);
2338        } else {
2339            state.write_u8(0);
2340        }
2341        if let Some(alignment) = self.column_alignment {
2342            state.write_u8(1);
2343            hash_horizontal_alignment(state, alignment);
2344        } else {
2345            state.write_u8(0);
2346        }
2347        if let Some(alignment) = self.row_alignment {
2348            state.write_u8(1);
2349            hash_vertical_alignment(state, alignment);
2350        } else {
2351            state.write_u8(0);
2352        }
2353    }
2354}
2355
2356impl ModifierNodeElement for AlignmentElement {
2357    type Node = AlignmentNode;
2358
2359    fn create(&self) -> Self::Node {
2360        AlignmentNode::new(
2361            self.box_alignment,
2362            self.column_alignment,
2363            self.row_alignment,
2364        )
2365    }
2366
2367    fn update(&self, node: &mut Self::Node) {
2368        if node.box_alignment != self.box_alignment {
2369            node.box_alignment = self.box_alignment;
2370        }
2371        if node.column_alignment != self.column_alignment {
2372            node.column_alignment = self.column_alignment;
2373        }
2374        if node.row_alignment != self.row_alignment {
2375            node.row_alignment = self.row_alignment;
2376        }
2377    }
2378
2379    fn capabilities(&self) -> NodeCapabilities {
2380        NodeCapabilities::LAYOUT
2381    }
2382}
2383
2384#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2385pub enum IntrinsicAxis {
2386    Width,
2387    Height,
2388}
2389
2390#[derive(Debug)]
2391pub struct IntrinsicSizeNode {
2392    axis: IntrinsicAxis,
2393    size: IntrinsicSize,
2394    state: NodeState,
2395}
2396
2397impl IntrinsicSizeNode {
2398    pub fn new(axis: IntrinsicAxis, size: IntrinsicSize) -> Self {
2399        Self {
2400            axis,
2401            size,
2402            state: NodeState::new(),
2403        }
2404    }
2405
2406    pub fn axis(&self) -> IntrinsicAxis {
2407        self.axis
2408    }
2409
2410    pub fn intrinsic_size(&self) -> IntrinsicSize {
2411        self.size
2412    }
2413}
2414
2415impl DelegatableNode for IntrinsicSizeNode {
2416    fn node_state(&self) -> &NodeState {
2417        &self.state
2418    }
2419}
2420
2421impl ModifierNode for IntrinsicSizeNode {
2422    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
2423        context.invalidate(cranpose_foundation::InvalidationKind::Layout);
2424    }
2425}
2426
2427#[derive(Debug, Clone, PartialEq)]
2428pub struct IntrinsicSizeElement {
2429    axis: IntrinsicAxis,
2430    size: IntrinsicSize,
2431}
2432
2433impl IntrinsicSizeElement {
2434    pub fn width(size: IntrinsicSize) -> Self {
2435        Self {
2436            axis: IntrinsicAxis::Width,
2437            size,
2438        }
2439    }
2440
2441    pub fn height(size: IntrinsicSize) -> Self {
2442        Self {
2443            axis: IntrinsicAxis::Height,
2444            size,
2445        }
2446    }
2447}
2448
2449impl Hash for IntrinsicSizeElement {
2450    fn hash<H: Hasher>(&self, state: &mut H) {
2451        state.write_u8(match self.axis {
2452            IntrinsicAxis::Width => 0,
2453            IntrinsicAxis::Height => 1,
2454        });
2455        state.write_u8(match self.size {
2456            IntrinsicSize::Min => 0,
2457            IntrinsicSize::Max => 1,
2458        });
2459    }
2460}
2461
2462impl ModifierNodeElement for IntrinsicSizeElement {
2463    type Node = IntrinsicSizeNode;
2464
2465    fn create(&self) -> Self::Node {
2466        IntrinsicSizeNode::new(self.axis, self.size)
2467    }
2468
2469    fn update(&self, node: &mut Self::Node) {
2470        if node.axis != self.axis {
2471            node.axis = self.axis;
2472        }
2473        if node.size != self.size {
2474            node.size = self.size;
2475        }
2476    }
2477
2478    fn capabilities(&self) -> NodeCapabilities {
2479        NodeCapabilities::LAYOUT
2480    }
2481}
2482
2483#[cfg(test)]
2484#[path = "tests/modifier_nodes_tests.rs"]
2485mod tests;