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