diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-11-17 23:09:23 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-11-17 23:09:23 +0100 |
| commit | 095fa52be5d7ed135f39553359e0253cfea6b71b (patch) | |
| tree | 71e8a71a8b7755b32221a30c32f62cc146acdd33 /src | |
| parent | e869c899bcaefb19c3c47955577396b85494b823 (diff) | |
Placed node
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/template.rs | 16 | ||||
| -rw-r--r-- | src/layout/mod.rs | 24 | ||||
| -rw-r--r-- | src/library/align.rs | 26 | ||||
| -rw-r--r-- | src/library/flow.rs | 35 | ||||
| -rw-r--r-- | src/library/mod.rs | 7 | ||||
| -rw-r--r-- | src/library/placed.rs | 39 | ||||
| -rw-r--r-- | src/library/stack.rs | 11 | ||||
| -rw-r--r-- | src/library/transform.rs | 19 |
8 files changed, 130 insertions, 47 deletions
diff --git a/src/eval/template.rs b/src/eval/template.rs index 2df347aa..6c1223cb 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -10,7 +10,7 @@ use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size}; use crate::layout::{Layout, PackedNode}; use crate::library::{ Decoration, DocumentNode, FlowChild, FlowNode, PadNode, PageNode, ParChild, ParNode, - Spacing, + PlacedNode, Spacing, }; use crate::style::Style; use crate::util::EcoString; @@ -331,15 +331,21 @@ impl Builder { } /// Push an inline node into the active paragraph. - fn inline(&mut self, node: impl Into<PackedNode>) { + fn inline(&mut self, node: PackedNode) { self.flow.par.push(ParChild::Node(node.into())); } /// Push a block node into the active flow, finishing the active paragraph. - fn block(&mut self, node: impl Into<PackedNode>) { - self.parbreak(); - self.flow.push(FlowChild::Node(node.into())); + fn block(&mut self, node: PackedNode) { 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.flow.last = Last::None; + } } /// Push spacing into the active paragraph or flow depending on the `axis`. diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 33502fff..3ac32722 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -19,7 +19,7 @@ use crate::font::FontStore; use crate::frame::Frame; use crate::geom::{Align, Linear, Spec}; use crate::image::ImageStore; -use crate::library::{AlignNode, DocumentNode, SizedNode}; +use crate::library::{AlignNode, DocumentNode, MoveNode, SizedNode}; use crate::Context; /// Layout a document node into a collection of frames. @@ -104,21 +104,27 @@ impl PackedNode { } /// Force a size for this node. - pub fn sized(self, width: Option<Linear>, height: Option<Linear>) -> PackedNode { - if width.is_some() || height.is_some() { - Layout::pack(SizedNode { - child: self, - sizing: Spec::new(width, height), - }) + pub fn sized(self, w: Option<Linear>, h: Option<Linear>) -> Self { + if w.is_some() || h.is_some() { + SizedNode { child: self, sizing: Spec::new(w, h) }.pack() } else { self } } /// Set alignments for this node. - pub fn aligned(self, x: Option<Align>, y: Option<Align>) -> PackedNode { + pub fn aligned(self, x: Option<Align>, y: Option<Align>) -> Self { if x.is_some() || y.is_some() { - Layout::pack(AlignNode { child: self, aligns: Spec::new(x, y) }) + AlignNode { child: self, aligns: Spec::new(x, y) }.pack() + } else { + self + } + } + + /// Move this node's contents without affecting layout. + pub fn moved(self, dx: Option<Linear>, dy: Option<Linear>) -> Self { + if dx.is_some() || dy.is_some() { + MoveNode { child: self, offset: Spec::new(dx, dy) }.pack() } else { self } diff --git a/src/library/align.rs b/src/library/align.rs index 591a4085..19c52f98 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -2,18 +2,8 @@ use super::prelude::*; /// `align`: Configure the alignment along the layouting axes. pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let mut x = args.named("horizontal")?; - let mut y = args.named("vertical")?; - for Spanned { v, span } in args.all::<Spanned<Align>>() { - match v.axis() { - None | Some(SpecAxis::Horizontal) if x.is_none() => x = Some(v), - None | Some(SpecAxis::Vertical) if y.is_none() => y = Some(v), - _ => bail!(span, "unexpected argument"), - } - } - + let Spec { x, y } = parse_aligns(args)?; let body = args.expect::<Template>("body")?; - Ok(Value::Template(Template::from_block(move |style| { let mut style = style.clone(); if let Some(x) = x { @@ -24,6 +14,20 @@ pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { }))) } +/// Parse alignment arguments with shorthand. +pub(super) fn parse_aligns(args: &mut Args) -> TypResult<Spec<Option<Align>>> { + let mut x = args.named("horizontal")?; + let mut y = args.named("vertical")?; + for Spanned { v, span } in args.all::<Spanned<Align>>() { + match v.axis() { + None | Some(SpecAxis::Horizontal) if x.is_none() => x = Some(v), + None | Some(SpecAxis::Vertical) if y.is_none() => y = Some(v), + _ => bail!(span, "unexpected argument"), + } + } + Ok(Spec::new(x, y)) +} + /// A node that aligns its child. #[derive(Debug, Hash)] pub struct AlignNode { diff --git a/src/library/flow.rs b/src/library/flow.rs index f58df536..185e60bb 100644 --- a/src/library/flow.rs +++ b/src/library/flow.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Debug, Formatter}; use super::prelude::*; -use super::{AlignNode, ParNode, Spacing}; +use super::{AlignNode, ParNode, PlacedNode, Spacing}; /// `flow`: A vertical flow of paragraphs and other layout nodes. pub fn flow(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { @@ -101,7 +101,9 @@ enum FlowItem { Absolute(Length), /// Fractional spacing between other items. Fractional(Fractional), - /// A layouted child node and how to align it vertically. + /// 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>), } @@ -157,13 +159,27 @@ 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 aligns = Spec::new( // For non-expanding paragraphs it is crucial that we align the // whole paragraph according to its internal alignment. - node.downcast::<ParNode>().map_or(Align::Left, |node| node.align), + node.downcast::<ParNode>().map_or(Align::Left, |par| par.align), // Vertical align node alignment is respected by the flow node. node.downcast::<AlignNode>() - .and_then(|node| node.aligns.y) + .and_then(|aligned| aligned.aligns.y) .unwrap_or(Align::Top), ); @@ -207,8 +223,15 @@ impl<'a> FlowLayouter<'a> { // Place all frames. for item in self.items.drain(..) { match item { - FlowItem::Absolute(v) => before += v, - FlowItem::Fractional(v) => before += v.resolve(self.fr, remaining), + FlowItem::Absolute(v) => { + before += v; + } + 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); diff --git a/src/library/mod.rs b/src/library/mod.rs index 4f33b715..7b8acf9e 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -12,6 +12,7 @@ mod image; mod pad; mod page; mod par; +mod placed; mod shape; mod sized; mod spacing; @@ -42,6 +43,7 @@ pub use grid::*; pub use pad::*; pub use page::*; pub use par::*; +pub use placed::*; pub use shape::*; pub use sized::*; pub use spacing::*; @@ -71,13 +73,14 @@ pub fn new() -> Scope { std.def_func("pagebreak", pagebreak); std.def_func("h", h); std.def_func("v", v); - std.def_func("align", align); std.def_func("box", box_); std.def_func("block", block); std.def_func("flow", flow); - std.def_func("pad", pad); + std.def_func("align", align); + std.def_func("place", place); std.def_func("move", move_); std.def_func("stack", stack); + std.def_func("pad", pad); std.def_func("grid", grid); // Elements. diff --git a/src/library/placed.rs b/src/library/placed.rs new file mode 100644 index 00000000..0d92fc35 --- /dev/null +++ b/src/library/placed.rs @@ -0,0 +1,39 @@ +use super::parse_aligns; +use super::prelude::*; + +/// `place`: Place content at an absolute position. +pub fn place(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { + let Spec { x, y } = parse_aligns(args)?; + let dx = args.named("dx")?; + let dy = args.named("dy")?; + let body: Template = args.expect("body")?; + Ok(Value::Template(Template::from_block(move |style| { + PlacedNode { + child: body + .pack(style) + .moved(dx, dy) + .aligned(Some(x.unwrap_or(Align::Left)), y), + } + }))) +} + +/// A node that places its child out-of-flow. +#[derive(Debug, Hash)] +pub struct PlacedNode { + /// The node to be placed. + pub child: PackedNode, +} + +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(); + } + frames + } +} diff --git a/src/library/stack.rs b/src/library/stack.rs index 7ac7e6ee..cc02592f 100644 --- a/src/library/stack.rs +++ b/src/library/stack.rs @@ -223,8 +223,12 @@ impl<'a> StackLayouter<'a> { // Place all frames. for item in self.items.drain(..) { match item { - StackItem::Absolute(v) => before += v, - StackItem::Fractional(v) => before += v.resolve(self.fr, remaining), + StackItem::Absolute(v) => { + before += v; + } + StackItem::Fractional(v) => { + before += v.resolve(self.fr, remaining); + } StackItem::Frame(frame, align) => { ruler = ruler.max(align); @@ -240,9 +244,8 @@ impl<'a> StackLayouter<'a> { after .. parent - before_with_self }); - before += child; - let pos = Gen::new(Length::zero(), block).to_point(self.axis); + before += child; output.push_frame(pos, frame); } } diff --git a/src/library/transform.rs b/src/library/transform.rs index d0a27622..ef9caf2e 100644 --- a/src/library/transform.rs +++ b/src/library/transform.rs @@ -2,22 +2,21 @@ use super::prelude::*; /// `move`: Move content without affecting layout. pub fn move_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let x = args.named("x")?; - let y = args.named("y")?; + let dx = args.named("dx")?; + let dy = args.named("dy")?; let body: Template = args.expect("body")?; - Ok(Value::Template(Template::from_inline(move |style| { - MoveNode { - offset: Spec::new(x, y), - child: body.pack(style), - } + body.pack(style).moved(dx, dy) }))) } +/// A node that moves its child without affecting layout. #[derive(Debug, Hash)] -struct MoveNode { - offset: Spec<Option<Linear>>, - child: PackedNode, +pub struct MoveNode { + /// The node whose contents should be moved. + pub child: PackedNode, + /// How much to move the contents. + pub offset: Spec<Option<Linear>>, } impl Layout for MoveNode { |
