Skip to main content

cranpose_ui/
modifier_nodes.rs

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