More independent placed node

This commit is contained in:
Laurenz 2021-11-26 23:51:18 +01:00
parent 3a15922d2f
commit 50bd863471
13 changed files with 126 additions and 63 deletions

View File

@ -337,13 +337,24 @@ impl Builder {
/// Push a block node into the active flow, finishing the active paragraph. /// Push a block node into the active flow, finishing the active paragraph.
fn block(&mut self, node: PackedNode) { 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(); self.parbreak();
let in_flow = node.downcast::<PlacedNode>().is_none();
self.flow.push(FlowChild::Node(node)); self.flow.push(FlowChild::Node(node));
if in_flow { self.parbreak();
self.parbreak();
} else { // This prevents paragraph spacing between the placed node and
// This prevents duplicate paragraph spacing around placed nodes. // the paragraph below it.
if is_placed {
self.flow.last = Last::None; self.flow.last = Last::None;
} }
} }

View File

@ -57,13 +57,13 @@ impl Frame {
/// Resize the frame to a new size, distributing new space according to the /// Resize the frame to a new size, distributing new space according to the
/// given alignments. /// given alignments.
pub fn resize(&mut self, new: Size, aligns: Spec<Align>) { pub fn resize(&mut self, target: Size, aligns: Spec<Align>) {
if self.size != new { if self.size != target {
let offset = Point::new( let offset = Point::new(
aligns.x.resolve(new.x - self.size.x), aligns.x.resolve(target.x - self.size.x),
aligns.y.resolve(new.y - self.size.y), aligns.y.resolve(target.y - self.size.y),
); );
self.size = new; self.size = target;
self.baseline += offset.y; self.baseline += offset.y;
self.translate(offset); self.translate(offset);
} }

View File

@ -18,6 +18,12 @@ pub enum Align {
} }
impl 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. /// The axis this alignment belongs to.
pub const fn axis(self) -> SpecAxis { pub const fn axis(self) -> SpecAxis {
match self { match self {

View File

@ -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> { impl BitAnd for Spec<bool> {
type Output = Self; 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> { impl BitOrAssign for Spec<bool> {
fn bitor_assign(&mut self, rhs: Self) { fn bitor_assign(&mut self, rhs: Self) {
self.x |= rhs.x; self.x |= rhs.x;

View File

@ -136,10 +136,7 @@ impl PackedNode {
/// Transform this node's contents without affecting layout. /// Transform this node's contents without affecting layout.
pub fn moved(self, offset: Point) -> Self { pub fn moved(self, offset: Point) -> Self {
self.transformed( self.transformed(Transform::translation(offset.x, offset.y), Align::LEFT_TOP)
Transform::translation(offset.x, offset.y),
Spec::new(Align::Left, Align::Top),
)
} }
/// Transform this node's contents without affecting layout. /// Transform this node's contents without affecting layout.

View File

@ -2,6 +2,18 @@ use super::prelude::*;
/// `align`: Configure the alignment along the layouting axes. /// `align`: Configure the alignment along the layouting axes.
pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { 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 aligns = args.expect::<Spec<_>>("alignment")?;
let body = args.expect::<Template>("body")?; let body = args.expect::<Template>("body")?;
Ok(Value::Template(Template::from_block(move |style| { Ok(Value::Template(Template::from_block(move |style| {
@ -36,8 +48,8 @@ impl Layout for AlignNode {
// Layout the child. // Layout the child.
let mut frames = self.child.layout(ctx, &pod); let mut frames = self.child.layout(ctx, &pod);
for (Constrained { item: frame, cts }, (current, base)) in for ((current, base), Constrained { item: frame, cts }) in
frames.iter_mut().zip(regions.iter()) regions.iter().zip(&mut frames)
{ {
// Align in the target size. The target size depends on whether we // Align in the target size. The target size depends on whether we
// should expand. // should expand.

View File

@ -101,10 +101,10 @@ enum FlowItem {
Absolute(Length), Absolute(Length),
/// Fractional spacing between other items. /// Fractional spacing between other items.
Fractional(Fractional), 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. /// A frame for a layouted child node and how to align it.
Frame(Rc<Frame>, Spec<Align>), Frame(Rc<Frame>, Spec<Align>),
/// An absolutely placed frame.
Placed(Rc<Frame>),
} }
impl<'a> FlowLayouter<'a> { impl<'a> FlowLayouter<'a> {
@ -166,17 +166,11 @@ impl<'a> FlowLayouter<'a> {
/// Layout a node. /// Layout a node.
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) { 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(placed) = node.downcast::<PlacedNode>() {
if let Some(aligned) = placed.child.downcast::<AlignNode>() { let frame = node.layout(ctx, &self.regions).remove(0);
if aligned.aligns.y.is_some() { if placed.out_of_flow() {
let base = self.regions.base; self.items.push(FlowItem::Placed(frame.item));
let pod = Regions::one(base, base, Spec::splat(true)); return;
let frame = placed.layout(ctx, &pod).remove(0);
self.items.push(FlowItem::Placed(frame.item));
return;
}
} }
} }
@ -233,9 +227,6 @@ impl<'a> FlowLayouter<'a> {
FlowItem::Fractional(v) => { FlowItem::Fractional(v) => {
before += v.resolve(self.fr, remaining); before += v.resolve(self.fr, remaining);
} }
FlowItem::Placed(frame) => {
output.push_frame(Point::zero(), frame);
}
FlowItem::Frame(frame, aligns) => { FlowItem::Frame(frame, aligns) => {
ruler = ruler.max(aligns.y); ruler = ruler.max(aligns.y);
@ -253,6 +244,9 @@ impl<'a> FlowLayouter<'a> {
output.push_frame(pos, frame); output.push_frame(pos, frame);
} }
FlowItem::Placed(frame) => {
output.push_frame(Point::with_y(before), frame);
}
} }
} }

View File

@ -51,7 +51,7 @@ impl Layout for ImageNode {
let wide = pixel_ratio > current_ratio; let wide = pixel_ratio > current_ratio;
// The space into which the image will be placed according to its fit. // 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 current
} else if expand.x || (wide && current.x.is_finite()) { } else if expand.x || (wide && current.x.is_finite()) {
Size::new(current.x, current.y.min(current.x.safe_div(pixel_ratio))) 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 { let size = match self.fit {
ImageFit::Contain | ImageFit::Cover => { ImageFit::Contain | ImageFit::Cover => {
if wide == (self.fit == ImageFit::Contain) { if wide == (self.fit == ImageFit::Contain) {
Size::new(canvas.x, canvas.x / pixel_ratio) Size::new(target.x, target.x / pixel_ratio)
} else { } 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 // 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. // process.
let mut frame = Frame::new(size); let mut frame = Frame::new(size);
frame.push(Point::zero(), Element::Image(self.id, 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". // Create a clipping group if the fit mode is "cover".
if self.fit == ImageFit::Cover { if self.fit == ImageFit::Cover {

View File

@ -153,15 +153,3 @@ castable! {
Expected: "color", Expected: "color",
Value::Color(color) => Paint::Solid(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),
}

View File

@ -39,8 +39,8 @@ impl Layout for PadNode {
let pod = regions.map(|size| shrink(size, self.padding)); let pod = regions.map(|size| shrink(size, self.padding));
let mut frames = self.child.layout(ctx, &pod); let mut frames = self.child.layout(ctx, &pod);
for (Constrained { item: frame, cts }, (current, base)) in for ((current, base), Constrained { item: frame, cts }) in
frames.iter_mut().zip(regions.iter()) regions.iter().zip(&mut frames)
{ {
// Apply the padding inversely such that the grown size padded // Apply the padding inversely such that the grown size padded
// yields the frame's size. // yields the frame's size.

View File

@ -1,4 +1,5 @@
use super::prelude::*; use super::prelude::*;
use super::AlignNode;
/// `place`: Place content at an absolute position. /// `place`: Place content at an absolute position.
pub fn place(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { 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)] #[derive(Debug, Hash)]
pub struct PlacedNode { pub struct PlacedNode {
/// The node to be placed. /// The node to be placed.
pub child: PackedNode, 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 { impl Layout for PlacedNode {
fn layout( fn layout(
&self, &self,
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
regions: &Regions, regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> { ) -> Vec<Constrained<Rc<Frame>>> {
let mut frames = self.child.layout(ctx, regions); let out_of_flow = self.out_of_flow();
for frame in frames.iter_mut() {
Rc::make_mut(&mut frame.item).size = Size::zero(); // 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 frames
} }
} }

View File

@ -56,14 +56,14 @@ impl Layout for SizedNode {
}; };
let mut frames = self.child.layout(ctx, &pod); let mut frames = self.child.layout(ctx, &pod);
let Constrained { cts, .. } = &mut frames[0];
// Set base & exact constraints if the child is automatically sized // 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 // since we don't know what the child might have done. Also set base if
// sizing is relative. // our sizing is relative.
let frame = &mut frames[0]; *cts = Constraints::new(regions.expand);
frame.cts = Constraints::new(regions.expand); cts.exact = regions.current.filter(is_auto);
frame.cts.exact = regions.current.filter(is_auto); cts.base = regions.base.filter(is_auto | is_rel);
frame.cts.base = regions.base.filter(is_auto | is_rel);
frames frames
} }

View File

@ -30,7 +30,7 @@ fn transform_impl(args: &mut Args, transform: Transform) -> TypResult<Value> {
let origin = args let origin = args
.named("origin")? .named("origin")?
.unwrap_or(Spec::splat(None)) .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| { Ok(Value::Template(Template::from_inline(move |style| {
body.pack(style).transformed(transform, origin) body.pack(style).transformed(transform, origin)
@ -56,11 +56,12 @@ impl Layout for TransformNode {
) -> Vec<Constrained<Rc<Frame>>> { ) -> Vec<Constrained<Rc<Frame>>> {
let mut frames = self.child.layout(ctx, regions); 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 Spec { x, y } = self.origin.zip(frame.size).map(|(o, s)| o.resolve(s));
let transform = Transform::translation(x, y) let transform = Transform::translation(x, y)
.pre_concat(self.transform) .pre_concat(self.transform)
.pre_concat(Transform::translation(-x, -y)); .pre_concat(Transform::translation(-x, -y));
Rc::make_mut(frame).transform(transform); Rc::make_mut(frame).transform(transform);
} }