use std::any::Any;
use std::rc::Rc;
use layout_core::LayoutError;
use crate::layout_item::LayoutItem;
#[derive(Default)]
pub struct Slots {
items: Vec<(Option<&'static str>, Box<dyn LayoutItem>)>,
}
impl Slots {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, name: Option<&'static str>, item: Box<dyn LayoutItem>) {
self.items.push((name, item));
}
pub fn extend_default(&mut self, items: impl IntoIterator<Item = Box<dyn LayoutItem>>) {
self.items
.extend(items.into_iter().map(|item| (None, item)));
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn take_default(&mut self) -> Vec<Box<dyn LayoutItem>> {
self.take_matching(|n| n.is_none())
}
pub fn take(&mut self, name: &str) -> Vec<Box<dyn LayoutItem>> {
self.take_matching(|n| *n == Some(name))
}
fn take_matching(
&mut self,
pred: impl Fn(&Option<&'static str>) -> bool,
) -> Vec<Box<dyn LayoutItem>> {
let mut taken = Vec::new();
let mut rest = Vec::new();
for (name, item) in std::mem::take(&mut self.items) {
if pred(&name) {
taken.push(item);
} else {
rest.push((name, item));
}
}
self.items = rest;
taken
}
}
#[derive(Clone)]
pub struct Children(Rc<dyn Fn() -> Result<Slots, LayoutError>>);
impl Children {
pub fn new(build: impl Fn() -> Result<Slots, LayoutError> + 'static) -> Self {
Self(Rc::new(build))
}
pub fn build_with<T: Any + 'static>(&self, context: T) -> Result<Slots, LayoutError> {
services_core::Scope::with(|| {
let _ = services_core::provide(context);
(self.0)()
})
}
pub fn build(&self) -> Result<Slots, LayoutError> {
(self.0)()
}
}
impl Default for Children {
fn default() -> Self {
Self::new(|| Ok(Slots::new()))
}
}
impl From<Slots> for Children {
fn from(slots: Slots) -> Self {
let cell = std::cell::RefCell::new(Some(slots));
Self::new(move || Ok(cell.borrow_mut().take().unwrap_or_default()))
}
}
pub fn use_context<T: Any + Clone + 'static>() -> Option<T> {
services_core::try_inject::<T>()
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use std::rc::Rc;
use layout_core::LayoutStyle;
use super::*;
use crate::container::Container;
use crate::context::reset_layout_runtime;
use crate::layout_item::box_item;
#[derive(Clone)]
struct Menu(&'static str);
fn spy(seen: Rc<RefCell<Vec<Option<&'static str>>>>) -> Children {
Children::new(move || {
seen.borrow_mut().push(use_context::<Menu>().map(|m| m.0));
let mut slots = Slots::new();
slots.push(None, box_item(Container::new(LayoutStyle::new(), vec![])?));
Ok(slots)
})
}
#[test]
fn a_child_built_from_the_recipe_can_see_the_parent_making_it() {
reset_layout_runtime();
let seen = Rc::new(RefCell::new(Vec::new()));
let children = spy(seen.clone());
let slots = children.build_with(Menu("edit")).unwrap();
assert_eq!(*seen.borrow(), vec![Some("edit")]);
assert_eq!(slots.len(), 1, "and it is still a child, not just a reader");
}
#[test]
fn a_child_outside_any_parent_sees_nothing() {
reset_layout_runtime();
let seen = Rc::new(RefCell::new(Vec::new()));
spy(seen.clone()).build().unwrap();
assert_eq!(*seen.borrow(), vec![None]);
}
#[test]
fn the_context_does_not_outlive_the_build_that_opened_it() {
reset_layout_runtime();
let seen = Rc::new(RefCell::new(Vec::new()));
let children = spy(seen.clone());
children.build_with(Menu("edit")).unwrap();
assert_eq!(use_context::<Menu>().map(|m| m.0), None);
}
#[test]
fn a_nested_parent_shadows_the_one_it_sits_in() {
reset_layout_runtime();
let inner_seen = Rc::new(RefCell::new(Vec::new()));
let inner = spy(inner_seen.clone());
let outer_seen = Rc::new(RefCell::new(Vec::new()));
let outer = {
let outer_seen = outer_seen.clone();
Children::new(move || {
outer_seen
.borrow_mut()
.push(use_context::<Menu>().map(|m| m.0));
inner.build_with(Menu("submenu"))?;
outer_seen
.borrow_mut()
.push(use_context::<Menu>().map(|m| m.0));
Ok(Slots::new())
})
};
outer.build_with(Menu("edit")).unwrap();
assert_eq!(*inner_seen.borrow(), vec![Some("submenu")]);
assert_eq!(*outer_seen.borrow(), vec![Some("edit"), Some("edit")]);
}
#[test]
fn the_recipe_can_be_run_more_than_once() {
reset_layout_runtime();
let seen = Rc::new(RefCell::new(Vec::new()));
let children = spy(seen.clone());
for _ in 0..3 {
assert_eq!(children.build_with(Menu("edit")).unwrap().len(), 1);
}
assert_eq!(seen.borrow().len(), 3, "a fresh set of rows each time");
}
}