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_or(f32::INFINITY, |v| v.max(0.0));
814        let max_height = self.max_height.map_or(f32::INFINITY, |v| v.max(0.0));
815
816        let min_width = self.min_width.map_or(0.0, |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
825        let min_height = self.min_height.map_or(0.0, |v| {
826            let clamped = v.clamp(0.0, max_height);
827            if clamped == f32::INFINITY {
828                0.0
829            } else {
830                clamped
831            }
832        });
833
834        Constraints {
835            min_width,
836            max_width,
837            min_height,
838            max_height,
839        }
840    }
841
842    pub fn min_width(&self) -> Option<f32> {
843        self.min_width
844    }
845
846    pub fn max_width(&self) -> Option<f32> {
847        self.max_width
848    }
849
850    pub fn min_height(&self) -> Option<f32> {
851        self.min_height
852    }
853
854    pub fn max_height(&self) -> Option<f32> {
855        self.max_height
856    }
857
858    pub fn enforce_incoming(&self) -> bool {
859        self.enforce_incoming
860    }
861}
862
863impl DelegatableNode for SizeNode {
864    fn node_state(&self) -> &NodeState {
865        &self.state
866    }
867}
868
869impl_layout_modifier_node!(SizeNode, invalidate = InvalidationKind::Layout);
870
871impl LayoutModifierNode for SizeNode {
872    fn measure(
873        &self,
874        _context: &mut dyn ModifierNodeContext,
875        measurable: &dyn Measurable,
876        constraints: Constraints,
877    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
878        let target = self.target_constraints();
879
880        let wrapped_constraints = if self.enforce_incoming {
881            Constraints {
882                min_width: target
883                    .min_width
884                    .max(constraints.min_width)
885                    .min(constraints.max_width),
886                max_width: target
887                    .max_width
888                    .min(constraints.max_width)
889                    .max(constraints.min_width),
890                min_height: target
891                    .min_height
892                    .max(constraints.min_height)
893                    .min(constraints.max_height),
894                max_height: target
895                    .max_height
896                    .min(constraints.max_height)
897                    .max(constraints.min_height),
898            }
899        } else {
900            let resolved_min_width = if self.min_width.is_some() {
901                target.min_width
902            } else {
903                constraints.min_width.min(target.max_width)
904            };
905            let resolved_max_width = if self.max_width.is_some() {
906                target.max_width
907            } else {
908                constraints.max_width.max(target.min_width)
909            };
910            let resolved_min_height = if self.min_height.is_some() {
911                target.min_height
912            } else {
913                constraints.min_height.min(target.max_height)
914            };
915            let resolved_max_height = if self.max_height.is_some() {
916                target.max_height
917            } else {
918                constraints.max_height.max(target.min_height)
919            };
920
921            Constraints {
922                min_width: resolved_min_width,
923                max_width: resolved_max_width,
924                min_height: resolved_min_height,
925                max_height: resolved_max_height,
926            }
927        };
928
929        let placeable = measurable.measure(wrapped_constraints);
930        let measured_width = placeable.width();
931        let measured_height = placeable.height();
932
933        let result_width = if self.min_width.is_some()
934            && self.max_width.is_some()
935            && self.min_width == self.max_width
936            && target.min_width >= wrapped_constraints.min_width
937            && target.min_width <= wrapped_constraints.max_width
938        {
939            target.min_width
940        } else {
941            measured_width
942        };
943
944        let result_height = if self.min_height.is_some()
945            && self.max_height.is_some()
946            && self.min_height == self.max_height
947            && target.min_height >= wrapped_constraints.min_height
948            && target.min_height <= wrapped_constraints.max_height
949        {
950            target.min_height
951        } else {
952            measured_height
953        };
954
955        cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size {
956            width: result_width,
957            height: result_height,
958        })
959    }
960
961    fn min_intrinsic_width(&self, measurable: &dyn Measurable, height: f32) -> f32 {
962        size_intrinsic(
963            self.target_constraints(),
964            SizeAxis::Width,
965            self.enforce_incoming,
966            height,
967            |h| measurable.min_intrinsic_width(h),
968        )
969    }
970
971    fn max_intrinsic_width(&self, measurable: &dyn Measurable, height: f32) -> f32 {
972        size_intrinsic(
973            self.target_constraints(),
974            SizeAxis::Width,
975            self.enforce_incoming,
976            height,
977            |h| measurable.max_intrinsic_width(h),
978        )
979    }
980
981    fn min_intrinsic_height(&self, measurable: &dyn Measurable, width: f32) -> f32 {
982        size_intrinsic(
983            self.target_constraints(),
984            SizeAxis::Height,
985            self.enforce_incoming,
986            width,
987            |w| measurable.min_intrinsic_height(w),
988        )
989    }
990
991    fn max_intrinsic_height(&self, measurable: &dyn Measurable, width: f32) -> f32 {
992        size_intrinsic(
993            self.target_constraints(),
994            SizeAxis::Height,
995            self.enforce_incoming,
996            width,
997            |w| measurable.max_intrinsic_height(w),
998        )
999    }
1000}
1001
1002/// Element that creates and updates size nodes.
1003///
1004/// Matches Kotlin: `SizeElement` in foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Size.kt
1005#[derive(Debug, Clone, PartialEq)]
1006pub struct SizeElement {
1007    min_width: Option<f32>,
1008    max_width: Option<f32>,
1009    min_height: Option<f32>,
1010    max_height: Option<f32>,
1011    enforce_incoming: bool,
1012}
1013
1014impl SizeElement {
1015    pub fn new(width: Option<f32>, height: Option<f32>) -> Self {
1016        Self {
1017            min_width: width,
1018            max_width: width,
1019            min_height: height,
1020            max_height: height,
1021            enforce_incoming: true,
1022        }
1023    }
1024
1025    pub fn with_constraints(
1026        min_width: Option<f32>,
1027        max_width: Option<f32>,
1028        min_height: Option<f32>,
1029        max_height: Option<f32>,
1030        enforce_incoming: bool,
1031    ) -> Self {
1032        Self {
1033            min_width,
1034            max_width,
1035            min_height,
1036            max_height,
1037            enforce_incoming,
1038        }
1039    }
1040}
1041
1042impl Hash for SizeElement {
1043    fn hash<H: Hasher>(&self, state: &mut H) {
1044        hash_option_f32(state, self.min_width);
1045        hash_option_f32(state, self.max_width);
1046        hash_option_f32(state, self.min_height);
1047        hash_option_f32(state, self.max_height);
1048        self.enforce_incoming.hash(state);
1049    }
1050}
1051
1052impl ModifierNodeElement for SizeElement {
1053    type Node = SizeNode;
1054
1055    fn create(&self) -> Self::Node {
1056        SizeNode::new(
1057            self.min_width,
1058            self.max_width,
1059            self.min_height,
1060            self.max_height,
1061            self.enforce_incoming,
1062        )
1063    }
1064
1065    fn update(&self, node: &mut Self::Node) {
1066        if node.min_width != self.min_width
1067            || node.max_width != self.max_width
1068            || node.min_height != self.min_height
1069            || node.max_height != self.max_height
1070            || node.enforce_incoming != self.enforce_incoming
1071        {
1072            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    }
1079
1080    fn capabilities(&self) -> NodeCapabilities {
1081        NodeCapabilities::LAYOUT
1082    }
1083
1084    fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
1085        Some(InvalidationKind::Layout)
1086    }
1087}
1088
1089use std::cell::RefCell;
1090
1091use cranpose_foundation::DRAG_THRESHOLD;
1092
1093pub struct ClickableNode {
1094    on_press: Option<Rc<dyn Fn(Point)>>,
1095    on_click: Rc<dyn Fn(Point)>,
1096    state: NodeState,
1097    press_position: Rc<RefCell<Option<Point>>>,
1098    cached_handler: Rc<dyn Fn(PointerEvent)>,
1099}
1100
1101impl std::fmt::Debug for ClickableNode {
1102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1103        f.debug_struct("ClickableNode").finish()
1104    }
1105}
1106
1107impl ClickableNode {
1108    pub fn new(on_click: impl Fn(Point) + 'static) -> Self {
1109        Self::with_handler(Rc::new(on_click))
1110    }
1111
1112    pub fn with_handler(on_click: Rc<dyn Fn(Point)>) -> Self {
1113        Self::with_handlers(None, on_click)
1114    }
1115
1116    pub fn with_handlers(on_press: Option<Rc<dyn Fn(Point)>>, on_click: Rc<dyn Fn(Point)>) -> Self {
1117        let press_position = Rc::new(RefCell::new(None));
1118        let cached_handler =
1119            Self::create_handler(on_press.clone(), on_click.clone(), press_position.clone());
1120        Self {
1121            on_press,
1122            on_click,
1123            state: NodeState::new(),
1124            press_position,
1125            cached_handler,
1126        }
1127    }
1128
1129    fn create_handler(
1130        on_press: Option<Rc<dyn Fn(Point)>>,
1131        on_click: Rc<dyn Fn(Point)>,
1132        press_position: Rc<RefCell<Option<Point>>>,
1133    ) -> Rc<dyn Fn(PointerEvent)> {
1134        Rc::new(move |event: PointerEvent| {
1135            if event.id != 0 {
1136                return;
1137            }
1138
1139            if event.is_consumed() {
1140                *press_position.borrow_mut() = None;
1141                return;
1142            }
1143
1144            match event.kind {
1145                PointerEventKind::Down => {
1146                    *press_position.borrow_mut() = Some(event.travelled_to());
1147                    if let Some(on_press) = on_press.as_ref() {
1148                        on_press(event.position);
1149                    }
1150                }
1151                PointerEventKind::Move => {}
1152                PointerEventKind::Up => {
1153                    let press_pos_value = *press_position.borrow();
1154
1155                    let should_click = if let Some(press_pos) = press_pos_value {
1156                        let travelled_to = event.travelled_to();
1157                        let dx = travelled_to.x - press_pos.x;
1158                        let dy = travelled_to.y - press_pos.y;
1159                        let distance = (dx * dx + dy * dy).sqrt();
1160                        distance <= DRAG_THRESHOLD
1161                    } else {
1162                        true
1163                    };
1164
1165                    *press_position.borrow_mut() = None;
1166
1167                    if should_click {
1168                        on_click(Point {
1169                            x: event.position.x,
1170                            y: event.position.y,
1171                        });
1172                        event.consume();
1173                    }
1174                }
1175                PointerEventKind::Cancel => {
1176                    *press_position.borrow_mut() = None;
1177                }
1178                PointerEventKind::Scroll
1179                | PointerEventKind::Zoom
1180                | PointerEventKind::RotaryScrollPre
1181                | PointerEventKind::RotaryScroll
1182                | PointerEventKind::Enter
1183                | PointerEventKind::Exit => {}
1184            }
1185        })
1186    }
1187
1188    pub fn handler(&self) -> Rc<dyn Fn(Point)> {
1189        self.on_click.clone()
1190    }
1191}
1192
1193impl DelegatableNode for ClickableNode {
1194    fn node_state(&self) -> &NodeState {
1195        &self.state
1196    }
1197}
1198
1199impl ModifierNode for ClickableNode {
1200    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
1201        context.invalidate(cranpose_foundation::InvalidationKind::PointerInput);
1202    }
1203
1204    fn as_pointer_input_node(&self) -> Option<&dyn PointerInputNode> {
1205        Some(self)
1206    }
1207
1208    fn as_pointer_input_node_mut(&mut self) -> Option<&mut dyn PointerInputNode> {
1209        Some(self)
1210    }
1211}
1212
1213impl PointerInputNode for ClickableNode {
1214    fn on_pointer_event(
1215        &mut self,
1216        _context: &mut dyn ModifierNodeContext,
1217        event: &PointerEvent,
1218    ) -> bool {
1219        (self.cached_handler)(event.clone());
1220        event.is_consumed()
1221    }
1222
1223    fn hit_test(&self, _x: f32, _y: f32) -> bool {
1224        true
1225    }
1226
1227    fn pointer_input_handler(&self) -> Option<Rc<dyn Fn(PointerEvent)>> {
1228        Some(self.cached_handler.clone())
1229    }
1230}
1231
1232/// Element that creates and updates clickable nodes.
1233#[derive(Clone)]
1234pub struct ClickableElement {
1235    on_press: Option<Rc<dyn Fn(Point)>>,
1236    on_click: Rc<dyn Fn(Point)>,
1237}
1238
1239impl ClickableElement {
1240    pub fn new(on_click: impl Fn(Point) + 'static) -> Self {
1241        Self {
1242            on_press: None,
1243            on_click: Rc::new(on_click),
1244        }
1245    }
1246
1247    pub fn with_handler(on_click: Rc<dyn Fn(Point)>) -> Self {
1248        Self {
1249            on_press: None,
1250            on_click,
1251        }
1252    }
1253
1254    pub fn with_handlers(on_press: Rc<dyn Fn(Point)>, on_click: Rc<dyn Fn(Point)>) -> Self {
1255        Self {
1256            on_press: Some(on_press),
1257            on_click,
1258        }
1259    }
1260}
1261
1262impl std::fmt::Debug for ClickableElement {
1263    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1264        f.debug_struct("ClickableElement").finish()
1265    }
1266}
1267
1268impl PartialEq for ClickableElement {
1269    fn eq(&self, _other: &Self) -> bool {
1270        true
1271    }
1272}
1273
1274impl Eq for ClickableElement {}
1275
1276impl Hash for ClickableElement {
1277    fn hash<H: Hasher>(&self, state: &mut H) {
1278        "clickable".hash(state);
1279    }
1280}
1281
1282impl ModifierNodeElement for ClickableElement {
1283    type Node = ClickableNode;
1284
1285    fn create(&self) -> Self::Node {
1286        ClickableNode::with_handlers(self.on_press.clone(), self.on_click.clone())
1287    }
1288
1289    fn update(&self, node: &mut Self::Node) {
1290        node.on_press.clone_from(&self.on_press);
1291        node.on_click.clone_from(&self.on_click);
1292        node.cached_handler = ClickableNode::create_handler(
1293            node.on_press.clone(),
1294            node.on_click.clone(),
1295            node.press_position.clone(),
1296        );
1297    }
1298
1299    fn capabilities(&self) -> NodeCapabilities {
1300        NodeCapabilities::POINTER_INPUT
1301    }
1302
1303    fn always_update(&self) -> bool {
1304        true
1305    }
1306}
1307
1308/// Node that names the pointer's appearance over its layout node.
1309///
1310/// It handles no pointer events; it carries [`PointerIcon`] as pointer-input
1311/// data so the node becomes a hit target and the shell can read the icon off
1312/// the topmost region under the pointer.
1313#[derive(Debug)]
1314pub struct PointerIconNode {
1315    icon: PointerIcon,
1316    state: NodeState,
1317}
1318
1319impl PointerIconNode {
1320    /// Creates a node requesting `icon` while the pointer is over it.
1321    pub fn new(icon: PointerIcon) -> Self {
1322        Self {
1323            icon,
1324            state: NodeState::new(),
1325        }
1326    }
1327
1328    /// The icon this node requests.
1329    pub fn icon(&self) -> &PointerIcon {
1330        &self.icon
1331    }
1332}
1333
1334impl DelegatableNode for PointerIconNode {
1335    fn node_state(&self) -> &NodeState {
1336        &self.state
1337    }
1338}
1339
1340impl ModifierNode for PointerIconNode {
1341    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
1342        context.invalidate(cranpose_foundation::InvalidationKind::PointerInput);
1343    }
1344
1345    fn as_pointer_input_node(&self) -> Option<&dyn PointerInputNode> {
1346        Some(self)
1347    }
1348
1349    fn as_pointer_input_node_mut(&mut self) -> Option<&mut dyn PointerInputNode> {
1350        Some(self)
1351    }
1352}
1353
1354impl PointerInputNode for PointerIconNode {
1355    fn on_pointer_event(
1356        &mut self,
1357        _context: &mut dyn ModifierNodeContext,
1358        _event: &PointerEvent,
1359    ) -> bool {
1360        false
1361    }
1362
1363    fn hit_test(&self, _x: f32, _y: f32) -> bool {
1364        true
1365    }
1366
1367    fn pointer_input_handler(&self) -> Option<Rc<dyn Fn(PointerEvent)>> {
1368        None
1369    }
1370}
1371
1372/// Element that creates and updates [`PointerIconNode`]s.
1373#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1374pub struct PointerIconElement {
1375    icon: PointerIcon,
1376}
1377
1378impl PointerIconElement {
1379    /// Creates an element requesting `icon`.
1380    pub fn new(icon: PointerIcon) -> Self {
1381        Self { icon }
1382    }
1383}
1384
1385impl ModifierNodeElement for PointerIconElement {
1386    type Node = PointerIconNode;
1387
1388    fn create(&self) -> Self::Node {
1389        PointerIconNode::new(self.icon.clone())
1390    }
1391
1392    fn update(&self, node: &mut Self::Node) {
1393        node.icon = self.icon.clone();
1394    }
1395
1396    fn capabilities(&self) -> NodeCapabilities {
1397        NodeCapabilities::POINTER_INPUT
1398    }
1399}
1400
1401/// Node that applies alpha transparency to its content.
1402#[derive(Debug)]
1403pub struct AlphaNode {
1404    alpha: f32,
1405    state: NodeState,
1406}
1407
1408impl AlphaNode {
1409    pub fn new(alpha: f32) -> Self {
1410        Self {
1411            alpha: alpha.clamp(0.0, 1.0),
1412            state: NodeState::new(),
1413        }
1414    }
1415}
1416
1417impl DelegatableNode for AlphaNode {
1418    fn node_state(&self) -> &NodeState {
1419        &self.state
1420    }
1421}
1422
1423impl_draw_modifier_node!(AlphaNode);
1424
1425/// Element that creates and updates alpha nodes.
1426#[derive(Debug, Clone, PartialEq)]
1427pub struct AlphaElement {
1428    alpha: f32,
1429}
1430
1431impl AlphaElement {
1432    pub fn new(alpha: f32) -> Self {
1433        Self {
1434            alpha: alpha.clamp(0.0, 1.0),
1435        }
1436    }
1437}
1438
1439impl Hash for AlphaElement {
1440    fn hash<H: Hasher>(&self, state: &mut H) {
1441        hash_f32_value(state, self.alpha);
1442    }
1443}
1444
1445impl ModifierNodeElement for AlphaElement {
1446    type Node = AlphaNode;
1447
1448    fn create(&self) -> Self::Node {
1449        AlphaNode::new(self.alpha)
1450    }
1451
1452    fn update(&self, node: &mut Self::Node) {
1453        let new_alpha = self.alpha.clamp(0.0, 1.0);
1454        if (node.alpha - new_alpha).abs() > f32::EPSILON {
1455            node.alpha = new_alpha;
1456        }
1457    }
1458
1459    fn capabilities(&self) -> NodeCapabilities {
1460        NodeCapabilities::DRAW
1461    }
1462}
1463
1464#[derive(Debug)]
1465pub struct ClipToBoundsNode {
1466    state: NodeState,
1467}
1468
1469impl ClipToBoundsNode {
1470    pub fn new() -> Self {
1471        Self {
1472            state: NodeState::new(),
1473        }
1474    }
1475}
1476
1477impl DelegatableNode for ClipToBoundsNode {
1478    fn node_state(&self) -> &NodeState {
1479        &self.state
1480    }
1481}
1482
1483impl_draw_modifier_node!(ClipToBoundsNode);
1484
1485#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1486pub struct ClipToBoundsElement;
1487
1488impl ClipToBoundsElement {
1489    pub fn new() -> Self {
1490        Self
1491    }
1492}
1493
1494impl ModifierNodeElement for ClipToBoundsElement {
1495    type Node = ClipToBoundsNode;
1496
1497    fn create(&self) -> Self::Node {
1498        ClipToBoundsNode::new()
1499    }
1500
1501    fn update(&self, _node: &mut Self::Node) {}
1502
1503    fn capabilities(&self) -> NodeCapabilities {
1504        NodeCapabilities::DRAW
1505    }
1506}
1507
1508pub trait WindowRectSink {
1509    fn set(&self, rect: cranpose_ui_graphics::Rect);
1510}
1511
1512impl WindowRectSink for Cell<cranpose_ui_graphics::Rect> {
1513    fn set(&self, rect: cranpose_ui_graphics::Rect) {
1514        Cell::set(self, rect);
1515    }
1516}
1517
1518struct StateWindowRectSink(cranpose_core::MutableState<cranpose_ui_graphics::Rect>);
1519
1520impl WindowRectSink for StateWindowRectSink {
1521    fn set(&self, rect: cranpose_ui_graphics::Rect) {
1522        self.0.set(rect);
1523    }
1524}
1525
1526pub struct WindowRectReporterNode {
1527    sink: Rc<dyn WindowRectSink>,
1528    state: NodeState,
1529}
1530
1531impl WindowRectReporterNode {
1532    pub(crate) fn new(sink: Rc<dyn WindowRectSink>) -> Self {
1533        Self {
1534            sink,
1535            state: NodeState::new(),
1536        }
1537    }
1538
1539    pub(crate) fn window_rect_sink(&self) -> Rc<dyn WindowRectSink> {
1540        self.sink.clone()
1541    }
1542}
1543
1544impl DelegatableNode for WindowRectReporterNode {
1545    fn node_state(&self) -> &NodeState {
1546        &self.state
1547    }
1548}
1549
1550impl_layout_modifier_node!(WindowRectReporterNode);
1551
1552impl LayoutModifierNode for WindowRectReporterNode {
1553    fn measure(
1554        &self,
1555        _context: &mut dyn ModifierNodeContext,
1556        measurable: &dyn Measurable,
1557        constraints: Constraints,
1558    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
1559        measure_pass_through(measurable, constraints, |_| (0.0, 0.0))
1560    }
1561}
1562
1563#[derive(Clone)]
1564pub struct WindowRectReporterElement {
1565    sink: Rc<dyn WindowRectSink>,
1566}
1567
1568impl WindowRectReporterElement {
1569    pub fn new(sink: Rc<Cell<cranpose_ui_graphics::Rect>>) -> Self {
1570        Self { sink }
1571    }
1572
1573    pub fn from_state(sink: cranpose_core::MutableState<cranpose_ui_graphics::Rect>) -> Self {
1574        Self {
1575            sink: Rc::new(StateWindowRectSink(sink)),
1576        }
1577    }
1578}
1579
1580impl std::fmt::Debug for WindowRectReporterElement {
1581    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1582        f.debug_struct("WindowRectReporterElement").finish()
1583    }
1584}
1585
1586impl PartialEq for WindowRectReporterElement {
1587    fn eq(&self, other: &Self) -> bool {
1588        Rc::ptr_eq(&self.sink, &other.sink)
1589    }
1590}
1591
1592impl Eq for WindowRectReporterElement {}
1593
1594impl Hash for WindowRectReporterElement {
1595    fn hash<H: Hasher>(&self, state: &mut H) {
1596        std::ptr::hash(Rc::as_ptr(&self.sink).cast::<()>(), state);
1597    }
1598}
1599
1600impl_sink_reporter_element!(WindowRectReporterElement, WindowRectReporterNode);
1601
1602pub trait SizeSink {
1603    fn set(&self, size: Size);
1604}
1605
1606impl SizeSink for Cell<Size> {
1607    fn set(&self, size: Size) {
1608        Cell::set(self, size);
1609    }
1610}
1611
1612struct StateSizeSink(cranpose_core::MutableState<Size>);
1613
1614impl SizeSink for StateSizeSink {
1615    fn set(&self, size: Size) {
1616        self.0.set(size);
1617    }
1618}
1619
1620pub struct SizeReporterNode {
1621    sink: Rc<dyn SizeSink>,
1622    state: NodeState,
1623    #[cfg(debug_assertions)]
1624    oscillation: Cell<(Size, Size, u32)>,
1625}
1626
1627impl SizeReporterNode {
1628    pub fn new(sink: Rc<dyn SizeSink>) -> Self {
1629        Self {
1630            sink,
1631            state: NodeState::new(),
1632            #[cfg(debug_assertions)]
1633            oscillation: Cell::new((Size::default(), Size::default(), 0)),
1634        }
1635    }
1636
1637    #[cfg(debug_assertions)]
1638    fn check_oscillation(&self, size: Size) {
1639        const ALTERNATION_CEILING: u32 = 64;
1640        let (last, second_last, count) = self.oscillation.get();
1641        let count = if size == second_last && size != last {
1642            count + 1
1643        } else if size == last {
1644            count
1645        } else {
1646            0
1647        };
1648        assert!(
1649            count <= ALTERNATION_CEILING,
1650            "size-reactive feedback loop: this node's measured size has \
1651             alternated between {last:?} and {size:?} for {count} passes — \
1652             its content's size depends on the size it reports (the \
1653             onSizeChanged self-reference hazard). Break the cycle by making \
1654             the reported size feed only content that does not change this \
1655             node's own measured size."
1656        );
1657        self.oscillation.set((size, last, count));
1658    }
1659}
1660
1661impl DelegatableNode for SizeReporterNode {
1662    fn node_state(&self) -> &NodeState {
1663        &self.state
1664    }
1665}
1666
1667impl_layout_modifier_node!(SizeReporterNode);
1668
1669impl LayoutModifierNode for SizeReporterNode {
1670    fn measure(
1671        &self,
1672        _context: &mut dyn ModifierNodeContext,
1673        measurable: &dyn Measurable,
1674        constraints: Constraints,
1675    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
1676        measure_pass_through(measurable, constraints, |size| {
1677            #[cfg(debug_assertions)]
1678            self.check_oscillation(size);
1679            self.sink.set(size);
1680            (0.0, 0.0)
1681        })
1682    }
1683}
1684
1685#[derive(Clone)]
1686pub struct SizeReporterElement {
1687    sink: Rc<dyn SizeSink>,
1688}
1689
1690impl SizeReporterElement {
1691    pub fn new(sink: Rc<Cell<Size>>) -> Self {
1692        Self { sink }
1693    }
1694
1695    pub fn from_state(sink: cranpose_core::MutableState<Size>) -> Self {
1696        Self {
1697            sink: Rc::new(StateSizeSink(sink)),
1698        }
1699    }
1700}
1701
1702impl std::fmt::Debug for SizeReporterElement {
1703    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1704        f.debug_struct("SizeReporterElement").finish()
1705    }
1706}
1707
1708impl PartialEq for SizeReporterElement {
1709    fn eq(&self, other: &Self) -> bool {
1710        std::ptr::addr_eq(Rc::as_ptr(&self.sink), Rc::as_ptr(&other.sink))
1711    }
1712}
1713
1714impl Hash for SizeReporterElement {
1715    fn hash<H: Hasher>(&self, state: &mut H) {
1716        (Rc::as_ptr(&self.sink) as *const () as usize).hash(state);
1717    }
1718}
1719
1720impl_sink_reporter_element!(SizeReporterElement, SizeReporterNode);
1721
1722pub struct DrawCommandNode {
1723    commands: Vec<DrawCommand>,
1724    node_id: Cell<Option<NodeId>>,
1725    state: NodeState,
1726}
1727
1728impl DrawCommandNode {
1729    pub fn new(commands: Vec<DrawCommand>) -> Self {
1730        Self {
1731            commands,
1732            node_id: Cell::new(None),
1733            state: NodeState::new(),
1734        }
1735    }
1736
1737    #[cfg(test)]
1738    pub fn commands(&self) -> &[DrawCommand] {
1739        &self.commands
1740    }
1741
1742    pub(crate) fn observed_commands(&self) -> Vec<DrawCommand> {
1743        let node_id = self.node_id.get();
1744        self.commands
1745            .iter()
1746            .cloned()
1747            .enumerate()
1748            .map(|(index, command)| observe_draw_command(command, node_id, index))
1749            .collect()
1750    }
1751}
1752
1753impl DelegatableNode for DrawCommandNode {
1754    fn node_state(&self) -> &NodeState {
1755        &self.state
1756    }
1757}
1758
1759impl ModifierNode for DrawCommandNode {
1760    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
1761        attach_draw_observer(&self.node_id, context);
1762    }
1763
1764    fn on_detach(&mut self) {
1765        detach_draw_observer(&self.node_id);
1766    }
1767
1768    fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
1769        Some(self)
1770    }
1771
1772    fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
1773        Some(self)
1774    }
1775}
1776
1777impl DrawModifierNode for DrawCommandNode {
1778    fn draw(&self, _draw_scope: &mut dyn DrawScope) {}
1779}
1780
1781fn observe_draw_command(
1782    command: DrawCommand,
1783    node_id: Option<NodeId>,
1784    command_index: usize,
1785) -> DrawCommand {
1786    let Some(node_id) = node_id else {
1787        return command;
1788    };
1789    let observation = crate::render_state::DrawObservationScope::new(node_id, command_index);
1790    match command {
1791        DrawCommand::Behind(draw) => DrawCommand::Behind(Rc::new(move |scope| {
1792            crate::render_state::observe_draw_reads(observation, || draw(scope));
1793        })),
1794        DrawCommand::WithContent(draw) => DrawCommand::WithContent(Rc::new(move |scope| {
1795            crate::render_state::observe_draw_reads(observation, || draw(scope));
1796        })),
1797        DrawCommand::Overlay(draw) => DrawCommand::Overlay(Rc::new(move |scope| {
1798            crate::render_state::observe_draw_reads(observation, || draw(scope));
1799        })),
1800    }
1801}
1802
1803fn draw_command_tag(cmd: &DrawCommand) -> u8 {
1804    match cmd {
1805        DrawCommand::Behind(_) => 0,
1806        DrawCommand::WithContent(_) => 1,
1807        DrawCommand::Overlay(_) => 2,
1808    }
1809}
1810
1811fn draw_command_closure_identity(cmd: &DrawCommand) -> *const () {
1812    match cmd {
1813        DrawCommand::Behind(f) | DrawCommand::WithContent(f) | DrawCommand::Overlay(f) => {
1814            Rc::as_ptr(f) as *const ()
1815        }
1816    }
1817}
1818
1819#[derive(Clone)]
1820pub struct DrawCommandElement {
1821    commands: Vec<DrawCommand>,
1822}
1823
1824impl DrawCommandElement {
1825    pub fn new(command: DrawCommand) -> Self {
1826        Self {
1827            commands: vec![command],
1828        }
1829    }
1830
1831    pub fn from_commands(commands: Vec<DrawCommand>) -> Self {
1832        Self { commands }
1833    }
1834}
1835
1836impl std::fmt::Debug for DrawCommandElement {
1837    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1838        f.debug_struct("DrawCommandElement")
1839            .field("commands", &self.commands.len())
1840            .finish()
1841    }
1842}
1843
1844impl PartialEq for DrawCommandElement {
1845    fn eq(&self, other: &Self) -> bool {
1846        if self.commands.len() != other.commands.len() {
1847            return false;
1848        }
1849        self.commands
1850            .iter()
1851            .zip(other.commands.iter())
1852            .all(|(a, b)| {
1853                draw_command_tag(a) == draw_command_tag(b)
1854                    && draw_command_closure_identity(a) == draw_command_closure_identity(b)
1855            })
1856    }
1857}
1858
1859impl Eq for DrawCommandElement {}
1860
1861impl std::hash::Hash for DrawCommandElement {
1862    fn hash<H: Hasher>(&self, state: &mut H) {
1863        "draw_commands".hash(state);
1864        self.commands.len().hash(state);
1865        for command in &self.commands {
1866            draw_command_tag(command).hash(state);
1867            (draw_command_closure_identity(command) as usize).hash(state);
1868        }
1869    }
1870}
1871
1872impl ModifierNodeElement for DrawCommandElement {
1873    type Node = DrawCommandNode;
1874
1875    fn create(&self) -> Self::Node {
1876        DrawCommandNode::new(self.commands.clone())
1877    }
1878
1879    fn update(&self, node: &mut Self::Node) {
1880        node.commands.clone_from(&self.commands);
1881    }
1882
1883    fn capabilities(&self) -> NodeCapabilities {
1884        NodeCapabilities::DRAW
1885    }
1886}
1887
1888/// Node that offsets its content by a fixed (x, y) amount.
1889///
1890/// Matches Kotlin: `OffsetNode` in foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Offset.kt
1891#[derive(Debug)]
1892pub struct OffsetNode {
1893    x: f32,
1894    y: f32,
1895    rtl_aware: bool,
1896    state: NodeState,
1897}
1898
1899impl OffsetNode {
1900    pub fn new(x: f32, y: f32, rtl_aware: bool) -> Self {
1901        Self {
1902            x,
1903            y,
1904            rtl_aware,
1905            state: NodeState::new(),
1906        }
1907    }
1908
1909    pub fn offset(&self) -> Point {
1910        Point {
1911            x: self.x,
1912            y: self.y,
1913        }
1914    }
1915
1916    pub fn rtl_aware(&self) -> bool {
1917        self.rtl_aware
1918    }
1919}
1920
1921impl DelegatableNode for OffsetNode {
1922    fn node_state(&self) -> &NodeState {
1923        &self.state
1924    }
1925}
1926
1927impl_layout_modifier_node!(OffsetNode, invalidate = InvalidationKind::Layout);
1928
1929impl LayoutModifierNode for OffsetNode {
1930    fn measure(
1931        &self,
1932        _context: &mut dyn ModifierNodeContext,
1933        measurable: &dyn Measurable,
1934        constraints: Constraints,
1935    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
1936        measure_pass_through(measurable, constraints, |_| (self.x, self.y))
1937    }
1938
1939    forward_intrinsics_to_child!();
1940}
1941
1942/// Element that creates and updates offset nodes.
1943///
1944/// Matches Kotlin: `OffsetElement` in foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Offset.kt
1945#[derive(Debug, Clone, PartialEq)]
1946pub struct OffsetElement {
1947    x: f32,
1948    y: f32,
1949    rtl_aware: bool,
1950}
1951
1952impl OffsetElement {
1953    pub fn new(x: f32, y: f32, rtl_aware: bool) -> Self {
1954        Self { x, y, rtl_aware }
1955    }
1956}
1957
1958impl Hash for OffsetElement {
1959    fn hash<H: Hasher>(&self, state: &mut H) {
1960        hash_f32_value(state, self.x);
1961        hash_f32_value(state, self.y);
1962        self.rtl_aware.hash(state);
1963    }
1964}
1965
1966impl ModifierNodeElement for OffsetElement {
1967    type Node = OffsetNode;
1968
1969    fn create(&self) -> Self::Node {
1970        OffsetNode::new(self.x, self.y, self.rtl_aware)
1971    }
1972
1973    fn update(&self, node: &mut Self::Node) {
1974        if node.x != self.x || node.y != self.y || node.rtl_aware != self.rtl_aware {
1975            node.x = self.x;
1976            node.y = self.y;
1977            node.rtl_aware = self.rtl_aware;
1978        }
1979    }
1980
1981    fn capabilities(&self) -> NodeCapabilities {
1982        NodeCapabilities::LAYOUT
1983    }
1984
1985    fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
1986        Some(InvalidationKind::Layout)
1987    }
1988}
1989
1990/// Node that offsets its content by a fraction of its own measured size.
1991///
1992/// There is no direct Jetpack Compose modifier equivalent; Compose's slide
1993/// transitions receive the measured size through a lambda instead. This node
1994/// backs `slide_in_vertically` / `slide_out_vertically` in
1995/// `AnimatedVisibility`, where the offset is expressed as a fraction of the
1996/// content height.
1997#[derive(Debug)]
1998pub struct FractionalOffsetNode {
1999    x_fraction: f32,
2000    y_fraction: f32,
2001    state: NodeState,
2002}
2003
2004impl FractionalOffsetNode {
2005    pub fn new(x_fraction: f32, y_fraction: f32) -> Self {
2006        Self {
2007            x_fraction,
2008            y_fraction,
2009            state: NodeState::new(),
2010        }
2011    }
2012
2013    pub fn fractions(&self) -> Point {
2014        Point {
2015            x: self.x_fraction,
2016            y: self.y_fraction,
2017        }
2018    }
2019}
2020
2021impl DelegatableNode for FractionalOffsetNode {
2022    fn node_state(&self) -> &NodeState {
2023        &self.state
2024    }
2025}
2026
2027impl_layout_modifier_node!(FractionalOffsetNode, invalidate = InvalidationKind::Layout);
2028
2029impl LayoutModifierNode for FractionalOffsetNode {
2030    fn measure(
2031        &self,
2032        _context: &mut dyn ModifierNodeContext,
2033        measurable: &dyn Measurable,
2034        constraints: Constraints,
2035    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
2036        measure_pass_through(measurable, constraints, |size| {
2037            (self.x_fraction * size.width, self.y_fraction * size.height)
2038        })
2039    }
2040
2041    forward_intrinsics_to_child!();
2042}
2043
2044/// Element that creates and updates fractional offset nodes.
2045#[derive(Debug, Clone, PartialEq)]
2046pub struct FractionalOffsetElement {
2047    x_fraction: f32,
2048    y_fraction: f32,
2049}
2050
2051impl FractionalOffsetElement {
2052    pub fn new(x_fraction: f32, y_fraction: f32) -> Self {
2053        Self {
2054            x_fraction,
2055            y_fraction,
2056        }
2057    }
2058}
2059
2060impl Hash for FractionalOffsetElement {
2061    fn hash<H: Hasher>(&self, state: &mut H) {
2062        "fractional_offset".hash(state);
2063        hash_f32_value(state, self.x_fraction);
2064        hash_f32_value(state, self.y_fraction);
2065    }
2066}
2067
2068impl ModifierNodeElement for FractionalOffsetElement {
2069    type Node = FractionalOffsetNode;
2070
2071    fn create(&self) -> Self::Node {
2072        FractionalOffsetNode::new(self.x_fraction, self.y_fraction)
2073    }
2074
2075    fn update(&self, node: &mut Self::Node) {
2076        if node.x_fraction != self.x_fraction || node.y_fraction != self.y_fraction {
2077            node.x_fraction = self.x_fraction;
2078            node.y_fraction = self.y_fraction;
2079        }
2080    }
2081
2082    fn capabilities(&self) -> NodeCapabilities {
2083        NodeCapabilities::LAYOUT
2084    }
2085
2086    fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
2087        Some(InvalidationKind::Layout)
2088    }
2089}
2090
2091/// Direction for fill modifiers (horizontal, vertical, or both).
2092#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2093pub enum FillDirection {
2094    Horizontal,
2095    Vertical,
2096    Both,
2097}
2098
2099/// Node that fills the maximum available space in one or both dimensions.
2100///
2101/// Matches Kotlin: `FillNode` in foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Size.kt
2102#[derive(Debug)]
2103pub struct FillNode {
2104    direction: FillDirection,
2105    fraction: f32,
2106    state: NodeState,
2107}
2108
2109impl FillNode {
2110    pub fn new(direction: FillDirection, fraction: f32) -> Self {
2111        Self {
2112            direction,
2113            fraction,
2114            state: NodeState::new(),
2115        }
2116    }
2117
2118    pub fn direction(&self) -> FillDirection {
2119        self.direction
2120    }
2121
2122    pub fn fraction(&self) -> f32 {
2123        self.fraction
2124    }
2125}
2126
2127impl DelegatableNode for FillNode {
2128    fn node_state(&self) -> &NodeState {
2129        &self.state
2130    }
2131}
2132
2133impl_layout_modifier_node!(FillNode, invalidate = InvalidationKind::Layout);
2134
2135impl LayoutModifierNode for FillNode {
2136    fn measure(
2137        &self,
2138        _context: &mut dyn ModifierNodeContext,
2139        measurable: &dyn Measurable,
2140        constraints: Constraints,
2141    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
2142        let (fill_width, child_min_width, child_max_width) = if self.direction
2143            != FillDirection::Vertical
2144            && constraints.max_width != f32::INFINITY
2145        {
2146            let width = (constraints.max_width * self.fraction)
2147                .round()
2148                .clamp(constraints.min_width, constraints.max_width);
2149            (width, width, width)
2150        } else {
2151            (
2152                constraints.max_width,
2153                constraints.min_width,
2154                constraints.max_width,
2155            )
2156        };
2157
2158        let (fill_height, child_min_height, child_max_height) = if self.direction
2159            != FillDirection::Horizontal
2160            && constraints.max_height != f32::INFINITY
2161        {
2162            let height = (constraints.max_height * self.fraction)
2163                .round()
2164                .clamp(constraints.min_height, constraints.max_height);
2165            (height, height, height)
2166        } else {
2167            (
2168                constraints.max_height,
2169                constraints.min_height,
2170                constraints.max_height,
2171            )
2172        };
2173
2174        let fill_constraints = Constraints {
2175            min_width: child_min_width,
2176            max_width: child_max_width,
2177            min_height: child_min_height,
2178            max_height: child_max_height,
2179        };
2180
2181        let placeable = measurable.measure(fill_constraints);
2182
2183        let result_width = if self.direction != FillDirection::Vertical
2184            && constraints.max_width != f32::INFINITY
2185        {
2186            fill_width
2187        } else {
2188            placeable.width()
2189        };
2190
2191        let result_height = if self.direction != FillDirection::Horizontal
2192            && constraints.max_height != f32::INFINITY
2193        {
2194            fill_height
2195        } else {
2196            placeable.height()
2197        };
2198
2199        cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size {
2200            width: result_width,
2201            height: result_height,
2202        })
2203    }
2204
2205    forward_intrinsics_to_child!();
2206}
2207
2208/// Element that creates and updates fill nodes.
2209///
2210/// Matches Kotlin: `FillElement` in foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/Size.kt
2211#[derive(Debug, Clone, PartialEq)]
2212pub struct FillElement {
2213    direction: FillDirection,
2214    fraction: f32,
2215}
2216
2217impl FillElement {
2218    pub fn width(fraction: f32) -> Self {
2219        Self {
2220            direction: FillDirection::Horizontal,
2221            fraction,
2222        }
2223    }
2224
2225    pub fn height(fraction: f32) -> Self {
2226        Self {
2227            direction: FillDirection::Vertical,
2228            fraction,
2229        }
2230    }
2231
2232    pub fn size(fraction: f32) -> Self {
2233        Self {
2234            direction: FillDirection::Both,
2235            fraction,
2236        }
2237    }
2238}
2239
2240impl Hash for FillElement {
2241    fn hash<H: Hasher>(&self, state: &mut H) {
2242        self.direction.hash(state);
2243        hash_f32_value(state, self.fraction);
2244    }
2245}
2246
2247impl ModifierNodeElement for FillElement {
2248    type Node = FillNode;
2249
2250    fn create(&self) -> Self::Node {
2251        FillNode::new(self.direction, self.fraction)
2252    }
2253
2254    fn update(&self, node: &mut Self::Node) {
2255        if node.direction != self.direction || node.fraction != self.fraction {
2256            node.direction = self.direction;
2257            node.fraction = self.fraction;
2258        }
2259    }
2260
2261    fn capabilities(&self) -> NodeCapabilities {
2262        NodeCapabilities::LAYOUT
2263    }
2264}
2265
2266#[derive(Debug)]
2267pub struct WeightNode {
2268    weight: f32,
2269    fill: bool,
2270    state: NodeState,
2271}
2272
2273impl WeightNode {
2274    pub fn new(weight: f32, fill: bool) -> Self {
2275        Self {
2276            weight,
2277            fill,
2278            state: NodeState::new(),
2279        }
2280    }
2281
2282    pub fn layout_weight(&self) -> LayoutWeight {
2283        LayoutWeight {
2284            weight: self.weight,
2285            fill: self.fill,
2286        }
2287    }
2288}
2289
2290impl DelegatableNode for WeightNode {
2291    fn node_state(&self) -> &NodeState {
2292        &self.state
2293    }
2294}
2295
2296impl ModifierNode for WeightNode {
2297    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
2298        context.invalidate(cranpose_foundation::InvalidationKind::Layout);
2299    }
2300}
2301
2302#[derive(Debug, Clone, PartialEq)]
2303pub struct WeightElement {
2304    weight: f32,
2305    fill: bool,
2306}
2307
2308impl WeightElement {
2309    pub fn new(weight: f32, fill: bool) -> Self {
2310        Self { weight, fill }
2311    }
2312}
2313
2314impl Hash for WeightElement {
2315    fn hash<H: Hasher>(&self, state: &mut H) {
2316        hash_f32_value(state, self.weight);
2317        self.fill.hash(state);
2318    }
2319}
2320
2321impl ModifierNodeElement for WeightElement {
2322    type Node = WeightNode;
2323
2324    fn create(&self) -> Self::Node {
2325        WeightNode::new(self.weight, self.fill)
2326    }
2327
2328    fn update(&self, node: &mut Self::Node) {
2329        if node.weight != self.weight || node.fill != self.fill {
2330            node.weight = self.weight;
2331            node.fill = self.fill;
2332        }
2333    }
2334
2335    fn capabilities(&self) -> NodeCapabilities {
2336        NodeCapabilities::LAYOUT
2337    }
2338}
2339
2340#[derive(Debug)]
2341pub struct AlignmentNode {
2342    box_alignment: Option<Alignment>,
2343    column_alignment: Option<HorizontalAlignment>,
2344    row_alignment: Option<VerticalAlignment>,
2345    state: NodeState,
2346}
2347
2348impl AlignmentNode {
2349    pub fn new(
2350        box_alignment: Option<Alignment>,
2351        column_alignment: Option<HorizontalAlignment>,
2352        row_alignment: Option<VerticalAlignment>,
2353    ) -> Self {
2354        Self {
2355            box_alignment,
2356            column_alignment,
2357            row_alignment,
2358            state: NodeState::new(),
2359        }
2360    }
2361
2362    pub fn box_alignment(&self) -> Option<Alignment> {
2363        self.box_alignment
2364    }
2365
2366    pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
2367        self.column_alignment
2368    }
2369
2370    pub fn row_alignment(&self) -> Option<VerticalAlignment> {
2371        self.row_alignment
2372    }
2373}
2374
2375impl DelegatableNode for AlignmentNode {
2376    fn node_state(&self) -> &NodeState {
2377        &self.state
2378    }
2379}
2380
2381impl ModifierNode for AlignmentNode {
2382    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
2383        context.invalidate(cranpose_foundation::InvalidationKind::Layout);
2384    }
2385}
2386
2387#[derive(Debug, Clone, PartialEq)]
2388pub struct AlignmentElement {
2389    box_alignment: Option<Alignment>,
2390    column_alignment: Option<HorizontalAlignment>,
2391    row_alignment: Option<VerticalAlignment>,
2392}
2393
2394impl AlignmentElement {
2395    pub fn box_alignment(alignment: Alignment) -> Self {
2396        Self {
2397            box_alignment: Some(alignment),
2398            column_alignment: None,
2399            row_alignment: None,
2400        }
2401    }
2402
2403    pub fn column_alignment(alignment: HorizontalAlignment) -> Self {
2404        Self {
2405            box_alignment: None,
2406            column_alignment: Some(alignment),
2407            row_alignment: None,
2408        }
2409    }
2410
2411    pub fn row_alignment(alignment: VerticalAlignment) -> Self {
2412        Self {
2413            box_alignment: None,
2414            column_alignment: None,
2415            row_alignment: Some(alignment),
2416        }
2417    }
2418}
2419
2420impl Hash for AlignmentElement {
2421    fn hash<H: Hasher>(&self, state: &mut H) {
2422        if let Some(alignment) = self.box_alignment {
2423            state.write_u8(1);
2424            hash_alignment(state, alignment);
2425        } else {
2426            state.write_u8(0);
2427        }
2428        if let Some(alignment) = self.column_alignment {
2429            state.write_u8(1);
2430            hash_horizontal_alignment(state, alignment);
2431        } else {
2432            state.write_u8(0);
2433        }
2434        if let Some(alignment) = self.row_alignment {
2435            state.write_u8(1);
2436            hash_vertical_alignment(state, alignment);
2437        } else {
2438            state.write_u8(0);
2439        }
2440    }
2441}
2442
2443impl ModifierNodeElement for AlignmentElement {
2444    type Node = AlignmentNode;
2445
2446    fn create(&self) -> Self::Node {
2447        AlignmentNode::new(
2448            self.box_alignment,
2449            self.column_alignment,
2450            self.row_alignment,
2451        )
2452    }
2453
2454    fn update(&self, node: &mut Self::Node) {
2455        if node.box_alignment != self.box_alignment {
2456            node.box_alignment = self.box_alignment;
2457        }
2458        if node.column_alignment != self.column_alignment {
2459            node.column_alignment = self.column_alignment;
2460        }
2461        if node.row_alignment != self.row_alignment {
2462            node.row_alignment = self.row_alignment;
2463        }
2464    }
2465
2466    fn capabilities(&self) -> NodeCapabilities {
2467        NodeCapabilities::LAYOUT
2468    }
2469}
2470
2471#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2472pub enum IntrinsicAxis {
2473    Width,
2474    Height,
2475}
2476
2477#[derive(Debug)]
2478pub struct IntrinsicSizeNode {
2479    axis: IntrinsicAxis,
2480    size: IntrinsicSize,
2481    state: NodeState,
2482}
2483
2484impl IntrinsicSizeNode {
2485    pub fn new(axis: IntrinsicAxis, size: IntrinsicSize) -> Self {
2486        Self {
2487            axis,
2488            size,
2489            state: NodeState::new(),
2490        }
2491    }
2492
2493    pub fn axis(&self) -> IntrinsicAxis {
2494        self.axis
2495    }
2496
2497    pub fn intrinsic_size(&self) -> IntrinsicSize {
2498        self.size
2499    }
2500}
2501
2502impl DelegatableNode for IntrinsicSizeNode {
2503    fn node_state(&self) -> &NodeState {
2504        &self.state
2505    }
2506}
2507
2508impl ModifierNode for IntrinsicSizeNode {
2509    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
2510        context.invalidate(cranpose_foundation::InvalidationKind::Layout);
2511    }
2512}
2513
2514#[derive(Debug, Clone, PartialEq)]
2515pub struct IntrinsicSizeElement {
2516    axis: IntrinsicAxis,
2517    size: IntrinsicSize,
2518}
2519
2520impl IntrinsicSizeElement {
2521    pub fn width(size: IntrinsicSize) -> Self {
2522        Self {
2523            axis: IntrinsicAxis::Width,
2524            size,
2525        }
2526    }
2527
2528    pub fn height(size: IntrinsicSize) -> Self {
2529        Self {
2530            axis: IntrinsicAxis::Height,
2531            size,
2532        }
2533    }
2534}
2535
2536impl Hash for IntrinsicSizeElement {
2537    fn hash<H: Hasher>(&self, state: &mut H) {
2538        state.write_u8(match self.axis {
2539            IntrinsicAxis::Width => 0,
2540            IntrinsicAxis::Height => 1,
2541        });
2542        state.write_u8(match self.size {
2543            IntrinsicSize::Min => 0,
2544            IntrinsicSize::Max => 1,
2545        });
2546    }
2547}
2548
2549impl ModifierNodeElement for IntrinsicSizeElement {
2550    type Node = IntrinsicSizeNode;
2551
2552    fn create(&self) -> Self::Node {
2553        IntrinsicSizeNode::new(self.axis, self.size)
2554    }
2555
2556    fn update(&self, node: &mut Self::Node) {
2557        if node.axis != self.axis {
2558            node.axis = self.axis;
2559        }
2560        if node.size != self.size {
2561            node.size = self.size;
2562        }
2563    }
2564
2565    fn capabilities(&self) -> NodeCapabilities {
2566        NodeCapabilities::LAYOUT
2567    }
2568}
2569
2570#[cfg(test)]
2571#[path = "tests/modifier_nodes_tests.rs"]
2572mod tests;