use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain,
Styles,
};
use crate::layout::{Dir, Em, HElem, Length, Sides, StackChild, StackElem, VElem};
use crate::model::{ListItemLike, ListLike, ParElem};
use crate::text::TextElem;
use crate::utils::Numeric;
#[elem(scope, title = "Term List", Show)]
pub struct TermsElem {
#[default(true)]
pub tight: bool,
#[default(HElem::new(Em::new(0.6).into()).with_weak(true).pack())]
#[borrowed]
pub separator: Content,
pub indent: Length,
#[default(Em::new(2.0).into())]
pub hanging_indent: Length,
pub spacing: Smart<Length>,
#[variadic]
pub children: Vec<Packed<TermItem>>,
}
#[scope]
impl TermsElem {
#[elem]
type TermItem;
}
impl Show for Packed<TermsElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let separator = self.separator(styles);
let indent = self.indent(styles);
let hanging_indent = self.hanging_indent(styles);
let gutter = self.spacing(styles).unwrap_or_else(|| {
if self.tight(styles) {
ParElem::leading_in(styles).into()
} else {
ParElem::spacing_in(styles).into()
}
});
let pad = hanging_indent + indent;
let unpad = (!hanging_indent.is_zero())
.then(|| HElem::new((-hanging_indent).into()).pack());
let mut children = vec![];
for child in self.children().iter() {
let mut seq = vec![];
seq.extend(unpad.clone());
seq.push(child.term().clone().strong());
seq.push((*separator).clone());
seq.push(child.description().clone());
children.push(StackChild::Block(Content::sequence(seq)));
}
let mut padding = Sides::default();
if TextElem::dir_in(styles) == Dir::LTR {
padding.left = pad.into();
} else {
padding.right = pad.into();
}
let mut realized = StackElem::new(children)
.with_spacing(Some(gutter.into()))
.pack()
.padded(padding);
if self.tight(styles) {
let leading = ParElem::leading_in(styles);
let spacing =
VElem::new(leading.into()).with_weak(true).with_attach(true).pack();
realized = spacing + realized;
}
Ok(realized)
}
}
#[elem(name = "item", title = "Term List Item")]
pub struct TermItem {
#[required]
pub term: Content,
#[required]
pub description: Content,
}
cast! {
TermItem,
array: Array => {
let mut iter = array.into_iter();
let (term, description) = match (iter.next(), iter.next(), iter.next()) {
(Some(a), Some(b), None) => (a.cast()?, b.cast()?),
_ => bail!("array must contain exactly two entries"),
};
Self::new(term, description)
},
v: Content => v.unpack::<Self>().map_err(|_| "expected term item or array")?,
}
impl ListLike for TermsElem {
type Item = TermItem;
fn create(children: Vec<Packed<Self::Item>>, tight: bool) -> Self {
Self::new(children).with_tight(tight)
}
}
impl ListItemLike for TermItem {
fn styled(mut item: Packed<Self>, styles: Styles) -> Packed<Self> {
item.term.style_in_place(styles.clone());
item.description.style_in_place(styles);
item
}
}