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, RoundedCornerShape};
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, WindowRectReporterNode,
15};
16use crate::text::{TextLayoutOptions, TextStyle};
17use crate::text_field_modifier_node::{TextFieldModifierNode, TextPanResolver};
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 pointer_input_sizes: Vec<Rc<std::cell::Cell<cranpose_ui_graphics::Size>>>,
33 click_handlers: Vec<Rc<dyn Fn(Point)>>,
34 clip_to_bounds: bool,
35 motion_context_animated: bool,
36 translated_content_context: bool,
37 translated_content_context_identity: Option<usize>,
38 translated_content_offset_reader: Option<Rc<dyn Fn() -> Point>>,
39 text_content: Option<Rc<crate::text::AnnotatedString>>,
40 text_style: Option<TextStyle>,
41 text_layout_options: Option<TextLayoutOptions>,
42 prepared_text_layout: Option<TextPreparedLayoutHandle>,
43 text_pan: Option<TextPanResolver>,
44 text_field_window_origin: Option<Rc<std::cell::Cell<Point>>>,
50 viewport_window_rect: Option<Rc<std::cell::Cell<cranpose_ui_graphics::Rect>>>,
56 graphics_layer: Option<GraphicsLayer>,
57 graphics_layer_resolver: Option<Rc<dyn Fn() -> GraphicsLayer>>,
58 corner_shape: Option<RoundedCornerShape>,
59 chain_guard: Option<Rc<ChainGuard>>,
60}
61
62struct ChainGuard {
63 _handle: ModifierChainHandle,
64}
65
66#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
67pub struct ModifierNodeSlicesDebugStats {
68 pub draw_command_count: usize,
69 pub draw_command_capacity: usize,
70 pub pointer_input_count: usize,
71 pub pointer_input_capacity: usize,
72 pub click_handler_count: usize,
73 pub click_handler_capacity: usize,
74 pub has_text_content: bool,
75 pub has_text_style: bool,
76 pub has_text_layout_options: bool,
77 pub has_prepared_text_layout: bool,
78 pub has_graphics_layer: bool,
79 pub has_graphics_layer_resolver: bool,
80 pub heap_bytes: usize,
81}
82
83impl Clone for ModifierNodeSlices {
84 fn clone(&self) -> Self {
85 Self {
86 draw_commands: self.draw_commands.clone(),
87 pointer_inputs: self.pointer_inputs.clone(),
88 pointer_input_sizes: self.pointer_input_sizes.clone(),
89 click_handlers: self.click_handlers.clone(),
90 clip_to_bounds: self.clip_to_bounds,
91 motion_context_animated: self.motion_context_animated,
92 translated_content_context: self.translated_content_context,
93 translated_content_context_identity: self.translated_content_context_identity,
94 translated_content_offset_reader: self.translated_content_offset_reader.clone(),
95 text_content: self.text_content.clone(),
96 text_style: self.text_style.clone(),
97 text_layout_options: self.text_layout_options,
98 prepared_text_layout: self.prepared_text_layout.clone(),
99 text_pan: self.text_pan.clone(),
100 text_field_window_origin: self.text_field_window_origin.clone(),
101 viewport_window_rect: self.viewport_window_rect.clone(),
102 graphics_layer: self.graphics_layer.clone(),
103 graphics_layer_resolver: self.graphics_layer_resolver.clone(),
104 corner_shape: self.corner_shape,
105 chain_guard: self.chain_guard.clone(),
106 }
107 }
108}
109
110fn merge_graphics_layers(base: GraphicsLayer, overlay: GraphicsLayer) -> GraphicsLayer {
125 GraphicsLayer {
126 alpha: (base.alpha * overlay.alpha).clamp(0.0, 1.0),
127 scale: base.scale * overlay.scale,
128 scale_x: base.scale_x * overlay.scale_x,
129 scale_y: base.scale_y * overlay.scale_y,
130 rotation_x: base.rotation_x + overlay.rotation_x,
131 rotation_y: base.rotation_y + overlay.rotation_y,
132 rotation_z: base.rotation_z + overlay.rotation_z,
133 camera_distance: overlay.camera_distance,
134 transform_origin: overlay.transform_origin,
135 translation_x: base.translation_x + overlay.translation_x,
136 translation_y: base.translation_y + overlay.translation_y,
137 shadow_elevation: overlay.shadow_elevation,
138 ambient_shadow_color: overlay.ambient_shadow_color,
139 spot_shadow_color: overlay.spot_shadow_color,
140 shape: overlay.shape,
141 clip: base.clip || overlay.clip,
142 compositing_strategy: overlay.compositing_strategy,
143 blend_mode: overlay.blend_mode,
144 color_filter: compose_color_filters(base.color_filter, overlay.color_filter),
145 render_effect: compose_render_effects(base.render_effect, overlay.render_effect),
148 backdrop_effect: overlay.backdrop_effect.or(base.backdrop_effect),
151 }
152}
153
154fn compose_render_effects(
155 outer: Option<RenderEffect>,
156 inner: Option<RenderEffect>,
157) -> Option<RenderEffect> {
158 match (outer, inner) {
159 (None, None) => None,
160 (Some(effect), None) | (None, Some(effect)) => Some(effect),
161 (Some(outer_effect), Some(inner_effect)) => Some(inner_effect.then(outer_effect)),
162 }
163}
164
165fn compose_color_filters(
166 base: Option<ColorFilter>,
167 overlay: Option<ColorFilter>,
168) -> Option<ColorFilter> {
169 match (base, overlay) {
170 (None, None) => None,
171 (Some(filter), None) | (None, Some(filter)) => Some(filter),
172 (Some(filter), Some(next)) => Some(filter.compose(next)),
173 }
174}
175
176impl ModifierNodeSlices {
177 pub fn draw_commands(&self) -> &[DrawCommand] {
178 &self.draw_commands
179 }
180
181 pub fn pointer_inputs(&self) -> &[Rc<dyn Fn(PointerEvent)>] {
182 &self.pointer_inputs
183 }
184
185 pub fn pointer_input_size_sinks(&self) -> &[Rc<std::cell::Cell<cranpose_ui_graphics::Size>>] {
189 &self.pointer_input_sizes
190 }
191
192 pub fn publish_pointer_input_size(&self, size: cranpose_ui_graphics::Size) {
204 for sink in &self.pointer_input_sizes {
205 sink.set(size);
206 }
207 }
208
209 pub fn click_handlers(&self) -> &[Rc<dyn Fn(Point)>] {
210 &self.click_handlers
211 }
212
213 pub fn clip_to_bounds(&self) -> bool {
214 self.clip_to_bounds
215 }
216
217 pub fn motion_context_animated(&self) -> bool {
218 self.motion_context_animated
219 }
220
221 pub fn translated_content_context(&self) -> bool {
222 self.translated_content_context
223 }
224
225 pub fn translated_content_context_identity(&self) -> Option<usize> {
226 self.translated_content_context_identity
227 }
228
229 pub fn translated_content_offset(&self) -> Option<Point> {
230 self.translated_content_offset_reader
231 .as_ref()
232 .map(|reader| reader())
233 }
234
235 pub fn text_content(&self) -> Option<&str> {
236 self.text_content.as_ref().map(|a| a.text.as_str())
237 }
238
239 pub fn annotated_text(&self) -> Option<&crate::text::AnnotatedString> {
240 self.text_content.as_deref()
241 }
242
243 pub fn annotated_string(&self) -> Option<crate::text::AnnotatedString> {
244 self.annotated_text().cloned()
245 }
246
247 pub fn text_style(&self) -> Option<&TextStyle> {
248 self.text_style.as_ref()
249 }
250
251 pub fn text_layout_options(&self) -> Option<TextLayoutOptions> {
252 self.text_layout_options
253 }
254
255 pub fn text_pan_resolver(&self) -> Option<TextPanResolver> {
262 self.text_pan.clone()
263 }
264
265 pub fn text_field_window_origin(&self) -> Option<Rc<std::cell::Cell<Point>>> {
270 self.text_field_window_origin.clone()
271 }
272
273 pub fn viewport_window_rect(&self) -> Option<Rc<std::cell::Cell<cranpose_ui_graphics::Rect>>> {
278 self.viewport_window_rect.clone()
279 }
280
281 pub fn prepare_text_layout(
282 &self,
283 max_width: Option<f32>,
284 ) -> Option<crate::text::PreparedTextLayout> {
285 if let Some(handle) = &self.prepared_text_layout {
286 return Some(handle.prepare(max_width));
287 }
288
289 let text = self.annotated_text()?;
290 let style = self.text_style.clone().unwrap_or_default();
291 Some(crate::text::prepare_text_layout(
292 text,
293 &style,
294 self.text_layout_options.unwrap_or_default(),
295 max_width,
296 ))
297 }
298
299 pub fn graphics_layer(&self) -> Option<GraphicsLayer> {
300 if let Some(resolve) = &self.graphics_layer_resolver {
301 Some(resolve())
302 } else {
303 self.graphics_layer.clone()
304 }
305 }
306
307 pub fn corner_shape(&self) -> Option<RoundedCornerShape> {
308 self.corner_shape
309 }
310
311 fn push_graphics_layer(
312 &mut self,
313 layer: GraphicsLayer,
314 resolver: Option<Rc<dyn Fn() -> GraphicsLayer>>,
315 ) {
316 let existing_snapshot = self.graphics_layer.clone();
317 let next_snapshot = existing_snapshot
318 .as_ref()
319 .map(|current| merge_graphics_layers(current.clone(), layer.clone()))
320 .unwrap_or_else(|| layer.clone());
321 let existing_resolver = self.graphics_layer_resolver.clone();
322
323 self.graphics_layer = Some(next_snapshot);
324 self.graphics_layer_resolver = match (existing_resolver, resolver) {
325 (None, None) => None,
326 (Some(current_resolver), None) => {
327 let layer = layer.clone();
328 Some(Rc::new(move || {
329 merge_graphics_layers(current_resolver(), layer.clone())
330 }))
331 }
332 (None, Some(next_resolver)) => {
333 let base = existing_snapshot.unwrap_or_default();
334 Some(Rc::new(move || {
335 merge_graphics_layers(base.clone(), next_resolver())
336 }))
337 }
338 (Some(current_resolver), Some(next_resolver)) => Some(Rc::new(move || {
339 merge_graphics_layers(current_resolver(), next_resolver())
340 })),
341 };
342 }
343
344 pub fn with_chain_guard(mut self, handle: ModifierChainHandle) -> Self {
345 self.chain_guard = Some(Rc::new(ChainGuard { _handle: handle }));
346 self
347 }
348
349 pub fn debug_stats(&self) -> ModifierNodeSlicesDebugStats {
350 let draw_command_bytes = self.draw_commands.capacity() * size_of::<DrawCommand>();
351 let pointer_input_bytes =
352 self.pointer_inputs.capacity() * size_of::<Rc<dyn Fn(PointerEvent)>>();
353 let click_handler_bytes = self.click_handlers.capacity() * size_of::<Rc<dyn Fn(Point)>>();
354 ModifierNodeSlicesDebugStats {
355 draw_command_count: self.draw_commands.len(),
356 draw_command_capacity: self.draw_commands.capacity(),
357 pointer_input_count: self.pointer_inputs.len(),
358 pointer_input_capacity: self.pointer_inputs.capacity(),
359 click_handler_count: self.click_handlers.len(),
360 click_handler_capacity: self.click_handlers.capacity(),
361 has_text_content: self.text_content.is_some(),
362 has_text_style: self.text_style.is_some(),
363 has_text_layout_options: self.text_layout_options.is_some(),
364 has_prepared_text_layout: self.prepared_text_layout.is_some(),
365 has_graphics_layer: self.graphics_layer.is_some(),
366 has_graphics_layer_resolver: self.graphics_layer_resolver.is_some(),
367 heap_bytes: draw_command_bytes + pointer_input_bytes + click_handler_bytes,
368 }
369 }
370
371 pub fn clear(&mut self) {
373 self.draw_commands.clear();
374 self.pointer_inputs.clear();
375 self.pointer_input_sizes.clear();
376 self.click_handlers.clear();
377 self.clip_to_bounds = false;
378 self.motion_context_animated = false;
379 self.translated_content_context = false;
380 self.translated_content_context_identity = None;
381 self.translated_content_offset_reader = None;
382 self.text_content = None;
383 self.text_style = None;
384 self.text_layout_options = None;
385 self.prepared_text_layout = None;
386 self.text_pan = None;
387 self.graphics_layer = None;
388 self.graphics_layer_resolver = None;
389 self.corner_shape = None;
390 self.chain_guard = None;
391 }
392}
393
394impl fmt::Debug for ModifierNodeSlices {
395 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396 f.debug_struct("ModifierNodeSlices")
397 .field("draw_commands", &self.draw_commands.len())
398 .field("pointer_inputs", &self.pointer_inputs.len())
399 .field("click_handlers", &self.click_handlers.len())
400 .field("clip_to_bounds", &self.clip_to_bounds)
401 .field("motion_context_animated", &self.motion_context_animated)
402 .field(
403 "translated_content_context",
404 &self.translated_content_context,
405 )
406 .field(
407 "translated_content_context_identity",
408 &self.translated_content_context_identity,
409 )
410 .field(
411 "translated_content_offset",
412 &self.translated_content_offset(),
413 )
414 .field("text_content", &self.text_content)
415 .field("text_style", &self.text_style)
416 .field("text_layout_options", &self.text_layout_options)
417 .field("prepared_text_layout", &self.prepared_text_layout.is_some())
418 .field("graphics_layer", &self.graphics_layer)
419 .field(
420 "graphics_layer_resolver",
421 &self.graphics_layer_resolver.is_some(),
422 )
423 .field("corner_shape", &self.corner_shape)
424 .finish()
425 }
426}
427
428pub fn collect_modifier_slices(chain: &ModifierNodeChain) -> ModifierNodeSlices {
430 let mut slices = ModifierNodeSlices::default();
431 collect_modifier_slices_into(chain, &mut slices);
432 slices
433}
434
435pub fn collect_modifier_slices_into(chain: &ModifierNodeChain, slices: &mut ModifierNodeSlices) {
440 slices.clear();
441
442 let caps = chain.capabilities();
443 let has_pointer = caps.intersects(NodeCapabilities::POINTER_INPUT);
444 let has_draw = caps.intersects(NodeCapabilities::DRAW);
445 let has_layout = caps.intersects(NodeCapabilities::LAYOUT);
446
447 if !has_pointer && !has_draw && !has_layout {
448 return;
449 }
450
451 let mut background_color = None;
452 let mut background_insert_index = None::<usize>;
453 let mut corner_shape = None;
454 let mut padding = EdgeInsets::default();
455
456 for node_ref in chain.head_to_tail() {
457 let node_caps = node_ref.kind_set();
458
459 node_ref.with_node(|node| {
460 let any = node.as_any();
461
462 if has_pointer && node_caps.intersects(NodeCapabilities::POINTER_INPUT) {
464 if let Some(pointer_node) = node.as_pointer_input_node() {
465 if let Some(handler) = pointer_node.pointer_input_handler() {
466 slices.pointer_inputs.push(handler);
467 }
468 if let Some(sink) = pointer_node.layout_size_sink() {
472 slices.pointer_input_sizes.push(sink);
473 }
474 }
475 }
476
477 if has_draw && node_caps.intersects(NodeCapabilities::DRAW) {
479 if let Some(bg_node) = any.downcast_ref::<BackgroundNode>() {
480 background_color = Some(bg_node.color());
481 background_insert_index = Some(slices.draw_commands.len());
482 if bg_node.shape().is_some() {
483 corner_shape = bg_node.shape();
484 }
485 }
486
487 if let Some(shape_node) = any.downcast_ref::<CornerShapeNode>() {
488 corner_shape = Some(shape_node.shape());
489 }
490
491 if let Some(commands) = any.downcast_ref::<DrawCommandNode>() {
492 slices.draw_commands.extend(commands.observed_commands());
493 }
494
495 if let Some(draw_node) = node.as_draw_node() {
496 if let Some(closure) = draw_node.create_behind_draw_closure() {
497 slices.draw_commands.push(DrawCommand::Behind(closure));
498 }
499 if let Some(closure) = draw_node.create_draw_closure() {
500 slices.draw_commands.push(DrawCommand::Overlay(closure));
501 } else {
502 use cranpose_ui_graphics::{DrawScope as _, DrawScopeDefault};
503 let mut scope = DrawScopeDefault::with_text_measurer(
504 crate::modifier::Size {
505 width: 0.0,
506 height: 0.0,
507 },
508 crate::text::AppContextTextMeasurer::shared(),
509 );
510 draw_node.draw(&mut scope);
511 let primitives = scope.into_primitives();
512 if !primitives.is_empty() {
513 let draw_cmd = Rc::new(
514 move |scope: &mut cranpose_ui_graphics::DrawScopeDefault| {
515 scope.push_recorded(primitives.clone())
516 },
517 );
518 slices.draw_commands.push(DrawCommand::Overlay(draw_cmd));
519 }
520 }
521 }
522
523 if let Some(layer_node) = any.downcast_ref::<GraphicsLayerNode>() {
524 slices.push_graphics_layer(
525 layer_node.layer_snapshot(),
526 layer_node.layer_resolver(),
527 );
528 }
529
530 if any.is::<ClipToBoundsNode>() {
531 slices.clip_to_bounds = true;
532 }
533 }
534
535 if has_layout && node_caps.intersects(NodeCapabilities::LAYOUT) {
537 if let Some(padding_node) = any.downcast_ref::<PaddingNode>() {
538 let p = padding_node.padding();
539 padding.left += p.left;
540 padding.top += p.top;
541 padding.right += p.right;
542 padding.bottom += p.bottom;
543 }
544
545 if let Some(motion_context_node) = any.downcast_ref::<MotionContextAnimatedNode>() {
546 slices.motion_context_animated = motion_context_node.is_active();
547 }
548
549 if let Some(reporter) = any.downcast_ref::<WindowRectReporterNode>() {
550 slices.viewport_window_rect = Some(reporter.window_rect_sink());
554 }
555
556 if let Some(translated_content_node) =
557 any.downcast_ref::<TranslatedContentContextNode>()
558 {
559 slices.translated_content_context = translated_content_node.is_active();
560 slices.translated_content_context_identity =
561 Some(translated_content_node.identity());
562 slices.translated_content_offset_reader =
563 translated_content_node.content_offset_reader();
564 }
565
566 if let Some(text_node) = any.downcast_ref::<TextModifierNode>() {
567 slices.text_content = Some(text_node.annotated_text());
568 slices.text_style = Some(text_node.style().clone());
569 slices.text_layout_options = Some(text_node.options());
570 slices.prepared_text_layout = Some(text_node.prepared_layout_handle());
571 }
572
573 if let Some(text_field_node) = any.downcast_ref::<TextFieldModifierNode>() {
574 let text = text_field_node.text();
575 slices.text_content = Some(Rc::new(crate::text::AnnotatedString::from(text)));
576 slices.text_style = Some(text_field_node.style().clone());
577 slices.text_layout_options = Some(TextLayoutOptions::default());
578 slices.prepared_text_layout = None;
579 slices.text_pan = text_field_node.text_pan_resolver();
580 slices.text_field_window_origin = Some(text_field_node.window_origin_sink());
584
585 text_field_node.set_content_offset(padding.left);
586 text_field_node.set_content_y_offset(padding.top);
587 }
588 }
589 });
590 }
591
592 slices.corner_shape = corner_shape;
593
594 if let Some(color) = background_color {
596 let draw_cmd = Rc::new(move |scope: &mut cranpose_ui_graphics::DrawScopeDefault| {
597 use crate::modifier::Brush;
598 use cranpose_ui_graphics::{CornerRadii, DrawScope as _};
599
600 let size = scope.size();
601 let brush = Brush::solid(color);
602 if let Some(shape) = corner_shape {
603 let radii: CornerRadii = shape.resolve(size.width, size.height);
604 scope.draw_round_rect(brush, radii);
605 } else {
606 scope.draw_rect(brush);
607 }
608 });
609
610 let insert_index = background_insert_index
611 .unwrap_or(0)
612 .min(slices.draw_commands.len());
613 slices
614 .draw_commands
615 .insert(insert_index, DrawCommand::Behind(draw_cmd));
616 }
617}
618
619pub fn collect_slices_from_modifier(modifier: &Modifier) -> ModifierNodeSlices {
621 let mut handle = ModifierChainHandle::new();
622 let _ = handle.update(modifier);
623 collect_modifier_slices(handle.chain()).with_chain_guard(handle)
624}