1use std::{
2 cell::Cell,
3 hash::{Hash, Hasher},
4 rc::Rc,
5};
6
7use cranpose_core::NodeId;
8use cranpose_foundation::{
9 Constraints, DelegatableNode, DrawModifierNode, InvalidationKind, LayoutModifierNode,
10 Measurable, ModifierNode, ModifierNodeContext, ModifierNodeElement, NodeCapabilities,
11 NodeState, PointerEvent, PointerEventKind, PointerInputNode, Size,
12};
13use cranpose_ui_graphics::{DrawScope, PointerIcon};
14use cranpose_ui_layout::{Alignment, HorizontalAlignment, IntrinsicSize, VerticalAlignment};
15
16use crate::{
17 draw::DrawCommand,
18 modifier::{
19 BlendMode, Brush, Color, ColorFilter, CompositingStrategy, EdgeInsets, GraphicsLayer,
20 LayoutWeight, Point, RoundedCornerShape,
21 },
22};
23
24fn hash_f32_value<H: Hasher>(state: &mut H, value: f32) {
25 state.write_u32(value.to_bits());
26}
27
28fn hash_option_f32<H: Hasher>(state: &mut H, value: Option<f32>) {
29 match value {
30 Some(v) => {
31 state.write_u8(1);
32 hash_f32_value(state, v);
33 }
34 None => state.write_u8(0),
35 }
36}
37
38fn hash_graphics_layer<H: Hasher>(state: &mut H, layer: &GraphicsLayer) {
39 hash_f32_value(state, layer.alpha);
40 hash_f32_value(state, layer.scale);
41 hash_f32_value(state, layer.scale_x);
42 hash_f32_value(state, layer.scale_y);
43 hash_f32_value(state, layer.rotation_x);
44 hash_f32_value(state, layer.rotation_y);
45 hash_f32_value(state, layer.rotation_z);
46 hash_f32_value(state, layer.camera_distance);
47 hash_f32_value(state, layer.transform_origin.pivot_fraction_x);
48 hash_f32_value(state, layer.transform_origin.pivot_fraction_y);
49 hash_f32_value(state, layer.translation_x);
50 hash_f32_value(state, layer.translation_y);
51 hash_f32_value(state, layer.shadow_elevation);
52 hash_f32_value(state, layer.ambient_shadow_color.r());
53 hash_f32_value(state, layer.ambient_shadow_color.g());
54 hash_f32_value(state, layer.ambient_shadow_color.b());
55 hash_f32_value(state, layer.ambient_shadow_color.a());
56 hash_f32_value(state, layer.spot_shadow_color.r());
57 hash_f32_value(state, layer.spot_shadow_color.g());
58 hash_f32_value(state, layer.spot_shadow_color.b());
59 hash_f32_value(state, layer.spot_shadow_color.a());
60 match layer.shape {
61 crate::modifier::LayerShape::Rectangle => {
62 state.write_u8(0);
63 }
64 crate::modifier::LayerShape::Rounded(shape) => {
65 state.write_u8(1);
66 let radii = shape.radii();
67 hash_f32_value(state, radii.top_left);
68 hash_f32_value(state, radii.top_right);
69 hash_f32_value(state, radii.bottom_right);
70 hash_f32_value(state, radii.bottom_left);
71 }
72 }
73 state.write_u8(layer.clip as u8);
74 match layer.color_filter {
75 Some(ColorFilter::Tint(color)) => {
76 state.write_u8(1);
77 hash_f32_value(state, color.r());
78 hash_f32_value(state, color.g());
79 hash_f32_value(state, color.b());
80 hash_f32_value(state, color.a());
81 }
82 Some(ColorFilter::Modulate(color)) => {
83 state.write_u8(2);
84 hash_f32_value(state, color.r());
85 hash_f32_value(state, color.g());
86 hash_f32_value(state, color.b());
87 hash_f32_value(state, color.a());
88 }
89 Some(ColorFilter::Matrix(matrix)) => {
90 state.write_u8(3);
91 for value in matrix {
92 hash_f32_value(state, value);
93 }
94 }
95 None => state.write_u8(0),
96 }
97 state.write_u8(layer.render_effect.is_some() as u8);
98 state.write_u8(layer.backdrop_effect.is_some() as u8);
99 let compositing_tag = match layer.compositing_strategy {
100 CompositingStrategy::Auto => 0,
101 CompositingStrategy::Offscreen => 1,
102 CompositingStrategy::ModulateAlpha => 2,
103 };
104 state.write_u8(compositing_tag);
105 let blend_tag = match layer.blend_mode {
106 BlendMode::Clear => 0,
107 BlendMode::Src => 1,
108 BlendMode::Dst => 2,
109 BlendMode::SrcOver => 3,
110 BlendMode::DstOver => 4,
111 BlendMode::SrcIn => 5,
112 BlendMode::DstIn => 6,
113 BlendMode::SrcOut => 7,
114 BlendMode::DstOut => 8,
115 BlendMode::SrcAtop => 9,
116 BlendMode::DstAtop => 10,
117 BlendMode::Xor => 11,
118 BlendMode::Plus => 12,
119 BlendMode::Modulate => 13,
120 BlendMode::Screen => 14,
121 BlendMode::Overlay => 15,
122 BlendMode::Darken => 16,
123 BlendMode::Lighten => 17,
124 BlendMode::ColorDodge => 18,
125 BlendMode::ColorBurn => 19,
126 BlendMode::HardLight => 20,
127 BlendMode::SoftLight => 21,
128 BlendMode::Difference => 22,
129 BlendMode::Exclusion => 23,
130 BlendMode::Multiply => 24,
131 BlendMode::Hue => 25,
132 BlendMode::Saturation => 26,
133 BlendMode::Color => 27,
134 BlendMode::Luminosity => 28,
135 };
136 state.write_u8(blend_tag);
137}
138
139fn hash_horizontal_alignment<H: Hasher>(state: &mut H, alignment: HorizontalAlignment) {
140 let tag = match alignment {
141 HorizontalAlignment::Start => 0,
142 HorizontalAlignment::CenterHorizontally => 1,
143 HorizontalAlignment::End => 2,
144 };
145 state.write_u8(tag);
146}
147
148fn hash_vertical_alignment<H: Hasher>(state: &mut H, alignment: VerticalAlignment) {
149 let tag = match alignment {
150 VerticalAlignment::Top => 0,
151 VerticalAlignment::CenterVertically => 1,
152 VerticalAlignment::Bottom => 2,
153 };
154 state.write_u8(tag);
155}
156
157fn hash_alignment<H: Hasher>(state: &mut H, alignment: Alignment) {
158 hash_horizontal_alignment(state, alignment.horizontal);
159 hash_vertical_alignment(state, alignment.vertical);
160}
161
162macro_rules! impl_layout_modifier_node {
163 ($ty:ty) => {
164 impl ModifierNode for $ty {
165 fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
166 Some(self)
167 }
168
169 fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
170 Some(self)
171 }
172 }
173 };
174 ($ty:ty, invalidate = $invalidation:expr) => {
175 impl ModifierNode for $ty {
176 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
177 context.invalidate($invalidation);
178 }
179
180 fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
181 Some(self)
182 }
183
184 fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
185 Some(self)
186 }
187 }
188 };
189}
190pub(crate) use impl_layout_modifier_node;
191
192macro_rules! impl_draw_modifier_node {
193 ($ty:ty) => {
194 impl ModifierNode for $ty {
195 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
196 context.invalidate(InvalidationKind::Draw);
197 }
198
199 fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
200 Some(self)
201 }
202
203 fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
204 Some(self)
205 }
206 }
207
208 impl DrawModifierNode for $ty {}
209 };
210}
211
212macro_rules! impl_sink_reporter_element {
213 ($element:ty, $node:ty) => {
214 impl ModifierNodeElement for $element {
215 type Node = $node;
216
217 fn create(&self) -> Self::Node {
218 <$node>::new(self.sink.clone())
219 }
220
221 fn update(&self, node: &mut Self::Node) {
222 node.sink = self.sink.clone();
223 }
224
225 fn capabilities(&self) -> NodeCapabilities {
226 NodeCapabilities::LAYOUT
227 }
228 }
229 };
230}
231
232fn size_axis_hold(
237 (min, max): (Option<f32>, Option<f32>),
238 target: f32,
239 size: f32,
240 wrapped: Option<cranpose_ui_layout::AxisHold>,
241) -> Option<cranpose_ui_layout::AxisHold> {
242 match (min, max) {
243 (Some(min), Some(max)) if min == max && size == target => {
244 Some(cranpose_ui_layout::AxisHold::sized(target))
245 }
246 (None, None) => wrapped,
247 _ => None,
248 }
249}
250
251fn fill_axis_hold(
254 fills: bool,
255 (max, size): (f32, f32),
256 (fraction, density): (f32, f32),
257 wrapped: Option<cranpose_ui_layout::AxisHold>,
258) -> Option<cranpose_ui_layout::AxisHold> {
259 use cranpose_ui_layout::{AxisHold, BoundRange};
260 if !fills {
261 return wrapped;
262 }
263 if max == f32::INFINITY {
264 let wrapped = wrapped?;
265 return Some(AxisHold {
266 min: wrapped.min,
267 max: wrapped.max.intersect(BoundRange::exactly(f32::INFINITY))?,
268 });
269 }
270 (size == cranpose_ui_layout::round_to_px(max * fraction, density)).then_some(AxisHold {
271 min: BoundRange::up_to(size),
272 max: BoundRange::exactly(max),
273 })
274}
275
276fn measure_pass_through(
277 measurable: &dyn Measurable,
278 constraints: Constraints,
279 offset: impl FnOnce(Size) -> (f32, f32),
280) -> cranpose_ui_layout::LayoutModifierMeasureResult {
281 let placeable = measurable.measure(constraints);
282 let size = Size {
283 width: placeable.width(),
284 height: placeable.height(),
285 };
286 let (x, y) = offset(size);
287 cranpose_ui_layout::LayoutModifierMeasureResult::new(size, x, y)
288}
289
290fn attach_draw_observer(
291 node_id_cell: &Cell<Option<NodeId>>,
292 context: &mut dyn ModifierNodeContext,
293) {
294 node_id_cell.set(context.node_id());
295 context.invalidate(InvalidationKind::Draw);
296}
297
298fn detach_draw_observer(node_id_cell: &Cell<Option<NodeId>>) {
299 if let Some(node_id) = node_id_cell.replace(None) {
300 crate::render_state::clear_draw_observations_for_node(node_id);
301 }
302}
303
304enum SizeAxis {
305 Width,
306 Height,
307}
308
309fn size_intrinsic(
310 target: Constraints,
311 axis: SizeAxis,
312 enforce_incoming: bool,
313 cross: f32,
314 intrinsic: impl FnOnce(f32) -> f32,
315) -> f32 {
316 let (target_min, target_max, cross_min, cross_max) = match axis {
317 SizeAxis::Width => (
318 target.min_width,
319 target.max_width,
320 target.min_height,
321 target.max_height,
322 ),
323 SizeAxis::Height => (
324 target.min_height,
325 target.max_height,
326 target.min_width,
327 target.max_width,
328 ),
329 };
330 if target_min == target_max && target_max != f32::INFINITY {
331 target_max
332 } else {
333 let child_cross = if enforce_incoming {
334 cross
335 } else {
336 cross.clamp(cross_min, cross_max)
337 };
338 intrinsic(child_cross).clamp(target_min, target_max)
339 }
340}
341
342#[derive(Debug)]
344pub struct PaddingNode {
345 padding: EdgeInsets,
346 state: NodeState,
347}
348
349impl PaddingNode {
350 pub fn new(padding: EdgeInsets) -> Self {
352 Self {
353 padding,
354 state: NodeState::new(),
355 }
356 }
357
358 pub fn padding(&self) -> EdgeInsets {
360 self.padding
361 }
362}
363
364impl DelegatableNode for PaddingNode {
365 fn node_state(&self) -> &NodeState {
366 &self.state
367 }
368}
369
370impl_layout_modifier_node!(PaddingNode, invalidate = InvalidationKind::Layout);
371
372impl LayoutModifierNode for PaddingNode {
373 fn measure_hold(
374 &self,
375 density: f32,
376 _constraints: Constraints,
377 size: Size,
378 wrapped: cranpose_ui_layout::WrappedHold,
379 ) -> Option<cranpose_ui_layout::ConstraintsHold> {
380 let inner = wrapped.hold?;
381 let padding = device_padding(self.padding, density);
382 let horizontal = padding.horizontal_sum();
383 let vertical = padding.vertical_sum();
384 if size.width != wrapped.size.width + horizontal
386 || size.height != wrapped.size.height + vertical
387 {
388 return None;
389 }
390 cranpose_ui_layout::ConstraintsHold {
391 width: inner.width.outset(horizontal),
392 height: inner.height.outset(vertical),
393 }
394 .intersect(cranpose_ui_layout::ConstraintsHold::sized(
395 size.width,
396 size.height,
397 ))
398 }
399
400 fn measure(
401 &self,
402 context: &mut dyn ModifierNodeContext,
403 measurable: &dyn Measurable,
404 constraints: Constraints,
405 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
406 let padding = device_padding(self.padding, context.density());
407 let horizontal_padding = padding.horizontal_sum();
408 let vertical_padding = padding.vertical_sum();
409
410 let inner_constraints = Constraints {
411 min_width: (constraints.min_width - horizontal_padding).max(0.0),
412 max_width: (constraints.max_width - horizontal_padding).max(0.0),
413 min_height: (constraints.min_height - vertical_padding).max(0.0),
414 max_height: (constraints.max_height - vertical_padding).max(0.0),
415 };
416
417 let inner_placeable = measurable.measure(inner_constraints);
418 let inner_width = inner_placeable.width();
419 let inner_height = inner_placeable.height();
420
421 let (width, height) = constraints.constrain(
422 inner_width + horizontal_padding,
423 inner_height + vertical_padding,
424 );
425
426 cranpose_ui_layout::LayoutModifierMeasureResult::new(
427 Size { width, height },
428 padding.left,
429 padding.top,
430 )
431 }
432
433 fn min_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, density: f32) -> f32 {
434 let padding = device_padding(self.padding, density);
435 let inner_height = (height - padding.vertical_sum()).max(0.0);
436 measurable.min_intrinsic_width(inner_height) + padding.horizontal_sum()
437 }
438
439 fn max_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, density: f32) -> f32 {
440 let padding = device_padding(self.padding, density);
441 let inner_height = (height - padding.vertical_sum()).max(0.0);
442 measurable.max_intrinsic_width(inner_height) + padding.horizontal_sum()
443 }
444
445 fn min_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, density: f32) -> f32 {
446 let padding = device_padding(self.padding, density);
447 let inner_width = (width - padding.horizontal_sum()).max(0.0);
448 measurable.min_intrinsic_height(inner_width) + padding.vertical_sum()
449 }
450
451 fn max_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, density: f32) -> f32 {
452 let padding = device_padding(self.padding, density);
453 let inner_width = (width - padding.horizontal_sum()).max(0.0);
454 measurable.max_intrinsic_height(inner_width) + padding.vertical_sum()
455 }
456}
457
458pub(crate) fn device_padding(padding: EdgeInsets, density: f32) -> EdgeInsets {
461 use cranpose_ui_layout::round_to_px;
462 EdgeInsets {
463 left: round_to_px(padding.left, density),
464 top: round_to_px(padding.top, density),
465 right: round_to_px(padding.right, density),
466 bottom: round_to_px(padding.bottom, density),
467 }
468}
469
470#[derive(Debug, Clone, PartialEq)]
472pub struct PaddingElement {
473 padding: EdgeInsets,
474}
475
476impl PaddingElement {
477 pub fn new(padding: EdgeInsets) -> Self {
479 Self { padding }
480 }
481}
482
483impl Hash for PaddingElement {
484 fn hash<H: Hasher>(&self, state: &mut H) {
485 hash_f32_value(state, self.padding.left);
486 hash_f32_value(state, self.padding.top);
487 hash_f32_value(state, self.padding.right);
488 hash_f32_value(state, self.padding.bottom);
489 }
490}
491
492impl ModifierNodeElement for PaddingElement {
493 type Node = PaddingNode;
494
495 fn create(&self) -> Self::Node {
496 PaddingNode::new(self.padding)
497 }
498
499 fn update(&self, node: &mut Self::Node) {
500 if node.padding != self.padding {
501 node.padding = self.padding;
502 }
503 }
504
505 fn capabilities(&self) -> NodeCapabilities {
506 NodeCapabilities::LAYOUT
507 }
508}
509
510#[derive(Debug)]
512pub struct BackgroundNode {
513 color: Color,
514 shape: Option<RoundedCornerShape>,
515 state: NodeState,
516}
517
518impl BackgroundNode {
519 pub fn new(color: Color) -> Self {
521 Self {
522 color,
523 shape: None,
524 state: NodeState::new(),
525 }
526 }
527
528 pub fn color(&self) -> Color {
530 self.color
531 }
532
533 pub fn shape(&self) -> Option<RoundedCornerShape> {
535 self.shape
536 }
537}
538
539impl DelegatableNode for BackgroundNode {
540 fn node_state(&self) -> &NodeState {
541 &self.state
542 }
543}
544
545impl_draw_modifier_node!(BackgroundNode);
546
547#[derive(Debug, Clone, PartialEq)]
549pub struct BackgroundElement {
550 color: Color,
551}
552
553impl BackgroundElement {
554 pub fn new(color: Color) -> Self {
556 Self { color }
557 }
558}
559
560impl Hash for BackgroundElement {
561 fn hash<H: Hasher>(&self, state: &mut H) {
562 hash_f32_value(state, self.color.0);
563 hash_f32_value(state, self.color.1);
564 hash_f32_value(state, self.color.2);
565 hash_f32_value(state, self.color.3);
566 }
567}
568
569impl ModifierNodeElement for BackgroundElement {
570 type Node = BackgroundNode;
571
572 fn create(&self) -> Self::Node {
573 BackgroundNode::new(self.color)
574 }
575
576 fn update(&self, node: &mut Self::Node) {
577 if node.color != self.color {
578 node.color = self.color;
579 }
580 }
581
582 fn capabilities(&self) -> NodeCapabilities {
583 NodeCapabilities::DRAW
584 }
585}
586
587#[derive(Debug)]
589pub struct CornerShapeNode {
590 shape: RoundedCornerShape,
591 state: NodeState,
592}
593
594impl CornerShapeNode {
595 pub fn new(shape: RoundedCornerShape) -> Self {
597 Self {
598 shape,
599 state: NodeState::new(),
600 }
601 }
602
603 pub fn shape(&self) -> RoundedCornerShape {
605 self.shape
606 }
607}
608
609impl DelegatableNode for CornerShapeNode {
610 fn node_state(&self) -> &NodeState {
611 &self.state
612 }
613}
614
615impl_draw_modifier_node!(CornerShapeNode);
616
617#[derive(Debug, Clone, PartialEq)]
619pub struct CornerShapeElement {
620 shape: RoundedCornerShape,
621}
622
623impl CornerShapeElement {
624 pub fn new(shape: RoundedCornerShape) -> Self {
626 Self { shape }
627 }
628}
629
630impl Hash for CornerShapeElement {
631 fn hash<H: Hasher>(&self, state: &mut H) {
632 let radii = self.shape.radii();
633 hash_f32_value(state, radii.top_left);
634 hash_f32_value(state, radii.top_right);
635 hash_f32_value(state, radii.bottom_right);
636 hash_f32_value(state, radii.bottom_left);
637 }
638}
639
640impl ModifierNodeElement for CornerShapeElement {
641 type Node = CornerShapeNode;
642
643 fn create(&self) -> Self::Node {
644 CornerShapeNode::new(self.shape)
645 }
646
647 fn update(&self, node: &mut Self::Node) {
648 if node.shape != self.shape {
649 node.shape = self.shape;
650 }
651 }
652
653 fn capabilities(&self) -> NodeCapabilities {
654 NodeCapabilities::DRAW
655 }
656}
657
658#[derive(Debug, Clone, PartialEq)]
662pub struct BorderElement {
663 width: f32,
664 brush: Brush,
665 shape: RoundedCornerShape,
666}
667
668impl BorderElement {
669 pub fn new(width: f32, brush: Brush, shape: RoundedCornerShape) -> Self {
670 Self {
671 width,
672 brush,
673 shape,
674 }
675 }
676
677 pub(crate) fn command(&self) -> DrawCommand {
679 let Self {
680 width,
681 brush,
682 shape,
683 } = self.clone();
684 DrawCommand::Overlay(Rc::new(move |scope| {
685 let size = scope.size();
686 if width <= 0.0 || size.width <= 0.0 || size.height <= 0.0 {
687 return;
688 }
689 let radii = shape.resolve(size.width, size.height);
690 let half = width / 2.0;
691 if width * 2.0 >= size.width.min(size.height) {
692 scope.draw_round_rect(brush.clone(), radii);
694 return;
695 }
696 let inset = |radius: f32| (radius - half).max(0.0);
697 scope.draw_round_rect_at_stroked(
698 cranpose_ui_graphics::Rect {
699 x: half,
700 y: half,
701 width: size.width - width,
702 height: size.height - width,
703 },
704 brush.clone(),
705 cranpose_ui_graphics::CornerRadii {
706 top_left: inset(radii.top_left),
707 top_right: inset(radii.top_right),
708 bottom_right: inset(radii.bottom_right),
709 bottom_left: inset(radii.bottom_left),
710 },
711 cranpose_ui_graphics::Stroke::new(width),
712 );
713 }))
714 }
715}
716
717impl Hash for BorderElement {
718 fn hash<H: Hasher>(&self, state: &mut H) {
719 hash_f32_value(state, self.width);
720 std::mem::discriminant(&self.brush).hash(state);
721 if let Brush::Solid(color) = self.brush {
722 hash_f32_value(state, color.0);
723 hash_f32_value(state, color.1);
724 hash_f32_value(state, color.2);
725 hash_f32_value(state, color.3);
726 }
727 let radii = self.shape.radii();
728 hash_f32_value(state, radii.top_left);
729 hash_f32_value(state, radii.top_right);
730 hash_f32_value(state, radii.bottom_right);
731 hash_f32_value(state, radii.bottom_left);
732 }
733}
734
735impl ModifierNodeElement for BorderElement {
736 type Node = DrawCommandNode;
737
738 fn create(&self) -> Self::Node {
739 DrawCommandNode::new(vec![self.command()])
740 }
741
742 fn update(&self, node: &mut Self::Node) {
743 node.commands = vec![self.command()];
744 }
745
746 fn capabilities(&self) -> NodeCapabilities {
747 NodeCapabilities::DRAW
748 }
749}
750
751pub struct GraphicsLayerNode {
752 layer: GraphicsLayer,
753 layer_resolver: Option<Rc<dyn Fn() -> GraphicsLayer>>,
754 node_id: Rc<Cell<Option<NodeId>>>,
755 state: NodeState,
756}
757
758impl GraphicsLayerNode {
759 pub fn new(layer: GraphicsLayer) -> Self {
760 Self {
761 layer,
762 layer_resolver: None,
763 node_id: Rc::new(Cell::new(None)),
764 state: NodeState::new(),
765 }
766 }
767
768 pub fn new_lazy(layer_resolver: Rc<dyn Fn() -> GraphicsLayer>) -> Self {
769 Self {
770 layer: GraphicsLayer::default(),
771 layer_resolver: Some(layer_resolver),
772 node_id: Rc::new(Cell::new(None)),
773 state: NodeState::new(),
774 }
775 }
776
777 #[cfg(test)]
778 pub fn layer(&self) -> GraphicsLayer {
779 if let Some(resolve) = self.layer_resolver(0) {
780 resolve()
781 } else {
782 self.layer.clone()
783 }
784 }
785
786 pub fn layer_snapshot(&self) -> GraphicsLayer {
787 self.layer.clone()
788 }
789
790 pub(crate) fn layer_resolver(
791 &self,
792 modifier_index: usize,
793 ) -> Option<Rc<dyn Fn() -> GraphicsLayer>> {
794 self.layer_resolver.as_ref().map(|resolve| {
795 let resolve = resolve.clone();
796 let node_id = Rc::clone(&self.node_id);
797 Rc::new(move || {
798 if let Some(node_id) = node_id.get() {
799 let scope = crate::render_state::DrawObservationScope::layer_properties(
800 node_id,
801 modifier_index,
802 );
803 crate::render_state::observe_draw_reads(scope, || resolve())
804 } else {
805 resolve()
806 }
807 }) as Rc<dyn Fn() -> GraphicsLayer>
808 })
809 }
810
811 fn set_static(&mut self, layer: GraphicsLayer) {
812 let changed = self.layer != layer || self.layer_resolver.is_some();
813 self.layer = layer;
814 self.layer_resolver = None;
815 if changed && let Some(node_id) = self.node_id.get() {
816 crate::render_state::schedule_draw_repass(node_id);
817 }
818 }
819
820 fn set_lazy(&mut self, layer_resolver: Rc<dyn Fn() -> GraphicsLayer>) {
821 let changed = self
822 .layer_resolver
823 .as_ref()
824 .is_none_or(|current| !Rc::ptr_eq(current, &layer_resolver));
825 self.layer_resolver = Some(layer_resolver);
826 if changed && let Some(node_id) = self.node_id.get() {
827 crate::render_state::schedule_draw_repass(node_id);
828 }
829 }
830}
831
832impl DelegatableNode for GraphicsLayerNode {
833 fn node_state(&self) -> &NodeState {
834 &self.state
835 }
836}
837
838impl ModifierNode for GraphicsLayerNode {
839 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
840 attach_draw_observer(&self.node_id, context);
841 }
842
843 fn on_detach(&mut self) {
844 detach_draw_observer(&self.node_id);
845 }
846}
847
848impl std::fmt::Debug for GraphicsLayerNode {
849 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
850 f.debug_struct("GraphicsLayerNode")
851 .field("layer", &self.layer)
852 .field("lazy", &self.layer_resolver.is_some())
853 .finish()
854 }
855}
856
857#[derive(Debug, Clone, PartialEq)]
858pub struct GraphicsLayerElement {
859 layer: GraphicsLayer,
860}
861
862impl GraphicsLayerElement {
863 pub fn new(layer: GraphicsLayer) -> Self {
864 Self { layer }
865 }
866}
867
868impl Hash for GraphicsLayerElement {
869 fn hash<H: Hasher>(&self, state: &mut H) {
870 hash_graphics_layer(state, &self.layer);
871 }
872}
873
874impl ModifierNodeElement for GraphicsLayerElement {
875 type Node = GraphicsLayerNode;
876
877 fn create(&self) -> Self::Node {
878 GraphicsLayerNode::new(self.layer.clone())
879 }
880
881 fn update(&self, node: &mut Self::Node) {
882 node.set_static(self.layer.clone());
883 }
884
885 fn capabilities(&self) -> NodeCapabilities {
886 NodeCapabilities::DRAW
887 }
888}
889
890#[derive(Clone)]
891pub struct LazyGraphicsLayerElement {
892 layer_resolver: Rc<dyn Fn() -> GraphicsLayer>,
893}
894
895impl LazyGraphicsLayerElement {
896 pub fn new(layer_resolver: Rc<dyn Fn() -> GraphicsLayer>) -> Self {
897 Self { layer_resolver }
898 }
899}
900
901impl std::fmt::Debug for LazyGraphicsLayerElement {
902 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
903 f.debug_struct("LazyGraphicsLayerElement")
904 .field("resolver", &"<closure>")
905 .finish()
906 }
907}
908
909impl PartialEq for LazyGraphicsLayerElement {
910 fn eq(&self, other: &Self) -> bool {
911 Rc::ptr_eq(&self.layer_resolver, &other.layer_resolver)
912 }
913}
914
915impl Eq for LazyGraphicsLayerElement {}
916
917impl Hash for LazyGraphicsLayerElement {
918 fn hash<H: Hasher>(&self, state: &mut H) {
919 let ptr = Rc::as_ptr(&self.layer_resolver) as *const ();
920 ptr.hash(state);
921 }
922}
923
924impl ModifierNodeElement for LazyGraphicsLayerElement {
925 type Node = GraphicsLayerNode;
926
927 fn create(&self) -> Self::Node {
928 GraphicsLayerNode::new_lazy(self.layer_resolver.clone())
929 }
930
931 fn update(&self, node: &mut Self::Node) {
932 node.set_lazy(self.layer_resolver.clone());
933 }
934
935 fn capabilities(&self) -> NodeCapabilities {
936 NodeCapabilities::DRAW
937 }
938
939 fn always_update(&self) -> bool {
940 true
941 }
942
943 fn auto_invalidate_on_update(&self) -> bool {
944 false
945 }
946}
947
948#[derive(Debug)]
952pub struct SizeNode {
953 min_width: Option<f32>,
954 max_width: Option<f32>,
955 min_height: Option<f32>,
956 max_height: Option<f32>,
957 enforce_incoming: bool,
958 state: NodeState,
959}
960
961impl SizeNode {
962 pub fn new(
966 min_width: Option<f32>,
967 max_width: Option<f32>,
968 min_height: Option<f32>,
969 max_height: Option<f32>,
970 enforce_incoming: bool,
971 ) -> Self {
972 Self {
973 min_width,
974 max_width,
975 min_height,
976 max_height,
977 enforce_incoming,
978 state: NodeState::new(),
979 }
980 }
981
982 fn target_constraints(&self, density: f32) -> Constraints {
985 use cranpose_ui_layout::round_to_px;
986
987 let max_width = self
988 .max_width
989 .map_or(f32::INFINITY, |v| round_to_px(v, density).max(0.0));
990 let max_height = self
991 .max_height
992 .map_or(f32::INFINITY, |v| round_to_px(v, density).max(0.0));
993
994 let min_width = self.min_width.map_or(0.0, |v| {
995 let clamped = round_to_px(v, density).clamp(0.0, max_width);
996 if clamped == f32::INFINITY {
997 0.0
998 } else {
999 clamped
1000 }
1001 });
1002
1003 let min_height = self.min_height.map_or(0.0, |v| {
1004 let clamped = round_to_px(v, density).clamp(0.0, max_height);
1005 if clamped == f32::INFINITY {
1006 0.0
1007 } else {
1008 clamped
1009 }
1010 });
1011
1012 Constraints {
1013 min_width,
1014 max_width,
1015 min_height,
1016 max_height,
1017 }
1018 }
1019
1020 pub fn min_width(&self) -> Option<f32> {
1022 self.min_width
1023 }
1024
1025 pub fn max_width(&self) -> Option<f32> {
1027 self.max_width
1028 }
1029
1030 pub fn min_height(&self) -> Option<f32> {
1032 self.min_height
1033 }
1034
1035 pub fn max_height(&self) -> Option<f32> {
1037 self.max_height
1038 }
1039
1040 pub fn enforce_incoming(&self) -> bool {
1042 self.enforce_incoming
1043 }
1044}
1045
1046impl DelegatableNode for SizeNode {
1047 fn node_state(&self) -> &NodeState {
1048 &self.state
1049 }
1050}
1051
1052impl_layout_modifier_node!(SizeNode, invalidate = InvalidationKind::Layout);
1053
1054impl LayoutModifierNode for SizeNode {
1055 fn measure_hold(
1056 &self,
1057 density: f32,
1058 _constraints: Constraints,
1059 size: Size,
1060 wrapped: cranpose_ui_layout::WrappedHold,
1061 ) -> Option<cranpose_ui_layout::ConstraintsHold> {
1062 if !self.enforce_incoming {
1063 return None;
1064 }
1065 let target = self.target_constraints(density);
1066 Some(cranpose_ui_layout::ConstraintsHold {
1067 width: size_axis_hold(
1068 (self.min_width, self.max_width),
1069 target.min_width,
1070 size.width,
1071 wrapped.hold.map(|hold| hold.width),
1072 )?,
1073 height: size_axis_hold(
1074 (self.min_height, self.max_height),
1075 target.min_height,
1076 size.height,
1077 wrapped.hold.map(|hold| hold.height),
1078 )?,
1079 })
1080 }
1081
1082 fn measure(
1083 &self,
1084 context: &mut dyn ModifierNodeContext,
1085 measurable: &dyn Measurable,
1086 constraints: Constraints,
1087 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
1088 let target = self.target_constraints(context.density());
1089
1090 let wrapped_constraints = if self.enforce_incoming {
1091 Constraints {
1092 min_width: target
1093 .min_width
1094 .max(constraints.min_width)
1095 .min(constraints.max_width),
1096 max_width: target
1097 .max_width
1098 .min(constraints.max_width)
1099 .max(constraints.min_width),
1100 min_height: target
1101 .min_height
1102 .max(constraints.min_height)
1103 .min(constraints.max_height),
1104 max_height: target
1105 .max_height
1106 .min(constraints.max_height)
1107 .max(constraints.min_height),
1108 }
1109 } else {
1110 let resolved_min_width = if self.min_width.is_some() {
1111 target.min_width
1112 } else {
1113 constraints.min_width.min(target.max_width)
1114 };
1115 let resolved_max_width = if self.max_width.is_some() {
1116 target.max_width
1117 } else {
1118 constraints.max_width.max(target.min_width)
1119 };
1120 let resolved_min_height = if self.min_height.is_some() {
1121 target.min_height
1122 } else {
1123 constraints.min_height.min(target.max_height)
1124 };
1125 let resolved_max_height = if self.max_height.is_some() {
1126 target.max_height
1127 } else {
1128 constraints.max_height.max(target.min_height)
1129 };
1130
1131 Constraints {
1132 min_width: resolved_min_width,
1133 max_width: resolved_max_width,
1134 min_height: resolved_min_height,
1135 max_height: resolved_max_height,
1136 }
1137 };
1138
1139 let placeable = measurable.measure(wrapped_constraints);
1140 let measured_width = placeable.width();
1141 let measured_height = placeable.height();
1142
1143 let result_width = if self.min_width.is_some()
1144 && self.max_width.is_some()
1145 && self.min_width == self.max_width
1146 && target.min_width >= wrapped_constraints.min_width
1147 && target.min_width <= wrapped_constraints.max_width
1148 {
1149 target.min_width
1150 } else {
1151 measured_width
1152 };
1153
1154 let result_height = if self.min_height.is_some()
1155 && self.max_height.is_some()
1156 && self.min_height == self.max_height
1157 && target.min_height >= wrapped_constraints.min_height
1158 && target.min_height <= wrapped_constraints.max_height
1159 {
1160 target.min_height
1161 } else {
1162 measured_height
1163 };
1164
1165 cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size {
1166 width: result_width,
1167 height: result_height,
1168 })
1169 }
1170
1171 fn min_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, density: f32) -> f32 {
1172 size_intrinsic(
1173 self.target_constraints(density),
1174 SizeAxis::Width,
1175 self.enforce_incoming,
1176 height,
1177 |h| measurable.min_intrinsic_width(h),
1178 )
1179 }
1180
1181 fn max_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, density: f32) -> f32 {
1182 size_intrinsic(
1183 self.target_constraints(density),
1184 SizeAxis::Width,
1185 self.enforce_incoming,
1186 height,
1187 |h| measurable.max_intrinsic_width(h),
1188 )
1189 }
1190
1191 fn min_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, density: f32) -> f32 {
1192 size_intrinsic(
1193 self.target_constraints(density),
1194 SizeAxis::Height,
1195 self.enforce_incoming,
1196 width,
1197 |w| measurable.min_intrinsic_height(w),
1198 )
1199 }
1200
1201 fn max_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, density: f32) -> f32 {
1202 size_intrinsic(
1203 self.target_constraints(density),
1204 SizeAxis::Height,
1205 self.enforce_incoming,
1206 width,
1207 |w| measurable.max_intrinsic_height(w),
1208 )
1209 }
1210}
1211
1212#[derive(Debug, Clone, PartialEq)]
1216pub struct SizeElement {
1217 min_width: Option<f32>,
1218 max_width: Option<f32>,
1219 min_height: Option<f32>,
1220 max_height: Option<f32>,
1221 enforce_incoming: bool,
1222}
1223
1224impl SizeElement {
1225 pub fn new(width: Option<f32>, height: Option<f32>) -> Self {
1228 Self {
1229 min_width: width,
1230 max_width: width,
1231 min_height: height,
1232 max_height: height,
1233 enforce_incoming: true,
1234 }
1235 }
1236
1237 pub fn with_constraints(
1241 min_width: Option<f32>,
1242 max_width: Option<f32>,
1243 min_height: Option<f32>,
1244 max_height: Option<f32>,
1245 enforce_incoming: bool,
1246 ) -> Self {
1247 Self {
1248 min_width,
1249 max_width,
1250 min_height,
1251 max_height,
1252 enforce_incoming,
1253 }
1254 }
1255}
1256
1257impl Hash for SizeElement {
1258 fn hash<H: Hasher>(&self, state: &mut H) {
1259 hash_option_f32(state, self.min_width);
1260 hash_option_f32(state, self.max_width);
1261 hash_option_f32(state, self.min_height);
1262 hash_option_f32(state, self.max_height);
1263 self.enforce_incoming.hash(state);
1264 }
1265}
1266
1267impl ModifierNodeElement for SizeElement {
1268 type Node = SizeNode;
1269
1270 fn create(&self) -> Self::Node {
1271 SizeNode::new(
1272 self.min_width,
1273 self.max_width,
1274 self.min_height,
1275 self.max_height,
1276 self.enforce_incoming,
1277 )
1278 }
1279
1280 fn update(&self, node: &mut Self::Node) {
1281 if node.min_width != self.min_width
1282 || node.max_width != self.max_width
1283 || node.min_height != self.min_height
1284 || node.max_height != self.max_height
1285 || node.enforce_incoming != self.enforce_incoming
1286 {
1287 node.min_width = self.min_width;
1288 node.max_width = self.max_width;
1289 node.min_height = self.min_height;
1290 node.max_height = self.max_height;
1291 node.enforce_incoming = self.enforce_incoming;
1292 }
1293 }
1294
1295 fn capabilities(&self) -> NodeCapabilities {
1296 NodeCapabilities::LAYOUT
1297 }
1298
1299 fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
1300 Some(InvalidationKind::Layout)
1301 }
1302}
1303
1304use cranpose_foundation::DRAG_THRESHOLD;
1305
1306#[derive(Clone, Copy, Debug)]
1308enum ClickPress {
1309 Idle,
1310 Pressed(Point),
1311 Cancelled,
1314}
1315
1316pub struct ClickableNode {
1319 on_press: Option<Rc<dyn Fn(Point)>>,
1320 on_click: Rc<dyn Fn(Point)>,
1321 state: NodeState,
1322 press_position: Rc<Cell<ClickPress>>,
1323 cached_handler: Rc<dyn Fn(PointerEvent)>,
1324}
1325
1326impl std::fmt::Debug for ClickableNode {
1327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1328 f.debug_struct("ClickableNode").finish()
1329 }
1330}
1331
1332impl ClickableNode {
1333 pub fn new(on_click: impl Fn(Point) + 'static) -> Self {
1335 Self::with_handler(Rc::new(on_click))
1336 }
1337
1338 pub fn with_handler(on_click: Rc<dyn Fn(Point)>) -> Self {
1340 Self::with_handlers(None, on_click)
1341 }
1342
1343 pub fn with_handlers(on_press: Option<Rc<dyn Fn(Point)>>, on_click: Rc<dyn Fn(Point)>) -> Self {
1346 let press_position = Rc::new(Cell::new(ClickPress::Idle));
1347 let cached_handler =
1348 Self::create_handler(on_press.clone(), on_click.clone(), press_position.clone());
1349 Self {
1350 on_press,
1351 on_click,
1352 state: NodeState::new(),
1353 press_position,
1354 cached_handler,
1355 }
1356 }
1357
1358 fn create_handler(
1359 on_press: Option<Rc<dyn Fn(Point)>>,
1360 on_click: Rc<dyn Fn(Point)>,
1361 press_position: Rc<Cell<ClickPress>>,
1362 ) -> Rc<dyn Fn(PointerEvent)> {
1363 Rc::new(move |event: PointerEvent| {
1364 if event.id != 0 {
1365 return;
1366 }
1367
1368 if event.is_consumed() {
1369 press_position.set(ClickPress::Idle);
1370 return;
1371 }
1372
1373 match event.kind {
1374 PointerEventKind::Down => {
1375 press_position.set(ClickPress::Pressed(event.travelled_to()));
1376 if let Some(on_press) = on_press.as_ref() {
1377 on_press(event.position);
1378 }
1379 }
1380 PointerEventKind::Move => {}
1381 PointerEventKind::Up => {
1382 let should_click = match press_position.replace(ClickPress::Idle) {
1383 ClickPress::Pressed(press_pos) => {
1384 let travelled_to = event.travelled_to();
1385 let dx = travelled_to.x - press_pos.x;
1386 let dy = travelled_to.y - press_pos.y;
1387 let distance = (dx * dx + dy * dy).sqrt();
1388 distance <= DRAG_THRESHOLD
1389 }
1390 ClickPress::Idle => true,
1391 ClickPress::Cancelled => false,
1392 };
1393
1394 if should_click {
1395 on_click(Point {
1396 x: event.position.x,
1397 y: event.position.y,
1398 });
1399 event.consume();
1400 }
1401 }
1402 PointerEventKind::Cancel => {
1403 press_position.set(ClickPress::Cancelled);
1404 }
1405 PointerEventKind::Scroll
1406 | PointerEventKind::Zoom
1407 | PointerEventKind::RotaryScrollPre
1408 | PointerEventKind::RotaryScroll
1409 | PointerEventKind::Enter
1410 | PointerEventKind::Exit => {}
1411 }
1412 })
1413 }
1414
1415 pub fn handler(&self) -> Rc<dyn Fn(Point)> {
1417 self.on_click.clone()
1418 }
1419}
1420
1421impl DelegatableNode for ClickableNode {
1422 fn node_state(&self) -> &NodeState {
1423 &self.state
1424 }
1425}
1426
1427impl ModifierNode for ClickableNode {
1428 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
1429 context.invalidate(cranpose_foundation::InvalidationKind::PointerInput);
1430 }
1431
1432 fn as_pointer_input_node(&self) -> Option<&dyn PointerInputNode> {
1433 Some(self)
1434 }
1435
1436 fn as_pointer_input_node_mut(&mut self) -> Option<&mut dyn PointerInputNode> {
1437 Some(self)
1438 }
1439}
1440
1441impl PointerInputNode for ClickableNode {
1442 fn on_pointer_event(
1443 &mut self,
1444 _context: &mut dyn ModifierNodeContext,
1445 event: &PointerEvent,
1446 ) -> bool {
1447 (self.cached_handler)(event.clone());
1448 event.is_consumed()
1449 }
1450
1451 fn hit_test(&self, _x: f32, _y: f32) -> bool {
1452 true
1453 }
1454
1455 fn pointer_input_handler(&self) -> Option<Rc<dyn Fn(PointerEvent)>> {
1456 Some(self.cached_handler.clone())
1457 }
1458}
1459
1460#[derive(Clone)]
1462pub struct ClickableElement {
1463 on_press: Option<Rc<dyn Fn(Point)>>,
1464 on_click: Rc<dyn Fn(Point)>,
1465}
1466
1467impl ClickableElement {
1468 pub fn new(on_click: impl Fn(Point) + 'static) -> Self {
1470 Self {
1471 on_press: None,
1472 on_click: Rc::new(on_click),
1473 }
1474 }
1475
1476 pub fn with_handler(on_click: Rc<dyn Fn(Point)>) -> Self {
1478 Self {
1479 on_press: None,
1480 on_click,
1481 }
1482 }
1483
1484 pub fn with_handlers(on_press: Rc<dyn Fn(Point)>, on_click: Rc<dyn Fn(Point)>) -> Self {
1487 Self {
1488 on_press: Some(on_press),
1489 on_click,
1490 }
1491 }
1492}
1493
1494impl std::fmt::Debug for ClickableElement {
1495 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1496 f.debug_struct("ClickableElement").finish()
1497 }
1498}
1499
1500impl PartialEq for ClickableElement {
1501 fn eq(&self, _other: &Self) -> bool {
1502 true
1503 }
1504}
1505
1506impl Eq for ClickableElement {}
1507
1508impl Hash for ClickableElement {
1509 fn hash<H: Hasher>(&self, state: &mut H) {
1510 "clickable".hash(state);
1511 }
1512}
1513
1514impl ModifierNodeElement for ClickableElement {
1515 type Node = ClickableNode;
1516
1517 fn create(&self) -> Self::Node {
1518 ClickableNode::with_handlers(self.on_press.clone(), self.on_click.clone())
1519 }
1520
1521 fn update(&self, node: &mut Self::Node) {
1522 node.on_press.clone_from(&self.on_press);
1523 node.on_click.clone_from(&self.on_click);
1524 node.cached_handler = ClickableNode::create_handler(
1525 node.on_press.clone(),
1526 node.on_click.clone(),
1527 node.press_position.clone(),
1528 );
1529 }
1530
1531 fn capabilities(&self) -> NodeCapabilities {
1532 NodeCapabilities::POINTER_INPUT
1533 }
1534
1535 fn always_update(&self) -> bool {
1536 true
1537 }
1538}
1539
1540#[derive(Debug)]
1546pub struct PointerIconNode {
1547 icon: PointerIcon,
1548 state: NodeState,
1549}
1550
1551impl PointerIconNode {
1552 pub fn new(icon: PointerIcon) -> Self {
1554 Self {
1555 icon,
1556 state: NodeState::new(),
1557 }
1558 }
1559
1560 pub fn icon(&self) -> &PointerIcon {
1562 &self.icon
1563 }
1564}
1565
1566impl DelegatableNode for PointerIconNode {
1567 fn node_state(&self) -> &NodeState {
1568 &self.state
1569 }
1570}
1571
1572impl ModifierNode for PointerIconNode {
1573 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
1574 context.invalidate(cranpose_foundation::InvalidationKind::PointerInput);
1575 }
1576
1577 fn as_pointer_input_node(&self) -> Option<&dyn PointerInputNode> {
1578 Some(self)
1579 }
1580
1581 fn as_pointer_input_node_mut(&mut self) -> Option<&mut dyn PointerInputNode> {
1582 Some(self)
1583 }
1584}
1585
1586impl PointerInputNode for PointerIconNode {
1587 fn on_pointer_event(
1588 &mut self,
1589 _context: &mut dyn ModifierNodeContext,
1590 _event: &PointerEvent,
1591 ) -> bool {
1592 false
1593 }
1594
1595 fn hit_test(&self, _x: f32, _y: f32) -> bool {
1596 true
1597 }
1598
1599 fn pointer_input_handler(&self) -> Option<Rc<dyn Fn(PointerEvent)>> {
1600 None
1601 }
1602}
1603
1604#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1606pub struct PointerIconElement {
1607 icon: PointerIcon,
1608}
1609
1610impl PointerIconElement {
1611 pub fn new(icon: PointerIcon) -> Self {
1613 Self { icon }
1614 }
1615}
1616
1617impl ModifierNodeElement for PointerIconElement {
1618 type Node = PointerIconNode;
1619
1620 fn create(&self) -> Self::Node {
1621 PointerIconNode::new(self.icon.clone())
1622 }
1623
1624 fn update(&self, node: &mut Self::Node) {
1625 node.icon = self.icon.clone();
1626 }
1627
1628 fn capabilities(&self) -> NodeCapabilities {
1629 NodeCapabilities::POINTER_INPUT
1630 }
1631}
1632
1633#[derive(Debug)]
1635pub struct AlphaNode {
1636 alpha: f32,
1637 state: NodeState,
1638}
1639
1640impl AlphaNode {
1641 pub fn new(alpha: f32) -> Self {
1643 Self {
1644 alpha: alpha.clamp(0.0, 1.0),
1645 state: NodeState::new(),
1646 }
1647 }
1648}
1649
1650impl DelegatableNode for AlphaNode {
1651 fn node_state(&self) -> &NodeState {
1652 &self.state
1653 }
1654}
1655
1656impl_draw_modifier_node!(AlphaNode);
1657
1658#[derive(Debug, Clone, PartialEq)]
1660pub struct AlphaElement {
1661 alpha: f32,
1662}
1663
1664impl AlphaElement {
1665 pub fn new(alpha: f32) -> Self {
1668 Self {
1669 alpha: alpha.clamp(0.0, 1.0),
1670 }
1671 }
1672}
1673
1674impl Hash for AlphaElement {
1675 fn hash<H: Hasher>(&self, state: &mut H) {
1676 hash_f32_value(state, self.alpha);
1677 }
1678}
1679
1680impl ModifierNodeElement for AlphaElement {
1681 type Node = AlphaNode;
1682
1683 fn create(&self) -> Self::Node {
1684 AlphaNode::new(self.alpha)
1685 }
1686
1687 fn update(&self, node: &mut Self::Node) {
1688 let new_alpha = self.alpha.clamp(0.0, 1.0);
1689 if (node.alpha - new_alpha).abs() > f32::EPSILON {
1690 node.alpha = new_alpha;
1691 }
1692 }
1693
1694 fn capabilities(&self) -> NodeCapabilities {
1695 NodeCapabilities::DRAW
1696 }
1697}
1698
1699#[derive(Debug)]
1700pub struct ClipToBoundsNode {
1701 state: NodeState,
1702}
1703
1704impl ClipToBoundsNode {
1705 pub fn new() -> Self {
1706 Self {
1707 state: NodeState::new(),
1708 }
1709 }
1710}
1711
1712impl DelegatableNode for ClipToBoundsNode {
1713 fn node_state(&self) -> &NodeState {
1714 &self.state
1715 }
1716}
1717
1718impl_draw_modifier_node!(ClipToBoundsNode);
1719
1720#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1721pub struct ClipToBoundsElement;
1722
1723impl ClipToBoundsElement {
1724 pub fn new() -> Self {
1725 Self
1726 }
1727}
1728
1729impl ModifierNodeElement for ClipToBoundsElement {
1730 type Node = ClipToBoundsNode;
1731
1732 fn create(&self) -> Self::Node {
1733 ClipToBoundsNode::new()
1734 }
1735
1736 fn update(&self, _node: &mut Self::Node) {}
1737
1738 fn capabilities(&self) -> NodeCapabilities {
1739 NodeCapabilities::DRAW
1740 }
1741}
1742
1743pub trait WindowRectSink {
1744 fn set(&self, coordinates: cranpose_ui_graphics::WindowCoordinates);
1745}
1746
1747impl WindowRectSink for Cell<cranpose_ui_graphics::Rect> {
1748 fn set(&self, coordinates: cranpose_ui_graphics::WindowCoordinates) {
1749 Cell::set(self, coordinates.bounds());
1750 }
1751}
1752
1753impl WindowRectSink for Cell<cranpose_ui_graphics::WindowCoordinates> {
1754 fn set(&self, coordinates: cranpose_ui_graphics::WindowCoordinates) {
1755 Cell::set(self, coordinates);
1756 }
1757}
1758
1759struct StateWindowRectSink(cranpose_core::MutableState<cranpose_ui_graphics::Rect>);
1760
1761impl WindowRectSink for StateWindowRectSink {
1762 fn set(&self, coordinates: cranpose_ui_graphics::WindowCoordinates) {
1763 self.0.set(coordinates.bounds());
1764 }
1765}
1766
1767pub struct WindowRectReporterNode {
1768 sink: Rc<dyn WindowRectSink>,
1769 state: NodeState,
1770}
1771
1772impl WindowRectReporterNode {
1773 pub(crate) fn new(sink: Rc<dyn WindowRectSink>) -> Self {
1774 Self {
1775 sink,
1776 state: NodeState::new(),
1777 }
1778 }
1779
1780 pub(crate) fn window_rect_sink(&self) -> Rc<dyn WindowRectSink> {
1781 self.sink.clone()
1782 }
1783}
1784
1785impl DelegatableNode for WindowRectReporterNode {
1786 fn node_state(&self) -> &NodeState {
1787 &self.state
1788 }
1789}
1790
1791impl_layout_modifier_node!(WindowRectReporterNode);
1792
1793impl LayoutModifierNode for WindowRectReporterNode {
1794 fn measure(
1795 &self,
1796 _context: &mut dyn ModifierNodeContext,
1797 measurable: &dyn Measurable,
1798 constraints: Constraints,
1799 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
1800 measure_pass_through(measurable, constraints, |_| (0.0, 0.0))
1801 }
1802}
1803
1804#[derive(Clone)]
1805pub struct WindowRectReporterElement {
1806 sink: Rc<dyn WindowRectSink>,
1807}
1808
1809impl WindowRectReporterElement {
1810 pub fn new(sink: Rc<Cell<cranpose_ui_graphics::Rect>>) -> Self {
1811 Self { sink }
1812 }
1813
1814 pub fn from_coordinates(sink: Rc<Cell<cranpose_ui_graphics::WindowCoordinates>>) -> Self {
1816 Self { sink }
1817 }
1818
1819 pub fn from_state(sink: cranpose_core::MutableState<cranpose_ui_graphics::Rect>) -> Self {
1820 Self {
1821 sink: Rc::new(StateWindowRectSink(sink)),
1822 }
1823 }
1824}
1825
1826impl std::fmt::Debug for WindowRectReporterElement {
1827 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1828 f.debug_struct("WindowRectReporterElement").finish()
1829 }
1830}
1831
1832impl PartialEq for WindowRectReporterElement {
1833 fn eq(&self, other: &Self) -> bool {
1834 Rc::ptr_eq(&self.sink, &other.sink)
1835 }
1836}
1837
1838impl Eq for WindowRectReporterElement {}
1839
1840impl Hash for WindowRectReporterElement {
1841 fn hash<H: Hasher>(&self, state: &mut H) {
1842 std::ptr::hash(Rc::as_ptr(&self.sink).cast::<()>(), state);
1843 }
1844}
1845
1846impl_sink_reporter_element!(WindowRectReporterElement, WindowRectReporterNode);
1847
1848pub(crate) struct SelectableTextNode {
1849 sink: Rc<crate::selection_container::SelectableGeometry>,
1850 state: NodeState,
1851}
1852
1853impl SelectableTextNode {
1854 pub(crate) fn new(sink: Rc<crate::selection_container::SelectableGeometry>) -> Self {
1855 Self {
1856 sink,
1857 state: NodeState::new(),
1858 }
1859 }
1860
1861 pub(crate) fn geometry(&self) -> &crate::selection_container::SelectableGeometry {
1862 &self.sink
1863 }
1864}
1865
1866impl DelegatableNode for SelectableTextNode {
1867 fn node_state(&self) -> &NodeState {
1868 &self.state
1869 }
1870}
1871
1872impl_layout_modifier_node!(SelectableTextNode);
1873
1874impl LayoutModifierNode for SelectableTextNode {
1875 fn measure(
1876 &self,
1877 _context: &mut dyn ModifierNodeContext,
1878 measurable: &dyn Measurable,
1879 constraints: Constraints,
1880 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
1881 measure_pass_through(measurable, constraints, |_| (0.0, 0.0))
1882 }
1883}
1884
1885#[derive(Clone)]
1886pub(crate) struct SelectableTextElement {
1887 sink: Rc<crate::selection_container::SelectableGeometry>,
1888}
1889
1890impl SelectableTextElement {
1891 pub(crate) fn new(sink: Rc<crate::selection_container::SelectableGeometry>) -> Self {
1892 Self { sink }
1893 }
1894}
1895
1896impl std::fmt::Debug for SelectableTextElement {
1897 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1898 f.debug_struct("SelectableTextElement").finish()
1899 }
1900}
1901
1902impl PartialEq for SelectableTextElement {
1903 fn eq(&self, other: &Self) -> bool {
1904 Rc::ptr_eq(&self.sink, &other.sink)
1905 }
1906}
1907
1908impl Eq for SelectableTextElement {}
1909
1910impl Hash for SelectableTextElement {
1911 fn hash<H: Hasher>(&self, state: &mut H) {
1912 std::ptr::hash(Rc::as_ptr(&self.sink), state);
1913 }
1914}
1915
1916impl_sink_reporter_element!(SelectableTextElement, SelectableTextNode);
1917
1918pub trait SizeSink {
1919 fn set(&self, size: Size);
1920}
1921
1922impl SizeSink for Cell<Size> {
1923 fn set(&self, size: Size) {
1924 Cell::set(self, size);
1925 }
1926}
1927
1928struct StateSizeSink(cranpose_core::MutableState<Size>);
1929
1930impl SizeSink for StateSizeSink {
1931 fn set(&self, size: Size) {
1932 self.0.set(size);
1933 }
1934}
1935
1936pub struct SizeReporterNode {
1937 sink: Rc<dyn SizeSink>,
1938 state: NodeState,
1939 #[cfg(debug_assertions)]
1940 oscillation: Cell<(Size, Size, u32)>,
1941}
1942
1943impl SizeReporterNode {
1944 pub fn new(sink: Rc<dyn SizeSink>) -> Self {
1945 Self {
1946 sink,
1947 state: NodeState::new(),
1948 #[cfg(debug_assertions)]
1949 oscillation: Cell::new((Size::default(), Size::default(), 0)),
1950 }
1951 }
1952
1953 #[cfg(debug_assertions)]
1954 fn check_oscillation(&self, size: Size) {
1955 const ALTERNATION_CEILING: u32 = 64;
1956 let (last, second_last, count) = self.oscillation.get();
1957 let count = if size == second_last && size != last {
1958 count + 1
1959 } else if size == last {
1960 count
1961 } else {
1962 0
1963 };
1964 assert!(
1965 count <= ALTERNATION_CEILING,
1966 "size-reactive feedback loop: this node's measured size has \
1967 alternated between {last:?} and {size:?} for {count} passes — \
1968 its content's size depends on the size it reports (the \
1969 onSizeChanged self-reference hazard). Break the cycle by making \
1970 the reported size feed only content that does not change this \
1971 node's own measured size."
1972 );
1973 self.oscillation.set((size, last, count));
1974 }
1975}
1976
1977impl DelegatableNode for SizeReporterNode {
1978 fn node_state(&self) -> &NodeState {
1979 &self.state
1980 }
1981}
1982
1983impl_layout_modifier_node!(SizeReporterNode);
1984
1985impl LayoutModifierNode for SizeReporterNode {
1986 fn measure(
1987 &self,
1988 _context: &mut dyn ModifierNodeContext,
1989 measurable: &dyn Measurable,
1990 constraints: Constraints,
1991 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
1992 measure_pass_through(measurable, constraints, |size| {
1993 #[cfg(debug_assertions)]
1994 self.check_oscillation(size);
1995 self.sink.set(size);
1996 (0.0, 0.0)
1997 })
1998 }
1999}
2000
2001#[derive(Clone)]
2002pub struct SizeReporterElement {
2003 sink: Rc<dyn SizeSink>,
2004}
2005
2006impl SizeReporterElement {
2007 pub fn new(sink: Rc<Cell<Size>>) -> Self {
2008 Self { sink }
2009 }
2010
2011 pub fn from_state(sink: cranpose_core::MutableState<Size>) -> Self {
2012 Self {
2013 sink: Rc::new(StateSizeSink(sink)),
2014 }
2015 }
2016}
2017
2018impl std::fmt::Debug for SizeReporterElement {
2019 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2020 f.debug_struct("SizeReporterElement").finish()
2021 }
2022}
2023
2024impl PartialEq for SizeReporterElement {
2025 fn eq(&self, other: &Self) -> bool {
2026 std::ptr::addr_eq(Rc::as_ptr(&self.sink), Rc::as_ptr(&other.sink))
2027 }
2028}
2029
2030impl Hash for SizeReporterElement {
2031 fn hash<H: Hasher>(&self, state: &mut H) {
2032 (Rc::as_ptr(&self.sink) as *const () as usize).hash(state);
2033 }
2034}
2035
2036impl_sink_reporter_element!(SizeReporterElement, SizeReporterNode);
2037
2038pub struct DrawCommandNode {
2039 commands: Vec<DrawCommand>,
2040 node_id: Cell<Option<NodeId>>,
2041 state: NodeState,
2042}
2043
2044impl DrawCommandNode {
2045 pub fn new(commands: Vec<DrawCommand>) -> Self {
2046 Self {
2047 commands,
2048 node_id: Cell::new(None),
2049 state: NodeState::new(),
2050 }
2051 }
2052
2053 #[cfg(test)]
2054 pub fn commands(&self) -> &[DrawCommand] {
2055 &self.commands
2056 }
2057
2058 pub(crate) fn observed_commands(
2059 &self,
2060 modifier_index: usize,
2061 ) -> impl Iterator<Item = DrawCommand> + '_ {
2062 let node_id = self.node_id.get();
2063 self.commands
2064 .iter()
2065 .cloned()
2066 .enumerate()
2067 .map(move |(index, command)| {
2068 observe_draw_command(command, node_id, modifier_index, index)
2069 })
2070 }
2071}
2072
2073impl DelegatableNode for DrawCommandNode {
2074 fn node_state(&self) -> &NodeState {
2075 &self.state
2076 }
2077}
2078
2079impl ModifierNode for DrawCommandNode {
2080 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
2081 attach_draw_observer(&self.node_id, context);
2082 }
2083
2084 fn on_detach(&mut self) {
2085 detach_draw_observer(&self.node_id);
2086 }
2087
2088 fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
2089 Some(self)
2090 }
2091
2092 fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
2093 Some(self)
2094 }
2095}
2096
2097impl DrawModifierNode for DrawCommandNode {}
2098
2099fn observe_draw_command(
2100 command: DrawCommand,
2101 node_id: Option<NodeId>,
2102 modifier_index: usize,
2103 command_index: usize,
2104) -> DrawCommand {
2105 let Some(node_id) = node_id else {
2106 return command;
2107 };
2108 let observation =
2109 crate::render_state::DrawObservationScope::new(node_id, modifier_index, command_index);
2110 match command {
2111 DrawCommand::Behind(draw) => DrawCommand::Behind(Rc::new(move |scope| {
2112 crate::render_state::observe_draw_reads(observation, || draw(scope));
2113 })),
2114 DrawCommand::WithContent(draw) => DrawCommand::WithContent(Rc::new(move |scope| {
2115 crate::render_state::observe_draw_reads(observation, || draw(scope));
2116 })),
2117 DrawCommand::Overlay(draw) => DrawCommand::Overlay(Rc::new(move |scope| {
2118 crate::render_state::observe_draw_reads(observation, || draw(scope));
2119 })),
2120 }
2121}
2122
2123fn draw_command_tag(cmd: &DrawCommand) -> u8 {
2124 match cmd {
2125 DrawCommand::Behind(_) => 0,
2126 DrawCommand::WithContent(_) => 1,
2127 DrawCommand::Overlay(_) => 2,
2128 }
2129}
2130
2131fn draw_command_closure_identity(cmd: &DrawCommand) -> *const () {
2132 match cmd {
2133 DrawCommand::Behind(f) | DrawCommand::WithContent(f) | DrawCommand::Overlay(f) => {
2134 Rc::as_ptr(f) as *const ()
2135 }
2136 }
2137}
2138
2139#[derive(Clone)]
2140pub struct DrawCommandElement {
2141 commands: Vec<DrawCommand>,
2142}
2143
2144impl DrawCommandElement {
2145 pub fn new(command: DrawCommand) -> Self {
2146 Self {
2147 commands: vec![command],
2148 }
2149 }
2150
2151 pub fn from_commands(commands: Vec<DrawCommand>) -> Self {
2152 Self { commands }
2153 }
2154}
2155
2156impl std::fmt::Debug for DrawCommandElement {
2157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2158 f.debug_struct("DrawCommandElement")
2159 .field("commands", &self.commands.len())
2160 .finish()
2161 }
2162}
2163
2164impl PartialEq for DrawCommandElement {
2165 fn eq(&self, other: &Self) -> bool {
2166 if self.commands.len() != other.commands.len() {
2167 return false;
2168 }
2169 self.commands
2170 .iter()
2171 .zip(other.commands.iter())
2172 .all(|(a, b)| {
2173 draw_command_tag(a) == draw_command_tag(b)
2174 && draw_command_closure_identity(a) == draw_command_closure_identity(b)
2175 })
2176 }
2177}
2178
2179impl Eq for DrawCommandElement {}
2180
2181impl std::hash::Hash for DrawCommandElement {
2182 fn hash<H: Hasher>(&self, state: &mut H) {
2183 "draw_commands".hash(state);
2184 self.commands.len().hash(state);
2185 for command in &self.commands {
2186 draw_command_tag(command).hash(state);
2187 (draw_command_closure_identity(command) as usize).hash(state);
2188 }
2189 }
2190}
2191
2192impl ModifierNodeElement for DrawCommandElement {
2193 type Node = DrawCommandNode;
2194
2195 fn create(&self) -> Self::Node {
2196 DrawCommandNode::new(self.commands.clone())
2197 }
2198
2199 fn update(&self, node: &mut Self::Node) {
2200 node.commands.clone_from(&self.commands);
2201 }
2202
2203 fn capabilities(&self) -> NodeCapabilities {
2204 NodeCapabilities::DRAW
2205 }
2206}
2207
2208#[derive(Debug)]
2212pub struct OffsetNode {
2213 x: f32,
2214 y: f32,
2215 rtl_aware: bool,
2216 layout_direction: crate::LayoutDirection,
2217 state: NodeState,
2218}
2219
2220impl OffsetNode {
2221 pub fn new(x: f32, y: f32, rtl_aware: bool) -> Self {
2224 Self {
2225 x,
2226 y,
2227 rtl_aware,
2228 layout_direction: crate::LayoutDirection::Ltr,
2229 state: NodeState::new(),
2230 }
2231 }
2232
2233 pub fn offset(&self) -> Point {
2236 Point {
2237 x: if self.rtl_aware && self.layout_direction.is_rtl() {
2238 -self.x
2239 } else {
2240 self.x
2241 },
2242 y: self.y,
2243 }
2244 }
2245
2246 pub fn device_offset(&self, density: f32) -> Point {
2249 use cranpose_ui_layout::round_to_px;
2250 let offset = self.offset();
2251 Point {
2252 x: round_to_px(offset.x, density),
2253 y: round_to_px(offset.y, density),
2254 }
2255 }
2256
2257 pub fn rtl_aware(&self) -> bool {
2259 self.rtl_aware
2260 }
2261
2262 pub(crate) fn set_layout_direction(&mut self, direction: crate::LayoutDirection) {
2263 self.layout_direction = direction;
2264 }
2265}
2266
2267impl DelegatableNode for OffsetNode {
2268 fn node_state(&self) -> &NodeState {
2269 &self.state
2270 }
2271}
2272
2273impl_layout_modifier_node!(OffsetNode, invalidate = InvalidationKind::Layout);
2274
2275impl LayoutModifierNode for OffsetNode {
2276 fn measure_hold(
2277 &self,
2278 _density: f32,
2279 _constraints: Constraints,
2280 _size: Size,
2281 wrapped: cranpose_ui_layout::WrappedHold,
2282 ) -> Option<cranpose_ui_layout::ConstraintsHold> {
2283 wrapped.hold
2284 }
2285
2286 fn measure(
2287 &self,
2288 context: &mut dyn ModifierNodeContext,
2289 measurable: &dyn Measurable,
2290 constraints: Constraints,
2291 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
2292 let offset = self.device_offset(context.density());
2293 measure_pass_through(measurable, constraints, |_| (offset.x, offset.y))
2294 }
2295}
2296
2297#[derive(Debug, Clone, PartialEq)]
2301pub struct OffsetElement {
2302 x: f32,
2303 y: f32,
2304 rtl_aware: bool,
2305}
2306
2307impl OffsetElement {
2308 pub fn new(x: f32, y: f32, rtl_aware: bool) -> Self {
2311 Self { x, y, rtl_aware }
2312 }
2313}
2314
2315impl Hash for OffsetElement {
2316 fn hash<H: Hasher>(&self, state: &mut H) {
2317 hash_f32_value(state, self.x);
2318 hash_f32_value(state, self.y);
2319 self.rtl_aware.hash(state);
2320 }
2321}
2322
2323impl ModifierNodeElement for OffsetElement {
2324 type Node = OffsetNode;
2325
2326 fn create(&self) -> Self::Node {
2327 OffsetNode::new(self.x, self.y, self.rtl_aware)
2328 }
2329
2330 fn update(&self, node: &mut Self::Node) {
2331 if node.x != self.x || node.y != self.y || node.rtl_aware != self.rtl_aware {
2332 node.x = self.x;
2333 node.y = self.y;
2334 node.rtl_aware = self.rtl_aware;
2335 }
2336 }
2337
2338 fn capabilities(&self) -> NodeCapabilities {
2339 NodeCapabilities::LAYOUT
2340 }
2341
2342 fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
2343 Some(InvalidationKind::Layout)
2344 }
2345}
2346
2347#[derive(Debug)]
2355pub struct FractionalOffsetNode {
2356 x_fraction: f32,
2357 y_fraction: f32,
2358 state: NodeState,
2359}
2360
2361impl FractionalOffsetNode {
2362 pub fn new(x_fraction: f32, y_fraction: f32) -> Self {
2365 Self {
2366 x_fraction,
2367 y_fraction,
2368 state: NodeState::new(),
2369 }
2370 }
2371
2372 pub fn fractions(&self) -> Point {
2375 Point {
2376 x: self.x_fraction,
2377 y: self.y_fraction,
2378 }
2379 }
2380}
2381
2382impl DelegatableNode for FractionalOffsetNode {
2383 fn node_state(&self) -> &NodeState {
2384 &self.state
2385 }
2386}
2387
2388impl_layout_modifier_node!(FractionalOffsetNode, invalidate = InvalidationKind::Layout);
2389
2390impl LayoutModifierNode for FractionalOffsetNode {
2391 fn measure_hold(
2392 &self,
2393 _density: f32,
2394 _constraints: Constraints,
2395 _size: Size,
2396 wrapped: cranpose_ui_layout::WrappedHold,
2397 ) -> Option<cranpose_ui_layout::ConstraintsHold> {
2398 wrapped.hold
2399 }
2400
2401 fn measure(
2402 &self,
2403 _context: &mut dyn ModifierNodeContext,
2404 measurable: &dyn Measurable,
2405 constraints: Constraints,
2406 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
2407 measure_pass_through(measurable, constraints, |size| {
2408 (self.x_fraction * size.width, self.y_fraction * size.height)
2409 })
2410 }
2411}
2412
2413#[derive(Debug, Clone, PartialEq)]
2415pub struct FractionalOffsetElement {
2416 x_fraction: f32,
2417 y_fraction: f32,
2418}
2419
2420impl FractionalOffsetElement {
2421 pub fn new(x_fraction: f32, y_fraction: f32) -> Self {
2424 Self {
2425 x_fraction,
2426 y_fraction,
2427 }
2428 }
2429}
2430
2431impl Hash for FractionalOffsetElement {
2432 fn hash<H: Hasher>(&self, state: &mut H) {
2433 "fractional_offset".hash(state);
2434 hash_f32_value(state, self.x_fraction);
2435 hash_f32_value(state, self.y_fraction);
2436 }
2437}
2438
2439impl ModifierNodeElement for FractionalOffsetElement {
2440 type Node = FractionalOffsetNode;
2441
2442 fn create(&self) -> Self::Node {
2443 FractionalOffsetNode::new(self.x_fraction, self.y_fraction)
2444 }
2445
2446 fn update(&self, node: &mut Self::Node) {
2447 if node.x_fraction != self.x_fraction || node.y_fraction != self.y_fraction {
2448 node.x_fraction = self.x_fraction;
2449 node.y_fraction = self.y_fraction;
2450 }
2451 }
2452
2453 fn capabilities(&self) -> NodeCapabilities {
2454 NodeCapabilities::LAYOUT
2455 }
2456
2457 fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
2458 Some(InvalidationKind::Layout)
2459 }
2460}
2461
2462#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2464pub enum FillDirection {
2465 Horizontal,
2467 Vertical,
2469 Both,
2471}
2472
2473#[derive(Debug)]
2477pub struct FillNode {
2478 direction: FillDirection,
2479 fraction: f32,
2480 state: NodeState,
2481}
2482
2483impl FillNode {
2484 pub fn new(direction: FillDirection, fraction: f32) -> Self {
2486 Self {
2487 direction,
2488 fraction,
2489 state: NodeState::new(),
2490 }
2491 }
2492
2493 pub fn direction(&self) -> FillDirection {
2495 self.direction
2496 }
2497
2498 pub fn fraction(&self) -> f32 {
2500 self.fraction
2501 }
2502
2503 fn filled(&self, extent: f32, other: FillDirection, density: f32) -> f32 {
2506 if self.direction == other || !extent.is_finite() {
2507 return extent;
2508 }
2509 cranpose_ui_layout::round_to_px(extent * self.fraction, density)
2510 }
2511}
2512
2513impl DelegatableNode for FillNode {
2514 fn node_state(&self) -> &NodeState {
2515 &self.state
2516 }
2517}
2518
2519impl_layout_modifier_node!(FillNode, invalidate = InvalidationKind::Layout);
2520
2521impl LayoutModifierNode for FillNode {
2522 fn measure_hold(
2526 &self,
2527 density: f32,
2528 constraints: Constraints,
2529 size: Size,
2530 wrapped: cranpose_ui_layout::WrappedHold,
2531 ) -> Option<cranpose_ui_layout::ConstraintsHold> {
2532 Some(cranpose_ui_layout::ConstraintsHold {
2533 width: fill_axis_hold(
2534 self.direction != FillDirection::Vertical,
2535 (constraints.max_width, size.width),
2536 (self.fraction, density),
2537 wrapped.hold.map(|hold| hold.width),
2538 )?,
2539 height: fill_axis_hold(
2540 self.direction != FillDirection::Horizontal,
2541 (constraints.max_height, size.height),
2542 (self.fraction, density),
2543 wrapped.hold.map(|hold| hold.height),
2544 )?,
2545 })
2546 }
2547
2548 fn measure(
2549 &self,
2550 context: &mut dyn ModifierNodeContext,
2551 measurable: &dyn Measurable,
2552 constraints: Constraints,
2553 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
2554 let density = context.density();
2555 let (fill_width, child_min_width, child_max_width) = if self.direction
2556 != FillDirection::Vertical
2557 && constraints.max_width != f32::INFINITY
2558 {
2559 let width =
2560 cranpose_ui_layout::round_to_px(constraints.max_width * self.fraction, density)
2561 .clamp(constraints.min_width, constraints.max_width);
2562 (width, width, width)
2563 } else {
2564 (
2565 constraints.max_width,
2566 constraints.min_width,
2567 constraints.max_width,
2568 )
2569 };
2570
2571 let (fill_height, child_min_height, child_max_height) = if self.direction
2572 != FillDirection::Horizontal
2573 && constraints.max_height != f32::INFINITY
2574 {
2575 let height =
2576 cranpose_ui_layout::round_to_px(constraints.max_height * self.fraction, density)
2577 .clamp(constraints.min_height, constraints.max_height);
2578 (height, height, height)
2579 } else {
2580 (
2581 constraints.max_height,
2582 constraints.min_height,
2583 constraints.max_height,
2584 )
2585 };
2586
2587 let fill_constraints = Constraints {
2588 min_width: child_min_width,
2589 max_width: child_max_width,
2590 min_height: child_min_height,
2591 max_height: child_max_height,
2592 };
2593
2594 let placeable = measurable.measure(fill_constraints);
2595
2596 let result_width = if self.direction != FillDirection::Vertical
2597 && constraints.max_width != f32::INFINITY
2598 {
2599 fill_width
2600 } else {
2601 placeable.width()
2602 };
2603
2604 let result_height = if self.direction != FillDirection::Horizontal
2605 && constraints.max_height != f32::INFINITY
2606 {
2607 fill_height
2608 } else {
2609 placeable.height()
2610 };
2611
2612 cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size {
2613 width: result_width,
2614 height: result_height,
2615 })
2616 }
2617
2618 fn min_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, density: f32) -> f32 {
2619 measurable.min_intrinsic_width(self.filled(height, FillDirection::Horizontal, density))
2620 }
2621
2622 fn max_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, density: f32) -> f32 {
2623 measurable.max_intrinsic_width(self.filled(height, FillDirection::Horizontal, density))
2624 }
2625
2626 fn min_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, density: f32) -> f32 {
2627 measurable.min_intrinsic_height(self.filled(width, FillDirection::Vertical, density))
2628 }
2629
2630 fn max_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, density: f32) -> f32 {
2631 measurable.max_intrinsic_height(self.filled(width, FillDirection::Vertical, density))
2632 }
2633}
2634
2635#[derive(Debug, Clone, PartialEq)]
2639pub struct FillElement {
2640 direction: FillDirection,
2641 fraction: f32,
2642}
2643
2644impl FillElement {
2645 pub fn width(fraction: f32) -> Self {
2647 Self {
2648 direction: FillDirection::Horizontal,
2649 fraction,
2650 }
2651 }
2652
2653 pub fn height(fraction: f32) -> Self {
2655 Self {
2656 direction: FillDirection::Vertical,
2657 fraction,
2658 }
2659 }
2660
2661 pub fn size(fraction: f32) -> Self {
2663 Self {
2664 direction: FillDirection::Both,
2665 fraction,
2666 }
2667 }
2668}
2669
2670impl Hash for FillElement {
2671 fn hash<H: Hasher>(&self, state: &mut H) {
2672 self.direction.hash(state);
2673 hash_f32_value(state, self.fraction);
2674 }
2675}
2676
2677impl ModifierNodeElement for FillElement {
2678 type Node = FillNode;
2679
2680 fn create(&self) -> Self::Node {
2681 FillNode::new(self.direction, self.fraction)
2682 }
2683
2684 fn update(&self, node: &mut Self::Node) {
2685 if node.direction != self.direction || node.fraction != self.fraction {
2686 node.direction = self.direction;
2687 node.fraction = self.fraction;
2688 }
2689 }
2690
2691 fn capabilities(&self) -> NodeCapabilities {
2692 NodeCapabilities::LAYOUT
2693 }
2694}
2695
2696#[derive(Debug)]
2697pub struct WeightNode {
2698 weight: f32,
2699 fill: bool,
2700 state: NodeState,
2701}
2702
2703impl WeightNode {
2704 pub fn new(weight: f32, fill: bool) -> Self {
2705 Self {
2706 weight,
2707 fill,
2708 state: NodeState::new(),
2709 }
2710 }
2711
2712 pub fn layout_weight(&self) -> LayoutWeight {
2713 LayoutWeight {
2714 weight: self.weight,
2715 fill: self.fill,
2716 }
2717 }
2718}
2719
2720impl DelegatableNode for WeightNode {
2721 fn node_state(&self) -> &NodeState {
2722 &self.state
2723 }
2724}
2725
2726impl ModifierNode for WeightNode {
2727 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
2728 context.invalidate(cranpose_foundation::InvalidationKind::Layout);
2729 }
2730}
2731
2732#[derive(Debug, Clone, PartialEq)]
2733pub struct WeightElement {
2734 weight: f32,
2735 fill: bool,
2736}
2737
2738impl WeightElement {
2739 pub fn new(weight: f32, fill: bool) -> Self {
2740 Self { weight, fill }
2741 }
2742}
2743
2744impl Hash for WeightElement {
2745 fn hash<H: Hasher>(&self, state: &mut H) {
2746 hash_f32_value(state, self.weight);
2747 self.fill.hash(state);
2748 }
2749}
2750
2751impl ModifierNodeElement for WeightElement {
2752 type Node = WeightNode;
2753
2754 fn create(&self) -> Self::Node {
2755 WeightNode::new(self.weight, self.fill)
2756 }
2757
2758 fn update(&self, node: &mut Self::Node) {
2759 if node.weight != self.weight || node.fill != self.fill {
2760 node.weight = self.weight;
2761 node.fill = self.fill;
2762 }
2763 }
2764
2765 fn capabilities(&self) -> NodeCapabilities {
2766 NodeCapabilities::LAYOUT
2767 }
2768}
2769
2770#[derive(Debug)]
2771pub struct AlignmentNode {
2772 box_alignment: Option<Alignment>,
2773 column_alignment: Option<HorizontalAlignment>,
2774 row_alignment: Option<VerticalAlignment>,
2775 row_baseline: bool,
2776 state: NodeState,
2777}
2778
2779impl AlignmentNode {
2780 pub fn new(
2781 box_alignment: Option<Alignment>,
2782 column_alignment: Option<HorizontalAlignment>,
2783 row_alignment: Option<VerticalAlignment>,
2784 row_baseline: bool,
2785 ) -> Self {
2786 Self {
2787 box_alignment,
2788 column_alignment,
2789 row_alignment,
2790 row_baseline,
2791 state: NodeState::new(),
2792 }
2793 }
2794
2795 pub fn box_alignment(&self) -> Option<Alignment> {
2796 self.box_alignment
2797 }
2798
2799 pub fn column_alignment(&self) -> Option<HorizontalAlignment> {
2800 self.column_alignment
2801 }
2802
2803 pub fn row_alignment(&self) -> Option<VerticalAlignment> {
2804 self.row_alignment
2805 }
2806
2807 pub fn row_baseline(&self) -> bool {
2809 self.row_baseline
2810 }
2811}
2812
2813impl DelegatableNode for AlignmentNode {
2814 fn node_state(&self) -> &NodeState {
2815 &self.state
2816 }
2817}
2818
2819impl ModifierNode for AlignmentNode {
2820 fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
2821 context.invalidate(cranpose_foundation::InvalidationKind::Layout);
2822 }
2823}
2824
2825#[derive(Debug, Clone, PartialEq)]
2826pub struct AlignmentElement {
2827 box_alignment: Option<Alignment>,
2828 column_alignment: Option<HorizontalAlignment>,
2829 row_alignment: Option<VerticalAlignment>,
2830 row_baseline: bool,
2831}
2832
2833impl AlignmentElement {
2834 pub fn box_alignment(alignment: Alignment) -> Self {
2835 Self {
2836 box_alignment: Some(alignment),
2837 column_alignment: None,
2838 row_alignment: None,
2839 row_baseline: false,
2840 }
2841 }
2842
2843 pub fn column_alignment(alignment: HorizontalAlignment) -> Self {
2844 Self {
2845 box_alignment: None,
2846 column_alignment: Some(alignment),
2847 row_alignment: None,
2848 row_baseline: false,
2849 }
2850 }
2851
2852 pub fn row_alignment(alignment: VerticalAlignment) -> Self {
2853 Self {
2854 box_alignment: None,
2855 column_alignment: None,
2856 row_alignment: Some(alignment),
2857 row_baseline: false,
2858 }
2859 }
2860
2861 pub fn row_baseline() -> Self {
2863 Self {
2864 box_alignment: None,
2865 column_alignment: None,
2866 row_alignment: None,
2867 row_baseline: true,
2868 }
2869 }
2870}
2871
2872impl Hash for AlignmentElement {
2873 fn hash<H: Hasher>(&self, state: &mut H) {
2874 if let Some(alignment) = self.box_alignment {
2875 state.write_u8(1);
2876 hash_alignment(state, alignment);
2877 } else {
2878 state.write_u8(0);
2879 }
2880 if let Some(alignment) = self.column_alignment {
2881 state.write_u8(1);
2882 hash_horizontal_alignment(state, alignment);
2883 } else {
2884 state.write_u8(0);
2885 }
2886 if let Some(alignment) = self.row_alignment {
2887 state.write_u8(1);
2888 hash_vertical_alignment(state, alignment);
2889 } else {
2890 state.write_u8(0);
2891 }
2892 self.row_baseline.hash(state);
2893 }
2894}
2895
2896impl ModifierNodeElement for AlignmentElement {
2897 type Node = AlignmentNode;
2898
2899 fn create(&self) -> Self::Node {
2900 AlignmentNode::new(
2901 self.box_alignment,
2902 self.column_alignment,
2903 self.row_alignment,
2904 self.row_baseline,
2905 )
2906 }
2907
2908 fn update(&self, node: &mut Self::Node) {
2909 if node.box_alignment != self.box_alignment {
2910 node.box_alignment = self.box_alignment;
2911 }
2912 if node.column_alignment != self.column_alignment {
2913 node.column_alignment = self.column_alignment;
2914 }
2915 if node.row_alignment != self.row_alignment {
2916 node.row_alignment = self.row_alignment;
2917 }
2918 node.row_baseline = self.row_baseline;
2919 }
2920
2921 fn capabilities(&self) -> NodeCapabilities {
2922 NodeCapabilities::LAYOUT
2923 }
2924}
2925
2926#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2927pub enum IntrinsicAxis {
2928 Width,
2929 Height,
2930}
2931
2932#[derive(Debug)]
2933pub struct IntrinsicSizeNode {
2934 axis: IntrinsicAxis,
2935 size: IntrinsicSize,
2936 state: NodeState,
2937}
2938
2939impl IntrinsicSizeNode {
2940 pub fn new(axis: IntrinsicAxis, size: IntrinsicSize) -> Self {
2941 Self {
2942 axis,
2943 size,
2944 state: NodeState::new(),
2945 }
2946 }
2947
2948 fn content_extent(&self, measurable: &dyn Measurable, constraints: Constraints) -> f32 {
2952 let extent = match (self.axis, self.size) {
2953 (IntrinsicAxis::Width, IntrinsicSize::Min) => {
2954 measurable.min_intrinsic_width(constraints.max_height)
2955 }
2956 (IntrinsicAxis::Width, IntrinsicSize::Max) => {
2957 measurable.max_intrinsic_width(constraints.max_height)
2958 }
2959 (IntrinsicAxis::Height, IntrinsicSize::Min) => {
2960 measurable.min_intrinsic_height(constraints.max_width)
2961 }
2962 (IntrinsicAxis::Height, IntrinsicSize::Max) => {
2963 measurable.max_intrinsic_height(constraints.max_width)
2964 }
2965 };
2966 extent.max(0.0)
2967 }
2968
2969 fn intrinsic_width(&self, measurable: &dyn Measurable, height: f32) -> Option<f32> {
2972 (self.axis == IntrinsicAxis::Width).then(|| match self.size {
2973 IntrinsicSize::Min => measurable.min_intrinsic_width(height),
2974 IntrinsicSize::Max => measurable.max_intrinsic_width(height),
2975 })
2976 }
2977
2978 fn intrinsic_height(&self, measurable: &dyn Measurable, width: f32) -> Option<f32> {
2979 (self.axis == IntrinsicAxis::Height).then(|| match self.size {
2980 IntrinsicSize::Min => measurable.min_intrinsic_height(width),
2981 IntrinsicSize::Max => measurable.max_intrinsic_height(width),
2982 })
2983 }
2984}
2985
2986impl DelegatableNode for IntrinsicSizeNode {
2987 fn node_state(&self) -> &NodeState {
2988 &self.state
2989 }
2990}
2991
2992impl_layout_modifier_node!(IntrinsicSizeNode, invalidate = InvalidationKind::Layout);
2993
2994impl LayoutModifierNode for IntrinsicSizeNode {
2999 fn measure(
3000 &self,
3001 _context: &mut dyn ModifierNodeContext,
3002 measurable: &dyn Measurable,
3003 constraints: Constraints,
3004 ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
3005 let extent = self.content_extent(measurable, constraints);
3006 let content = match self.axis {
3007 IntrinsicAxis::Width => {
3008 let width = extent.max(constraints.min_width).min(constraints.max_width);
3009 Constraints {
3010 min_width: width,
3011 max_width: width,
3012 ..constraints
3013 }
3014 }
3015 IntrinsicAxis::Height => {
3016 let height = extent
3017 .max(constraints.min_height)
3018 .min(constraints.max_height);
3019 Constraints {
3020 min_height: height,
3021 max_height: height,
3022 ..constraints
3023 }
3024 }
3025 };
3026 let placeable = measurable.measure(content);
3027 cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size {
3028 width: placeable.width(),
3029 height: placeable.height(),
3030 })
3031 }
3032
3033 fn min_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, _density: f32) -> f32 {
3034 self.intrinsic_width(measurable, height)
3035 .unwrap_or_else(|| measurable.min_intrinsic_width(height))
3036 }
3037
3038 fn max_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, _density: f32) -> f32 {
3039 self.intrinsic_width(measurable, height)
3040 .unwrap_or_else(|| measurable.max_intrinsic_width(height))
3041 }
3042
3043 fn min_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
3044 self.intrinsic_height(measurable, width)
3045 .unwrap_or_else(|| measurable.min_intrinsic_height(width))
3046 }
3047
3048 fn max_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
3049 self.intrinsic_height(measurable, width)
3050 .unwrap_or_else(|| measurable.max_intrinsic_height(width))
3051 }
3052}
3053
3054#[derive(Debug, Clone, PartialEq)]
3055pub struct IntrinsicSizeElement {
3056 axis: IntrinsicAxis,
3057 size: IntrinsicSize,
3058}
3059
3060impl IntrinsicSizeElement {
3061 pub fn width(size: IntrinsicSize) -> Self {
3062 Self {
3063 axis: IntrinsicAxis::Width,
3064 size,
3065 }
3066 }
3067
3068 pub fn height(size: IntrinsicSize) -> Self {
3069 Self {
3070 axis: IntrinsicAxis::Height,
3071 size,
3072 }
3073 }
3074}
3075
3076impl Hash for IntrinsicSizeElement {
3077 fn hash<H: Hasher>(&self, state: &mut H) {
3078 state.write_u8(match self.axis {
3079 IntrinsicAxis::Width => 0,
3080 IntrinsicAxis::Height => 1,
3081 });
3082 state.write_u8(match self.size {
3083 IntrinsicSize::Min => 0,
3084 IntrinsicSize::Max => 1,
3085 });
3086 }
3087}
3088
3089impl ModifierNodeElement for IntrinsicSizeElement {
3090 type Node = IntrinsicSizeNode;
3091
3092 fn create(&self) -> Self::Node {
3093 IntrinsicSizeNode::new(self.axis, self.size)
3094 }
3095
3096 fn update(&self, node: &mut Self::Node) {
3097 if node.axis != self.axis {
3098 node.axis = self.axis;
3099 }
3100 if node.size != self.size {
3101 node.size = self.size;
3102 }
3103 }
3104
3105 fn capabilities(&self) -> NodeCapabilities {
3106 NodeCapabilities::LAYOUT
3107 }
3108}
3109
3110#[cfg(test)]
3111#[path = "tests/modifier_nodes_tests.rs"]
3112mod tests;