1use std::fmt;
2use std::mem::size_of;
3use std::rc::Rc;
4
5use cranpose_foundation::{ModifierNodeChain, NodeCapabilities, PointerEvent};
6use cranpose_ui_graphics::{ColorFilter, GraphicsLayer, RenderEffect};
7
8use super::{ModifierChainHandle, Point};
9use crate::draw::DrawCommand;
10use crate::modifier::scroll::{MotionContextAnimatedNode, TranslatedContentContextNode};
11use crate::modifier::Modifier;
12use crate::modifier_nodes::{
13 BackgroundNode, ClipToBoundsNode, CornerShapeNode, DrawCommandNode, GraphicsLayerNode,
14 PaddingNode,
15};
16use crate::text::{TextLayoutOptions, TextStyle};
17use crate::text_field_modifier_node::TextFieldModifierNode;
18use crate::text_modifier_node::{TextModifierNode, TextPreparedLayoutHandle};
19use cranpose_ui_graphics::EdgeInsets;
20
21#[derive(Default)]
23pub struct ModifierNodeSlices {
24 draw_commands: Vec<DrawCommand>,
25 pointer_inputs: Vec<Rc<dyn Fn(PointerEvent)>>,
26 click_handlers: Vec<Rc<dyn Fn(Point)>>,
27 clip_to_bounds: bool,
28 motion_context_animated: bool,
29 translated_content_context: bool,
30 translated_content_context_identity: Option<usize>,
31 text_content: Option<Rc<crate::text::AnnotatedString>>,
32 text_style: Option<TextStyle>,
33 text_layout_options: Option<TextLayoutOptions>,
34 prepared_text_layout: Option<TextPreparedLayoutHandle>,
35 graphics_layer: Option<GraphicsLayer>,
36 graphics_layer_resolver: Option<Rc<dyn Fn() -> GraphicsLayer>>,
37 chain_guard: Option<Rc<ChainGuard>>,
38}
39
40struct ChainGuard {
41 _handle: ModifierChainHandle,
42}
43
44#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
45pub struct ModifierNodeSlicesDebugStats {
46 pub draw_command_count: usize,
47 pub draw_command_capacity: usize,
48 pub pointer_input_count: usize,
49 pub pointer_input_capacity: usize,
50 pub click_handler_count: usize,
51 pub click_handler_capacity: usize,
52 pub has_text_content: bool,
53 pub has_text_style: bool,
54 pub has_text_layout_options: bool,
55 pub has_prepared_text_layout: bool,
56 pub has_graphics_layer: bool,
57 pub has_graphics_layer_resolver: bool,
58 pub heap_bytes: usize,
59}
60
61impl Clone for ModifierNodeSlices {
62 fn clone(&self) -> Self {
63 Self {
64 draw_commands: self.draw_commands.clone(),
65 pointer_inputs: self.pointer_inputs.clone(),
66 click_handlers: self.click_handlers.clone(),
67 clip_to_bounds: self.clip_to_bounds,
68 motion_context_animated: self.motion_context_animated,
69 translated_content_context: self.translated_content_context,
70 translated_content_context_identity: self.translated_content_context_identity,
71 text_content: self.text_content.clone(),
72 text_style: self.text_style.clone(),
73 text_layout_options: self.text_layout_options,
74 prepared_text_layout: self.prepared_text_layout.clone(),
75 graphics_layer: self.graphics_layer.clone(),
76 graphics_layer_resolver: self.graphics_layer_resolver.clone(),
77 chain_guard: self.chain_guard.clone(),
78 }
79 }
80}
81
82fn merge_graphics_layers(base: GraphicsLayer, overlay: GraphicsLayer) -> GraphicsLayer {
97 GraphicsLayer {
98 alpha: (base.alpha * overlay.alpha).clamp(0.0, 1.0),
99 scale: base.scale * overlay.scale,
100 scale_x: base.scale_x * overlay.scale_x,
101 scale_y: base.scale_y * overlay.scale_y,
102 rotation_x: base.rotation_x + overlay.rotation_x,
103 rotation_y: base.rotation_y + overlay.rotation_y,
104 rotation_z: base.rotation_z + overlay.rotation_z,
105 camera_distance: overlay.camera_distance,
106 transform_origin: overlay.transform_origin,
107 translation_x: base.translation_x + overlay.translation_x,
108 translation_y: base.translation_y + overlay.translation_y,
109 shadow_elevation: overlay.shadow_elevation,
110 ambient_shadow_color: overlay.ambient_shadow_color,
111 spot_shadow_color: overlay.spot_shadow_color,
112 shape: overlay.shape,
113 clip: base.clip || overlay.clip,
114 compositing_strategy: overlay.compositing_strategy,
115 blend_mode: overlay.blend_mode,
116 color_filter: compose_color_filters(base.color_filter, overlay.color_filter),
117 render_effect: compose_render_effects(base.render_effect, overlay.render_effect),
120 backdrop_effect: overlay.backdrop_effect.or(base.backdrop_effect),
123 }
124}
125
126fn compose_render_effects(
127 outer: Option<RenderEffect>,
128 inner: Option<RenderEffect>,
129) -> Option<RenderEffect> {
130 match (outer, inner) {
131 (None, None) => None,
132 (Some(effect), None) | (None, Some(effect)) => Some(effect),
133 (Some(outer_effect), Some(inner_effect)) => Some(inner_effect.then(outer_effect)),
134 }
135}
136
137fn compose_color_filters(
138 base: Option<ColorFilter>,
139 overlay: Option<ColorFilter>,
140) -> Option<ColorFilter> {
141 match (base, overlay) {
142 (None, None) => None,
143 (Some(filter), None) | (None, Some(filter)) => Some(filter),
144 (Some(filter), Some(next)) => Some(filter.compose(next)),
145 }
146}
147
148impl ModifierNodeSlices {
149 pub fn draw_commands(&self) -> &[DrawCommand] {
150 &self.draw_commands
151 }
152
153 pub fn pointer_inputs(&self) -> &[Rc<dyn Fn(PointerEvent)>] {
154 &self.pointer_inputs
155 }
156
157 pub fn click_handlers(&self) -> &[Rc<dyn Fn(Point)>] {
158 &self.click_handlers
159 }
160
161 pub fn clip_to_bounds(&self) -> bool {
162 self.clip_to_bounds
163 }
164
165 pub fn motion_context_animated(&self) -> bool {
166 self.motion_context_animated
167 }
168
169 pub fn translated_content_context(&self) -> bool {
170 self.translated_content_context
171 }
172
173 pub fn translated_content_context_identity(&self) -> Option<usize> {
174 self.translated_content_context_identity
175 }
176
177 pub fn text_content(&self) -> Option<&str> {
178 self.text_content.as_ref().map(|a| a.text.as_str())
179 }
180
181 pub fn annotated_text(&self) -> Option<&crate::text::AnnotatedString> {
182 self.text_content.as_deref()
183 }
184
185 pub fn annotated_string(&self) -> Option<crate::text::AnnotatedString> {
186 self.annotated_text().cloned()
187 }
188
189 pub fn text_style(&self) -> Option<&TextStyle> {
190 self.text_style.as_ref()
191 }
192
193 pub fn text_layout_options(&self) -> Option<TextLayoutOptions> {
194 self.text_layout_options
195 }
196
197 pub fn prepare_text_layout(
198 &self,
199 max_width: Option<f32>,
200 ) -> Option<crate::text::PreparedTextLayout> {
201 if let Some(handle) = &self.prepared_text_layout {
202 return Some(handle.prepare(max_width));
203 }
204
205 let text = self.annotated_text()?;
206 let style = self.text_style.clone().unwrap_or_default();
207 Some(crate::text::prepare_text_layout(
208 text,
209 &style,
210 self.text_layout_options.unwrap_or_default(),
211 max_width,
212 ))
213 }
214
215 pub fn graphics_layer(&self) -> Option<GraphicsLayer> {
216 if let Some(resolve) = &self.graphics_layer_resolver {
217 Some(resolve())
218 } else {
219 self.graphics_layer.clone()
220 }
221 }
222
223 fn push_graphics_layer(
224 &mut self,
225 layer: GraphicsLayer,
226 resolver: Option<Rc<dyn Fn() -> GraphicsLayer>>,
227 ) {
228 let existing_snapshot = self.graphics_layer.clone();
229 let next_snapshot = existing_snapshot
230 .as_ref()
231 .map(|current| merge_graphics_layers(current.clone(), layer.clone()))
232 .unwrap_or_else(|| layer.clone());
233 let existing_resolver = self.graphics_layer_resolver.clone();
234
235 self.graphics_layer = Some(next_snapshot);
236 self.graphics_layer_resolver = match (existing_resolver, resolver) {
237 (None, None) => None,
238 (Some(current_resolver), None) => {
239 let layer = layer.clone();
240 Some(Rc::new(move || {
241 merge_graphics_layers(current_resolver(), layer.clone())
242 }))
243 }
244 (None, Some(next_resolver)) => {
245 let base = existing_snapshot.unwrap_or_default();
246 Some(Rc::new(move || {
247 merge_graphics_layers(base.clone(), next_resolver())
248 }))
249 }
250 (Some(current_resolver), Some(next_resolver)) => Some(Rc::new(move || {
251 merge_graphics_layers(current_resolver(), next_resolver())
252 })),
253 };
254 }
255
256 pub fn with_chain_guard(mut self, handle: ModifierChainHandle) -> Self {
257 self.chain_guard = Some(Rc::new(ChainGuard { _handle: handle }));
258 self
259 }
260
261 pub fn debug_stats(&self) -> ModifierNodeSlicesDebugStats {
262 let draw_command_bytes = self.draw_commands.capacity() * size_of::<DrawCommand>();
263 let pointer_input_bytes =
264 self.pointer_inputs.capacity() * size_of::<Rc<dyn Fn(PointerEvent)>>();
265 let click_handler_bytes = self.click_handlers.capacity() * size_of::<Rc<dyn Fn(Point)>>();
266 ModifierNodeSlicesDebugStats {
267 draw_command_count: self.draw_commands.len(),
268 draw_command_capacity: self.draw_commands.capacity(),
269 pointer_input_count: self.pointer_inputs.len(),
270 pointer_input_capacity: self.pointer_inputs.capacity(),
271 click_handler_count: self.click_handlers.len(),
272 click_handler_capacity: self.click_handlers.capacity(),
273 has_text_content: self.text_content.is_some(),
274 has_text_style: self.text_style.is_some(),
275 has_text_layout_options: self.text_layout_options.is_some(),
276 has_prepared_text_layout: self.prepared_text_layout.is_some(),
277 has_graphics_layer: self.graphics_layer.is_some(),
278 has_graphics_layer_resolver: self.graphics_layer_resolver.is_some(),
279 heap_bytes: draw_command_bytes + pointer_input_bytes + click_handler_bytes,
280 }
281 }
282
283 pub fn clear(&mut self) {
285 self.draw_commands.clear();
286 self.pointer_inputs.clear();
287 self.click_handlers.clear();
288 self.clip_to_bounds = false;
289 self.motion_context_animated = false;
290 self.translated_content_context = false;
291 self.translated_content_context_identity = None;
292 self.text_content = None;
293 self.text_style = None;
294 self.text_layout_options = None;
295 self.prepared_text_layout = None;
296 self.graphics_layer = None;
297 self.graphics_layer_resolver = None;
298 self.chain_guard = None;
299 }
300}
301
302impl fmt::Debug for ModifierNodeSlices {
303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304 f.debug_struct("ModifierNodeSlices")
305 .field("draw_commands", &self.draw_commands.len())
306 .field("pointer_inputs", &self.pointer_inputs.len())
307 .field("click_handlers", &self.click_handlers.len())
308 .field("clip_to_bounds", &self.clip_to_bounds)
309 .field("motion_context_animated", &self.motion_context_animated)
310 .field(
311 "translated_content_context",
312 &self.translated_content_context,
313 )
314 .field(
315 "translated_content_context_identity",
316 &self.translated_content_context_identity,
317 )
318 .field("text_content", &self.text_content)
319 .field("text_style", &self.text_style)
320 .field("text_layout_options", &self.text_layout_options)
321 .field("prepared_text_layout", &self.prepared_text_layout.is_some())
322 .field("graphics_layer", &self.graphics_layer)
323 .field(
324 "graphics_layer_resolver",
325 &self.graphics_layer_resolver.is_some(),
326 )
327 .finish()
328 }
329}
330
331pub fn collect_modifier_slices(chain: &ModifierNodeChain) -> ModifierNodeSlices {
333 let mut slices = ModifierNodeSlices::default();
334 collect_modifier_slices_into(chain, &mut slices);
335 slices
336}
337
338pub fn collect_modifier_slices_into(chain: &ModifierNodeChain, slices: &mut ModifierNodeSlices) {
343 slices.clear();
344
345 let caps = chain.capabilities();
346 let has_pointer = caps.intersects(NodeCapabilities::POINTER_INPUT);
347 let has_draw = caps.intersects(NodeCapabilities::DRAW);
348 let has_layout = caps.intersects(NodeCapabilities::LAYOUT);
349
350 if !has_pointer && !has_draw && !has_layout {
351 return;
352 }
353
354 let mut background_color = None;
355 let mut background_insert_index = None::<usize>;
356 let mut corner_shape = None;
357 let mut padding = EdgeInsets::default();
358
359 for node_ref in chain.head_to_tail() {
360 let node_caps = node_ref.kind_set();
361
362 node_ref.with_node(|node| {
363 let any = node.as_any();
364
365 if has_pointer && node_caps.intersects(NodeCapabilities::POINTER_INPUT) {
367 if let Some(handler) = node
368 .as_pointer_input_node()
369 .and_then(|n| n.pointer_input_handler())
370 {
371 slices.pointer_inputs.push(handler);
372 }
373 }
374
375 if has_draw && node_caps.intersects(NodeCapabilities::DRAW) {
377 if let Some(bg_node) = any.downcast_ref::<BackgroundNode>() {
378 background_color = Some(bg_node.color());
379 background_insert_index = Some(slices.draw_commands.len());
380 if bg_node.shape().is_some() {
381 corner_shape = bg_node.shape();
382 }
383 }
384
385 if let Some(shape_node) = any.downcast_ref::<CornerShapeNode>() {
386 corner_shape = Some(shape_node.shape());
387 }
388
389 if let Some(commands) = any.downcast_ref::<DrawCommandNode>() {
390 slices
391 .draw_commands
392 .extend(commands.commands().iter().cloned());
393 }
394
395 if let Some(draw_node) = node.as_draw_node() {
396 if let Some(closure) = draw_node.create_draw_closure() {
397 slices.draw_commands.push(DrawCommand::Overlay(closure));
398 } else {
399 use cranpose_ui_graphics::{DrawScope as _, DrawScopeDefault};
400 let mut scope = DrawScopeDefault::new(crate::modifier::Size {
401 width: 0.0,
402 height: 0.0,
403 });
404 draw_node.draw(&mut scope);
405 let primitives = scope.into_primitives();
406 if !primitives.is_empty() {
407 let draw_cmd =
408 Rc::new(move |_size: crate::modifier::Size| primitives.clone());
409 slices.draw_commands.push(DrawCommand::Overlay(draw_cmd));
410 }
411 }
412 }
413
414 if let Some(layer_node) = any.downcast_ref::<GraphicsLayerNode>() {
415 slices.push_graphics_layer(
416 layer_node.layer_snapshot(),
417 layer_node.layer_resolver(),
418 );
419 }
420
421 if any.is::<ClipToBoundsNode>() {
422 slices.clip_to_bounds = true;
423 }
424 }
425
426 if has_layout && node_caps.intersects(NodeCapabilities::LAYOUT) {
428 if let Some(padding_node) = any.downcast_ref::<PaddingNode>() {
429 let p = padding_node.padding();
430 padding.left += p.left;
431 padding.top += p.top;
432 padding.right += p.right;
433 padding.bottom += p.bottom;
434 }
435
436 if let Some(motion_context_node) = any.downcast_ref::<MotionContextAnimatedNode>() {
437 slices.motion_context_animated = motion_context_node.is_active();
438 }
439
440 if let Some(translated_content_node) =
441 any.downcast_ref::<TranslatedContentContextNode>()
442 {
443 slices.translated_content_context = translated_content_node.is_active();
444 slices.translated_content_context_identity =
445 Some(translated_content_node.identity());
446 }
447
448 if let Some(text_node) = any.downcast_ref::<TextModifierNode>() {
449 slices.text_content = Some(text_node.annotated_text());
450 slices.text_style = Some(text_node.style().clone());
451 slices.text_layout_options = Some(text_node.options());
452 slices.prepared_text_layout = Some(text_node.prepared_layout_handle());
453 }
454
455 if let Some(text_field_node) = any.downcast_ref::<TextFieldModifierNode>() {
456 let text = text_field_node.text();
457 slices.text_content = Some(Rc::new(crate::text::AnnotatedString::from(text)));
458 slices.text_style = Some(text_field_node.style().clone());
459 slices.text_layout_options = Some(TextLayoutOptions::default());
460 slices.prepared_text_layout = None;
461
462 text_field_node.set_content_offset(padding.left);
463 text_field_node.set_content_y_offset(padding.top);
464 }
465 }
466 });
467 }
468
469 if let Some(color) = background_color {
471 let draw_cmd = Rc::new(move |size: crate::modifier::Size| {
472 use crate::modifier::{Brush, Rect};
473 use cranpose_ui_graphics::DrawPrimitive;
474
475 let brush = Brush::solid(color);
476 let rect = Rect {
477 x: 0.0,
478 y: 0.0,
479 width: size.width,
480 height: size.height,
481 };
482
483 if let Some(shape) = corner_shape {
484 let radii = shape.resolve(size.width, size.height);
485 vec![DrawPrimitive::RoundRect { rect, brush, radii }]
486 } else {
487 vec![DrawPrimitive::Rect { rect, brush }]
488 }
489 });
490
491 let insert_index = background_insert_index
492 .unwrap_or(0)
493 .min(slices.draw_commands.len());
494 slices
495 .draw_commands
496 .insert(insert_index, DrawCommand::Behind(draw_cmd));
497 }
498}
499
500pub fn collect_slices_from_modifier(modifier: &Modifier) -> ModifierNodeSlices {
502 let mut handle = ModifierChainHandle::new();
503 let _ = handle.update(modifier);
504 collect_modifier_slices(handle.chain()).with_chain_guard(handle)
505}