Skip to main content

cranpose_ui/modifier/
slices.rs

1use std::fmt;
2use std::rc::Rc;
3
4use cranpose_foundation::{ModifierNodeChain, NodeCapabilities, PointerEvent};
5use cranpose_ui_graphics::GraphicsLayer;
6
7use crate::draw::DrawCommand;
8use crate::modifier::Modifier;
9use crate::modifier_nodes::{
10    BackgroundNode, ClipToBoundsNode, CornerShapeNode, DrawCommandNode, GraphicsLayerNode,
11    PaddingNode,
12};
13use crate::text_field_modifier_node::TextFieldModifierNode;
14use crate::text_modifier_node::TextModifierNode;
15use cranpose_ui_graphics::EdgeInsets;
16use std::cell::RefCell;
17
18use super::{ModifierChainHandle, Point};
19
20/// Snapshot of modifier node slices that impact draw and pointer subsystems.
21#[derive(Default)]
22pub struct ModifierNodeSlices {
23    draw_commands: Vec<DrawCommand>,
24    pointer_inputs: Vec<Rc<dyn Fn(PointerEvent)>>,
25    click_handlers: Vec<Rc<dyn Fn(Point)>>,
26    clip_to_bounds: bool,
27    text_content: Option<Rc<str>>,
28    graphics_layer: Option<GraphicsLayer>,
29    chain_guard: Option<Rc<ChainGuard>>,
30}
31
32struct ChainGuard {
33    _handle: ModifierChainHandle,
34}
35
36impl Clone for ModifierNodeSlices {
37    fn clone(&self) -> Self {
38        Self {
39            draw_commands: self.draw_commands.clone(),
40            pointer_inputs: self.pointer_inputs.clone(),
41            click_handlers: self.click_handlers.clone(),
42            clip_to_bounds: self.clip_to_bounds,
43            text_content: self.text_content.clone(),
44            graphics_layer: self.graphics_layer,
45            chain_guard: self.chain_guard.clone(),
46        }
47    }
48}
49
50impl ModifierNodeSlices {
51    pub fn draw_commands(&self) -> &[DrawCommand] {
52        &self.draw_commands
53    }
54
55    pub fn pointer_inputs(&self) -> &[Rc<dyn Fn(PointerEvent)>] {
56        &self.pointer_inputs
57    }
58
59    pub fn click_handlers(&self) -> &[Rc<dyn Fn(Point)>] {
60        &self.click_handlers
61    }
62
63    pub fn clip_to_bounds(&self) -> bool {
64        self.clip_to_bounds
65    }
66
67    pub fn text_content(&self) -> Option<&str> {
68        self.text_content.as_deref()
69    }
70
71    pub fn text_content_rc(&self) -> Option<Rc<str>> {
72        self.text_content.clone()
73    }
74
75    pub fn graphics_layer(&self) -> Option<GraphicsLayer> {
76        self.graphics_layer
77    }
78
79    pub fn with_chain_guard(mut self, handle: ModifierChainHandle) -> Self {
80        self.chain_guard = Some(Rc::new(ChainGuard { _handle: handle }));
81        self
82    }
83
84    /// Resets the slice collection for reuse, retaining vector capacity.
85    pub fn clear(&mut self) {
86        self.draw_commands.clear();
87        self.pointer_inputs.clear();
88        self.click_handlers.clear();
89        self.clip_to_bounds = false;
90        self.text_content = None;
91        self.graphics_layer = None;
92        self.chain_guard = None;
93    }
94}
95
96impl fmt::Debug for ModifierNodeSlices {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        f.debug_struct("ModifierNodeSlices")
99            .field("draw_commands", &self.draw_commands.len())
100            .field("pointer_inputs", &self.pointer_inputs.len())
101            .field("click_handlers", &self.click_handlers.len())
102            .field("clip_to_bounds", &self.clip_to_bounds)
103            .field("text_content", &self.text_content)
104            .field("graphics_layer", &self.graphics_layer)
105            .finish()
106    }
107}
108
109/// Collects modifier node slices directly from a reconciled [`ModifierNodeChain`].
110pub fn collect_modifier_slices(chain: &ModifierNodeChain) -> ModifierNodeSlices {
111    let mut slices = ModifierNodeSlices::default();
112    collect_modifier_slices_into(chain, &mut slices);
113    slices
114}
115
116/// Collects modifier node slices into an existing buffer to reuse allocations.
117pub fn collect_modifier_slices_into(chain: &ModifierNodeChain, slices: &mut ModifierNodeSlices) {
118    slices.clear();
119
120    chain.for_each_node_with_capability(NodeCapabilities::POINTER_INPUT, |_ref, node| {
121        let _any = node.as_any();
122
123        // ClickableNode is now handled as a standard PointerInputNode
124        // to support drag cancellation and proper click semantics (Up vs Down)
125
126        // Collect general pointer input handlers (non-clickable)
127        if let Some(handler) = node
128            .as_pointer_input_node()
129            .and_then(|n| n.pointer_input_handler())
130        {
131            slices.pointer_inputs.push(handler);
132        }
133    });
134
135    // Track background and shape to combine them in draw commands
136    let background_color = RefCell::new(None);
137    let corner_shape = RefCell::new(None);
138
139    chain.for_each_node_with_capability(NodeCapabilities::DRAW, |_ref, node| {
140        let any = node.as_any();
141
142        // Collect background color from BackgroundNode
143        if let Some(bg_node) = any.downcast_ref::<BackgroundNode>() {
144            *background_color.borrow_mut() = Some(bg_node.color());
145            // Note: BackgroundNode can have an optional shape, but we primarily track
146            // shape via CornerShapeNode for flexibility
147            if bg_node.shape().is_some() {
148                *corner_shape.borrow_mut() = bg_node.shape();
149            }
150        }
151
152        // Collect corner shape from CornerShapeNode
153        if let Some(shape_node) = any.downcast_ref::<CornerShapeNode>() {
154            *corner_shape.borrow_mut() = Some(shape_node.shape());
155        }
156
157        // Collect draw commands from DrawCommandNode
158        if let Some(commands) = any.downcast_ref::<DrawCommandNode>() {
159            slices
160                .draw_commands
161                .extend(commands.commands().iter().cloned());
162        }
163
164        // Use create_draw_closure() for nodes with dynamic content (cursor blink, selection)
165        // This defers evaluation to render time, enabling live updates.
166        // Fallback to draw() for nodes with static content.
167        if let Some(draw_node) = node.as_draw_node() {
168            if let Some(closure) = draw_node.create_draw_closure() {
169                // Deferred closure - evaluates at render time
170                slices.draw_commands.push(DrawCommand::Overlay(closure));
171            } else {
172                // Static draw - evaluate now
173                use cranpose_ui_graphics::{DrawScope as _, DrawScopeDefault};
174                let mut scope = DrawScopeDefault::new(crate::modifier::Size {
175                    width: 0.0,
176                    height: 0.0,
177                });
178                draw_node.draw(&mut scope);
179                let primitives = scope.into_primitives();
180                if !primitives.is_empty() {
181                    let draw_cmd = Rc::new(move |_size: crate::modifier::Size| primitives.clone());
182                    slices.draw_commands.push(DrawCommand::Overlay(draw_cmd));
183                }
184            }
185        }
186
187        // Collect graphics layer from GraphicsLayerNode
188        if let Some(layer_node) = any.downcast_ref::<GraphicsLayerNode>() {
189            slices.graphics_layer = Some(layer_node.layer());
190        }
191
192        if any.is::<ClipToBoundsNode>() {
193            slices.clip_to_bounds = true;
194        }
195    });
196
197    // Collect padding from modifier chain for cursor positioning
198    let mut padding = EdgeInsets::default();
199    chain.for_each_node_with_capability(NodeCapabilities::LAYOUT, |_ref, node| {
200        let any = node.as_any();
201        if let Some(padding_node) = any.downcast_ref::<PaddingNode>() {
202            let p = padding_node.padding();
203            padding.left += p.left;
204            padding.top += p.top;
205            padding.right += p.right;
206            padding.bottom += p.bottom;
207        }
208    });
209
210    // Collect text content from TextModifierNode or TextFieldModifierNode (LAYOUT capability)
211    chain.for_each_node_with_capability(NodeCapabilities::LAYOUT, |_ref, node| {
212        let any = node.as_any();
213        if let Some(text_node) = any.downcast_ref::<TextModifierNode>() {
214            // Rightmost text modifier wins
215            slices.text_content = Some(text_node.text_arc());
216        }
217        // Also check for TextFieldModifierNode (editable text fields)
218        if let Some(text_field_node) = any.downcast_ref::<TextFieldModifierNode>() {
219            let text = text_field_node.text();
220            slices.text_content = Some(Rc::from(text));
221
222            // Update content offsets for cursor positioning in collect_draw_primitives()
223            text_field_node.set_content_offset(padding.left);
224            text_field_node.set_content_y_offset(padding.top);
225
226            // Cursor/selection rendering is now handled via DrawModifierNode::collect_draw_primitives()
227            // in the DRAW capability loop above
228        }
229    });
230
231    // Convert background + shape into a draw command
232    if let Some(color) = background_color.into_inner() {
233        let shape = corner_shape.into_inner();
234
235        let draw_cmd = Rc::new(move |size: crate::modifier::Size| {
236            use crate::modifier::{Brush, Rect};
237            use cranpose_ui_graphics::DrawPrimitive;
238
239            let brush = Brush::solid(color);
240            let rect = Rect {
241                x: 0.0,
242                y: 0.0,
243                width: size.width,
244                height: size.height,
245            };
246
247            if let Some(shape) = shape {
248                let radii = shape.resolve(size.width, size.height);
249                vec![DrawPrimitive::RoundRect { rect, brush, radii }]
250            } else {
251                vec![DrawPrimitive::Rect { rect, brush }]
252            }
253        });
254
255        slices
256            .draw_commands
257            .insert(0, DrawCommand::Behind(draw_cmd));
258    }
259}
260
261/// Collects modifier node slices by instantiating a temporary node chain from a [`Modifier`].
262pub fn collect_slices_from_modifier(modifier: &Modifier) -> ModifierNodeSlices {
263    let mut handle = ModifierChainHandle::new();
264    let _ = handle.update(modifier);
265    collect_modifier_slices(handle.chain()).with_chain_guard(handle)
266}