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