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