diff options
Diffstat (limited to 'library/src/layout/terms.rs')
| -rw-r--r-- | library/src/layout/terms.rs | 174 |
1 files changed, 75 insertions, 99 deletions
diff --git a/library/src/layout/terms.rs b/library/src/layout/terms.rs index f2902b80..33b59d4d 100644 --- a/library/src/layout/terms.rs +++ b/library/src/layout/terms.rs @@ -1,8 +1,7 @@ -use crate::layout::{BlockNode, GridNode, HNode, ParNode, Sizing, Spacing}; +use crate::layout::{BlockNode, GridLayouter, HNode, ParNode, Sizing, Spacing}; use crate::prelude::*; use crate::text::{SpaceNode, TextNode}; -/// # Term List /// A list of terms and their descriptions. /// /// Displays a sequence of terms and their descriptions vertically. When the @@ -20,55 +19,49 @@ use crate::text::{SpaceNode, TextNode}; /// between two adjacent letters. /// ``` /// -/// ## Parameters -/// - items: `Content` (positional, variadic) -/// The term list's children. -/// -/// When using the term list syntax, adjacent items are automatically -/// collected into term lists, even through constructs like for loops. -/// -/// ```example -/// #for year, product in ( -/// "1978": "TeX", -/// "1984": "LaTeX", -/// "2019": "Typst", -/// ) [/ #product: Born in #year.] -/// ``` -/// -/// - tight: `bool` (named) -/// If this is `{false}`, the items are spaced apart with [term list -/// spacing]($func/terms.spacing). If it is `{true}`, they use normal -/// [leading]($func/par.leading) instead. This makes the term list more -/// compact, which can look better if the items are short. -/// -/// ```example -/// / Fact: If a term list has a lot -/// of text, and maybe other inline -/// content, it should not be tight -/// anymore. -/// -/// / Tip: To make it wide, simply -/// insert a blank line between the -/// items. -/// ``` -/// -/// ## Category -/// layout -#[func] -#[capable(Layout)] -#[derive(Debug, Hash)] +/// Display: Term List +/// Category: layout +#[node(Layout)] pub struct TermsNode { - /// If true, the items are separated by leading instead of list spacing. + /// The term list's children. + /// + /// When using the term list syntax, adjacent items are automatically + /// collected into term lists, even through constructs like for loops. + /// + /// ```example + /// #for year, product in ( + /// "1978": "TeX", + /// "1984": "LaTeX", + /// "2019": "Typst", + /// ) [/ #product: Born in #year.] + /// ``` + #[variadic] + pub items: Vec<TermItem>, + + /// If this is `{false}`, the items are spaced apart with [term list + /// spacing]($func/terms.spacing). If it is `{true}`, they use normal + /// [leading]($func/par.leading) instead. This makes the term list more + /// compact, which can look better if the items are short. + /// + /// ```example + /// / Fact: If a term list has a lot + /// of text, and maybe other inline + /// content, it should not be tight + /// anymore. + /// + /// / Tip: To make it wide, simply + /// insert a blank line between the + /// items. + /// ``` + #[named] + #[default(true)] pub tight: bool, - /// The individual bulleted or numbered items. - pub items: StyleVec<TermItem>, -} -#[node] -impl TermsNode { /// The indentation of each item's term. - #[property(resolve)] - pub const INDENT: Length = Length::zero(); + #[settable] + #[resolve] + #[default] + pub indent: Length, /// The hanging indent of the description. /// @@ -77,31 +70,17 @@ impl TermsNode { /// / Term: This term list does not /// make use of hanging indents. /// ``` - #[property(resolve)] - pub const HANGING_INDENT: Length = Em::new(1.0).into(); + #[settable] + #[resolve] + #[default(Em::new(1.0).into())] + pub hanging_indent: Length, /// The spacing between the items of a wide (non-tight) term list. /// /// If set to `{auto}`, uses the spacing [below blocks]($func/block.below). - pub const SPACING: Smart<Spacing> = Smart::Auto; - - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self { - tight: args.named("tight")?.unwrap_or(true), - items: args.all()?.into_iter().collect(), - } - .pack()) - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "tight" => Some(Value::Bool(self.tight)), - "items" => { - Some(Value::Array(self.items.items().map(|item| item.encode()).collect())) - } - _ => None, - } - } + #[settable] + #[default] + pub spacing: Smart<Spacing>, } impl Layout for TermsNode { @@ -113,66 +92,63 @@ impl Layout for TermsNode { ) -> SourceResult<Fragment> { let indent = styles.get(Self::INDENT); let body_indent = styles.get(Self::HANGING_INDENT); - let gutter = if self.tight { + let gutter = if self.tight() { styles.get(ParNode::LEADING).into() } else { styles .get(Self::SPACING) - .unwrap_or_else(|| styles.get(BlockNode::BELOW).amount) + .unwrap_or_else(|| styles.get(BlockNode::BELOW).amount()) }; let mut cells = vec![]; - for (item, map) in self.items.iter() { + for item in self.items() { let body = Content::sequence(vec![ - HNode { amount: (-body_indent).into(), weak: false }.pack(), - (item.term.clone() + TextNode::packed(':')).strong(), - SpaceNode.pack(), - item.description.clone(), + HNode::new((-body_indent).into()).pack(), + (item.term() + TextNode::packed(':')).strong(), + SpaceNode::new().pack(), + item.description(), ]); cells.push(Content::empty()); - cells.push(body.styled_with_map(map.clone())); + cells.push(body); } - GridNode { - tracks: Axes::with_x(vec![ - Sizing::Rel((indent + body_indent).into()), - Sizing::Auto, - ]), - gutter: Axes::with_y(vec![gutter.into()]), - cells, - } - .layout(vt, styles, regions) + let layouter = GridLayouter::new( + vt, + Axes::with_x(&[Sizing::Rel((indent + body_indent).into()), Sizing::Auto]), + Axes::with_y(&[gutter.into()]), + &cells, + regions, + styles, + ); + + Ok(layouter.layout()?.fragment) } } /// A term list item. -#[derive(Debug, Clone, Hash)] +#[node] pub struct TermItem { /// The term described by the list item. + #[positional] + #[required] pub term: Content, + /// The description of the term. + #[positional] + #[required] pub description: Content, } -impl TermItem { - /// Encode the item into a value. - fn encode(&self) -> Value { - Value::Array(array![ - Value::Content(self.term.clone()), - Value::Content(self.description.clone()), - ]) - } -} - -castable! { +cast_from_value! { 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()?), - _ => Err("term array must contain exactly two entries")?, + _ => Err("array must contain exactly two entries")?, }; - Self { term, description } + Self::new(term, description) }, + v: Content => v.to::<Self>().cloned().ok_or("expected term item or array")?, } |
