summaryrefslogtreecommitdiff
path: root/src/library/columns.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-12-28 00:29:15 +0100
committerGitHub <noreply@github.com>2021-12-28 00:29:15 +0100
commit9624ad635bd8adb0e421c37c63c7310ecc71a708 (patch)
tree0062b38db54c1fd785eedecbd9b77a12800bc340 /src/library/columns.rs
parentf6c7a8292dc1ab0560408fca9d74505e9d7cf13a (diff)
parent7f7e14d95f7240727e0163451190ff63b230e393 (diff)
Merge pull request #52 from typst/basicc-cols
Introduce equal-width columns
Diffstat (limited to 'src/library/columns.rs')
-rw-r--r--src/library/columns.rs145
1 files changed, 145 insertions, 0 deletions
diff --git a/src/library/columns.rs b/src/library/columns.rs
new file mode 100644
index 00000000..25d6da9d
--- /dev/null
+++ b/src/library/columns.rs
@@ -0,0 +1,145 @@
+use super::prelude::*;
+use super::ParNode;
+
+/// `columns`: Stack children along an axis.
+pub fn columns(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
+ let columns = args.expect("column count")?;
+ let gutter = args.named("gutter")?.unwrap_or(Relative::new(0.04).into());
+ let body: Node = args.expect("body")?;
+ Ok(Value::block(ColumnsNode {
+ columns,
+ gutter,
+ child: body.into_block(),
+ }))
+}
+
+/// `colbreak`: Start a new column.
+pub fn colbreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
+ Ok(Value::Node(Node::Colbreak))
+}
+
+/// A node that separates a region into multiple equally sized columns.
+#[derive(Debug, Hash)]
+pub struct ColumnsNode {
+ /// How many columns there should be.
+ pub columns: NonZeroUsize,
+ /// The size of the gutter space between each column.
+ pub gutter: Linear,
+ /// The child to be layouted into the columns. Most likely, this should be a
+ /// flow or stack node.
+ pub child: PackedNode,
+}
+
+impl Layout for ColumnsNode {
+ fn layout(
+ &self,
+ ctx: &mut LayoutContext,
+ regions: &Regions,
+ ) -> Vec<Constrained<Rc<Frame>>> {
+ // Separating the infinite space into infinite columns does not make
+ // much sense. Note that this line assumes that no infinitely wide
+ // region will follow if the first region's width is finite.
+ if regions.current.x.is_infinite() {
+ return self.child.layout(ctx, regions);
+ }
+
+ // Gutter width for each region. (Can be different because the relative
+ // component is calculated seperately for each region.)
+ let mut gutters = vec![];
+
+ // Sizes of all columns resulting from `region.current`,
+ // `region.backlog` and `regions.last`.
+ let mut sizes = vec![];
+
+ let columns = self.columns.get();
+
+ for (current, base) in regions
+ .iter()
+ .take(1 + regions.backlog.len() + if regions.last.is_some() { 1 } else { 0 })
+ {
+ let gutter = self.gutter.resolve(base.x);
+ gutters.push(gutter);
+ let size = Size::new(
+ (current.x - gutter * (columns - 1) as f64) / columns as f64,
+ current.y,
+ );
+ for _ in 0 .. columns {
+ sizes.push(size);
+ }
+ }
+
+ let first = sizes.remove(0);
+ let mut pod = Regions::one(
+ first,
+ Size::new(first.x, regions.base.y),
+ Spec::new(true, regions.expand.y),
+ );
+
+ // Retrieve elements for the last region from the vectors.
+ let last_gutter = if regions.last.is_some() {
+ let gutter = gutters.pop().unwrap();
+ let size = sizes.drain(sizes.len() - columns ..).next().unwrap();
+ pod.last = Some(size);
+ Some(gutter)
+ } else {
+ None
+ };
+
+ pod.backlog = sizes.into_iter();
+
+ let mut frames = self.child.layout(ctx, &pod).into_iter();
+
+ let dir = ctx.styles.get(ParNode::DIR);
+
+ let mut finished = vec![];
+ let total_regions = (frames.len() as f32 / columns as f32).ceil() as usize;
+
+ for ((current, base), gutter) in regions
+ .iter()
+ .take(total_regions)
+ .zip(gutters.into_iter().chain(last_gutter.into_iter().cycle()))
+ {
+ // The height should be the parent height if the node shall expand.
+ // Otherwise its the maximum column height for the frame. In that
+ // case, the frame is first created with zero height and then
+ // resized.
+ let mut height = if regions.expand.y { current.y } else { Length::zero() };
+ let mut frame = Frame::new(Spec::new(regions.current.x, height));
+
+ let mut cursor = Length::zero();
+
+ for _ in 0 .. columns {
+ let child_frame = match frames.next() {
+ Some(frame) => frame.item,
+ None => break,
+ };
+
+ let width = child_frame.size.x;
+
+ if !regions.expand.y {
+ height.set_max(child_frame.size.y);
+ }
+
+ frame.push_frame(
+ Point::with_x(if dir.is_positive() {
+ cursor
+ } else {
+ regions.current.x - cursor - width
+ }),
+ child_frame,
+ );
+
+ cursor += width + gutter;
+ }
+
+ frame.size.y = height;
+
+ let mut cts = Constraints::new(regions.expand);
+ cts.base = base.map(Some);
+ cts.exact = current.map(Some);
+ finished.push(frame.constrain(cts));
+ }
+
+ finished
+ }
+}