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