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