Skip to main content

cranpose_ui/
layout_modifier.rs

1//! `Modifier::layout`: Compose's `Modifier.layout { measurable, constraints -> }`.
2
3use std::{
4    hash::{Hash, Hasher},
5    rc::Rc,
6};
7
8use cranpose_core::NodeId;
9use cranpose_foundation::{
10    Constraints, DelegatableNode, InvalidationKind, LayoutModifierNode, Measurable, ModifierNode,
11    ModifierNodeContext, ModifierNodeElement, NodeCapabilities, NodeState,
12};
13use cranpose_ui_layout::{LayoutModifierMeasureResult, Placeable};
14
15use crate::{
16    modifier::{Modifier, inspector_metadata},
17    modifier_nodes::impl_layout_modifier_node,
18};
19
20type MeasureBlock = dyn Fn(&dyn Measurable, Constraints) -> LayoutModifierMeasureResult;
21
22impl Modifier {
23    /// Measures and places the content this modifier wraps by `measure`:
24    /// Compose's `Modifier.layout { measurable, constraints -> }`. `measure`
25    /// measures the content with the constraints it chooses and gives the
26    /// size this modifier takes and where the content lies in it, in
27    /// logical pixels. A state it reads measures it again, and the intrinsic
28    /// sizes of this modifier run it on the content's intrinsic sizes, as in
29    /// Compose.
30    ///
31    /// Example: content 20 taller, with 10 above it:
32    ///
33    /// ```
34    /// use cranpose_ui::{LayoutModifierMeasureResult, Modifier, Size};
35    ///
36    /// let taller = Modifier::empty().layout(|measurable, constraints| {
37    ///     let placeable = measurable.measure(constraints);
38    ///     LayoutModifierMeasureResult::new(
39    ///         Size::new(placeable.width(), placeable.height() + 20.0),
40    ///         0.0,
41    ///         10.0,
42    ///     )
43    /// });
44    /// ```
45    pub fn layout(
46        self,
47        measure: impl Fn(&dyn Measurable, Constraints) -> LayoutModifierMeasureResult + 'static,
48    ) -> Self {
49        let modifier = Self::with_element(LayoutElement {
50            measure: Rc::new(measure),
51        })
52        .with_inspector_metadata(inspector_metadata("layout", |info| {
53            info.add_property("measure", "<closure>");
54        }));
55        self.then(modifier)
56    }
57}
58
59// Compose's `LayoutElement`: equal to another only for the same closure.
60#[derive(Clone)]
61struct LayoutElement {
62    measure: Rc<MeasureBlock>,
63}
64
65impl std::fmt::Debug for LayoutElement {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        f.debug_struct("LayoutElement").finish_non_exhaustive()
68    }
69}
70
71impl PartialEq for LayoutElement {
72    fn eq(&self, other: &Self) -> bool {
73        Rc::ptr_eq(&self.measure, &other.measure)
74    }
75}
76
77impl Eq for LayoutElement {}
78
79impl Hash for LayoutElement {
80    fn hash<H: Hasher>(&self, state: &mut H) {
81        Rc::as_ptr(&self.measure).cast::<()>().hash(state);
82    }
83}
84
85impl ModifierNodeElement for LayoutElement {
86    type Node = LayoutModifierImpl;
87
88    fn create(&self) -> Self::Node {
89        LayoutModifierImpl {
90            measure: Rc::clone(&self.measure),
91            state: NodeState::new(),
92        }
93    }
94
95    fn update(&self, node: &mut Self::Node) {
96        node.measure = Rc::clone(&self.measure);
97    }
98
99    fn capabilities(&self) -> NodeCapabilities {
100        NodeCapabilities::LAYOUT
101    }
102}
103
104// Compose's `LayoutModifierImpl`, the node of a `LayoutElement`.
105struct LayoutModifierImpl {
106    measure: Rc<MeasureBlock>,
107    state: NodeState,
108}
109
110impl DelegatableNode for LayoutModifierImpl {
111    fn node_state(&self) -> &NodeState {
112        &self.state
113    }
114}
115
116impl_layout_modifier_node!(LayoutModifierImpl, invalidate = InvalidationKind::Layout);
117
118impl LayoutModifierNode for LayoutModifierImpl {
119    fn measure(
120        &self,
121        _context: &mut dyn ModifierNodeContext,
122        measurable: &dyn Measurable,
123        constraints: Constraints,
124    ) -> LayoutModifierMeasureResult {
125        (self.measure)(measurable, constraints)
126    }
127
128    fn min_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, _density: f32) -> f32 {
129        self.intrinsic(measurable, Intrinsic::MinWidth, height)
130    }
131
132    fn max_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, _density: f32) -> f32 {
133        self.intrinsic(measurable, Intrinsic::MaxWidth, height)
134    }
135
136    fn min_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
137        self.intrinsic(measurable, Intrinsic::MinHeight, width)
138    }
139
140    fn max_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
141        self.intrinsic(measurable, Intrinsic::MaxHeight, width)
142    }
143}
144
145impl LayoutModifierImpl {
146    // The intrinsic size `asked`, across `extent`: Compose's
147    // `MeasuringIntrinsics`, which measures with the content standing in at
148    // its intrinsic size.
149    fn intrinsic(&self, content: &dyn Measurable, asked: Intrinsic, extent: f32) -> f32 {
150        let stand_in = IntrinsicStandIn { content, asked };
151        if asked.is_width() {
152            (self.measure)(&stand_in, Constraints::loose(f32::INFINITY, extent))
153                .size
154                .width
155        } else {
156            (self.measure)(&stand_in, Constraints::loose(extent, f32::INFINITY))
157                .size
158                .height
159        }
160    }
161}
162
163#[derive(Clone, Copy)]
164enum Intrinsic {
165    MinWidth,
166    MaxWidth,
167    MinHeight,
168    MaxHeight,
169}
170
171impl Intrinsic {
172    fn is_width(self) -> bool {
173        matches!(self, Self::MinWidth | Self::MaxWidth)
174    }
175}
176
177// The content as it measures for an intrinsic size: Compose's
178// `DefaultIntrinsicMeasurable`, whose placeable is the content's intrinsic
179// size along the asked axis and the constraints' bound along the other.
180struct IntrinsicStandIn<'a> {
181    content: &'a dyn Measurable,
182    asked: Intrinsic,
183}
184
185impl Measurable for IntrinsicStandIn<'_> {
186    fn measure(&self, constraints: Constraints) -> Placeable {
187        let (width, height) = match self.asked {
188            Intrinsic::MinWidth => (
189                self.content.min_intrinsic_width(constraints.max_height),
190                constraints.max_height,
191            ),
192            Intrinsic::MaxWidth => (
193                self.content.max_intrinsic_width(constraints.max_height),
194                constraints.max_height,
195            ),
196            Intrinsic::MinHeight => (
197                constraints.max_width,
198                self.content.min_intrinsic_height(constraints.max_width),
199            ),
200            Intrinsic::MaxHeight => (
201                constraints.max_width,
202                self.content.max_intrinsic_height(constraints.max_width),
203            ),
204        };
205        Placeable::value(width, height, NodeId::default())
206    }
207
208    fn min_intrinsic_width(&self, height: f32) -> f32 {
209        self.content.min_intrinsic_width(height)
210    }
211
212    fn max_intrinsic_width(&self, height: f32) -> f32 {
213        self.content.max_intrinsic_width(height)
214    }
215
216    fn min_intrinsic_height(&self, width: f32) -> f32 {
217        self.content.min_intrinsic_height(width)
218    }
219
220    fn max_intrinsic_height(&self, width: f32) -> f32 {
221        self.content.max_intrinsic_height(width)
222    }
223
224    fn parent_data(&self) -> cranpose_ui_layout::ParentData {
225        self.content.parent_data()
226    }
227}