summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-11-26 23:51:18 +0100
committerLaurenz <laurmaedje@gmail.com>2021-11-26 23:51:18 +0100
commit50bd8634711507ead8491d8d0c2abad0481e6a83 (patch)
treeeb4a9cfc659334be5444f1e7f9d06e35439d455c /src
parent3a15922d2ffc041c3523edb479f008a9034fd400 (diff)
More independent placed node
Diffstat (limited to 'src')
-rw-r--r--src/eval/template.rs21
-rw-r--r--src/frame.rs10
-rw-r--r--src/geom/align.rs6
-rw-r--r--src/geom/spec.rs16
-rw-r--r--src/layout/mod.rs5
-rw-r--r--src/library/align.rs16
-rw-r--r--src/library/flow.rs24
-rw-r--r--src/library/image.rs12
-rw-r--r--src/library/mod.rs12
-rw-r--r--src/library/pad.rs4
-rw-r--r--src/library/placed.rs46
-rw-r--r--src/library/sized.rs12
-rw-r--r--src/library/transform.rs5
13 files changed, 126 insertions, 63 deletions
diff --git a/src/eval/template.rs b/src/eval/template.rs
index e9cbbfcf..d447161a 100644
--- a/src/eval/template.rs
+++ b/src/eval/template.rs
@@ -337,13 +337,24 @@ impl Builder {
/// Push a block node into the active flow, finishing the active paragraph.
fn block(&mut self, node: PackedNode) {
+ let mut is_placed = false;
+ if let Some(placed) = node.downcast::<PlacedNode>() {
+ is_placed = true;
+
+ // This prevents paragraph spacing after the placed node if it
+ // is completely out-of-flow.
+ if placed.out_of_flow() {
+ self.flow.last = Last::None;
+ }
+ }
+
self.parbreak();
- let in_flow = node.downcast::<PlacedNode>().is_none();
self.flow.push(FlowChild::Node(node));
- if in_flow {
- self.parbreak();
- } else {
- // This prevents duplicate paragraph spacing around placed nodes.
+ self.parbreak();
+
+ // This prevents paragraph spacing between the placed node and
+ // the paragraph below it.
+ if is_placed {
self.flow.last = Last::None;
}
}
diff --git a/src/frame.rs b/src/frame.rs
index 4eea0578..e02fdf46 100644
--- a/src/frame.rs
+++ b/src/frame.rs
@@ -57,13 +57,13 @@ impl Frame {
/// Resize the frame to a new size, distributing new space according to the
/// given alignments.
- pub fn resize(&mut self, new: Size, aligns: Spec<Align>) {
- if self.size != new {
+ pub fn resize(&mut self, target: Size, aligns: Spec<Align>) {
+ if self.size != target {
let offset = Point::new(
- aligns.x.resolve(new.x - self.size.x),
- aligns.y.resolve(new.y - self.size.y),
+ aligns.x.resolve(target.x - self.size.x),
+ aligns.y.resolve(target.y - self.size.y),
);
- self.size = new;
+ self.size = target;
self.baseline += offset.y;
self.translate(offset);
}
diff --git a/src/geom/align.rs b/src/geom/align.rs
index f068b821..be2eac96 100644
--- a/src/geom/align.rs
+++ b/src/geom/align.rs
@@ -18,6 +18,12 @@ pub enum Align {
}
impl Align {
+ /// Top-left alignment.
+ pub const LEFT_TOP: Spec<Self> = Spec { x: Align::Left, y: Align::Top };
+
+ /// Center-horizon alignment.
+ pub const CENTER_HORIZON: Spec<Self> = Spec { x: Align::Center, y: Align::Horizon };
+
/// The axis this alignment belongs to.
pub const fn axis(self) -> SpecAxis {
match self {
diff --git a/src/geom/spec.rs b/src/geom/spec.rs
index 640d6231..16d63959 100644
--- a/src/geom/spec.rs
+++ b/src/geom/spec.rs
@@ -281,6 +281,14 @@ impl BitOr for Spec<bool> {
}
}
+impl BitOr<bool> for Spec<bool> {
+ type Output = Self;
+
+ fn bitor(self, rhs: bool) -> Self::Output {
+ Self { x: self.x | rhs, y: self.y | rhs }
+ }
+}
+
impl BitAnd for Spec<bool> {
type Output = Self;
@@ -289,6 +297,14 @@ impl BitAnd for Spec<bool> {
}
}
+impl BitAnd<bool> for Spec<bool> {
+ type Output = Self;
+
+ fn bitand(self, rhs: bool) -> Self::Output {
+ Self { x: self.x & rhs, y: self.y & rhs }
+ }
+}
+
impl BitOrAssign for Spec<bool> {
fn bitor_assign(&mut self, rhs: Self) {
self.x |= rhs.x;
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 405e4e83..9c57152a 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -136,10 +136,7 @@ impl PackedNode {
/// Transform this node's contents without affecting layout.
pub fn moved(self, offset: Point) -> Self {
- self.transformed(
- Transform::translation(offset.x, offset.y),
- Spec::new(Align::Left, Align::Top),
- )
+ self.transformed(Transform::translation(offset.x, offset.y), Align::LEFT_TOP)
}
/// Transform this node's contents without affecting layout.
diff --git a/src/library/align.rs b/src/library/align.rs
index 7ad5a2d4..a2881cce 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -2,6 +2,18 @@ use super::prelude::*;
/// `align`: Configure the alignment along the layouting axes.
pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
+ castable! {
+ Spec<Option<Align>>,
+ Expected: "1d or 2d alignment",
+ @align: Align => {
+ let mut aligns = Spec::default();
+ aligns.set(align.axis(), Some(*align));
+ aligns
+ },
+ @aligns: Spec<Align> => aligns.map(Some),
+
+ }
+
let aligns = args.expect::<Spec<_>>("alignment")?;
let body = args.expect::<Template>("body")?;
Ok(Value::Template(Template::from_block(move |style| {
@@ -36,8 +48,8 @@ impl Layout for AlignNode {
// Layout the child.
let mut frames = self.child.layout(ctx, &pod);
- for (Constrained { item: frame, cts }, (current, base)) in
- frames.iter_mut().zip(regions.iter())
+ for ((current, base), Constrained { item: frame, cts }) in
+ regions.iter().zip(&mut frames)
{
// Align in the target size. The target size depends on whether we
// should expand.
diff --git a/src/library/flow.rs b/src/library/flow.rs
index e105c596..587b5a80 100644
--- a/src/library/flow.rs
+++ b/src/library/flow.rs
@@ -101,10 +101,10 @@ enum FlowItem {
Absolute(Length),
/// Fractional spacing between other items.
Fractional(Fractional),
- /// A frame to be placed directly at the origin.
- Placed(Rc<Frame>),
/// A frame for a layouted child node and how to align it.
Frame(Rc<Frame>, Spec<Align>),
+ /// An absolutely placed frame.
+ Placed(Rc<Frame>),
}
impl<'a> FlowLayouter<'a> {
@@ -166,17 +166,11 @@ impl<'a> FlowLayouter<'a> {
/// Layout a node.
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) {
- // Placed nodes with vertical alignment are handled separately
- // because their position shouldn't depend on other flow elements.
if let Some(placed) = node.downcast::<PlacedNode>() {
- if let Some(aligned) = placed.child.downcast::<AlignNode>() {
- if aligned.aligns.y.is_some() {
- let base = self.regions.base;
- let pod = Regions::one(base, base, Spec::splat(true));
- let frame = placed.layout(ctx, &pod).remove(0);
- self.items.push(FlowItem::Placed(frame.item));
- return;
- }
+ let frame = node.layout(ctx, &self.regions).remove(0);
+ if placed.out_of_flow() {
+ self.items.push(FlowItem::Placed(frame.item));
+ return;
}
}
@@ -233,9 +227,6 @@ impl<'a> FlowLayouter<'a> {
FlowItem::Fractional(v) => {
before += v.resolve(self.fr, remaining);
}
- FlowItem::Placed(frame) => {
- output.push_frame(Point::zero(), frame);
- }
FlowItem::Frame(frame, aligns) => {
ruler = ruler.max(aligns.y);
@@ -253,6 +244,9 @@ impl<'a> FlowLayouter<'a> {
output.push_frame(pos, frame);
}
+ FlowItem::Placed(frame) => {
+ output.push_frame(Point::with_y(before), frame);
+ }
}
}
diff --git a/src/library/image.rs b/src/library/image.rs
index 92580f6e..a20f3856 100644
--- a/src/library/image.rs
+++ b/src/library/image.rs
@@ -51,7 +51,7 @@ impl Layout for ImageNode {
let wide = pixel_ratio > current_ratio;
// The space into which the image will be placed according to its fit.
- let canvas = if expand.x && expand.y {
+ let target = if expand.x && expand.y {
current
} else if expand.x || (wide && current.x.is_finite()) {
Size::new(current.x, current.y.min(current.x.safe_div(pixel_ratio)))
@@ -65,20 +65,20 @@ impl Layout for ImageNode {
let size = match self.fit {
ImageFit::Contain | ImageFit::Cover => {
if wide == (self.fit == ImageFit::Contain) {
- Size::new(canvas.x, canvas.x / pixel_ratio)
+ Size::new(target.x, target.x / pixel_ratio)
} else {
- Size::new(canvas.y * pixel_ratio, canvas.y)
+ Size::new(target.y * pixel_ratio, target.y)
}
}
- ImageFit::Stretch => canvas,
+ ImageFit::Stretch => target,
};
// First, place the image in a frame of exactly its size and then resize
- // the frame to the canvas size, center aligning the image in the
+ // the frame to the target size, center aligning the image in the
// process.
let mut frame = Frame::new(size);
frame.push(Point::zero(), Element::Image(self.id, size));
- frame.resize(canvas, Spec::new(Align::Center, Align::Horizon));
+ frame.resize(target, Align::CENTER_HORIZON);
// Create a clipping group if the fit mode is "cover".
if self.fit == ImageFit::Cover {
diff --git a/src/library/mod.rs b/src/library/mod.rs
index c953a76e..d60a13ea 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -153,15 +153,3 @@ castable! {
Expected: "color",
Value::Color(color) => Paint::Solid(color),
}
-
-castable! {
- Spec<Option<Align>>,
- Expected: "1d or 2d alignment",
- @align: Align => {
- let mut aligns = Spec::default();
- aligns.set(align.axis(), Some(*align));
- aligns
- },
- @aligns: Spec<Align> => aligns.map(Some),
-
-}
diff --git a/src/library/pad.rs b/src/library/pad.rs
index ce7f4150..4bd7fd64 100644
--- a/src/library/pad.rs
+++ b/src/library/pad.rs
@@ -39,8 +39,8 @@ impl Layout for PadNode {
let pod = regions.map(|size| shrink(size, self.padding));
let mut frames = self.child.layout(ctx, &pod);
- for (Constrained { item: frame, cts }, (current, base)) in
- frames.iter_mut().zip(regions.iter())
+ for ((current, base), Constrained { item: frame, cts }) in
+ regions.iter().zip(&mut frames)
{
// Apply the padding inversely such that the grown size padded
// yields the frame's size.
diff --git a/src/library/placed.rs b/src/library/placed.rs
index 4f1c5b85..1ae51530 100644
--- a/src/library/placed.rs
+++ b/src/library/placed.rs
@@ -1,4 +1,5 @@
use super::prelude::*;
+use super::AlignNode;
/// `place`: Place content at an absolute position.
pub fn place(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
@@ -13,23 +14,60 @@ pub fn place(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
})))
}
-/// A node that places its child out-of-flow.
+/// A node that places its child absolutely.
#[derive(Debug, Hash)]
pub struct PlacedNode {
/// The node to be placed.
pub child: PackedNode,
}
+impl PlacedNode {
+ /// Whether this node wants to be placed relative to its its parent's base
+ /// origin. instead of relative to the parent's current flow/cursor
+ /// position.
+ pub fn out_of_flow(&self) -> bool {
+ self.child
+ .downcast::<AlignNode>()
+ .map_or(false, |node| node.aligns.y.is_some())
+ }
+}
+
impl Layout for PlacedNode {
fn layout(
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> {
- let mut frames = self.child.layout(ctx, regions);
- for frame in frames.iter_mut() {
- Rc::make_mut(&mut frame.item).size = Size::zero();
+ let out_of_flow = self.out_of_flow();
+
+ // The pod is the base area of the region because for absolute
+ // placement we don't really care about the already used area (current).
+ let pod = {
+ let expand = if out_of_flow { Spec::splat(true) } else { regions.expand };
+ Regions::one(regions.base, regions.base, expand)
+ };
+
+ let mut frames = self.child.layout(ctx, &pod);
+ let Constrained { item: frame, cts } = &mut frames[0];
+
+ // If expansion is off, zero all sizes so that we don't take up any
+ // space in our parent. Otherwise, respect the expand settings.
+ let target = regions.expand.select(regions.current, Size::zero());
+ Rc::make_mut(frame).resize(target, Align::LEFT_TOP);
+
+ // Place relative to parent's base origin by offsetting our elements by
+ // the negative cursor position.
+ if out_of_flow {
+ let offset = (regions.current - regions.base).to_point();
+ Rc::make_mut(frame).translate(offset);
}
+
+ // Set base constraint because our pod size is base and exact
+ // constraints if we needed to expand or offset.
+ *cts = Constraints::new(regions.expand);
+ cts.base = regions.base.map(Some);
+ cts.exact = regions.current.filter(regions.expand | out_of_flow);
+
frames
}
}
diff --git a/src/library/sized.rs b/src/library/sized.rs
index 9b2cdf22..f421637b 100644
--- a/src/library/sized.rs
+++ b/src/library/sized.rs
@@ -56,14 +56,14 @@ impl Layout for SizedNode {
};
let mut frames = self.child.layout(ctx, &pod);
+ let Constrained { cts, .. } = &mut frames[0];
// Set base & exact constraints if the child is automatically sized
- // since we don't know what the child might do. Also set base if our
- // sizing is relative.
- let frame = &mut frames[0];
- frame.cts = Constraints::new(regions.expand);
- frame.cts.exact = regions.current.filter(is_auto);
- frame.cts.base = regions.base.filter(is_auto | is_rel);
+ // since we don't know what the child might have done. Also set base if
+ // our sizing is relative.
+ *cts = Constraints::new(regions.expand);
+ cts.exact = regions.current.filter(is_auto);
+ cts.base = regions.base.filter(is_auto | is_rel);
frames
}
diff --git a/src/library/transform.rs b/src/library/transform.rs
index c8b48666..9ba71ecf 100644
--- a/src/library/transform.rs
+++ b/src/library/transform.rs
@@ -30,7 +30,7 @@ fn transform_impl(args: &mut Args, transform: Transform) -> TypResult<Value> {
let origin = args
.named("origin")?
.unwrap_or(Spec::splat(None))
- .unwrap_or(Spec::new(Align::Center, Align::Horizon));
+ .unwrap_or(Align::CENTER_HORIZON);
Ok(Value::Template(Template::from_inline(move |style| {
body.pack(style).transformed(transform, origin)
@@ -56,11 +56,12 @@ impl Layout for TransformNode {
) -> Vec<Constrained<Rc<Frame>>> {
let mut frames = self.child.layout(ctx, regions);
- for Constrained { item: frame, .. } in frames.iter_mut() {
+ for Constrained { item: frame, .. } in &mut frames {
let Spec { x, y } = self.origin.zip(frame.size).map(|(o, s)| o.resolve(s));
let transform = Transform::translation(x, y)
.pre_concat(self.transform)
.pre_concat(Transform::translation(-x, -y));
+
Rc::make_mut(frame).transform(transform);
}