1use std::hash::{DefaultHasher, Hash, Hasher};
8use std::sync::Arc;
9
10use repose_core::{BoxWithConstraintsScope, Modifier, SubcomposeScope, View, ViewKind};
11
12pub fn subcompose_hash_key<K: Hash>(key: &K) -> u64 {
18 let mut h = DefaultHasher::new();
19 key.hash(&mut h);
20 h.finish()
21}
22
23pub fn SubcomposeLayout<F>(modifier: Modifier, content: F) -> View
41where
42 F: Fn(SubcomposeScope) -> View + 'static,
43{
44 let wrapped: Arc<dyn Fn(&SubcomposeScope) -> Vec<(u64, View)>> =
45 Arc::new(move |scope| vec![(0, content(*scope))]);
46 View {
47 id: 0,
48 kind: ViewKind::SubcomposeLayout { content: wrapped },
49 modifier,
50 children: Vec::new(),
51 scope_key: None,
52 semantics: None,
53 }
54}
55
56pub fn subcompose_layout_with_slots<F>(modifier: Modifier, content: F) -> View
61where
62 F: Fn(SubcomposeScope) -> Vec<(u64, View)> + 'static,
63{
64 let wrapped: Arc<dyn Fn(&SubcomposeScope) -> Vec<(u64, View)>> =
65 Arc::new(move |scope| content(*scope));
66 View {
67 id: 0,
68 kind: ViewKind::SubcomposeLayout { content: wrapped },
69 modifier,
70 children: Vec::new(),
71 scope_key: None,
72 semantics: None,
73 }
74}
75
76pub fn BoxWithConstraints<F>(modifier: Modifier, content: F) -> View
83where
84 F: Fn(BoxWithConstraintsScope) -> View + 'static,
85{
86 SubcomposeLayout(modifier, move |scope| {
87 content(BoxWithConstraintsScope {
88 min_width: scope.min_width,
89 max_width: scope.max_width,
90 min_height: scope.min_height,
91 max_height: scope.max_height,
92 })
93 })
94}
95
96pub fn subcompose_with_key<K, F>(key: K, modifier: Modifier, content: F) -> View
111where
112 K: Hash,
113 F: Fn(SubcomposeScope) -> View + 'static,
114{
115 let hashed = subcompose_hash_key(&key);
116 SubcomposeLayout(modifier.key(hashed), content)
117}
118
119pub fn box_with_constraints_with_key<K, F>(key: K, modifier: Modifier, content: F) -> View
122where
123 K: Hash,
124 F: Fn(BoxWithConstraintsScope) -> View + 'static,
125{
126 subcompose_with_key(key, modifier, move |scope| {
127 content(BoxWithConstraintsScope {
128 min_width: scope.min_width,
129 max_width: scope.max_width,
130 min_height: scope.min_height,
131 max_height: scope.max_height,
132 })
133 })
134}
135
136pub fn subcompose_with_key_slots<K, F>(key: K, modifier: Modifier, content: F) -> View
140where
141 K: Hash,
142 F: Fn(SubcomposeScope) -> Vec<(u64, View)> + 'static,
143{
144 let hashed = subcompose_hash_key(&key);
145 subcompose_layout_with_slots(modifier.key(hashed), content)
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::layout::LayoutEngine;
152 use crate::{Column, Interactions, ViewExt};
153 use std::collections::HashMap;
154 use std::sync::Arc;
155 use std::sync::atomic::{AtomicUsize, Ordering};
156
157 fn text_view(text: &str) -> View {
158 use repose_core::{
159 Color, FontStyle, FontWeight, TextAlign, TextDecoration, TextOverflow, ViewKind,
160 };
161 View {
162 id: 0,
163 kind: ViewKind::Text {
164 text: text.to_string(),
165 color: Color::WHITE,
166 font_size: 14.0,
167 soft_wrap: true,
168 max_lines: None,
169 overflow: TextOverflow::Visible,
170 font_family: None,
171 annotations: None,
172 text_align: TextAlign::Unspecified,
173 font_weight: FontWeight::NORMAL,
174 font_style: FontStyle::Normal,
175 text_decoration: TextDecoration::default(),
176 letter_spacing: 0.0,
177 line_height: 0.0,
178 url: None,
179 },
180 modifier: Modifier::default(),
181 children: vec![],
182 scope_key: None,
183 semantics: None,
184 }
185 }
186
187 fn make_root(view: View) -> View {
188 Column(Modifier::new()).child(view)
189 }
190
191 #[test]
192 fn subcompose_hash_key_is_deterministic_and_distinguishes_values() {
193 assert_eq!(subcompose_hash_key(&"hello"), subcompose_hash_key(&"hello"));
194 assert_ne!(subcompose_hash_key(&"hello"), subcompose_hash_key(&"world"));
195 assert_eq!(
196 subcompose_hash_key(&(1u32, 2u32)),
197 subcompose_hash_key(&(1u32, 2u32))
198 );
199 assert_ne!(
200 subcompose_hash_key(&(1u32, 2u32)),
201 subcompose_hash_key(&(1u32, 3u32))
202 );
203 }
204
205 #[test]
206 fn subcompose_with_key_runs_closure_once_until_key_changes() {
207 let calls = Arc::new(AtomicUsize::new(0));
208 let calls_c = calls.clone();
209
210 let sub = subcompose_with_key(1u64, Modifier::new(), move |_scope| {
213 calls_c.fetch_add(1, Ordering::SeqCst);
214 text_view("k=1")
215 });
216 let root_v1 = make_root(sub);
217
218 let calls2 = calls.clone();
219 let sub = subcompose_with_key(2u64, Modifier::new(), move |_scope| {
220 calls2.fetch_add(1, Ordering::SeqCst);
221 text_view("k=2")
222 });
223 let root_v2 = make_root(sub);
224
225 let mut engine = LayoutEngine::new();
226
227 let _ = engine.layout_frame(
229 &root_v1,
230 (400, 400),
231 &HashMap::new(),
232 &Interactions::default(),
233 None,
234 );
235 assert_eq!(calls.load(Ordering::SeqCst), 1);
236
237 let _ = engine.layout_frame(
240 &root_v1,
241 (400, 400),
242 &HashMap::new(),
243 &Interactions::default(),
244 None,
245 );
246 assert_eq!(calls.load(Ordering::SeqCst), 2);
247
248 let _ = engine.layout_frame(
250 &root_v1,
251 (400, 400),
252 &HashMap::new(),
253 &Interactions::default(),
254 None,
255 );
256 assert_eq!(calls.load(Ordering::SeqCst), 2);
257
258 let _ = engine.layout_frame(
261 &root_v2,
262 (400, 400),
263 &HashMap::new(),
264 &Interactions::default(),
265 None,
266 );
267 assert_eq!(calls.load(Ordering::SeqCst), 3);
268 }
269
270 #[test]
271 fn box_with_constraints_with_key_forwards_scope() {
272 use crate::Box as RBox;
273 let sub = box_with_constraints_with_key(42u64, Modifier::new(), |scope| {
274 assert!(scope.max_width > 0.0);
275 RBox(Modifier::new())
276 });
277 match sub.kind {
279 ViewKind::SubcomposeLayout { .. } => {}
280 _ => panic!("expected SubcomposeLayout"),
281 }
282 }
283
284 #[test]
285 fn subcompose_with_key_slots_runs_closure_once_until_key_changes() {
286 let calls = Arc::new(AtomicUsize::new(0));
287 let calls_c = calls.clone();
288
289 let sub = subcompose_with_key_slots(1u64, Modifier::new(), move |_scope| {
290 calls_c.fetch_add(1, Ordering::SeqCst);
291 vec![(0, text_view("k=1")), (1, text_view("k=1b"))]
292 });
293 let root_v1 = make_root(sub);
294
295 let calls2 = calls.clone();
296 let sub2 = subcompose_with_key_slots(2u64, Modifier::new(), move |_scope| {
297 calls2.fetch_add(1, Ordering::SeqCst);
298 vec![(0, text_view("k=2"))]
299 });
300 let root_v2 = make_root(sub2);
301
302 let mut engine = LayoutEngine::new();
303
304 let _ = engine.layout_frame(
305 &root_v1,
306 (400, 400),
307 &HashMap::new(),
308 &Interactions::default(),
309 None,
310 );
311 assert_eq!(calls.load(Ordering::SeqCst), 1);
312
313 let _ = engine.layout_frame(
315 &root_v1,
316 (400, 400),
317 &HashMap::new(),
318 &Interactions::default(),
319 None,
320 );
321 assert_eq!(calls.load(Ordering::SeqCst), 2);
322
323 let _ = engine.layout_frame(
325 &root_v1,
326 (400, 400),
327 &HashMap::new(),
328 &Interactions::default(),
329 None,
330 );
331 assert_eq!(calls.load(Ordering::SeqCst), 2);
332
333 let _ = engine.layout_frame(
335 &root_v2,
336 (400, 400),
337 &HashMap::new(),
338 &Interactions::default(),
339 None,
340 );
341 assert_eq!(calls.load(Ordering::SeqCst), 3);
342 }
343}