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 semantics: None,
52 }
53}
54
55pub fn subcompose_layout_with_slots<F>(modifier: Modifier, content: F) -> View
60where
61 F: Fn(SubcomposeScope) -> Vec<(u64, View)> + 'static,
62{
63 let wrapped: Arc<dyn Fn(&SubcomposeScope) -> Vec<(u64, View)>> =
64 Arc::new(move |scope| content(*scope));
65 View {
66 id: 0,
67 kind: ViewKind::SubcomposeLayout { content: wrapped },
68 modifier,
69 children: Vec::new(),
70 semantics: None,
71 }
72}
73
74pub fn BoxWithConstraints<F>(modifier: Modifier, content: F) -> View
81where
82 F: Fn(BoxWithConstraintsScope) -> View + 'static,
83{
84 SubcomposeLayout(modifier, move |scope| {
85 content(BoxWithConstraintsScope {
86 min_width: scope.min_width,
87 max_width: scope.max_width,
88 min_height: scope.min_height,
89 max_height: scope.max_height,
90 })
91 })
92}
93
94pub fn subcompose_with_key<K, F>(key: K, modifier: Modifier, content: F) -> View
109where
110 K: Hash,
111 F: Fn(SubcomposeScope) -> View + 'static,
112{
113 let hashed = subcompose_hash_key(&key);
114 SubcomposeLayout(modifier.key(hashed), content)
115}
116
117pub fn box_with_constraints_with_key<K, F>(key: K, modifier: Modifier, content: F) -> View
120where
121 K: Hash,
122 F: Fn(BoxWithConstraintsScope) -> View + 'static,
123{
124 subcompose_with_key(key, modifier, move |scope| {
125 content(BoxWithConstraintsScope {
126 min_width: scope.min_width,
127 max_width: scope.max_width,
128 min_height: scope.min_height,
129 max_height: scope.max_height,
130 })
131 })
132}
133
134pub fn subcompose_with_key_slots<K, F>(key: K, modifier: Modifier, content: F) -> View
138where
139 K: Hash,
140 F: Fn(SubcomposeScope) -> Vec<(u64, View)> + 'static,
141{
142 let hashed = subcompose_hash_key(&key);
143 subcompose_layout_with_slots(modifier.key(hashed), content)
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use crate::layout::LayoutEngine;
150 use crate::{Column, Interactions, ViewExt};
151 use std::collections::HashMap;
152 use std::sync::Arc;
153 use std::sync::atomic::{AtomicUsize, Ordering};
154
155 fn text_view(text: &str) -> View {
156 use repose_core::{Color, TextOverflow, ViewKind};
157 View {
158 id: 0,
159 kind: ViewKind::Text {
160 text: text.to_string(),
161 color: Color::WHITE,
162 font_size: 14.0,
163 soft_wrap: true,
164 max_lines: None,
165 overflow: TextOverflow::Visible,
166 font_family: None,
167 annotations: None,
168 },
169 modifier: Modifier::default(),
170 children: vec![],
171 semantics: None,
172 }
173 }
174
175 fn make_root(view: View) -> View {
176 Column(Modifier::new()).child(view)
177 }
178
179 #[test]
180 fn subcompose_hash_key_is_deterministic_and_distinguishes_values() {
181 assert_eq!(subcompose_hash_key(&"hello"), subcompose_hash_key(&"hello"));
182 assert_ne!(subcompose_hash_key(&"hello"), subcompose_hash_key(&"world"));
183 assert_eq!(
184 subcompose_hash_key(&(1u32, 2u32)),
185 subcompose_hash_key(&(1u32, 2u32))
186 );
187 assert_ne!(
188 subcompose_hash_key(&(1u32, 2u32)),
189 subcompose_hash_key(&(1u32, 3u32))
190 );
191 }
192
193 #[test]
194 fn subcompose_with_key_runs_closure_once_until_key_changes() {
195 let calls = Arc::new(AtomicUsize::new(0));
196 let calls_c = calls.clone();
197
198 let sub = subcompose_with_key(1u64, Modifier::new(), move |_scope| {
201 calls_c.fetch_add(1, Ordering::SeqCst);
202 text_view("k=1")
203 });
204 let root_v1 = make_root(sub);
205
206 let calls2 = calls.clone();
207 let sub = subcompose_with_key(2u64, Modifier::new(), move |_scope| {
208 calls2.fetch_add(1, Ordering::SeqCst);
209 text_view("k=2")
210 });
211 let root_v2 = make_root(sub);
212
213 let mut engine = LayoutEngine::new();
214
215 let _ = engine.layout_frame(
217 &root_v1,
218 (400, 400),
219 &HashMap::new(),
220 &Interactions::default(),
221 None,
222 );
223 assert_eq!(calls.load(Ordering::SeqCst), 1);
224
225 let _ = engine.layout_frame(
227 &root_v1,
228 (400, 400),
229 &HashMap::new(),
230 &Interactions::default(),
231 None,
232 );
233 assert_eq!(calls.load(Ordering::SeqCst), 1);
234
235 let _ = engine.layout_frame(
238 &root_v2,
239 (400, 400),
240 &HashMap::new(),
241 &Interactions::default(),
242 None,
243 );
244 assert_eq!(calls.load(Ordering::SeqCst), 2);
245 }
246
247 #[test]
248 fn box_with_constraints_with_key_forwards_scope() {
249 use crate::Box as RBox;
250 let sub = box_with_constraints_with_key(42u64, Modifier::new(), |scope| {
251 assert!(scope.max_width > 0.0);
252 RBox(Modifier::new())
253 });
254 match sub.kind {
256 ViewKind::SubcomposeLayout { .. } => {}
257 _ => panic!("expected SubcomposeLayout"),
258 }
259 }
260
261 #[test]
262 fn subcompose_with_key_slots_runs_closure_once_until_key_changes() {
263 let calls = Arc::new(AtomicUsize::new(0));
264 let calls_c = calls.clone();
265
266 let sub = subcompose_with_key_slots(1u64, Modifier::new(), move |_scope| {
267 calls_c.fetch_add(1, Ordering::SeqCst);
268 vec![(0, text_view("k=1")), (1, text_view("k=1b"))]
269 });
270 let root_v1 = make_root(sub);
271
272 let calls2 = calls.clone();
273 let sub2 = subcompose_with_key_slots(2u64, Modifier::new(), move |_scope| {
274 calls2.fetch_add(1, Ordering::SeqCst);
275 vec![(0, text_view("k=2"))]
276 });
277 let root_v2 = make_root(sub2);
278
279 let mut engine = LayoutEngine::new();
280
281 let _ = engine.layout_frame(
282 &root_v1,
283 (400, 400),
284 &HashMap::new(),
285 &Interactions::default(),
286 None,
287 );
288 assert_eq!(calls.load(Ordering::SeqCst), 1);
289
290 let _ = engine.layout_frame(
292 &root_v1,
293 (400, 400),
294 &HashMap::new(),
295 &Interactions::default(),
296 None,
297 );
298 assert_eq!(calls.load(Ordering::SeqCst), 1);
299
300 let _ = engine.layout_frame(
302 &root_v2,
303 (400, 400),
304 &HashMap::new(),
305 &Interactions::default(),
306 None,
307 );
308 assert_eq!(calls.load(Ordering::SeqCst), 2);
309 }
310}