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