summaryrefslogtreecommitdiff
path: root/library/src/layout/terms.rs
diff options
context:
space:
mode:
Diffstat (limited to 'library/src/layout/terms.rs')
-rw-r--r--library/src/layout/terms.rs174
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")?,
}