diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-02-22 12:42:02 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-02-22 12:42:02 +0100 |
| commit | 2bf32c51bceb2f3a8b7ebea3d7c7d6d96757591b (patch) | |
| tree | 3524388a7394dd35ccef10b89a7a034e6ae1ab60 /src/layout | |
| parent | c7e52f20483971a39f54c56700b31980f744a410 (diff) | |
Remove layout cache
Diffstat (limited to 'src/layout')
| -rw-r--r-- | src/layout/constraints.rs | 88 | ||||
| -rw-r--r-- | src/layout/incremental.rs | 451 | ||||
| -rw-r--r-- | src/layout/mod.rs | 334 | ||||
| -rw-r--r-- | src/layout/regions.rs | 102 |
4 files changed, 0 insertions, 975 deletions
diff --git a/src/layout/constraints.rs b/src/layout/constraints.rs deleted file mode 100644 index 3bdbc4bc..00000000 --- a/src/layout/constraints.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::sync::Arc; - -use super::Regions; -use crate::frame::Frame; -use crate::geom::{Length, Size, Spec}; - -/// Constrain a frame with constraints. -pub trait Constrain { - /// Reference-count the frame and wrap it with constraints. - fn constrain(self, cts: Constraints) -> Constrained<Arc<Frame>>; -} - -impl Constrain for Frame { - fn constrain(self, cts: Constraints) -> Constrained<Arc<Frame>> { - Constrained::new(Arc::new(self), cts) - } -} - -/// Carries an item that is only valid in certain regions and the constraints -/// that describe these regions. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct Constrained<T> { - /// The item that is only valid if the constraints are fullfilled. - pub item: T, - /// Constraints on regions in which the item is valid. - pub cts: Constraints, -} - -impl<T> Constrained<T> { - /// Constrain an item with constraints. - pub fn new(item: T, cts: Constraints) -> Self { - Self { item, cts } - } -} - -/// Describe regions that match them. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct Constraints { - /// The minimum available length in the region. - pub min: Spec<Option<Length>>, - /// The maximum available length in the region. - pub max: Spec<Option<Length>>, - /// The available length in the region. - pub exact: Spec<Option<Length>>, - /// The base length of the region used for relative length resolution. - pub base: Spec<Option<Length>>, - /// The expand settings of the region. - pub expand: Spec<bool>, -} - -impl Constraints { - /// Create a new region constraint. - pub fn new(expand: Spec<bool>) -> Self { - Self { - min: Spec::default(), - max: Spec::default(), - exact: Spec::default(), - base: Spec::default(), - expand, - } - } - - /// Create tight constraints for a region. - pub fn tight(regions: &Regions) -> Self { - Self { - min: Spec::default(), - max: Spec::default(), - exact: regions.current.map(Some), - base: regions.base.map(Some), - expand: regions.expand, - } - } - - /// Check whether the constraints are fullfilled in a region with the given - /// properties. - pub fn check(&self, current: Size, base: Size, expand: Spec<bool>) -> bool { - self.expand == expand - && verify(self.min, current, |m, c| c.fits(m)) - && verify(self.max, current, |m, c| m.fits(c)) - && verify(self.exact, current, Length::approx_eq) - && verify(self.base, base, Length::approx_eq) - } -} - -/// Verify a single constraint. -fn verify(spec: Spec<Option<Length>>, size: Size, f: fn(Length, Length) -> bool) -> bool { - spec.zip(size).all(|&(opt, s)| opt.map_or(true, |m| f(m, s))) -} diff --git a/src/layout/incremental.rs b/src/layout/incremental.rs deleted file mode 100644 index b68ddcdc..00000000 --- a/src/layout/incremental.rs +++ /dev/null @@ -1,451 +0,0 @@ -use std::cmp::Reverse; -use std::collections::HashMap; -use std::sync::Arc; - -use itertools::Itertools; - -use super::{Constrained, Regions}; -use crate::frame::Frame; -use crate::geom::Scalar; - -const TEMP_LEN: usize = 4; - -/// Caches layouting artifacts. -#[derive(Default, Clone)] -pub struct LayoutCache { - /// Maps from node hashes to the resulting frames and regions in which the - /// frames are valid. The right hand side of the hash map is a vector of - /// results because across one or more compilations, multiple different - /// layouts of the same node may have been requested. - frames: HashMap<u64, Vec<FramesEntry>>, - /// In how many compilations this cache has been used. - age: usize, - /// What cache eviction policy should be used. - policy: EvictionPolicy, - /// The maximum number of entries this cache should have. Can be exceeded if - /// there are more must-keep entries. - max_size: usize, -} - -impl LayoutCache { - /// Create a new, empty layout cache. - pub fn new(policy: EvictionPolicy, max_size: usize) -> Self { - Self { - frames: HashMap::default(), - age: 0, - policy, - max_size, - } - } - - /// Whether the cache is empty. - pub fn is_empty(&self) -> bool { - self.frames.values().all(|entry| entry.is_empty()) - } - - /// Amount of items in the cache. - pub fn len(&self) -> usize { - self.frames.values().map(Vec::len).sum() - } - - /// The number of levels stored in the cache. - pub fn levels(&self) -> usize { - self.entries().map(|entry| entry.level + 1).max().unwrap_or(0) - } - - /// An iterator over all entries in the cache. - pub fn entries(&self) -> impl Iterator<Item = &FramesEntry> + '_ { - self.frames.values().flatten() - } - - /// Fetch matching cached frames if there are any. - pub fn get( - &mut self, - hash: u64, - regions: &Regions, - ) -> Option<Vec<Constrained<Arc<Frame>>>> { - self.frames - .get_mut(&hash)? - .iter_mut() - .find_map(|entry| entry.lookup(regions)) - } - - /// Insert a new frame entry into the cache. - pub fn insert(&mut self, hash: u64, entry: FramesEntry) { - self.frames.entry(hash).or_default().push(entry); - } - - /// Clear the cache. - pub fn clear(&mut self) { - self.frames.clear(); - } - - /// Retains all elements for which the closure on the level returns `true`. - pub fn retain<F>(&mut self, mut f: F) - where - F: FnMut(usize) -> bool, - { - for entries in self.frames.values_mut() { - entries.retain(|entry| f(entry.level)); - } - } - - /// Prepare the cache for the next round of compilation. - pub fn turnaround(&mut self) { - self.age += 1; - for entry in self.frames.values_mut().flatten() { - if entry.temperature[0] > 0 { - entry.used_cycles += 1; - } - - let last = *entry.temperature.last().unwrap(); - for i in (1 .. TEMP_LEN).rev() { - entry.temperature[i] = entry.temperature[i - 1]; - } - - entry.temperature[0] = 0; - entry.ancient_hits += last as usize; - entry.age += 1; - } - - self.evict(); - self.frames.retain(|_, v| !v.is_empty()); - } - - /// Evict the cache according to the policy. - fn evict(&mut self) { - let len = self.len(); - if len <= self.max_size { - return; - } - - match self.policy { - EvictionPolicy::LeastRecentlyUsed => { - // We find the element with the largest cooldown that cannot fit - // anymore. - let threshold = self - .entries() - .map(|f| Reverse(f.cooldown())) - .k_smallest(len - self.max_size) - .last() - .unwrap() - .0; - - for entries in self.frames.values_mut() { - entries.retain(|f| f.cooldown() < threshold); - } - } - EvictionPolicy::LeastFrequentlyUsed => { - let threshold = self - .entries() - .map(|f| Scalar(f.hits() as f64 / f.age() as f64)) - .k_smallest(len - self.max_size) - .last() - .unwrap() - .0; - - for entries in self.frames.values_mut() { - entries.retain(|f| f.hits() as f64 / f.age() as f64 > threshold); - } - } - EvictionPolicy::Patterns => { - let kept = self.entries().filter(|f| f.properties().must_keep()).count(); - - let remaining_capacity = self.max_size - kept.min(self.max_size); - if len - kept <= remaining_capacity { - return; - } - - let threshold = self - .entries() - .filter(|f| !f.properties().must_keep()) - .map(|f| Scalar(f.hits() as f64 / f.age() as f64)) - .k_smallest((len - kept) - remaining_capacity) - .last() - .unwrap() - .0; - - for entries in self.frames.values_mut() { - entries.retain(|f| { - f.properties().must_keep() - || f.hits() as f64 / f.age() as f64 > threshold - }); - } - } - EvictionPolicy::None => {} - } - } -} - -/// Cached frames from past layouting. -#[derive(Debug, Clone)] -pub struct FramesEntry { - /// The cached frames for a node. - frames: Vec<Constrained<Arc<Frame>>>, - /// How nested the frame was in the context is was originally appearing in. - level: usize, - /// For how long the element already exists. - age: usize, - /// How much the element was accessed during the last five compilations, the - /// most recent one being the first element. - temperature: [u8; TEMP_LEN], - /// All past usages that do not fit in the temperature array. - ancient_hits: usize, - /// Amount of cycles in which the element has been used at all. - used_cycles: usize, -} - -impl FramesEntry { - /// Construct a new instance. - pub fn new(frames: Vec<Constrained<Arc<Frame>>>, level: usize) -> Self { - Self { - frames, - level, - age: 1, - temperature: [0; TEMP_LEN], - ancient_hits: 0, - used_cycles: 0, - } - } - - /// Checks if the cached frames are valid in the given regions and returns - /// them if so. - pub fn lookup(&mut self, regions: &Regions) -> Option<Vec<Constrained<Arc<Frame>>>> { - self.check(regions).then(|| { - self.temperature[0] = self.temperature[0].saturating_add(1); - self.frames.clone() - }) - } - - /// Checks if the cached frames are valid in the given regions. - pub fn check(&self, regions: &Regions) -> bool { - let mut iter = regions.iter(); - self.frames.iter().all(|frame| { - iter.next().map_or(false, |(current, base)| { - frame.cts.check(current, base, regions.expand) - }) - }) - } - - /// How nested the frame was in the context is was originally appearing in. - pub fn level(&self) -> usize { - self.level - } - - /// The number of compilation cycles this item has remained in the cache. - pub fn age(&self) -> usize { - self.age - } - - /// Whether this element was used in the last compilation cycle. - pub fn hit(&self) -> bool { - self.temperature[0] != 0 - } - - /// Get the total amount of hits over the lifetime of this item. - pub fn hits(&self) -> usize { - self.temperature.into_iter().map(usize::from).sum::<usize>() + self.ancient_hits - } - - /// The amount of consecutive cycles in which this item has not been used. - pub fn cooldown(&self) -> usize { - let mut cycle = 0; - for &temp in &self.temperature[.. self.age.min(TEMP_LEN)] { - if temp > 0 { - return cycle; - } - cycle += 1; - } - cycle - } - - /// Properties that describe how this entry's temperature evolved. - pub fn properties(&self) -> PatternProperties { - let mut all_zeros = true; - let mut multi_use = false; - let mut decreasing = true; - let mut sparse = false; - let mut abandoned = false; - - let mut last = None; - let mut all_same = true; - - for (i, &temp) in self.temperature.iter().enumerate() { - if temp == 0 && !all_zeros { - sparse = true; - } - - if temp != 0 { - all_zeros = false; - } - - if all_zeros && i == 1 { - abandoned = true; - } - - if temp > 1 { - multi_use = true; - } - - if let Some(prev) = last { - if prev > temp { - decreasing = false; - } - - if temp != prev { - all_same = false; - } - } - - last = Some(temp); - } - - if self.age > TEMP_LEN && self.age - TEMP_LEN <= self.ancient_hits { - multi_use = true; - } - - if self.ancient_hits > 0 { - all_zeros = false; - } - - PatternProperties { - mature: self.age > TEMP_LEN, - hit: self.temperature[0] >= 1, - top_level: self.level == 0, - all_zeros, - multi_use, - decreasing: decreasing && !all_same, - sparse, - abandoned, - } - } -} - -/// Cache eviction strategies. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum EvictionPolicy { - /// Evict the least recently used item. - LeastRecentlyUsed, - /// Evict the least frequently used item. - LeastFrequentlyUsed, - /// Use the pattern verdicts. - Patterns, - /// Do not evict. - None, -} - -impl Default for EvictionPolicy { - fn default() -> Self { - Self::Patterns - } -} - -/// Describes the properties that this entry's temperature array has. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct PatternProperties { - /// There only are zero values. - pub all_zeros: bool, - /// The entry exists for more or equal time as the temperature array is long. - pub mature: bool, - /// The entry was used more than one time in at least one compilation. - pub multi_use: bool, - /// The entry was used in the last compilation. - pub hit: bool, - /// The temperature is monotonously decreasing in non-terminal temperature fields. - pub decreasing: bool, - /// There are zero temperatures after non-zero temperatures. - pub sparse: bool, - /// There are multiple zero temperatures at the front of the temperature array. - pub abandoned: bool, - /// If the item is on the top level. - pub top_level: bool, -} - -impl PatternProperties { - /// Check if it is vital to keep an entry based on its properties. - pub fn must_keep(&self) -> bool { - // Keep an undo stack. - (self.top_level && !self.mature) - // Keep the most recently created items, even if they have not yet - // been used. - || (self.all_zeros && !self.mature) - || (self.multi_use && !self.abandoned) - || self.hit - || self.sparse - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::geom::{Size, Spec}; - use crate::layout::Constraints; - - fn empty_frames() -> Vec<Constrained<Arc<Frame>>> { - vec![Constrained { - item: Arc::new(Frame::default()), - cts: Constraints::new(Spec::splat(false)), - }] - } - - fn zero_regions() -> Regions { - Regions::one(Size::zero(), Size::zero(), Spec::splat(false)) - } - - #[test] - fn test_layout_incremental_temperature() { - let mut cache = LayoutCache::new(EvictionPolicy::None, 20); - let regions = zero_regions(); - cache.policy = EvictionPolicy::None; - cache.insert(0, FramesEntry::new(empty_frames(), 0)); - - let entry = cache.frames.get(&0).unwrap().first().unwrap(); - assert_eq!(entry.age(), 1); - assert_eq!(entry.temperature, [0, 0, 0, 0]); - assert_eq!(entry.ancient_hits, 0); - assert_eq!(entry.used_cycles, 0); - assert_eq!(entry.level, 0); - - cache.get(0, ®ions).unwrap(); - let entry = cache.frames.get(&0).unwrap().first().unwrap(); - assert_eq!(entry.age(), 1); - assert_eq!(entry.temperature, [1, 0, 0, 0]); - assert_eq!(entry.ancient_hits, 0); - - cache.turnaround(); - let entry = cache.frames.get(&0).unwrap().first().unwrap(); - assert_eq!(entry.age(), 2); - assert_eq!(entry.temperature, [0, 1, 0, 0]); - assert_eq!(entry.ancient_hits, 0); - assert_eq!(entry.used_cycles, 1); - - cache.get(0, ®ions).unwrap(); - for _ in 0 .. 4 { - cache.turnaround(); - } - - let entry = cache.frames.get(&0).unwrap().first().unwrap(); - assert_eq!(entry.age(), 6); - assert_eq!(entry.temperature, [0, 0, 0, 0]); - assert_eq!(entry.ancient_hits, 2); - assert_eq!(entry.used_cycles, 2); - } - - #[test] - fn test_layout_incremental_properties() { - let mut cache = LayoutCache::new(EvictionPolicy::None, 20); - cache.policy = EvictionPolicy::None; - cache.insert(0, FramesEntry::new(empty_frames(), 1)); - - let props = cache.frames.get(&0).unwrap().first().unwrap().properties(); - assert_eq!(props.top_level, false); - assert_eq!(props.mature, false); - assert_eq!(props.multi_use, false); - assert_eq!(props.hit, false); - assert_eq!(props.decreasing, false); - assert_eq!(props.sparse, false); - assert_eq!(props.abandoned, true); - assert_eq!(props.all_zeros, true); - assert_eq!(props.must_keep(), true); - } -} diff --git a/src/layout/mod.rs b/src/layout/mod.rs deleted file mode 100644 index afb2621b..00000000 --- a/src/layout/mod.rs +++ /dev/null @@ -1,334 +0,0 @@ -//! Layouting infrastructure. - -mod constraints; -mod incremental; -mod regions; - -pub use constraints::*; -pub use incremental::*; -pub use regions::*; - -use std::any::{Any, TypeId}; -use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::sync::Arc; - -use crate::diag::TypResult; -use crate::eval::StyleChain; -use crate::frame::{Element, Frame, Geometry, Shape, Stroke}; -use crate::geom::{Align, 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<Constrained<Arc<Frame>>>>; - - /// Convert to a packed node. - fn pack(self) -> LayoutNode - where - Self: Debug + Hash + Sized + Sync + Send + 'static, - { - LayoutNode::new(self) - } -} - -/// 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<Constrained<Arc<Frame>>>> { - let styles = styles.barred(self.id()); - - let hash = { - let mut state = fxhash::FxHasher64::default(); - self.hash(&mut state); - styles.hash(&mut state); - state.finish() - }; - - // This is not written with `unwrap_or_else`, because then the - // #[track_caller] annotation doesn't work. - if let Some(frames) = vm.layout_cache.get(hash, regions) { - Ok(frames) - } else { - vm.level += 1; - let frames = self.0.layout(vm, regions, styles)?; - vm.level -= 1; - - let entry = FramesEntry::new(frames.clone(), vm.level); - - #[cfg(debug_assertions)] - if !entry.check(regions) { - eprintln!("node: {:#?}", self.0); - eprintln!("regions: {regions:#?}"); - eprintln!( - "constraints: {:#?}", - frames.iter().map(|c| c.cts).collect::<Vec<_>>(), - ); - panic!("constraints did not match regions they were created for"); - } - - vm.layout_cache.insert(hash, entry); - Ok(frames) - } - } - - 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<Constrained<Arc<Frame>>>> { - let size = regions.expand.select(regions.current, Size::zero()); - let mut cts = Constraints::new(regions.expand); - cts.exact = regions.current.filter(regions.expand); - Ok(vec![Frame::new(size).constrain(cts)]) - } -} - -/// 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<Constrained<Arc<Frame>>>> { - let is_auto = self.sizing.map_is_none(); - let is_rel = self.sizing.map(|s| s.map_or(false, Linear::is_relative)); - - // 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.current); - - // Select the appropriate base and expansion for the child depending - // on whether it is automatically or linearly sized. - let base = is_auto.select(regions.base, size); - let expand = regions.expand | !is_auto; - - Regions::one(size, base, expand) - }; - - let mut frames = self.child.layout(vm, &pod, styles)?; - let Constrained { item: frame, cts } = &mut frames[0]; - - // Ensure frame size matches regions size if expansion is on. - let target = regions.expand.select(regions.current, frame.size); - Arc::make_mut(frame).resize(target, Align::LEFT_TOP); - - // Set base & exact constraints if the child is automatically sized - // since we don't know what the child might have done. Also set base if - // our sizing is relative. - *cts = Constraints::new(regions.expand); - cts.exact = regions.current.filter(regions.expand | is_auto); - cts.base = regions.base.filter(is_rel | is_auto); - - 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<Constrained<Arc<Frame>>>> { - let mut frames = self.child.layout(vm, regions, styles)?; - for Constrained { item: 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<Constrained<Arc<Frame>>>> { - let mut frames = self.child.layout(vm, regions, styles)?; - for Constrained { item: 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) - } -} diff --git a/src/layout/regions.rs b/src/layout/regions.rs deleted file mode 100644 index 3f8b6d25..00000000 --- a/src/layout/regions.rs +++ /dev/null @@ -1,102 +0,0 @@ -use crate::geom::{Length, Size, Spec}; - -/// A sequence of regions to layout into. -#[derive(Debug, Clone)] -pub struct Regions { - /// The remaining size of the current region. - pub current: 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 { - current: 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 { - current: 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.current.x; - Self { - current: f(self.current), - 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 current region is full and a region break is called for. - pub fn is_full(&self) -> bool { - Length::zero().fits(self.current.y) && !self.in_last() - } - - /// Whether `current` 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.current.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.current.y = height; - self.base.y = height; - } - } - - /// An iterator that returns pairs of `(current, base)` that are equivalent - /// to what would be produced by calling [`next()`](Self::next) repeatedly - /// until all regions are exhausted. - pub fn iter(&self) -> impl Iterator<Item = (Size, Size)> + '_ { - let first = std::iter::once((self.current, self.base)); - let backlog = self.backlog.as_slice().iter(); - let last = self.last.iter().cycle(); - first.chain(backlog.chain(last).map(|&height| { - ( - Size::new(self.current.x, height), - Size::new(self.base.x, height), - ) - })) - } -} |
