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