Introduce SizedNode

This commit is contained in:
Laurenz 2021-11-16 11:40:42 +01:00
parent 0e0f340502
commit 73c4701749
7 changed files with 149 additions and 97 deletions

View File

@ -45,10 +45,15 @@ impl RgbaColor {
/// White color.
pub const WHITE: Self = Self { r: 255, g: 255, b: 255, a: 255 };
/// Constructs a new RGBA color.
/// Construct a new RGBA color.
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
/// Construct a new, opaque gray color.
pub fn gray(luma: u8) -> Self {
Self::new(luma, luma, luma, 255)
}
}
impl FromStr for RgbaColor {

View File

@ -1,26 +0,0 @@
use super::prelude::*;
use super::{ShapeKind, ShapeNode};
/// `box`: Place content in a rectangular box.
pub fn box_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let width = args.named("width")?;
let height = args.named("height")?;
let body: Template = args.find().unwrap_or_default();
Ok(Value::Template(Template::from_inline(move |style| {
ShapeNode {
shape: ShapeKind::Rect,
width,
height,
fill: None,
child: Some(body.to_flow(style).pack()),
}
})))
}
/// `block`: Place content in a block.
pub fn block(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let body: Template = args.expect("body")?;
Ok(Value::Template(Template::from_block(move |style| {
body.to_flow(style)
})))
}

View File

@ -4,7 +4,6 @@
//! definitions.
mod align;
mod container;
mod deco;
mod document;
mod flow;
@ -14,6 +13,7 @@ mod pad;
mod page;
mod par;
mod shape;
mod sized;
mod spacing;
mod stack;
mod text;
@ -35,7 +35,6 @@ mod prelude {
pub use self::image::*;
pub use align::*;
pub use container::*;
pub use deco::*;
pub use document::*;
pub use flow::*;
@ -44,6 +43,7 @@ pub use pad::*;
pub use page::*;
pub use par::*;
pub use shape::*;
pub use sized::*;
pub use spacing::*;
pub use stack::*;
pub use text::*;

View File

@ -77,7 +77,7 @@ impl Layout for ParNode {
// Prepare paragraph layout by building a representation on which we can
// do line breaking without layouting each and every line from scratch.
let layouter = ParLayouter::new(self, ctx, regions.clone(), bidi);
let layouter = ParLayouter::new(self, ctx, regions, bidi);
// Find suitable linebreaks.
layouter.layout(ctx, regions.clone())
@ -182,12 +182,9 @@ impl<'a> ParLayouter<'a> {
fn new(
par: &'a ParNode,
ctx: &mut LayoutContext,
mut regions: Regions,
regions: &Regions,
bidi: BidiInfo<'a>,
) -> Self {
// Disable expansion for children.
regions.expand = Spec::splat(false);
let mut items = vec![];
let mut ranges = vec![];
let mut starts = vec![];
@ -219,7 +216,10 @@ impl<'a> ParLayouter<'a> {
}
}
ParChild::Node(ref node, align) => {
let frame = node.layout(ctx, &regions).remove(0);
let size = Size::new(regions.current.w, regions.base.h);
let expand = Spec::splat(false);
let pod = Regions::one(size, regions.base, expand);
let frame = node.layout(ctx, &pod).remove(0);
items.push(ParItem::Frame(Rc::take(frame.item), align));
ranges.push(range);
}

View File

@ -1,7 +1,7 @@
use std::f64::consts::SQRT_2;
use super::prelude::*;
use super::PadNode;
use super::{PadNode, SizedNode};
use crate::util::RcExt;
/// `rect`: A rectangle with optional content.
@ -55,7 +55,7 @@ pub fn circle(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
}
fn shape_impl(
shape: ShapeKind,
kind: ShapeKind,
mut width: Option<Linear>,
mut height: Option<Linear>,
fill: Option<Color>,
@ -65,20 +65,30 @@ fn shape_impl(
if body.is_none() {
let v = Length::pt(30.0).into();
height.get_or_insert(v);
width.get_or_insert(match shape {
width.get_or_insert(match kind {
ShapeKind::Square | ShapeKind::Circle => v,
ShapeKind::Rect | ShapeKind::Ellipse => 1.5 * v,
});
}
Value::Template(Template::from_inline(move |style| ShapeNode {
shape,
width,
height,
fill: Some(Paint::Color(
fill.unwrap_or(Color::Rgba(RgbaColor::new(175, 175, 175, 255))),
)),
child: body.as_ref().map(|template| template.to_flow(style).pack()),
// Set default fill if there's no fill.
let fill = fill.unwrap_or(Color::Rgba(RgbaColor::gray(175)));
Value::Template(Template::from_inline(move |style| {
let shape = Layout::pack(ShapeNode {
kind,
fill: Some(Paint::Color(fill)),
child: body.as_ref().map(|body| body.to_flow(style).pack()),
});
if width.is_some() || height.is_some() {
Layout::pack(SizedNode {
sizing: Spec::new(width, height),
child: shape,
})
} else {
shape
}
}))
}
@ -86,11 +96,7 @@ fn shape_impl(
#[derive(Debug, Hash)]
pub struct ShapeNode {
/// Which shape to place the child into.
pub shape: ShapeKind,
/// The width, if any.
pub width: Option<Linear>,
/// The height, if any.
pub height: Option<Linear>,
pub kind: ShapeKind,
/// How to fill the shape, if at all.
pub fill: Option<Paint>,
/// The child node to place into the shape, if any.
@ -116,33 +122,12 @@ impl Layout for ShapeNode {
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> {
// Resolve width and height relative to the region's base.
let width = self.width.map(|w| w.resolve(regions.base.w));
let height = self.height.map(|h| h.resolve(regions.base.h));
// Generate constraints.
let mut cts = Constraints::new(regions.expand);
cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height));
// Set tight exact and base constraints if the child is
// automatically sized since we don't know what the child might do.
if self.width.is_none() {
cts.exact.x = Some(regions.current.w);
cts.base.x = Some(regions.base.w);
}
// Same here.
if self.height.is_none() {
cts.exact.y = Some(regions.current.h);
cts.base.y = Some(regions.base.h);
}
// Layout.
let mut frame = if let Some(child) = &self.child {
let mut node: &dyn Layout = child;
let padded;
if matches!(self.shape, ShapeKind::Circle | ShapeKind::Ellipse) {
if matches!(self.kind, ShapeKind::Circle | ShapeKind::Ellipse) {
// Padding with this ratio ensures that a rectangular child fits
// perfectly into a circle / an ellipse.
padded = PadNode {
@ -152,29 +137,14 @@ impl Layout for ShapeNode {
node = &padded;
}
// The "pod" is the region into which the child will be layouted.
let mut pod = {
let size = Size::new(
width.unwrap_or(regions.current.w),
height.unwrap_or(regions.base.h),
);
let base = Size::new(
if width.is_some() { size.w } else { regions.base.w },
if height.is_some() { size.h } else { regions.base.h },
);
let expand = Spec::new(width.is_some(), height.is_some());
Regions::one(size, base, expand)
};
// Now, layout the child.
let mut frames = node.layout(ctx, &pod);
let mut frames = node.layout(ctx, regions);
if matches!(self.shape, ShapeKind::Square | ShapeKind::Circle) {
if matches!(self.kind, ShapeKind::Square | ShapeKind::Circle) {
// Relayout with full expansion into square region to make sure
// the result is really a square or circle.
let size = frames[0].item.size;
let mut pod = regions.clone();
pod.current.w = size.w.max(size.h).min(pod.current.w);
pod.current.h = pod.current.w;
pod.expand = Spec::splat(true);
@ -185,14 +155,12 @@ impl Layout for ShapeNode {
assert_eq!(frames.len(), 1);
Rc::take(frames.into_iter().next().unwrap().item)
} else {
// Resolve shape size.
let size = Size::new(width.unwrap_or_default(), height.unwrap_or_default());
Frame::new(size, size.h)
Frame::new(regions.current, regions.current.h)
};
// Add background shape if desired.
if let Some(fill) = self.fill {
let (pos, geometry) = match self.shape {
let (pos, geometry) = match self.kind {
ShapeKind::Square | ShapeKind::Rect => {
(Point::zero(), Geometry::Rect(frame.size))
}
@ -204,6 +172,11 @@ impl Layout for ShapeNode {
frame.prepend(pos, Element::Geometry(geometry, fill));
}
// Generate tight constraints for now.
let mut cts = Constraints::new(regions.expand);
cts.exact = regions.current.to_spec().map(Some);
cts.base = regions.base.to_spec().map(Some);
vec![frame.constrain(cts)]
}
}

100
src/library/sized.rs Normal file
View File

@ -0,0 +1,100 @@
use super::prelude::*;
/// `box`: Size content and place it into a paragraph.
pub fn box_(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let width = args.named("width")?;
let height = args.named("height")?;
let body: Template = args.find().unwrap_or_default();
Ok(Value::Template(Template::from_inline(move |style| {
let flow = body.to_flow(style).pack();
if width.is_some() || height.is_some() {
Layout::pack(SizedNode {
sizing: Spec::new(width, height),
child: flow,
})
} else {
flow
}
})))
}
/// `block`: Size content and place it into the flow.
pub fn block(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let width = args.named("width")?;
let height = args.named("height")?;
let body: Template = args.find().unwrap_or_default();
Ok(Value::Template(Template::from_block(move |style| {
let flow = body.to_flow(style).pack();
if width.is_some() || height.is_some() {
Layout::pack(SizedNode {
sizing: Spec::new(width, height),
child: flow,
})
} else {
flow
}
})))
}
/// A node that sizes its child.
#[derive(Debug, Hash)]
pub struct SizedNode {
/// The node to-be-sized.
pub child: PackedNode,
/// How to size the node horizontally and vertically.
pub sizing: Spec<Option<Linear>>,
}
impl Layout for SizedNode {
fn layout(
&self,
ctx: &mut LayoutContext,
regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> {
// Resolve width and height relative to the region's base.
let width = self.sizing.x.map(|w| w.resolve(regions.base.w));
let height = self.sizing.y.map(|h| h.resolve(regions.base.h));
// Generate constraints.
let mut cts = Constraints::new(regions.expand);
cts.set_base_if_linear(regions.base, self.sizing);
// Set tight exact and base constraints if the child is
// automatically sized since we don't know what the child might do.
if self.sizing.x.is_none() {
cts.exact.x = Some(regions.current.w);
cts.base.x = Some(regions.base.w);
}
// Same here.
if self.sizing.y.is_none() {
cts.exact.y = Some(regions.current.h);
cts.base.y = Some(regions.base.h);
}
// The "pod" is the region into which the child will be layouted.
let pod = {
let size = Size::new(
width.unwrap_or(regions.current.w),
height.unwrap_or(regions.current.h),
);
let base = Size::new(
if width.is_some() { size.w } else { regions.base.w },
if height.is_some() { size.h } else { regions.base.h },
);
let expand = Spec::new(
width.is_some() || regions.expand.x,
height.is_some() || regions.expand.y,
);
// TODO: Allow multiple regions if only width is set.
Regions::one(size, base, expand)
};
let mut frames = self.child.layout(ctx, &pod);
frames[0].cts = cts;
frames
}
}

View File

@ -35,12 +35,12 @@ pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
children.push(StackChild::Spacing(*v));
delayed = None;
}
Child::Any(template) => {
Child::Any(child) => {
if let Some(v) = delayed {
children.push(StackChild::Spacing(v));
}
let node = template.to_flow(style).pack();
let node = child.to_flow(style).pack();
children.push(StackChild::Node(node));
delayed = spacing;
}