use std::hash::{DefaultHasher, Hash, Hasher};
use std::sync::Arc;
use repose_core::{BoxWithConstraintsScope, Modifier, SubcomposeScope, View, ViewKind};
pub fn subcompose_hash_key<K: Hash>(key: &K) -> u64 {
let mut h = DefaultHasher::new();
key.hash(&mut h);
h.finish()
}
pub fn SubcomposeLayout<F>(modifier: Modifier, content: F) -> View
where
F: Fn(SubcomposeScope) -> View + 'static,
{
let wrapped: Arc<dyn Fn(&SubcomposeScope) -> Vec<(u64, View)>> =
Arc::new(move |scope| vec![(0, content(*scope))]);
View {
id: 0,
kind: ViewKind::SubcomposeLayout { content: wrapped },
modifier,
children: Vec::new(),
semantics: None,
}
}
pub fn subcompose_layout_with_slots<F>(modifier: Modifier, content: F) -> View
where
F: Fn(SubcomposeScope) -> Vec<(u64, View)> + 'static,
{
let wrapped: Arc<dyn Fn(&SubcomposeScope) -> Vec<(u64, View)>> =
Arc::new(move |scope| content(*scope));
View {
id: 0,
kind: ViewKind::SubcomposeLayout { content: wrapped },
modifier,
children: Vec::new(),
semantics: None,
}
}
pub fn BoxWithConstraints<F>(modifier: Modifier, content: F) -> View
where
F: Fn(BoxWithConstraintsScope) -> View + 'static,
{
SubcomposeLayout(modifier, move |scope| {
content(BoxWithConstraintsScope {
min_width: scope.min_width,
max_width: scope.max_width,
min_height: scope.min_height,
max_height: scope.max_height,
})
})
}
pub fn subcompose_with_key<K, F>(key: K, modifier: Modifier, content: F) -> View
where
K: Hash,
F: Fn(SubcomposeScope) -> View + 'static,
{
let hashed = subcompose_hash_key(&key);
SubcomposeLayout(modifier.key(hashed), content)
}
pub fn box_with_constraints_with_key<K, F>(key: K, modifier: Modifier, content: F) -> View
where
K: Hash,
F: Fn(BoxWithConstraintsScope) -> View + 'static,
{
subcompose_with_key(key, modifier, move |scope| {
content(BoxWithConstraintsScope {
min_width: scope.min_width,
max_width: scope.max_width,
min_height: scope.min_height,
max_height: scope.max_height,
})
})
}
pub fn subcompose_with_key_slots<K, F>(key: K, modifier: Modifier, content: F) -> View
where
K: Hash,
F: Fn(SubcomposeScope) -> Vec<(u64, View)> + 'static,
{
let hashed = subcompose_hash_key(&key);
subcompose_layout_with_slots(modifier.key(hashed), content)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::LayoutEngine;
use crate::{Column, Interactions, ViewExt};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
fn text_view(text: &str) -> View {
use repose_core::{Color, TextOverflow, ViewKind};
View {
id: 0,
kind: ViewKind::Text {
text: text.to_string(),
color: Color::WHITE,
font_size: 14.0,
soft_wrap: true,
max_lines: None,
overflow: TextOverflow::Visible,
font_family: None,
annotations: None,
},
modifier: Modifier::default(),
children: vec![],
semantics: None,
}
}
fn make_root(view: View) -> View {
Column(Modifier::new()).child(view)
}
#[test]
fn subcompose_hash_key_is_deterministic_and_distinguishes_values() {
assert_eq!(subcompose_hash_key(&"hello"), subcompose_hash_key(&"hello"));
assert_ne!(subcompose_hash_key(&"hello"), subcompose_hash_key(&"world"));
assert_eq!(
subcompose_hash_key(&(1u32, 2u32)),
subcompose_hash_key(&(1u32, 2u32))
);
assert_ne!(
subcompose_hash_key(&(1u32, 2u32)),
subcompose_hash_key(&(1u32, 3u32))
);
}
#[test]
fn subcompose_with_key_runs_closure_once_until_key_changes() {
let calls = Arc::new(AtomicUsize::new(0));
let calls_c = calls.clone();
let sub = subcompose_with_key(1u64, Modifier::new(), move |_scope| {
calls_c.fetch_add(1, Ordering::SeqCst);
text_view("k=1")
});
let root_v1 = make_root(sub);
let calls2 = calls.clone();
let sub = subcompose_with_key(2u64, Modifier::new(), move |_scope| {
calls2.fetch_add(1, Ordering::SeqCst);
text_view("k=2")
});
let root_v2 = make_root(sub);
let mut engine = LayoutEngine::new();
let _ = engine.layout_frame(
&root_v1,
(400, 400),
&HashMap::new(),
&Interactions::default(),
None,
);
assert_eq!(calls.load(Ordering::SeqCst), 1);
let _ = engine.layout_frame(
&root_v1,
(400, 400),
&HashMap::new(),
&Interactions::default(),
None,
);
assert_eq!(calls.load(Ordering::SeqCst), 1);
let _ = engine.layout_frame(
&root_v2,
(400, 400),
&HashMap::new(),
&Interactions::default(),
None,
);
assert_eq!(calls.load(Ordering::SeqCst), 2);
}
#[test]
fn box_with_constraints_with_key_forwards_scope() {
use crate::Box as RBox;
let sub = box_with_constraints_with_key(42u64, Modifier::new(), |scope| {
assert!(scope.max_width > 0.0);
RBox(Modifier::new())
});
match sub.kind {
ViewKind::SubcomposeLayout { .. } => {}
_ => panic!("expected SubcomposeLayout"),
}
}
#[test]
fn subcompose_with_key_slots_runs_closure_once_until_key_changes() {
let calls = Arc::new(AtomicUsize::new(0));
let calls_c = calls.clone();
let sub = subcompose_with_key_slots(1u64, Modifier::new(), move |_scope| {
calls_c.fetch_add(1, Ordering::SeqCst);
vec![(0, text_view("k=1")), (1, text_view("k=1b"))]
});
let root_v1 = make_root(sub);
let calls2 = calls.clone();
let sub2 = subcompose_with_key_slots(2u64, Modifier::new(), move |_scope| {
calls2.fetch_add(1, Ordering::SeqCst);
vec![(0, text_view("k=2"))]
});
let root_v2 = make_root(sub2);
let mut engine = LayoutEngine::new();
let _ = engine.layout_frame(
&root_v1,
(400, 400),
&HashMap::new(),
&Interactions::default(),
None,
);
assert_eq!(calls.load(Ordering::SeqCst), 1);
let _ = engine.layout_frame(
&root_v1,
(400, 400),
&HashMap::new(),
&Interactions::default(),
None,
);
assert_eq!(calls.load(Ordering::SeqCst), 1);
let _ = engine.layout_frame(
&root_v2,
(400, 400),
&HashMap::new(),
&Interactions::default(),
None,
);
assert_eq!(calls.load(Ordering::SeqCst), 2);
}
}