Skip to main content

cranpose_ui/
modifier_nodes.rs

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