summaryrefslogtreecommitdiff
path: root/src/eval/layout.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-02-22 12:42:02 +0100
committerLaurenz <laurmaedje@gmail.com>2022-02-22 12:42:02 +0100
commit2bf32c51bceb2f3a8b7ebea3d7c7d6d96757591b (patch)
tree3524388a7394dd35ccef10b89a7a034e6ae1ab60 /src/eval/layout.rs
parentc7e52f20483971a39f54c56700b31980f744a410 (diff)
Remove layout cache
Diffstat (limited to 'src/eval/layout.rs')
-rw-r--r--src/eval/layout.rs381
1 files changed, 381 insertions, 0 deletions
diff --git a/src/eval/layout.rs b/src/eval/layout.rs
new file mode 100644
index 00000000..7608023a
--- /dev/null
+++ b/src/eval/layout.rs
@@ -0,0 +1,381 @@
+//! Layouting infrastructure.
+
+use std::any::{Any, TypeId};
+use std::fmt::{self, Debug, Formatter};
+use std::hash::Hash;
+use std::sync::Arc;
+
+use crate::diag::TypResult;
+use crate::eval::StyleChain;
+use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
+use crate::geom::{Align, Length, Linear, Paint, Point, Sides, Size, Spec, Transform};
+use crate::library::{AlignNode, PadNode, TransformNode, MOVE};
+use crate::util::Prehashed;
+use crate::Vm;
+
+/// A node that can be layouted into a sequence of regions.
+///
+/// Layout return one frame per used region alongside constraints that define
+/// whether the result is reusable in other regions.
+pub trait Layout {
+ /// Layout the node into the given regions, producing constrained frames.
+ fn layout(
+ &self,
+ vm: &mut Vm,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> TypResult<Vec<Arc<Frame>>>;
+
+ /// Convert to a packed node.
+ fn pack(self) -> LayoutNode
+ where
+ Self: Debug + Hash + Sized + Sync + Send + 'static,
+ {
+ LayoutNode::new(self)
+ }
+}
+
+/// A sequence of regions to layout into.
+#[derive(Debug, Clone)]
+pub struct Regions {
+ /// The (remaining) size of the first region.
+ pub first: Size,
+ /// The base size for relative sizing.
+ pub base: Size,
+ /// The height of followup regions. The width is the same for all regions.
+ pub backlog: std::vec::IntoIter<Length>,
+ /// The height of the final region that is repeated once the backlog is
+ /// drained. The width is the same for all regions.
+ pub last: Option<Length>,
+ /// Whether nodes should expand to fill the regions instead of shrinking to
+ /// fit the content.
+ pub expand: Spec<bool>,
+}
+
+impl Regions {
+ /// Create a new region sequence with exactly one region.
+ pub fn one(size: Size, base: Size, expand: Spec<bool>) -> Self {
+ Self {
+ first: size,
+ base,
+ backlog: vec![].into_iter(),
+ last: None,
+ expand,
+ }
+ }
+
+ /// Create a new sequence of same-size regions that repeats indefinitely.
+ pub fn repeat(size: Size, base: Size, expand: Spec<bool>) -> Self {
+ Self {
+ first: size,
+ base,
+ backlog: vec![].into_iter(),
+ last: Some(size.y),
+ expand,
+ }
+ }
+
+ /// Create new regions where all sizes are mapped with `f`.
+ ///
+ /// Note that since all regions must have the same width, the width returned
+ /// by `f` is ignored for the backlog and the final region.
+ pub fn map<F>(&self, mut f: F) -> Self
+ where
+ F: FnMut(Size) -> Size,
+ {
+ let x = self.first.x;
+ Self {
+ first: f(self.first),
+ base: f(self.base),
+ backlog: self
+ .backlog
+ .as_slice()
+ .iter()
+ .map(|&y| f(Size::new(x, y)).y)
+ .collect::<Vec<_>>()
+ .into_iter(),
+ last: self.last.map(|y| f(Size::new(x, y)).y),
+ expand: self.expand,
+ }
+ }
+
+ /// Whether the first region is full and a region break is called for.
+ pub fn is_full(&self) -> bool {
+ Length::zero().fits(self.first.y) && !self.in_last()
+ }
+
+ /// Whether the first region is the last usable region.
+ ///
+ /// If this is true, calling `next()` will have no effect.
+ pub fn in_last(&self) -> bool {
+ self.backlog.len() == 0 && self.last.map_or(true, |height| self.first.y == height)
+ }
+
+ /// Advance to the next region if there is any.
+ pub fn next(&mut self) {
+ if let Some(height) = self.backlog.next().or(self.last) {
+ self.first.y = height;
+ self.base.y = height;
+ }
+ }
+
+ /// An iterator that returns the sizes of the first and all following
+ /// regions, equivalently to what would be produced by calling
+ /// [`next()`](Self::next) repeatedly until all regions are exhausted.
+ /// This iterater may be infinite.
+ pub fn iter(&self) -> impl Iterator<Item = Size> + '_ {
+ let first = std::iter::once(self.first);
+ let backlog = self.backlog.as_slice().iter();
+ let last = self.last.iter().cycle();
+ first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h)))
+ }
+}
+
+/// A type-erased layouting node with a precomputed hash.
+#[derive(Clone, Hash)]
+pub struct LayoutNode(Arc<Prehashed<dyn Bounds>>);
+
+impl LayoutNode {
+ /// Pack any layoutable node.
+ pub fn new<T>(node: T) -> Self
+ where
+ T: Layout + Debug + Hash + Sync + Send + 'static,
+ {
+ Self(Arc::new(Prehashed::new(node)))
+ }
+
+ /// Check whether the contained node is a specific layout node.
+ pub fn is<T: 'static>(&self) -> bool {
+ self.0.as_any().is::<T>()
+ }
+
+ /// The type id of this node.
+ pub fn id(&self) -> TypeId {
+ self.0.as_any().type_id()
+ }
+
+ /// Try to downcast to a specific layout node.
+ pub fn downcast<T>(&self) -> Option<&T>
+ where
+ T: Layout + Debug + Hash + 'static,
+ {
+ self.0.as_any().downcast_ref()
+ }
+
+ /// Force a size for this node.
+ pub fn sized(self, sizing: Spec<Option<Linear>>) -> Self {
+ if sizing.any(Option::is_some) {
+ SizedNode { sizing, child: self }.pack()
+ } else {
+ self
+ }
+ }
+
+ /// Fill the frames resulting from a node.
+ pub fn filled(self, fill: Paint) -> Self {
+ FillNode { fill, child: self }.pack()
+ }
+
+ /// Stroke the frames resulting from a node.
+ pub fn stroked(self, stroke: Stroke) -> Self {
+ StrokeNode { stroke, child: self }.pack()
+ }
+
+ /// Set alignments for this node.
+ pub fn aligned(self, aligns: Spec<Option<Align>>) -> Self {
+ if aligns.any(Option::is_some) {
+ AlignNode { aligns, child: self }.pack()
+ } else {
+ self
+ }
+ }
+
+ /// Pad this node at the sides.
+ pub fn padded(self, padding: Sides<Linear>) -> Self {
+ if !padding.left.is_zero()
+ || !padding.top.is_zero()
+ || !padding.right.is_zero()
+ || !padding.bottom.is_zero()
+ {
+ PadNode { padding, child: self }.pack()
+ } else {
+ self
+ }
+ }
+
+ /// Transform this node's contents without affecting layout.
+ pub fn moved(self, offset: Point) -> Self {
+ if !offset.is_zero() {
+ TransformNode::<MOVE> {
+ transform: Transform::translation(offset.x, offset.y),
+ child: self,
+ }
+ .pack()
+ } else {
+ self
+ }
+ }
+}
+
+impl Layout for LayoutNode {
+ #[track_caller]
+ fn layout(
+ &self,
+ vm: &mut Vm,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> TypResult<Vec<Arc<Frame>>> {
+ // TODO(query)
+ self.0.layout(vm, regions, styles.barred(self.id()))
+ }
+
+ fn pack(self) -> LayoutNode {
+ self
+ }
+}
+
+impl Default for LayoutNode {
+ fn default() -> Self {
+ EmptyNode.pack()
+ }
+}
+
+impl Debug for LayoutNode {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl PartialEq for LayoutNode {
+ fn eq(&self, other: &Self) -> bool {
+ self.0.eq(&other.0)
+ }
+}
+
+trait Bounds: Layout + Debug + Sync + Send + 'static {
+ fn as_any(&self) -> &dyn Any;
+}
+
+impl<T> Bounds for T
+where
+ T: Layout + Debug + Hash + Sync + Send + 'static,
+{
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+
+/// A layout node that produces an empty frame.
+///
+/// The packed version of this is returned by [`PackedNode::default`].
+#[derive(Debug, Hash)]
+struct EmptyNode;
+
+impl Layout for EmptyNode {
+ fn layout(
+ &self,
+ _: &mut Vm,
+ regions: &Regions,
+ _: StyleChain,
+ ) -> TypResult<Vec<Arc<Frame>>> {
+ Ok(vec![Arc::new(Frame::new(
+ regions.expand.select(regions.first, Size::zero()),
+ ))])
+ }
+}
+
+/// Fix the size of a node.
+#[derive(Debug, Hash)]
+struct SizedNode {
+ /// How to size the node horizontally and vertically.
+ sizing: Spec<Option<Linear>>,
+ /// The node to be sized.
+ child: LayoutNode,
+}
+
+impl Layout for SizedNode {
+ fn layout(
+ &self,
+ vm: &mut Vm,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> TypResult<Vec<Arc<Frame>>> {
+ // The "pod" is the region into which the child will be layouted.
+ let pod = {
+ // Resolve the sizing to a concrete size.
+ let size = self
+ .sizing
+ .zip(regions.base)
+ .map(|(s, b)| s.map(|v| v.resolve(b)))
+ .unwrap_or(regions.first);
+
+ // Select the appropriate base and expansion for the child depending
+ // on whether it is automatically or linearly sized.
+ let is_auto = self.sizing.map_is_none();
+ let base = is_auto.select(regions.base, size);
+ let expand = regions.expand | !is_auto;
+
+ Regions::one(size, base, expand)
+ };
+
+ // Layout the child.
+ let mut frames = self.child.layout(vm, &pod, styles)?;
+
+ // Ensure frame size matches regions size if expansion is on.
+ let frame = &mut frames[0];
+ let target = regions.expand.select(regions.first, frame.size);
+ Arc::make_mut(frame).resize(target, Align::LEFT_TOP);
+
+ Ok(frames)
+ }
+}
+
+/// Fill the frames resulting from a node.
+#[derive(Debug, Hash)]
+struct FillNode {
+ /// How to fill the frames resulting from the `child`.
+ fill: Paint,
+ /// The node to fill.
+ child: LayoutNode,
+}
+
+impl Layout for FillNode {
+ fn layout(
+ &self,
+ vm: &mut Vm,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> TypResult<Vec<Arc<Frame>>> {
+ let mut frames = self.child.layout(vm, regions, styles)?;
+ for frame in &mut frames {
+ let shape = Shape::filled(Geometry::Rect(frame.size), self.fill);
+ Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
+ }
+ Ok(frames)
+ }
+}
+
+/// Stroke the frames resulting from a node.
+#[derive(Debug, Hash)]
+struct StrokeNode {
+ /// How to stroke the frames resulting from the `child`.
+ stroke: Stroke,
+ /// The node to stroke.
+ child: LayoutNode,
+}
+
+impl Layout for StrokeNode {
+ fn layout(
+ &self,
+ vm: &mut Vm,
+ regions: &Regions,
+ styles: StyleChain,
+ ) -> TypResult<Vec<Arc<Frame>>> {
+ let mut frames = self.child.layout(vm, regions, styles)?;
+ for frame in &mut frames {
+ let shape = Shape::stroked(Geometry::Rect(frame.size), self.stroke);
+ Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
+ }
+ Ok(frames)
+ }
+}