summaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-03-20 20:19:30 +0100
committerLaurenz <laurmaedje@gmail.com>2021-03-20 20:19:30 +0100
commit898728f260923a91444eb23b522d0abf01a4299b (patch)
tree64fdd3f78e16e6428e765a8e2d99c3cd910bd9df /src/layout
parent6cb9fe9064a037224b6560b69b441b72e787fa94 (diff)
Square, circle and ellipse 🔵
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/background.rs29
-rw-r--r--src/layout/fixed.rs16
-rw-r--r--src/layout/frame.rs8
-rw-r--r--src/layout/mod.rs47
-rw-r--r--src/layout/pad.rs35
-rw-r--r--src/layout/par.rs16
-rw-r--r--src/layout/stack.rs35
7 files changed, 133 insertions, 53 deletions
diff --git a/src/layout/background.rs b/src/layout/background.rs
index bb155073..17280a86 100644
--- a/src/layout/background.rs
+++ b/src/layout/background.rs
@@ -3,25 +3,38 @@ use super::*;
/// A node that places a rectangular filled background behind its child.
#[derive(Debug, Clone, PartialEq)]
pub struct BackgroundNode {
+ /// The kind of shape to use as a background.
+ pub shape: BackgroundShape,
/// The background fill.
pub fill: Fill,
/// The child node to be filled.
pub child: Node,
}
+/// The kind of shape to use as a background.
+#[derive(Debug, Clone, PartialEq)]
+pub enum BackgroundShape {
+ Rect,
+ Ellipse,
+}
+
impl Layout for BackgroundNode {
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment {
- let mut layouted = self.child.layout(ctx, areas);
+ let mut fragment = self.child.layout(ctx, areas);
+
+ for frame in fragment.frames_mut() {
+ let (point, shape) = match self.shape {
+ BackgroundShape::Rect => (Point::ZERO, Shape::Rect(frame.size)),
+ BackgroundShape::Ellipse => {
+ (frame.size.to_point() / 2.0, Shape::Ellipse(frame.size))
+ }
+ };
- for frame in layouted.frames_mut() {
- let element = Element::Geometry(Geometry {
- shape: Shape::Rect(frame.size),
- fill: self.fill,
- });
- frame.elements.insert(0, (Point::ZERO, element));
+ let element = Element::Geometry(Geometry { shape, fill: self.fill });
+ frame.elements.insert(0, (point, element));
}
- layouted
+ fragment
}
}
diff --git a/src/layout/fixed.rs b/src/layout/fixed.rs
index e3365668..22c45ef1 100644
--- a/src/layout/fixed.rs
+++ b/src/layout/fixed.rs
@@ -7,6 +7,10 @@ pub struct FixedNode {
pub width: Option<Linear>,
/// The fixed height, if any.
pub height: Option<Linear>,
+ /// The fixed aspect ratio between width and height, if any.
+ ///
+ /// The resulting frame will satisfy `width = aspect * height`.
+ pub aspect: Option<f64>,
/// The child node whose size to fix.
pub child: Node,
}
@@ -14,18 +18,26 @@ pub struct FixedNode {
impl Layout for FixedNode {
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment {
let Areas { current, full, .. } = areas;
- let size = Size::new(
+
+ let full = Size::new(
self.width.map(|w| w.resolve(full.width)).unwrap_or(current.width),
self.height.map(|h| h.resolve(full.height)).unwrap_or(current.height),
);
+ let mut size = full;
+ if let Some(aspect) = self.aspect {
+ // Shrink the size to ensure that the aspect ratio can be satisfied.
+ let width = size.width.min(aspect * size.height);
+ size = Size::new(width, width / aspect);
+ }
+
let fill_if = |cond| if cond { Expand::Fill } else { Expand::Fit };
let expand = Spec::new(
fill_if(self.width.is_some()),
fill_if(self.height.is_some()),
);
- let areas = Areas::once(size, expand);
+ let areas = Areas::once(size, full, expand).with_aspect(self.aspect);
self.child.layout(ctx, &areas)
}
}
diff --git a/src/layout/frame.rs b/src/layout/frame.rs
index c85d7539..6e876151 100644
--- a/src/layout/frame.rs
+++ b/src/layout/frame.rs
@@ -1,7 +1,7 @@
use super::Shaped;
use crate::color::Color;
use crate::env::ResourceId;
-use crate::geom::{Point, Size};
+use crate::geom::{Path, Point, Size};
/// A finished layout with elements at fixed positions.
#[derive(Debug, Clone, PartialEq)]
@@ -59,8 +59,12 @@ pub struct Geometry {
/// Some shape.
#[derive(Debug, Clone, PartialEq)]
pub enum Shape {
- /// A rectangle.
+ /// A rectangle with its origin in the topleft corner.
Rect(Size),
+ /// An ellipse with its origin in the center.
+ Ellipse(Size),
+ /// A bezier path.
+ Path(Path),
}
/// The kind of graphic fill to be applied to a [`Shape`].
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index ae4ab89d..360c9d84 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -58,8 +58,7 @@ impl PageRun {
/// Layout the page run.
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Frame> {
let areas = Areas::repeat(self.size, Spec::uniform(Expand::Fill));
- let layouted = self.child.layout(ctx, &areas);
- layouted.into_frames()
+ self.child.layout(ctx, &areas).into_frames()
}
}
@@ -89,29 +88,59 @@ pub struct Areas {
pub last: Option<Size>,
/// Whether the frames resulting from layouting into this areas should be
/// shrunk to fit their content or expanded to fill the area.
+ ///
+ /// This property is handled partially by the par layouter and fully by the
+ /// stack layouter.
pub expand: Spec<Expand>,
+ /// The aspect ratio the resulting frame should respect.
+ ///
+ /// This property is only handled by the stack layouter.
+ pub aspect: Option<f64>,
}
impl Areas {
- /// Create a new length-1 sequence of areas with just one `area`.
- pub fn once(size: Size, expand: Spec<Expand>) -> Self {
+ /// Create a new sequence of areas that repeats `area` indefinitely.
+ pub fn repeat(size: Size, expand: Spec<Expand>) -> Self {
Self {
current: size,
full: size,
backlog: vec![],
- last: None,
+ last: Some(size),
expand,
+ aspect: None,
}
}
- /// Create a new sequence of areas that repeats `area` indefinitely.
- pub fn repeat(size: Size, expand: Spec<Expand>) -> Self {
+ /// Create a new length-1 sequence of areas with just one `area`.
+ pub fn once(size: Size, full: Size, expand: Spec<Expand>) -> Self {
Self {
current: size,
- full: size,
+ full,
backlog: vec![],
- last: Some(size),
+ last: None,
expand,
+ aspect: None,
+ }
+ }
+
+ /// Builder-style method for setting the aspect ratio.
+ pub fn with_aspect(mut self, aspect: Option<f64>) -> Self {
+ self.aspect = aspect;
+ self
+ }
+
+ /// Map all areas.
+ pub fn map<F>(&self, mut f: F) -> Self
+ where
+ F: FnMut(Size) -> Size,
+ {
+ Self {
+ current: f(self.current),
+ full: f(self.full),
+ backlog: self.backlog.iter().copied().map(|s| f(s)).collect(),
+ last: self.last.map(f),
+ expand: self.expand,
+ aspect: self.aspect,
}
}
diff --git a/src/layout/pad.rs b/src/layout/pad.rs
index 33ce217d..fb038996 100644
--- a/src/layout/pad.rs
+++ b/src/layout/pad.rs
@@ -13,12 +13,12 @@ impl Layout for PadNode {
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment {
let areas = shrink(areas, self.padding);
- let mut layouted = self.child.layout(ctx, &areas);
- for frame in layouted.frames_mut() {
+ let mut fragment = self.child.layout(ctx, &areas);
+ for frame in fragment.frames_mut() {
pad(frame, self.padding);
}
- layouted
+ fragment
}
}
@@ -30,23 +30,30 @@ impl From<PadNode> for AnyNode {
/// Shrink all areas by the padding.
fn shrink(areas: &Areas, padding: Sides<Linear>) -> Areas {
- let shrink = |size| size - padding.resolve(size).size();
- Areas {
- current: shrink(areas.current),
- full: shrink(areas.full),
- backlog: areas.backlog.iter().copied().map(shrink).collect(),
- last: areas.last.map(shrink),
- expand: areas.expand,
- }
+ areas.map(|size| size - padding.resolve(size).size())
}
-/// Enlarge the frame and move all elements inwards.
+/// Pad the frame and move all elements inwards.
fn pad(frame: &mut Frame, padding: Sides<Linear>) {
- let padding = padding.resolve(frame.size);
+ let padded = solve(padding, frame.size);
+ let padding = padding.resolve(padded);
let origin = Point::new(padding.left, padding.top);
- frame.size += padding.size();
+ frame.size = padded;
for (point, _) in &mut frame.elements {
*point += origin;
}
}
+
+/// Solve for the size `padded` that satisfies (approximately):
+/// `padded - padding.resolve(padded).size() == size`
+fn solve(padding: Sides<Linear>, size: Size) -> Size {
+ fn solve_axis(length: Length, padding: Linear) -> Length {
+ (length + padding.abs) / (1.0 - padding.rel.get())
+ }
+
+ Size::new(
+ solve_axis(size.width, padding.left + padding.right),
+ solve_axis(size.height, padding.top + padding.bottom),
+ )
+}
diff --git a/src/layout/par.rs b/src/layout/par.rs
index e9fda015..0364a03a 100644
--- a/src/layout/par.rs
+++ b/src/layout/par.rs
@@ -18,7 +18,7 @@ pub struct ParNode {
impl Layout for ParNode {
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment {
- let mut layouter = ParLayouter::new(self, areas.clone());
+ let mut layouter = ParLayouter::new(self.dirs, self.line_spacing, areas.clone());
for child in &self.children {
match child.layout(ctx, &layouter.areas) {
Fragment::Spacing(spacing) => layouter.push_spacing(spacing),
@@ -57,12 +57,12 @@ struct ParLayouter {
}
impl ParLayouter {
- fn new(par: &ParNode, areas: Areas) -> Self {
+ fn new(dirs: LayoutDirs, line_spacing: Length, areas: Areas) -> Self {
Self {
- main: par.dirs.main.axis(),
- cross: par.dirs.cross.axis(),
- dirs: par.dirs,
- line_spacing: par.line_spacing,
+ main: dirs.main.axis(),
+ cross: dirs.cross.axis(),
+ dirs,
+ line_spacing,
areas,
finished: vec![],
lines: vec![],
@@ -134,12 +134,12 @@ impl ParLayouter {
}
fn finish_line(&mut self) {
- let expand = self.areas.expand.switch(self.dirs);
let full_size = {
+ let expand = self.areas.expand.switch(self.dirs);
let full = self.areas.full.switch(self.dirs);
Gen::new(
self.line_size.main,
- expand.cross.resolve(self.line_size.cross.min(full.cross), full.cross),
+ expand.cross.resolve(self.line_size.cross, full.cross),
)
};
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index 32eba676..6a87290e 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -16,7 +16,7 @@ pub struct StackNode {
impl Layout for StackNode {
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Fragment {
- let mut layouter = StackLayouter::new(self, areas.clone());
+ let mut layouter = StackLayouter::new(self.dirs, areas.clone());
for child in &self.children {
match child.layout(ctx, &layouter.areas) {
Fragment::Spacing(spacing) => layouter.push_spacing(spacing),
@@ -49,10 +49,10 @@ struct StackLayouter {
}
impl StackLayouter {
- fn new(stack: &StackNode, areas: Areas) -> Self {
+ fn new(dirs: LayoutDirs, areas: Areas) -> Self {
Self {
- main: stack.dirs.main.axis(),
- dirs: stack.dirs,
+ main: dirs.main.axis(),
+ dirs,
areas,
finished: vec![],
frames: vec![],
@@ -93,12 +93,27 @@ impl StackLayouter {
fn finish_area(&mut self) {
let full_size = {
- let expand = self.areas.expand.switch(self.dirs);
- let full = self.areas.full.switch(self.dirs);
- Gen::new(
- expand.main.resolve(self.used.main.min(full.main), full.main),
- expand.cross.resolve(self.used.cross.min(full.cross), full.cross),
- )
+ let expand = self.areas.expand;
+ let full = self.areas.full;
+ let current = self.areas.current;
+ let used = self.used.switch(self.dirs).to_size();
+
+ let mut size = Size::new(
+ expand.horizontal.resolve(used.width, full.width),
+ expand.vertical.resolve(used.height, full.height),
+ );
+
+ if let Some(aspect) = self.areas.aspect {
+ let width = size
+ .width
+ .max(aspect * size.height)
+ .min(current.width)
+ .min((current.height + used.height) / aspect);
+
+ size = Size::new(width, width / aspect);
+ }
+
+ size.switch(self.dirs)
};
let mut output = Frame::new(full_size.switch(self.dirs).to_size());