summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-06-09 14:05:57 +0200
committerLaurenz <laurmaedje@gmail.com>2021-06-09 22:44:40 +0200
commitbce553a991f19b2b5bb9efef6b74bd12e15a10c6 (patch)
treefa8ed4b3974927b30e6cffd0d71d4c3e980698de
parent29cfef0a6dfef5820bda339d327638e285aaf4d3 (diff)
Tidy up
-rw-r--r--src/eval/mod.rs3
-rw-r--r--src/eval/ops.rs58
-rw-r--r--src/eval/value.rs32
-rw-r--r--src/geom/gen.rs40
-rw-r--r--src/geom/gridu.rs73
-rw-r--r--src/geom/mod.rs14
-rw-r--r--src/geom/point.rs19
-rw-r--r--src/geom/size.rs19
-rw-r--r--src/geom/spec.rs27
-rw-r--r--src/layout/frame.rs8
-rw-r--r--src/layout/grid.rs398
-rw-r--r--src/layout/stack.rs165
-rw-r--r--src/library/grid.rs85
-rw-r--r--src/parse/mod.rs6
-rw-r--r--src/parse/tokens.rs8
-rw-r--r--src/pretty.rs41
-rw-r--r--src/syntax/expr.rs2
-rw-r--r--src/syntax/token.rs6
-rw-r--r--tests/ref/library/grid-table.pngbin0 -> 10495 bytes
-rw-r--r--tests/ref/library/grid.pngbin17246 -> 4673 bytes
-rw-r--r--tests/typ/library/grid-table.typ29
-rw-r--r--tests/typ/library/grid.typ53
-rw-r--r--tools/support/typst.tmLanguage.json4
23 files changed, 498 insertions, 592 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 80be2e82..4b5feb0d 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -18,7 +18,6 @@ use std::rc::Rc;
use crate::cache::Cache;
use crate::color::Color;
use crate::diag::{Diag, DiagSet, Pass};
-use crate::geom::TrackSizing;
use crate::geom::{Angle, Fractional, Length, Relative};
use crate::loading::{FileHash, Loader};
use crate::parse::parse;
@@ -245,6 +244,7 @@ impl Eval for Expr {
fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
match *self {
Self::None(_) => Value::None,
+ Self::Auto(_) => Value::Auto,
Self::Bool(_, v) => Value::Bool(v),
Self::Int(_, v) => Value::Int(v),
Self::Float(_, v) => Value::Float(v),
@@ -252,7 +252,6 @@ impl Eval for Expr {
Self::Angle(_, v, unit) => Value::Angle(Angle::with_unit(v, unit)),
Self::Percent(_, v) => Value::Relative(Relative::new(v / 100.0)),
Self::Fractional(_, v) => Value::Fractional(Fractional::new(v)),
- Self::Auto(_) => Value::TrackSizing(TrackSizing::Auto),
Self::Color(_, v) => Value::Color(Color::Rgba(v)),
Self::Str(_, ref v) => Value::Str(v.clone()),
Self::Ident(ref v) => match ctx.scopes.get(&v) {
diff --git a/src/eval/ops.rs b/src/eval/ops.rs
index 15c09e03..b6bd5402 100644
--- a/src/eval/ops.rs
+++ b/src/eval/ops.rs
@@ -11,8 +11,8 @@ pub fn pos(value: Value) -> Value {
Length(v) => Length(v),
Angle(v) => Angle(v),
Relative(v) => Relative(v),
- Fractional(v) => Fractional(v),
Linear(v) => Linear(v),
+ Fractional(v) => Fractional(v),
_ => Error,
}
}
@@ -25,8 +25,8 @@ pub fn neg(value: Value) -> Value {
Length(v) => Length(-v),
Angle(v) => Angle(-v),
Relative(v) => Relative(-v),
- Fractional(v) => Fractional(-v),
Linear(v) => Linear(-v),
+ Fractional(v) => Fractional(-v),
_ => Error,
}
}
@@ -34,29 +34,31 @@ pub fn neg(value: Value) -> Value {
/// Compute the sum of two values.
pub fn add(lhs: Value, rhs: Value) -> Value {
match (lhs, rhs) {
- // Math.
(Int(a), Int(b)) => Int(a + b),
(Int(a), Float(b)) => Float(a as f64 + b),
(Float(a), Int(b)) => Float(a + b as f64),
(Float(a), Float(b)) => Float(a + b),
+
(Angle(a), Angle(b)) => Angle(a + b),
+
(Length(a), Length(b)) => Length(a + b),
(Length(a), Relative(b)) => Linear(a + b),
(Length(a), Linear(b)) => Linear(a + b),
+
(Relative(a), Length(b)) => Linear(a + b),
(Relative(a), Relative(b)) => Relative(a + b),
(Relative(a), Linear(b)) => Linear(a + b),
- (Fractional(a), Fractional(b)) => Fractional(a + b),
+
(Linear(a), Length(b)) => Linear(a + b),
(Linear(a), Relative(b)) => Linear(a + b),
(Linear(a), Linear(b)) => Linear(a + b),
- // Collections.
+ (Fractional(a), Fractional(b)) => Fractional(a + b),
+
(Str(a), Str(b)) => Str(a + &b),
(Array(a), Array(b)) => Array(concat(a, b)),
(Dict(a), Dict(b)) => Dict(concat(a, b)),
- // Templates.
(Template(a), Template(b)) => Template(concat(a, b)),
(Template(a), None) => Template(a),
(None, Template(b)) => Template(b),
@@ -80,17 +82,23 @@ pub fn sub(lhs: Value, rhs: Value) -> Value {
(Int(a), Float(b)) => Float(a as f64 - b),
(Float(a), Int(b)) => Float(a - b as f64),
(Float(a), Float(b)) => Float(a - b),
+
(Angle(a), Angle(b)) => Angle(a - b),
+
(Length(a), Length(b)) => Length(a - b),
(Length(a), Relative(b)) => Linear(a - b),
(Length(a), Linear(b)) => Linear(a - b),
+
(Relative(a), Length(b)) => Linear(a - b),
(Relative(a), Relative(b)) => Relative(a - b),
(Relative(a), Linear(b)) => Linear(a - b),
- (Fractional(a), Fractional(b)) => Fractional(a - b),
+
(Linear(a), Length(b)) => Linear(a - b),
(Linear(a), Relative(b)) => Linear(a - b),
(Linear(a), Linear(b)) => Linear(a - b),
+
+ (Fractional(a), Fractional(b)) => Fractional(a - b),
+
_ => Error,
}
}
@@ -102,27 +110,37 @@ pub fn mul(lhs: Value, rhs: Value) -> Value {
(Int(a), Float(b)) => Float(a as f64 * b),
(Float(a), Int(b)) => Float(a * b as f64),
(Float(a), Float(b)) => Float(a * b),
+
(Length(a), Int(b)) => Length(a * b as f64),
(Length(a), Float(b)) => Length(a * b),
(Int(a), Length(b)) => Length(a as f64 * b),
(Float(a), Length(b)) => Length(a * b),
+
(Angle(a), Int(b)) => Angle(a * b as f64),
(Angle(a), Float(b)) => Angle(a * b),
(Int(a), Angle(b)) => Angle(a as f64 * b),
(Float(a), Angle(b)) => Angle(a * b),
+
(Relative(a), Int(b)) => Relative(a * b as f64),
(Relative(a), Float(b)) => Relative(a * b),
- (Fractional(a), Fractional(b)) => Fractional(a * b.get()),
- (Fractional(a), Int(b)) => Fractional(a * b as f64),
- (Fractional(a), Float(b)) => Fractional(a * b),
- (Int(a), Relative(b)) => Relative(a as f64 * b),
- (Int(a), Fractional(b)) => Fractional(a as f64 * b),
(Float(a), Relative(b)) => Relative(a * b),
- (Float(a), Fractional(b)) => Fractional(a * b),
+ (Int(a), Relative(b)) => Relative(a as f64 * b),
+
(Linear(a), Int(b)) => Linear(a * b as f64),
(Linear(a), Float(b)) => Linear(a * b),
(Int(a), Linear(b)) => Linear(a as f64 * b),
(Float(a), Linear(b)) => Linear(a * b),
+
+ (Float(a), Fractional(b)) => Fractional(a * b),
+ (Fractional(a), Int(b)) => Fractional(a * b as f64),
+ (Fractional(a), Float(b)) => Fractional(a * b),
+ (Int(a), Fractional(b)) => Fractional(a as f64 * b),
+
+ (Str(a), Int(b)) => Str(a.repeat(b.max(0) as usize)),
+ (Int(a), Str(b)) => Str(b.repeat(a.max(0) as usize)),
+ (Array(a), Int(b)) => Array(repeat(a, b.max(0) as usize)),
+ (Int(a), Array(b)) => Array(repeat(b, a.max(0) as usize)),
+
_ => Error,
}
}
@@ -134,20 +152,26 @@ pub fn div(lhs: Value, rhs: Value) -> Value {
(Int(a), Float(b)) => Float(a as f64 / b),
(Float(a), Int(b)) => Float(a / b as f64),
(Float(a), Float(b)) => Float(a / b),
+
(Length(a), Int(b)) => Length(a / b as f64),
(Length(a), Float(b)) => Length(a / b),
(Length(a), Length(b)) => Float(a / b),
+
(Angle(a), Int(b)) => Angle(a / b as f64),
(Angle(a), Float(b)) => Angle(a / b),
(Angle(a), Angle(b)) => Float(a / b),
+
(Relative(a), Int(b)) => Relative(a / b as f64),
(Relative(a), Float(b)) => Relative(a / b),
(Relative(a), Relative(b)) => Float(a / b),
- (Fractional(a), Fractional(b)) => Float(a.get() / b.get()),
+
(Fractional(a), Int(b)) => Fractional(a / b as f64),
(Fractional(a), Float(b)) => Fractional(a / b),
+ (Fractional(a), Fractional(b)) => Float(a / b),
+
(Linear(a), Int(b)) => Linear(a / b as f64),
(Linear(a), Float(b)) => Linear(a / b),
+
_ => Error,
}
}
@@ -209,3 +233,9 @@ where
a.extend(b);
a
}
+
+/// Repeat a vector `n` times.
+fn repeat<T: Clone>(vec: Vec<T>, n: usize) -> Vec<T> {
+ let len = n * vec.len();
+ vec.into_iter().cycle().take(len).collect()
+}
diff --git a/src/eval/value.rs b/src/eval/value.rs
index fa7993ed..51e45190 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -8,7 +8,7 @@ use std::rc::Rc;
use super::EvalContext;
use crate::color::{Color, RgbaColor};
use crate::exec::ExecContext;
-use crate::geom::{Angle, Fractional, Length, Linear, Relative, TrackSizing};
+use crate::geom::{Angle, Fractional, Length, Linear, Relative};
use crate::syntax::{Expr, Span, Spanned, Tree};
/// A computational value.
@@ -16,6 +16,8 @@ use crate::syntax::{Expr, Span, Spanned, Tree};
pub enum Value {
/// The value that indicates the absence of a meaningful value.
None,
+ /// A value that indicates some smart default behaviour.
+ Auto,
/// A boolean: `true, false`.
Bool(bool),
/// An integer: `120`.
@@ -28,12 +30,10 @@ pub enum Value {
Angle(Angle),
/// A relative value: `50%`.
Relative(Relative),
- /// A fractional value: `1fr`.
- Fractional(Fractional),
/// A combination of an absolute length and a relative value: `20% + 5cm`.
Linear(Linear),
- /// One of the units that can appear in a grid definition.
- TrackSizing(TrackSizing),
+ /// A fractional value: `1fr`.
+ Fractional(Fractional),
/// A color value: `#f79143ff`.
Color(Color),
/// A string: `"string"`.
@@ -73,15 +73,15 @@ impl Value {
pub fn type_name(&self) -> &'static str {
match self {
Self::None => "none",
+ Self::Auto => "auto",
Self::Bool(_) => bool::TYPE_NAME,
Self::Int(_) => i64::TYPE_NAME,
Self::Float(_) => f64::TYPE_NAME,
Self::Length(_) => Length::TYPE_NAME,
Self::Angle(_) => Angle::TYPE_NAME,
Self::Relative(_) => Relative::TYPE_NAME,
- Self::Fractional(_) => Fractional::TYPE_NAME,
Self::Linear(_) => Linear::TYPE_NAME,
- Self::TrackSizing(_) => TrackSizing::TYPE_NAME,
+ Self::Fractional(_) => Fractional::TYPE_NAME,
Self::Color(_) => Color::TYPE_NAME,
Self::Str(_) => String::TYPE_NAME,
Self::Array(_) => ArrayValue::TYPE_NAME,
@@ -100,14 +100,6 @@ impl Value {
(&Self::Float(a), &Self::Int(b)) => a == b as f64,
(&Self::Length(a), &Self::Linear(b)) => a == b.abs && b.rel.is_zero(),
(&Self::Relative(a), &Self::Linear(b)) => a == b.rel && b.abs.is_zero(),
- (&Self::Length(a), &Self::TrackSizing(b)) => TrackSizing::from(a) == b,
- (&Self::Relative(a), &Self::TrackSizing(b)) => TrackSizing::from(a) == b,
- (&Self::Linear(a), &Self::TrackSizing(b)) => TrackSizing::from(a) == b,
- (&Self::Fractional(a), &Self::TrackSizing(b)) => TrackSizing::from(a) == b,
- (&Self::TrackSizing(a), &Self::Length(b)) => TrackSizing::from(b) == a,
- (&Self::TrackSizing(a), &Self::Relative(b)) => TrackSizing::from(b) == a,
- (&Self::TrackSizing(a), &Self::Linear(b)) => TrackSizing::from(b) == a,
- (&Self::TrackSizing(a), &Self::Fractional(b)) => TrackSizing::from(b) == a,
(&Self::Linear(a), &Self::Length(b)) => a.abs == b && a.rel.is_zero(),
(&Self::Linear(a), &Self::Relative(b)) => a.rel == b && a.abs.is_zero(),
(Self::Array(a), Self::Array(b)) => {
@@ -615,21 +607,13 @@ primitive! {
primitive! { Length: "length", Value::Length }
primitive! { Angle: "angle", Value::Angle }
primitive! { Relative: "relative", Value::Relative }
-primitive! { Fractional: "fractional", Value::Fractional }
primitive! {
Linear: "linear",
Value::Linear,
Value::Length(v) => v.into(),
Value::Relative(v) => v.into(),
}
-primitive! {
- TrackSizing: "GridUnit",
- Value::TrackSizing,
- Value::Length(v) => v.into(),
- Value::Relative(v) => v.into(),
- Value::Linear(v) => v.into(),
- Value::Fractional(v) => v.into(),
-}
+primitive! { Fractional: "fractional", Value::Fractional }
primitive! { Color: "color", Value::Color }
primitive! { String: "string", Value::Str }
primitive! { ArrayValue: "array", Value::Array }
diff --git a/src/geom/gen.rs b/src/geom/gen.rs
index c530ff2b..075b7377 100644
--- a/src/geom/gen.rs
+++ b/src/geom/gen.rs
@@ -22,6 +22,14 @@ impl<T> Gen<T> {
{
Self { cross: value.clone(), main: value }
}
+
+ /// Convert to the specific representation.
+ pub fn to_spec(self, main: SpecAxis) -> Spec<T> {
+ match main {
+ SpecAxis::Horizontal => Spec::new(self.main, self.cross),
+ SpecAxis::Vertical => Spec::new(self.cross, self.main),
+ }
+ }
}
impl Gen<Length> {
@@ -32,6 +40,16 @@ impl Gen<Length> {
cross: Length::zero(),
}
}
+
+ /// Convert to a point.
+ pub fn to_point(self, main: SpecAxis) -> Point {
+ self.to_spec(main).to_point()
+ }
+
+ /// Convert to a size.
+ pub fn to_size(self, main: SpecAxis) -> Size {
+ self.to_spec(main).to_size()
+ }
}
impl<T> Get<GenAxis> for Gen<T> {
@@ -52,17 +70,6 @@ impl<T> Get<GenAxis> for Gen<T> {
}
}
-impl<T> Switch for Gen<T> {
- type Other = Spec<T>;
-
- fn switch(self, main: SpecAxis) -> Self::Other {
- match main {
- SpecAxis::Horizontal => Spec::new(self.main, self.cross),
- SpecAxis::Vertical => Spec::new(self.cross, self.main),
- }
- }
-}
-
impl<T: Debug> Debug for Gen<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Gen({:?}, {:?})", self.main, self.cross)
@@ -88,17 +95,6 @@ impl GenAxis {
}
}
-impl Switch for GenAxis {
- type Other = SpecAxis;
-
- fn switch(self, main: SpecAxis) -> Self::Other {
- match self {
- Self::Main => main,
- Self::Cross => main.other(),
- }
- }
-}
-
impl Display for GenAxis {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
diff --git a/src/geom/gridu.rs b/src/geom/gridu.rs
deleted file mode 100644
index 70fc17e4..00000000
--- a/src/geom/gridu.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-use super::*;
-
-/// An enum with the length that a grid cell may have.
-#[derive(Copy, Clone, PartialEq, Hash)]
-pub enum TrackSizing {
- /// A length stated in absolute values and fractions of the parent's size.
- Linear(Linear),
- /// A length that is the fraction of the remaining free space in the parent.
- Fractional(Fractional),
- /// The cell will fit its contents.
- Auto,
-}
-
-impl TrackSizing {
- pub fn is_zero(&self) -> bool {
- match self {
- Self::Linear(l) => l.is_zero(),
- Self::Fractional(f) => f.is_zero(),
- Self::Auto => false,
- }
- }
-
- pub fn preliminary_length(&self, resolve: Length) -> Length {
- match self {
- Self::Linear(l) => l.resolve(resolve),
- _ => resolve,
- }
- }
-}
-
-impl Display for TrackSizing {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Self::Linear(x) => <Linear as Display>::fmt(x, f),
- Self::Fractional(x) => <Fractional as Display>::fmt(x, f),
- Self::Auto => write!(f, "auto"),
- }
- }
-}
-
-impl Debug for TrackSizing {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- match self {
- Self::Linear(x) => <Linear as Debug>::fmt(x, f),
- Self::Fractional(x) => <Fractional as Debug>::fmt(x, f),
- Self::Auto => write!(f, "auto"),
- }
- }
-}
-
-impl From<Length> for TrackSizing {
- fn from(abs: Length) -> Self {
- Self::Linear(abs.into())
- }
-}
-
-impl From<Relative> for TrackSizing {
- fn from(rel: Relative) -> Self {
- Self::Linear(rel.into())
- }
-}
-
-impl From<Linear> for TrackSizing {
- fn from(lin: Linear) -> Self {
- Self::Linear(lin)
- }
-}
-
-impl From<Fractional> for TrackSizing {
- fn from(fr: Fractional) -> Self {
- Self::Fractional(fr)
- }
-}
diff --git a/src/geom/mod.rs b/src/geom/mod.rs
index fdc3980e..25344f0e 100644
--- a/src/geom/mod.rs
+++ b/src/geom/mod.rs
@@ -7,7 +7,6 @@ mod angle;
mod dir;
mod fr;
mod gen;
-mod gridu;
mod length;
mod linear;
mod path;
@@ -22,7 +21,6 @@ pub use angle::*;
pub use dir::*;
pub use fr::*;
pub use gen::*;
-pub use gridu::*;
pub use length::*;
pub use linear::*;
pub use path::*;
@@ -48,15 +46,3 @@ pub trait Get<Index> {
/// Borrow the component for the specified index mutably.
fn get_mut(&mut self, index: Index) -> &mut Self::Component;
}
-
-/// Switch between the specific and generic representations of a type.
-///
-/// The generic representation deals with main and cross axes while the specific
-/// representation deals with horizontal and vertical axes.
-pub trait Switch {
- /// The type of the other version.
- type Other;
-
- /// The other version of this type based on the current main axis.
- fn switch(self, main: SpecAxis) -> Self::Other;
-}
diff --git a/src/geom/point.rs b/src/geom/point.rs
index babbdfef..8982dcea 100644
--- a/src/geom/point.rs
+++ b/src/geom/point.rs
@@ -26,6 +26,14 @@ impl Point {
pub fn splat(value: Length) -> Self {
Self { x: value, y: value }
}
+
+ /// Convert to the generic representation.
+ pub fn to_gen(self, main: SpecAxis) -> Gen<Length> {
+ match main {
+ SpecAxis::Horizontal => Gen::new(self.y, self.x),
+ SpecAxis::Vertical => Gen::new(self.x, self.y),
+ }
+ }
}
impl Get<SpecAxis> for Point {
@@ -46,17 +54,6 @@ impl Get<SpecAxis> for Point {
}
}
-impl Switch for Point {
- type Other = Gen<Length>;
-
- fn switch(self, main: SpecAxis) -> Self::Other {
- match main {
- SpecAxis::Horizontal => Gen::new(self.y, self.x),
- SpecAxis::Vertical => Gen::new(self.x, self.y),
- }
- }
-}
-
impl Debug for Point {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Point({:?}, {:?})", self.x, self.y)
diff --git a/src/geom/size.rs b/src/geom/size.rs
index e20c24af..b859cb80 100644
--- a/src/geom/size.rs
+++ b/src/geom/size.rs
@@ -49,6 +49,14 @@ impl Size {
pub fn to_point(self) -> Point {
Point::new(self.width, self.height)
}
+
+ /// Convert to the generic representation.
+ pub fn to_gen(self, main: SpecAxis) -> Gen<Length> {
+ match main {
+ SpecAxis::Horizontal => Gen::new(self.height, self.width),
+ SpecAxis::Vertical => Gen::new(self.width, self.height),
+ }
+ }
}
impl Get<SpecAxis> for Size {
@@ -69,17 +77,6 @@ impl Get<SpecAxis> for Size {
}
}
-impl Switch for Size {
- type Other = Gen<Length>;
-
- fn switch(self, main: SpecAxis) -> Self::Other {
- match main {
- SpecAxis::Horizontal => Gen::new(self.height, self.width),
- SpecAxis::Vertical => Gen::new(self.width, self.height),
- }
- }
-}
-
impl Debug for Size {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Size({:?}, {:?})", self.width, self.height)
diff --git a/src/geom/spec.rs b/src/geom/spec.rs
index 713f0a16..e7a2d056 100644
--- a/src/geom/spec.rs
+++ b/src/geom/spec.rs
@@ -25,6 +25,14 @@ impl<T> Spec<T> {
vertical: value,
}
}
+
+ /// Convert to the generic representation.
+ pub fn to_gen(self, main: SpecAxis) -> Gen<T> {
+ match main {
+ SpecAxis::Horizontal => Gen::new(self.vertical, self.horizontal),
+ SpecAxis::Vertical => Gen::new(self.horizontal, self.vertical),
+ }
+ }
}
impl Spec<Length> {
@@ -65,17 +73,6 @@ impl<T> Get<SpecAxis> for Spec<T> {
}
}
-impl<T> Switch for Spec<T> {
- type Other = Gen<T>;
-
- fn switch(self, main: SpecAxis) -> Self::Other {
- match main {
- SpecAxis::Horizontal => Gen::new(self.vertical, self.horizontal),
- SpecAxis::Vertical => Gen::new(self.horizontal, self.vertical),
- }
- }
-}
-
impl<T: Debug> Debug for Spec<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Spec({:?}, {:?})", self.horizontal, self.vertical)
@@ -101,14 +98,6 @@ impl SpecAxis {
}
}
-impl Switch for SpecAxis {
- type Other = GenAxis;
-
- fn switch(self, main: SpecAxis) -> Self::Other {
- if self == main { GenAxis::Main } else { GenAxis::Cross }
- }
-}
-
impl Display for SpecAxis {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
diff --git a/src/layout/frame.rs b/src/layout/frame.rs
index f1dc07e6..6cecc7a3 100644
--- a/src/layout/frame.rs
+++ b/src/layout/frame.rs
@@ -35,14 +35,6 @@ impl Frame {
self.push(pos + subpos, element);
}
}
-
- /// Translate the positions of all elements in the frame by adding the
- /// argument to their position.
- pub fn translate(&mut self, amount: Point) {
- for (pos, _) in &mut self.elements {
- *pos += amount;
- }
- }
}
/// The building block frames are composed of.
diff --git a/src/layout/grid.rs b/src/layout/grid.rs
index 9c4c2e0e..3ba6c16b 100644
--- a/src/layout/grid.rs
+++ b/src/layout/grid.rs
@@ -1,216 +1,128 @@
-use std::usize;
-
use super::*;
-use crate::library::GridUnits;
-/// A node that stacks its children.
+/// A node that arranges its children in a grid.
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct GridNode {
- /// The `main` and `cross` directions of this stack.
- ///
- /// The children are stacked along the `main` direction. The `cross`
- /// direction is required for aligning the children.
- pub dir: Dir,
- /// The nodes to be stacked.
+ /// The column (cross) direction of this stack.
+ pub column_dir: Dir,
+ /// The nodes to be arranged in a grid.
pub children: Vec<AnyNode>,
- pub tracks: Gen<GridUnits>,
- pub gutter: Gen<GridUnits>,
+ /// Defines sizing for rows and columns.
+ pub tracks: Gen<Tracks>,
+ /// Defines sizing of the gutter between rows and columns.
+ pub gutter: Gen<Tracks>,
}
impl Layout for GridNode {
fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> {
- let layout = GridLayouter::new(self, regions).layout(ctx);
- layout
+ GridLayouter::new(self, regions.clone()).layout(ctx)
}
}
-#[derive(Debug)]
-enum GridItem<'a> {
- Node(&'a AnyNode),
- Gutter,
+impl From<GridNode> for AnyNode {
+ fn from(grid: GridNode) -> Self {
+ Self::new(grid)
+ }
}
-#[derive(Debug)]
struct GridLayouter<'a> {
- items: Vec<GridItem<'a>>,
+ cross: SpecAxis,
+ main: SpecAxis,
cols: Vec<TrackSizing>,
rows: Vec<TrackSizing>,
- region: Regions,
- dir: Dir,
+ cells: Vec<Cell<'a>>,
+ regions: Regions,
rrows: Vec<(usize, Option<Length>)>,
rcols: Vec<Length>,
- frames: Vec<Frame>,
+ finished: Vec<Frame>,
+}
+
+enum Cell<'a> {
+ Node(&'a AnyNode),
+ Gutter,
}
impl<'a> GridLayouter<'a> {
- fn new(
- grid: &'a GridNode,
- regions: &Regions,
- ) -> Self {
- let mut items = vec![];
+ fn new(grid: &'a GridNode, regions: Regions) -> Self {
let mut col_sizes = vec![];
let mut row_sizes = vec![];
- let cols = grid.tracks.cross.0.len();
- // Create at least as many rows as specified and a row to fit every item.
- let rows = if cols > 0 {
- let res = grid
- .tracks
- .main
- .0
- .len()
- .max(grid.children.len() / cols + (grid.children.len() % cols).clamp(0, 1));
- res
- } else {
- 0
+ let mut cells = vec![];
+
+ // A grid always needs to have at least one column.
+ let cols = grid.tracks.cross.0.len().max(1);
+
+ // Create at least as many rows as specified and also at least as many
+ // as necessary to place each item.
+ let rows = {
+ let len = grid.children.len();
+ let specified = grid.tracks.main.0.len();
+ let necessary = len / cols + (len % cols).clamp(0, 1);
+ specified.max(necessary)
};
- for (i, col_size) in grid.tracks.cross.0.iter().enumerate() {
- let last = i == cols - 1;
- col_sizes.push(*col_size);
-
- if !last {
- let gutter = grid.gutter.cross.get(i);
- col_sizes.push(gutter);
+ // Collect the track sizing for all columns, including gutter columns.
+ for i in 0 .. cols {
+ col_sizes.push(grid.tracks.cross.get(i));
+ if i < cols - 1 {
+ col_sizes.push(grid.gutter.cross.get(i));
}
}
- for (i, row_size) in (0 .. rows).map(|i| (i, grid.tracks.main.get(i))) {
- let last = i == rows - 1;
- row_sizes.push(row_size);
-
- if !last {
- let gutter = grid.gutter.main.get(i);
- row_sizes.push(gutter);
+ // Collect the track sizing for all rows, including gutter rows.
+ for i in 0 .. rows {
+ row_sizes.push(grid.tracks.main.get(i));
+ if i < rows - 1 {
+ row_sizes.push(grid.gutter.main.get(i));
}
}
+ // Build up the matrix of cells, including gutter cells.
for (i, item) in grid.children.iter().enumerate() {
- if cols == 0 {
- break;
- }
+ cells.push(Cell::Node(item));
let row = i / cols;
let col = i % cols;
- items.push(GridItem::Node(item));
-
- if col != cols - 1 {
- // Push gutter
- items.push(GridItem::Gutter);
- } else if row != rows - 1 {
- // Push gutter row.
+ if col < cols - 1 {
+ // Push gutter after each child.
+ cells.push(Cell::Gutter);
+ } else if row < rows - 1 {
+ // Except for the last child of each row.
+ // There we push a gutter row.
for _ in 0 .. col_sizes.len() {
- items.push(GridItem::Gutter);
+ cells.push(Cell::Gutter);
}
}
}
- // Fill the thing up
- while items.len() < col_sizes.len() * row_sizes.len() {
- items.push(GridItem::Gutter)
+ // Fill the thing up.
+ while cells.len() < col_sizes.len() * row_sizes.len() {
+ cells.push(Cell::Gutter)
}
- GridLayouter {
+ Self {
+ cross: grid.column_dir.axis(),
+ main: grid.column_dir.axis().other(),
cols: col_sizes,
rows: row_sizes,
- region: regions.clone(),
- dir: grid.dir,
- items,
+ cells,
+ regions,
rrows: vec![],
rcols: vec![],
- frames: vec![],
- }
- }
-
- fn get(&self, x: usize, y: usize) -> &GridItem<'_> {
- assert!(x < self.cols.len());
- assert!(y < self.rows.len());
- let row_cmp = y * self.cols.len();
-
- self.items.get(row_cmp + x).unwrap()
- }
-
- fn main(&self) -> SpecAxis {
- self.dir.axis().other()
- }
-
- fn cross(&self) -> SpecAxis {
- self.dir.axis()
- }
-
- fn finish_region(&mut self, ctx: &mut LayoutContext, total_frs: f64) {
- let mut pos = Gen::splat(Length::zero());
- let pos2point = |mut pos: Gen<Length>| {
- if !self.dir.is_positive() {
- pos.cross = -pos.cross;
- }
- pos.switch(self.main()).to_point()
- };
- let mut frame = Frame::new(Size::zero(), Length::zero());
- let mut total_cross = Length::zero();
- let mut total_main = Length::zero();
-
- for (x, &w) in self.rcols.iter().enumerate() {
- let total: Length = self.rrows.iter().filter_map(|(_, x)| *x).sum();
- let available = self.region.current.get(self.main()) - total;
- total_cross += w;
-
- for (y, h) in self.rrows.iter() {
- let element = self.get(x, *y);
- let h = if let Some(len) = h {
- *len
- } else {
- if let TrackSizing::Fractional(f) = self.rows[*y] {
- if total_frs > 0.0 {
- let res = available * (f.get() / total_frs);
- if res.is_finite() {
- res
- } else {
- Length::zero()
- }
- } else {
- Length::zero()
- }
- } else {
- unreachable!()
- }
- };
- if x == 0 {
- total_main += h;
- }
-
- if let GridItem::Node(n) = element {
- let item = n.layout(ctx, &Regions::one(Gen::new(w, h).switch(self.main()).to_size(), Spec::splat(false))).remove(0);
- frame.push_frame(pos2point(pos), item);
- }
-
- pos.main += h;
- }
- pos.main = Length::zero();
- pos.cross += self.dir.factor() as f64 * w;
+ finished: vec![],
}
-
- if !self.dir.is_positive() {
- frame.translate(Gen::new(total_cross, Length::zero()).switch(self.main()).to_point());
- }
-
- frame.size = Gen::new(total_cross, total_main).switch(self.main()).to_size();
- frame.baseline = frame.size.height;
-
- self.frames.push(frame);
-
- self.rrows.clear();
- self.region.next();
}
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Frame> {
// Shrink area by linear sizing.
- let mut available = self.region.current.get(self.cross());
+ let mut available = self.regions.current.get(self.cross);
available -= self
.cols
.iter()
.filter_map(|x| match x {
- TrackSizing::Linear(l) => Some(l.resolve(self.region.base.get(self.cross()))),
+ TrackSizing::Linear(l) => {
+ Some(l.resolve(self.regions.base.get(self.cross)))
+ }
_ => None,
})
.sum();
@@ -232,34 +144,31 @@ impl<'a> GridLayouter<'a> {
let mut col_width = vec![];
- // For each of the auto columns, lay out all elements with `preliminary_length`
- // rows and build max.
+ // For each of the auto columns, lay out all elements with
+ // `preliminary_length` rows and build max.
for x in auto_columns {
let mut max = Length::zero();
- for (y, row_height) in
- self.rows.iter().enumerate().map(|(y, s)| {
- (y, s.preliminary_length(self.region.base.get(self.main())))
- })
- {
- let item = self.get(x, y);
- let size =
- Gen::new(self.region.current.get(self.cross()), row_height).switch(self.main()).to_size();
+
+ for (y, row) in self.rows.iter().enumerate() {
+ let mut size = self.regions.current;
+ if let TrackSizing::Linear(l) = row {
+ *size.get_mut(self.main) =
+ l.resolve(self.regions.base.get(self.main));
+ }
+
let region = Regions::one(size, Spec::splat(false));
- match item {
- GridItem::Node(n) => {
- max = max.max(
- n.layout(ctx, &region).first().unwrap().size.get(self.cross()),
- )
- }
- GridItem::Gutter => {}
+ if let Cell::Node(node) = self.get(x, y) {
+ let frame = node.layout(ctx, &region).remove(0);
+ max = max.max(frame.size.get(self.cross))
}
}
col_width.push((x, max));
}
- // If accumulated auto column size exceeds available size, redistribute space
- // proportionally amongst elements that exceed their size allocation.
+ // If accumulated auto column size exceeds available size, redistribute
+ // space proportionally amongst elements that exceed their size
+ // allocation.
let mut total: Length = col_width.iter().map(|(_, x)| *x).sum();
if total > available {
let alloc = available / col_width.len() as f64;
@@ -288,23 +197,25 @@ impl<'a> GridLayouter<'a> {
}
// Build rcols
- for (x, len) in col_width.into_iter().map(|(x, s)| (x, Some(s))).chain(std::iter::once((self.cols.len(), None))) {
+ for (x, len) in col_width
+ .into_iter()
+ .map(|(x, s)| (x, Some(s)))
+ .chain(std::iter::once((self.cols.len(), None)))
+ {
for i in self.rcols.len() .. x {
let len = match self.cols[i] {
- TrackSizing::Linear(l) => l.resolve(self.region.base.get(self.cross())),
+ TrackSizing::Linear(l) => {
+ l.resolve(self.regions.base.get(self.cross))
+ }
TrackSizing::Fractional(f) => {
if col_frac == 0.0 {
Length::zero()
} else {
let res: Length = (available - total) * (f.get() / col_frac);
- if res.is_finite() {
- res
- } else {
- Length::zero()
- }
+ if res.is_finite() { res } else { Length::zero() }
}
}
- TrackSizing::Auto => unreachable!(),
+ TrackSizing::Auto => unreachable!("x is an auto track"),
};
self.rcols.push(len);
@@ -317,27 +228,23 @@ impl<'a> GridLayouter<'a> {
// Determine non-`fr` row heights
let mut total_frs = 0.0;
- let mut current = self.region.current.get(self.main());
+ let mut current = self.regions.current.get(self.main);
- for y in 0..self.rows.len() {
- let height = &self.rows[y];
- let resolved = match height {
- TrackSizing::Linear(l) => Some(l.resolve(self.region.base.get(self.main()))),
+ for y in 0 .. self.rows.len() {
+ let resolved = match self.rows[y] {
+ TrackSizing::Linear(l) => {
+ Some(l.resolve(self.regions.base.get(self.main)))
+ }
TrackSizing::Auto => {
let mut max = Length::zero();
for (x, len) in self.rcols.iter().enumerate() {
- let node = self.get(x, y);
- if let GridItem::Node(node) = node {
- let frames = node.layout(
- ctx,
- &Regions::one(
- Gen::new(*len, current)
- .switch(self.main())
- .to_size(),
- Spec::splat(false),
- ),
+ if let Cell::Node(node) = self.get(x, y) {
+ let regions = Regions::one(
+ Gen::new(*len, current).to_size(self.main),
+ Spec::splat(false),
);
- max = max.max(frames.first().unwrap().size.get(self.main()));
+ let frame = node.layout(ctx, &regions).remove(0);
+ max = max.max(frame.size.get(self.main));
}
}
Some(max)
@@ -345,13 +252,13 @@ impl<'a> GridLayouter<'a> {
TrackSizing::Fractional(f) => {
total_frs += f.get();
None
- },
+ }
};
if let Some(resolved) = resolved {
- while !current.fits(resolved) && !self.region.in_full_last() {
+ while !current.fits(resolved) && !self.regions.in_full_last() {
self.finish_region(ctx, total_frs);
- current = self.region.current.get(self.main());
+ current = self.regions.current.get(self.main);
total_frs = 0.0;
}
current -= resolved;
@@ -361,12 +268,91 @@ impl<'a> GridLayouter<'a> {
}
self.finish_region(ctx, total_frs);
- self.frames
+ self.finished
+ }
+
+ fn finish_region(&mut self, ctx: &mut LayoutContext, total_frs: f64) {
+ let mut pos = Gen::splat(Length::zero());
+ let mut frame = Frame::new(Size::zero(), Length::zero());
+ let mut total_cross = Length::zero();
+ let mut total_main = Length::zero();
+
+ for (x, &w) in self.rcols.iter().enumerate() {
+ let total: Length = self.rrows.iter().filter_map(|(_, x)| *x).sum();
+ let available = self.regions.current.get(self.main) - total;
+ total_cross += w;
+
+ for (y, h) in self.rrows.iter() {
+ let element = self.get(x, *y);
+ let h = if let Some(len) = h {
+ *len
+ } else if let TrackSizing::Fractional(f) = self.rows[*y] {
+ if total_frs > 0.0 {
+ let res = available * (f.get() / total_frs);
+ if res.is_finite() { res } else { Length::zero() }
+ } else {
+ Length::zero()
+ }
+ } else {
+ unreachable!("non-fractional tracks are already resolved");
+ };
+
+ if x == 0 {
+ total_main += h;
+ }
+
+ if let Cell::Node(n) = element {
+ let regions = Regions::one(
+ Gen::new(w, h).to_size(self.main),
+ Spec::splat(false),
+ );
+ let item = n.layout(ctx, &regions).remove(0);
+ frame.push_frame(pos.to_point(self.main), item);
+ }
+
+ pos.main += h;
+ }
+ pos.main = Length::zero();
+ pos.cross += w;
+ }
+
+ frame.size = Gen::new(total_cross, total_main).to_size(self.main);
+ frame.baseline = frame.size.height;
+
+ self.rrows.clear();
+ self.regions.next();
+ self.finished.push(frame);
+ }
+
+ fn get(&self, x: usize, y: usize) -> &Cell<'a> {
+ assert!(x < self.cols.len());
+ assert!(y < self.rows.len());
+ self.cells.get(y * self.cols.len() + x).unwrap()
}
}
-impl From<GridNode> for AnyNode {
- fn from(grid: GridNode) -> Self {
- Self::new(grid)
+/// A list of track sizing definitions.
+#[derive(Default, Debug, Clone, PartialEq, Hash)]
+pub struct Tracks(pub Vec<TrackSizing>);
+
+impl Tracks {
+ /// Get the sizing for the track at the given `idx`.
+ fn get(&self, idx: usize) -> TrackSizing {
+ self.0
+ .get(idx)
+ .or(self.0.last())
+ .copied()
+ .unwrap_or(TrackSizing::Auto)
}
}
+
+/// Defines how to size a grid cell along an axis.
+#[derive(Debug, Copy, Clone, PartialEq, Hash)]
+pub enum TrackSizing {
+ /// Fit the cell to its contents.
+ Auto,
+ /// A length stated in absolute values and fractions of the parent's size.
+ Linear(Linear),
+ /// A length that is the fraction of the remaining free space in the parent.
+ Fractional(Fractional),
+}
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index 410f53c6..e4c0708d 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -29,24 +29,7 @@ pub enum StackChild {
impl Layout for StackNode {
fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> {
- let mut layouter = StackLayouter::new(self.dirs, self.aspect, regions.clone());
- for child in &self.children {
- match *child {
- StackChild::Spacing(amount) => layouter.push_spacing(amount),
- StackChild::Any(ref node, aligns) => {
- let mut frames = node.layout(ctx, &layouter.regions).into_iter();
- if let Some(frame) = frames.next() {
- layouter.push_frame(frame, aligns);
- }
-
- for frame in frames {
- layouter.finish_region();
- layouter.push_frame(frame, aligns);
- }
- }
- }
- }
- layouter.finish()
+ StackLayouter::new(self, regions.clone()).layout(ctx)
}
}
@@ -56,107 +39,154 @@ impl From<StackNode> for AnyNode {
}
}
-struct StackLayouter {
- dirs: Gen<Dir>,
- aspect: Option<N64>,
+struct StackLayouter<'a> {
+ /// The directions of the stack.
+ stack: &'a StackNode,
+ /// The axis of the main direction.
main: SpecAxis,
+ /// The region to layout into.
regions: Regions,
- finished: Vec<Frame>,
- frames: Vec<(Length, Frame, Gen<Align>)>,
+ /// Offset, alignment and frame for all children that fit into the current
+ /// region. The exact positions are not known yet.
+ frames: Vec<(Length, Gen<Align>, Frame)>,
+ /// The full size of `regions.current` that was available before we started
+ /// subtracting.
full: Size,
- size: Gen<Length>,
+ /// The generic size used by the frames for the current region.
+ used: Gen<Length>,
+ /// The alignment ruler for the current region.
ruler: Align,
+ /// Finished frames for previous regions.
+ finished: Vec<Frame>,
}
-impl StackLayouter {
- fn new(dirs: Gen<Dir>, aspect: Option<N64>, mut regions: Regions) -> Self {
- if let Some(aspect) = aspect {
+impl<'a> StackLayouter<'a> {
+ fn new(stack: &'a StackNode, mut regions: Regions) -> Self {
+ if let Some(aspect) = stack.aspect {
regions.apply_aspect_ratio(aspect);
}
Self {
- dirs,
- aspect,
- main: dirs.main.axis(),
+ stack,
+ main: stack.dirs.main.axis(),
finished: vec![],
frames: vec![],
full: regions.current,
- size: Gen::zero(),
+ used: Gen::zero(),
ruler: Align::Start,
regions,
}
}
+ fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Frame> {
+ for child in &self.stack.children {
+ match *child {
+ StackChild::Spacing(amount) => self.push_spacing(amount),
+ StackChild::Any(ref node, aligns) => {
+ let mut frames = node.layout(ctx, &self.regions).into_iter();
+ if let Some(frame) = frames.next() {
+ self.push_frame(frame, aligns);
+ }
+
+ for frame in frames {
+ self.finish_region();
+ self.push_frame(frame, aligns);
+ }
+ }
+ }
+ }
+
+ self.finish_region();
+ self.finished
+ }
+
fn push_spacing(&mut self, amount: Length) {
+ // Cap the spacing to the remaining available space.
let remaining = self.regions.current.get_mut(self.main);
let capped = amount.min(*remaining);
+
+ // Grow our size and shrink the available space in the region.
+ self.used.main += capped;
*remaining -= capped;
- self.size.main += capped;
}
fn push_frame(&mut self, frame: Frame, aligns: Gen<Align>) {
+ let size = frame.size;
+
+ // Don't allow `Start` after `End` in the same region.
if self.ruler > aligns.main {
self.finish_region();
}
- while !self.regions.current.fits(frame.size) && !self.regions.in_full_last() {
+ // Adjust the ruler.
+ self.ruler = aligns.main;
+
+ // Find a fitting region.
+ while !self.regions.current.fits(size) && !self.regions.in_full_last() {
self.finish_region();
}
- let offset = self.size.main;
- let size = frame.size.switch(self.main);
- self.size.main += size.main;
- self.size.cross.set_max(size.cross);
- self.ruler = aligns.main;
- *self.regions.current.get_mut(self.main) -= size.main;
- self.frames.push((offset, frame, aligns));
+ // Remember the frame with offset and alignment.
+ self.frames.push((self.used.main, aligns, frame));
+
+ // Grow our size and shrink available space in the region.
+ let gen = size.to_gen(self.main);
+ self.used.main += gen.main;
+ self.used.cross.set_max(gen.cross);
+ *self.regions.current.get_mut(self.main) -= gen.main;
}
fn finish_region(&mut self) {
+ let used = self.used.to_size(self.main);
let fixed = self.regions.fixed;
- let used = self.size.switch(self.main).to_size();
- let mut size = Size::new(
+ // Determine the stack's size dependening on whether the region is
+ // fixed.
+ let mut stack_size = Size::new(
if fixed.horizontal { self.full.width } else { used.width },
if fixed.vertical { self.full.height } else { used.height },
);
- if let Some(aspect) = self.aspect {
- let width = size
+ // Make sure the stack's size satisfies the aspect ratio.
+ if let Some(aspect) = self.stack.aspect {
+ let width = stack_size
.width
- .max(aspect.into_inner() * size.height)
+ .max(aspect.into_inner() * stack_size.height)
.min(self.full.width)
.min(aspect.into_inner() * self.full.height);
- size = Size::new(width, width / aspect.into_inner());
+ stack_size = Size::new(width, width / aspect.into_inner());
}
- let mut output = Frame::new(size, size.height);
+ let mut output = Frame::new(stack_size, stack_size.height);
let mut first = true;
- let used = self.size;
- let size = size.switch(self.main);
-
- for (offset, frame, aligns) in std::mem::take(&mut self.frames) {
- let child = frame.size.switch(self.main);
+ // Place all frames.
+ for (offset, aligns, frame) in std::mem::take(&mut self.frames) {
+ let stack_size = stack_size.to_gen(self.main);
+ let child_size = frame.size.to_gen(self.main);
// Align along the cross axis.
- let cross = aligns
- .cross
- .resolve(self.dirs.cross, Length::zero() .. size.cross - child.cross);
+ let cross = aligns.cross.resolve(
+ self.stack.dirs.cross,
+ Length::zero() .. stack_size.cross - child_size.cross,
+ );
// Align along the main axis.
let main = aligns.main.resolve(
- self.dirs.main,
- if self.dirs.main.is_positive() {
- offset .. size.main - used.main + offset
+ self.stack.dirs.main,
+ if self.stack.dirs.main.is_positive() {
+ offset .. stack_size.main - self.used.main + offset
} else {
- let offset_with_self = offset + child.main;
- used.main - offset_with_self .. size.main - offset_with_self
+ let offset_with_self = offset + child_size.main;
+ self.used.main - offset_with_self
+ .. stack_size.main - offset_with_self
},
);
- let pos = Gen::new(cross, main).switch(self.main).to_point();
+ let pos = Gen::new(cross, main).to_point(self.main);
+
+ // The baseline of the stack is that of the first frame.
if first {
output.baseline = pos.y + frame.baseline;
first = false;
@@ -165,18 +195,15 @@ impl StackLayouter {
output.push_frame(pos, frame);
}
- self.size = Gen::zero();
- self.ruler = Align::Start;
+ // Move on to the next region.
self.regions.next();
- if let Some(aspect) = self.aspect {
+ if let Some(aspect) = self.stack.aspect {
self.regions.apply_aspect_ratio(aspect);
}
+ self.full = self.regions.current;
+ self.used = Gen::zero();
+ self.ruler = Align::Start;
self.finished.push(output);
}
-
- fn finish(mut self) -> Vec<Frame> {
- self.finish_region();
- self.finished
- }
}
diff --git a/src/library/grid.rs b/src/library/grid.rs
index 0aa1fc9d..cfe46bf4 100644
--- a/src/library/grid.rs
+++ b/src/library/grid.rs
@@ -1,76 +1,79 @@
-use crate::layout::GridNode;
+use crate::layout::{GridNode, TrackSizing, Tracks};
use super::*;
-/// `stack`: Stack children along an axis.
+/// `grid`: Arrange children into a grid.
///
/// # Positional parameters
/// - Children: variadic, of type `template`.
///
/// # Named parameters
-/// - Column widths: `columns`, of type `Array<GridUnit>`.
-/// - Row widths: `rows`, of type `Array<GridUnit>`.
-/// - Gutter: `gutter-vertical` and `gutter-horizontal` for individual track axis or `gutter` for both, of type `Array<GridUnit>` respectively.
-/// - Stacking direction: `dir`, of type `direction`.
+/// - Column sizing: `columns`, of type `tracks`.
+/// - Row sizing: `rows`, of type `tracks`.
+/// - Column direction: `column-dir`, of type `direction`.
+/// - Gutter: `gutter`, shorthand for equal column and row gutter, of type `tracks`.
+/// - Gutter for rows: `gutter-rows`, of type `tracks`.
+/// - Gutter for columns: `gutter-columns`, of type `tracks`.
///
/// # Return value
/// A template that arranges its children along the specified grid cells.
///
/// # Relevant types and constants
+/// - Type `tracks`
+/// - coerces from `array` of `track-sizing`
+/// - Type `track-sizing`
+/// - `auto`
+// - coerces from `length`
+// - coerces from `relative`
+// - coerces from `linear`
+// - coerces from `fractional`
/// - Type `direction`
/// - `ltr`
/// - `rtl`
/// - `ttb`
/// - `btt`
pub fn grid(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- let cols = args.eat_named::<GridUnits>(ctx, "columns").unwrap_or_default();
- let rows = args.eat_named::<GridUnits>(ctx, "rows").unwrap_or_default();
-
- let gutter = args.eat_named(ctx, "gutter");
- let gutter_vertical = args
- .eat_named::<GridUnits>(ctx, "gutter-col")
- .or_else(|| gutter.clone())
- .unwrap_or_default();
- let gutter_horizontal = args
- .eat_named::<GridUnits>(ctx, "gutter-row")
- .or(gutter)
- .unwrap_or_default();
-
- let dir = args.eat_named(ctx, "dir");
+ let columns = args.eat_named::<Tracks>(ctx, "columns").unwrap_or_default();
+ let rows = args.eat_named::<Tracks>(ctx, "rows").unwrap_or_default();
+ let column_dir = args.eat_named(ctx, "column-dir");
+ let gutter = args.eat_named::<Tracks>(ctx, "gutter").unwrap_or_default();
+ let gutter_columns = args.eat_named::<Tracks>(ctx, "gutter-columns");
+ let gutter_rows = args.eat_named::<Tracks>(ctx, "gutter-rows");
let children = args.eat_all::<TemplateValue>(ctx);
Value::template("grid", move |ctx| {
- let children =
- children.iter().map(|child| ctx.exec_template_stack(child).into()).collect();
+ let children = children
+ .iter()
+ .map(|child| ctx.exec_template_stack(child).into())
+ .collect();
+
ctx.push(GridNode {
- dir: dir.unwrap_or_else(|| ctx.state.lang.dir),
+ column_dir: column_dir.unwrap_or(ctx.state.lang.dir),
children,
- gutter: Gen::new(gutter_vertical.clone(), gutter_horizontal.clone()),
- tracks: Gen::new(cols.clone(), rows.clone()),
+ tracks: Gen::new(columns.clone(), rows.clone()),
+ gutter: Gen::new(
+ gutter_columns.as_ref().unwrap_or(&gutter).clone(),
+ gutter_rows.as_ref().unwrap_or(&gutter).clone(),
+ ),
})
})
}
-/// A list of [`GridUnit`]s.
-#[derive(Default, Debug, Clone, PartialEq, Hash)]
-pub struct GridUnits(pub Vec<TrackSizing>);
-
-impl GridUnits {
- pub fn get(&self, index: usize) -> TrackSizing {
- if self.0.is_empty() {
- TrackSizing::Auto
- } else {
- *self.0.get(index).unwrap_or(self.0.last().unwrap())
- }
- }
-}
-
value! {
- GridUnits: "array of fractional values, lengths, and the `auto` keyword",
- Value::TrackSizing(value) => Self(vec![value]),
+ Tracks: "array of `auto`s, linears, and fractionals",
+ Value::Int(count) => Self(vec![TrackSizing::Auto; count.max(0) as usize]),
Value::Array(values) => Self(values
.into_iter()
.filter_map(|v| v.cast().ok())
.collect()
),
}
+
+value! {
+ TrackSizing: "`auto`, linear, or fractional",
+ Value::Auto => TrackSizing::Auto,
+ Value::Length(v) => TrackSizing::Linear(v.into()),
+ Value::Relative(v) => TrackSizing::Linear(v.into()),
+ Value::Linear(v) => TrackSizing::Linear(v),
+ Value::Fractional(v) => TrackSizing::Fractional(v),
+}
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index 1f14f36f..5ab5b2d8 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -314,11 +314,6 @@ fn primary(p: &mut Parser, atomic: bool) -> Option<Expr> {
Some(Token::For) => expr_for(p),
Some(Token::Import) => expr_import(p),
Some(Token::Include) => expr_include(p),
- Some(Token::Auto) => {
- let start = p.next_start();
- p.assert(Token::Auto);
- Some(Expr::Auto(p.span(start)))
- }
// Nothing.
_ => {
@@ -334,6 +329,7 @@ fn literal(p: &mut Parser) -> Option<Expr> {
let expr = match p.peek()? {
// Basic values.
Token::None => Expr::None(span),
+ Token::Auto => Expr::Auto(span),
Token::Bool(b) => Expr::Bool(span, b),
Token::Int(i) => Expr::Int(span, i),
Token::Float(f) => Expr::Float(span, f),
diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs
index f82d0ae4..f3ca25d9 100644
--- a/src/parse/tokens.rs
+++ b/src/parse/tokens.rs
@@ -350,6 +350,7 @@ impl<'s> Tokens<'s> {
"and" => Token::And,
"or" => Token::Or,
"none" => Token::None,
+ "auto" => Token::Auto,
"true" => Token::Bool(true),
"false" => Token::Bool(false),
id => keyword(id).unwrap_or(Token::Ident(id)),
@@ -474,7 +475,6 @@ fn keyword(id: &str) -> Option<Token<'static>> {
"for" => Token::For,
"in" => Token::In,
"while" => Token::While,
- "auto" => Token::Auto,
"break" => Token::Break,
"continue" => Token::Continue,
"return" => Token::Return,
@@ -759,12 +759,6 @@ mod tests {
("for", For),
("in", In),
("import", Import),
- ("while", While),
- ("break", Break),
- ("continue", Continue),
- ("using", Using),
- ("auto", Auto),
- ("return", Return),
];
for &(s, t) in &list {
diff --git a/src/pretty.rs b/src/pretty.rs
index 82e81ce6..1281e27b 100644
--- a/src/pretty.rs
+++ b/src/pretty.rs
@@ -4,7 +4,7 @@ use std::fmt::{self, Arguments, Write};
use crate::color::{Color, RgbaColor};
use crate::eval::*;
-use crate::geom::{Angle, Fractional, Length, Linear, Relative, TrackSizing};
+use crate::geom::{Angle, Fractional, Length, Linear, Relative};
use crate::syntax::*;
/// Pretty print an item and return the resulting string.
@@ -451,24 +451,24 @@ impl Pretty for Ident {
impl Pretty for Value {
fn pretty(&self, p: &mut Printer) {
match self {
- Value::None => p.push_str("none"),
- Value::Bool(v) => v.pretty(p),
- Value::Int(v) => v.pretty(p),
- Value::Float(v) => v.pretty(p),
- Value::Length(v) => v.pretty(p),
- Value::Angle(v) => v.pretty(p),
- Value::Relative(v) => v.pretty(p),
- Value::Fractional(v) => v.pretty(p),
- Value::Linear(v) => v.pretty(p),
- Value::TrackSizing(v) => v.pretty(p),
- Value::Color(v) => v.pretty(p),
- Value::Str(v) => v.pretty(p),
- Value::Array(v) => v.pretty(p),
- Value::Dict(v) => v.pretty(p),
- Value::Template(v) => v.pretty(p),
- Value::Func(v) => v.pretty(p),
- Value::Any(v) => v.pretty(p),
- Value::Error => p.push_str("<error>"),
+ Self::None => p.push_str("none"),
+ Self::Auto => p.push_str("auto"),
+ Self::Bool(v) => v.pretty(p),
+ Self::Int(v) => v.pretty(p),
+ Self::Float(v) => v.pretty(p),
+ Self::Length(v) => v.pretty(p),
+ Self::Angle(v) => v.pretty(p),
+ Self::Relative(v) => v.pretty(p),
+ Self::Linear(v) => v.pretty(p),
+ Self::Fractional(v) => v.pretty(p),
+ Self::Color(v) => v.pretty(p),
+ Self::Str(v) => v.pretty(p),
+ Self::Array(v) => v.pretty(p),
+ Self::Dict(v) => v.pretty(p),
+ Self::Template(v) => v.pretty(p),
+ Self::Func(v) => v.pretty(p),
+ Self::Any(v) => v.pretty(p),
+ Self::Error => p.push_str("<error>"),
}
}
}
@@ -579,9 +579,8 @@ pretty_display! {
Length,
Angle,
Relative,
- Fractional,
Linear,
- TrackSizing,
+ Fractional,
RgbaColor,
Color,
AnyValue,
diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs
index 17e4a196..24850d65 100644
--- a/src/syntax/expr.rs
+++ b/src/syntax/expr.rs
@@ -9,7 +9,7 @@ use crate::geom::{AngularUnit, LengthUnit};
pub enum Expr {
/// The none literal: `none`.
None(Span),
- /// The `auto` constant.
+ /// The auto literal: `auto`.
Auto(Span),
/// A boolean literal: `true`, `false`.
Bool(Span, bool),
diff --git a/src/syntax/token.rs b/src/syntax/token.rs
index 26c01fbb..2263f806 100644
--- a/src/syntax/token.rs
+++ b/src/syntax/token.rs
@@ -76,6 +76,8 @@ pub enum Token<'s> {
Or,
/// The none literal: `none`.
None,
+ /// The auto literal: `auto`.
+ Auto,
/// The `let` keyword.
Let,
/// The `if` keyword.
@@ -100,8 +102,6 @@ pub enum Token<'s> {
Include,
/// The `using` keyword.
Using,
- /// The `auto` keyword.
- Auto,
/// One or more whitespace characters.
///
/// The contained `usize` denotes the number of newlines that were contained
@@ -238,6 +238,7 @@ impl<'s> Token<'s> {
Self::And => "operator `and`",
Self::Or => "operator `or`",
Self::None => "`none`",
+ Self::Auto => "`auto`",
Self::Let => "keyword `let`",
Self::If => "keyword `if`",
Self::Else => "keyword `else`",
@@ -250,7 +251,6 @@ impl<'s> Token<'s> {
Self::Import => "keyword `import`",
Self::Include => "keyword `include`",
Self::Using => "keyword `using`",
- Self::Auto => "keyword `auto`",
Self::Space(_) => "space",
Self::Text(_) => "text",
Self::UnicodeEscape(_) => "unicode escape sequence",
diff --git a/tests/ref/library/grid-table.png b/tests/ref/library/grid-table.png
new file mode 100644
index 00000000..36837663
--- /dev/null
+++ b/tests/ref/library/grid-table.png
Binary files differ
diff --git a/tests/ref/library/grid.png b/tests/ref/library/grid.png
index 278ead89..f7dda520 100644
--- a/tests/ref/library/grid.png
+++ b/tests/ref/library/grid.png
Binary files differ
diff --git a/tests/typ/library/grid-table.typ b/tests/typ/library/grid-table.typ
new file mode 100644
index 00000000..277e695c
--- /dev/null
+++ b/tests/typ/library/grid-table.typ
@@ -0,0 +1,29 @@
+// Test using the `grid` function to create a finance table.
+
+---
+#page(width: 12cm, height: 2.5cm)
+#grid(
+ columns: 5,
+ gutter-columns: (2fr, 1fr, 1fr),
+ gutter-rows: 4 * (6pt,),
+ [*Quarter*],
+ [Expenditure],
+ [External Revenue],
+ [Financial ROI],
+ [_total_],
+ [*Q1*],
+ [173,472.57 \$],
+ [472,860.91 \$],
+ [51,286.84 \$],
+ [_350,675.18 \$_],
+ [*Q2*],
+ [93,382.12 \$],
+ [439,382.85 \$],
+ [-1,134.30 \$],
+ [_344,866.43 \$_],
+ [*Q3*],
+ [96,421.49 \$],
+ [238,583.54 \$],
+ [3,497.12 \$],
+ [_145,659.17 \$_],
+)
diff --git a/tests/typ/library/grid.typ b/tests/typ/library/grid.typ
index dcc0797f..afb43684 100644
--- a/tests/typ/library/grid.typ
+++ b/tests/typ/library/grid.typ
@@ -1,8 +1,9 @@
// Test the `grid` function.
---
-#page(width: 100pt, height: 140pt)
#let rect(width, color) = rect(width: width, height: 2cm, fill: color)
+
+#page(width: 100pt, height: 140pt)
#grid(
columns: (auto, 1fr, 3fr, 0.25cm, 3%, 2mm + 10%),
rect(0.5cm, #2a631a),
@@ -22,15 +23,6 @@
#grid()
---
-
-#grid(
- columns: (auto, auto, 40%),
- gutter: (1fr,),
- rect(fill: eastern)[dddaa aaa aaa],
- rect(fill: conifer)[ccc],
- rect(width: 100%, fill: #dddddd)[aaa],
-)
-
#grid(
columns: (auto, auto, 40%),
gutter: (1fr,),
@@ -39,41 +31,11 @@
rect(width: 100%, fill: #dddddd)[aaa],
)
-
----
-
-#page(width: 12cm, height: 2.5cm)
-#grid(
- columns: (auto, auto, auto, auto, auto),
- gutter-col: (2fr, 1fr, 1fr),
- gutter-row: (6pt, 6pt, 6pt, 6pt),
- [*Quarter*],
- [Expenditure],
- [External Revenue],
- [Financial ROI],
- [_total_],
- [*Q1*],
- [173,472.57 \$],
- [472,860.91 \$],
- [51,286.84 \$],
- [_350,675.18 \$_],
- [*Q2*],
- [93,382.12 \$],
- [439,382.85 \$],
- [-1,134.30 \$],
- [_344,866.43 \$_],
- [*Q3*],
- [96,421.49 \$],
- [238,583.54 \$],
- [3,497.12 \$],
- [_145,659.17 \$_],
-)
-
---
#page(height: 3cm, width: 2cm)
#grid(
- dir: ttb,
columns: (1fr, 1cm, 1fr, 1fr),
+ column-dir: ttb,
rows: (auto, 1fr),
rect(height: 100%, fill: #222222)[foo],
rect(height: 100%, fill: #547d0a)[bar],
@@ -81,3 +43,12 @@
rect(height: 100%, fill: conifer)[baz],
rect(height: 100%, width: 100%, fill: #547d0a)[bar],
)
+
+---
+#page(height: 3cm, margins: 0pt)
+#align(center)
+#grid(
+ columns: (1fr,),
+ rows: (1fr, auto, 2fr),
+ [], rect(width: 100%)[A bit more to the top], [],
+)
diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json
index 0b0298fe..cdf92649 100644
--- a/tools/support/typst.tmLanguage.json
+++ b/tools/support/typst.tmLanguage.json
@@ -260,6 +260,10 @@
"match": "\\bnone\\b"
},
{
+ "name": "constant.language.auto.typst",
+ "match": "\\bauto\\b"
+ },
+ {
"name": "constant.language.boolean.typst",
"match": "\\b(true|false)\\b"
},