Skip to main content

cranpose_ui/
modifier_nodes.rs

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