1use 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 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#[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
104struct 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 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
177struct 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}