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