use comemo::Track;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, Array, Content, Context, Depth, Func, NativeElement, Packed, Show,
Smart, StyleChain, Styles, Value,
};
use crate::introspection::Locator;
use crate::layout::{
Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment, Length,
Regions, Sizing, VAlignment, VElem,
};
use crate::model::ParElem;
use crate::text::TextElem;
#[elem(scope, title = "Bullet List", Show)]
pub struct ListElem {
#[default(true)]
pub tight: bool,
#[borrowed]
#[default(ListMarker::Content(vec![
// These are all available in the default font, vertically centered, and
// roughly of the same size (with the last one having slightly lower
// weight because it is not filled).
TextElem::packed('\u{2022}'), // Bullet
TextElem::packed('\u{2023}'), // Triangular Bullet
TextElem::packed('\u{2013}'), // En-dash
]))]
pub marker: ListMarker,
#[resolve]
pub indent: Length,
#[resolve]
#[default(Em::new(0.5).into())]
pub body_indent: Length,
pub spacing: Smart<Length>,
#[variadic]
pub children: Vec<Packed<ListItem>>,
#[internal]
#[fold]
#[ghost]
depth: Depth,
}
#[scope]
impl ListElem {
#[elem]
type ListItem;
}
impl Show for Packed<ListElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let mut realized = BlockElem::multi_layouter(self.clone(), layout_list)
.pack()
.spanned(self.span());
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)
}
}
#[typst_macros::time(span = elem.span())]
fn layout_list(
elem: &Packed<ListElem>,
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
let indent = elem.indent(styles);
let body_indent = elem.body_indent(styles);
let gutter = elem.spacing(styles).unwrap_or_else(|| {
if elem.tight(styles) {
ParElem::leading_in(styles).into()
} else {
ParElem::spacing_in(styles).into()
}
});
let Depth(depth) = ListElem::depth_in(styles);
let marker = elem
.marker(styles)
.resolve(engine, styles, depth)?
.aligned(HAlignment::Start + VAlignment::Top);
let mut cells = vec![];
let mut locator = locator.split();
for item in elem.children() {
cells.push(Cell::new(Content::empty(), locator.next(&())));
cells.push(Cell::new(marker.clone(), locator.next(&marker.span())));
cells.push(Cell::new(Content::empty(), locator.next(&())));
cells.push(Cell::new(
item.body.clone().styled(ListElem::set_depth(Depth(1))),
locator.next(&item.body.span()),
));
}
let grid = CellGrid::new(
Axes::with_x(&[
Sizing::Rel(indent.into()),
Sizing::Auto,
Sizing::Rel(body_indent.into()),
Sizing::Auto,
]),
Axes::with_y(&[gutter.into()]),
cells,
);
let layouter = GridLayouter::new(&grid, regions, styles, elem.span());
layouter.layout(engine)
}
#[elem(name = "item", title = "Bullet List Item")]
pub struct ListItem {
#[required]
pub body: Content,
}
cast! {
ListItem,
v: Content => v.unpack::<Self>().unwrap_or_else(Self::new)
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum ListMarker {
Content(Vec<Content>),
Func(Func),
}
impl ListMarker {
fn resolve(
&self,
engine: &mut Engine,
styles: StyleChain,
depth: usize,
) -> SourceResult<Content> {
Ok(match self {
Self::Content(list) => {
list.get(depth % list.len()).cloned().unwrap_or_default()
}
Self::Func(func) => func
.call(engine, Context::new(None, Some(styles)).track(), [depth])?
.display(),
})
}
}
cast! {
ListMarker,
self => match self {
Self::Content(vec) => if vec.len() == 1 {
vec.into_iter().next().unwrap().into_value()
} else {
vec.into_value()
},
Self::Func(func) => func.into_value(),
},
v: Content => Self::Content(vec![v]),
array: Array => {
if array.is_empty() {
bail!("array must contain at least one marker");
}
Self::Content(array.into_iter().map(Value::display).collect())
},
v: Func => Self::Func(v),
}
pub trait ListLike: NativeElement {
type Item: ListItemLike;
fn create(children: Vec<Packed<Self::Item>>, tight: bool) -> Self;
}
pub trait ListItemLike: NativeElement {
fn styled(item: Packed<Self>, styles: Styles) -> Packed<Self>;
}
impl ListLike for ListElem {
type Item = ListItem;
fn create(children: Vec<Packed<Self::Item>>, tight: bool) -> Self {
Self::new(children).with_tight(tight)
}
}
impl ListItemLike for ListItem {
fn styled(mut item: Packed<Self>, styles: Styles) -> Packed<Self> {
item.body.style_in_place(styles);
item
}
}