Refactor flow, stack and grid layouters a bit
This commit is contained in:
parent
f9d3802492
commit
bdc7127adf
@ -39,6 +39,14 @@ impl<T> Spec<T> {
|
||||
Spec { x: &self.x, y: &self.y }
|
||||
}
|
||||
|
||||
/// Convert from `&Spec<T>` to `Spec<&<T as Deref>::Target>`.
|
||||
pub fn as_deref(&self) -> Spec<&T::Target>
|
||||
where
|
||||
T: Deref,
|
||||
{
|
||||
Spec { x: &self.x, y: &self.y }
|
||||
}
|
||||
|
||||
/// Convert from `&mut Spec<T>` to `Spec<&mut T>`.
|
||||
pub fn as_mut(&mut self) -> Spec<&mut T> {
|
||||
Spec { x: &mut self.x, y: &mut self.y }
|
||||
|
@ -190,11 +190,7 @@ impl<'a> FlowLayouter<'a> {
|
||||
let aligns = Spec::new(
|
||||
// For non-expanding paragraphs it is crucial that we align the
|
||||
// whole paragraph as it is itself aligned.
|
||||
if node.is::<ParNode>() {
|
||||
styles.get(ParNode::ALIGN)
|
||||
} else {
|
||||
Align::Left
|
||||
},
|
||||
styles.get(ParNode::ALIGN),
|
||||
// Vertical align node alignment is respected by the flow node.
|
||||
node.downcast::<AlignNode>()
|
||||
.and_then(|aligned| aligned.aligns.y)
|
||||
|
@ -40,12 +40,15 @@ impl Layout for GridNode {
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
// Prepare grid layout by unifying content and gutter tracks.
|
||||
let mut layouter = GridLayouter::new(self, regions.clone(), styles);
|
||||
let layouter = GridLayouter::new(
|
||||
self.tracks.as_deref(),
|
||||
self.gutter.as_deref(),
|
||||
&self.children,
|
||||
regions,
|
||||
styles,
|
||||
);
|
||||
|
||||
// Determine all column sizes.
|
||||
layouter.measure_columns(ctx);
|
||||
|
||||
// Layout the grid row-by-row.
|
||||
// Measure the columsna nd layout the grid row-by-row.
|
||||
layouter.layout(ctx)
|
||||
}
|
||||
}
|
||||
@ -87,9 +90,9 @@ castable! {
|
||||
}
|
||||
|
||||
/// Performs grid layout.
|
||||
struct GridLayouter<'a> {
|
||||
/// The children of the grid.
|
||||
children: &'a [PackedNode],
|
||||
pub struct GridLayouter<'a> {
|
||||
/// The grid cells.
|
||||
cells: &'a [PackedNode],
|
||||
/// The column tracks including gutter tracks.
|
||||
cols: Vec<TrackSizing>,
|
||||
/// The row tracks including gutter tracks.
|
||||
@ -102,7 +105,7 @@ struct GridLayouter<'a> {
|
||||
rcols: Vec<Length>,
|
||||
/// Rows in the current region.
|
||||
lrows: Vec<Row>,
|
||||
/// Whether the grid should expand to fill the region.
|
||||
/// Whether the grid itself should expand to fill the region.
|
||||
expand: Spec<bool>,
|
||||
/// The full height of the current region.
|
||||
full: Length,
|
||||
@ -127,19 +130,27 @@ enum Row {
|
||||
}
|
||||
|
||||
impl<'a> GridLayouter<'a> {
|
||||
/// Prepare grid layout by unifying content and gutter tracks.
|
||||
fn new(grid: &'a GridNode, mut regions: Regions, styles: StyleChain<'a>) -> Self {
|
||||
/// Create a new grid layouter.
|
||||
///
|
||||
/// This prepares grid layout by unifying content and gutter tracks.
|
||||
pub fn new(
|
||||
tracks: Spec<&[TrackSizing]>,
|
||||
gutter: Spec<&[TrackSizing]>,
|
||||
cells: &'a [PackedNode],
|
||||
regions: &Regions,
|
||||
styles: StyleChain<'a>,
|
||||
) -> Self {
|
||||
let mut cols = vec![];
|
||||
let mut rows = vec![];
|
||||
|
||||
// Number of content columns: Always at least one.
|
||||
let c = grid.tracks.x.len().max(1);
|
||||
let c = tracks.x.len().max(1);
|
||||
|
||||
// Number of content rows: At least as many as given, but also at least
|
||||
// as many as needed to place each item.
|
||||
let r = {
|
||||
let len = grid.children.len();
|
||||
let given = grid.tracks.y.len();
|
||||
let len = cells.len();
|
||||
let given = tracks.y.len();
|
||||
let needed = len / c + (len % c).clamp(0, 1);
|
||||
given.max(needed)
|
||||
};
|
||||
@ -152,14 +163,14 @@ impl<'a> GridLayouter<'a> {
|
||||
|
||||
// Collect content and gutter columns.
|
||||
for x in 0 .. c {
|
||||
cols.push(get_or(&grid.tracks.x, x, auto));
|
||||
cols.push(get_or(&grid.gutter.x, x, zero));
|
||||
cols.push(get_or(tracks.x, x, auto));
|
||||
cols.push(get_or(gutter.x, x, zero));
|
||||
}
|
||||
|
||||
// Collect content and gutter rows.
|
||||
for y in 0 .. r {
|
||||
rows.push(get_or(&grid.tracks.y, y, auto));
|
||||
rows.push(get_or(&grid.gutter.y, y, zero));
|
||||
rows.push(get_or(tracks.y, y, auto));
|
||||
rows.push(get_or(gutter.y, y, zero));
|
||||
}
|
||||
|
||||
// Remove superfluous gutter tracks.
|
||||
@ -173,10 +184,11 @@ impl<'a> GridLayouter<'a> {
|
||||
|
||||
// We use the regions for auto row measurement. Since at that moment,
|
||||
// columns are already sized, we can enable horizontal expansion.
|
||||
let mut regions = regions.clone();
|
||||
regions.expand = Spec::new(true, false);
|
||||
|
||||
Self {
|
||||
children: &grid.children,
|
||||
cells,
|
||||
cols,
|
||||
rows,
|
||||
regions,
|
||||
@ -192,6 +204,32 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines the columns sizes and then layouts the grid row-by-row.
|
||||
pub fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Arc<Frame>>> {
|
||||
self.measure_columns(ctx);
|
||||
|
||||
for y in 0 .. self.rows.len() {
|
||||
// Skip to next region if current one is full, but only for content
|
||||
// rows, not for gutter rows.
|
||||
if y % 2 == 0 && self.regions.is_full() {
|
||||
self.finish_region(ctx);
|
||||
}
|
||||
|
||||
match self.rows[y] {
|
||||
TrackSizing::Auto => self.layout_auto_row(ctx, y),
|
||||
TrackSizing::Linear(v) => self.layout_linear_row(ctx, v, y),
|
||||
TrackSizing::Fractional(v) => {
|
||||
self.cts.exact.y = Some(self.full);
|
||||
self.lrows.push(Row::Fr(v, y));
|
||||
self.fr += v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.finish_region(ctx);
|
||||
self.finished
|
||||
}
|
||||
|
||||
/// Determine all column sizes.
|
||||
fn measure_columns(&mut self, ctx: &mut LayoutContext) {
|
||||
enum Case {
|
||||
@ -354,30 +392,6 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout the grid row-by-row.
|
||||
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Arc<Frame>>> {
|
||||
for y in 0 .. self.rows.len() {
|
||||
// Skip to next region if current one is full, but only for content
|
||||
// rows, not for gutter rows.
|
||||
if y % 2 == 0 && self.regions.is_full() {
|
||||
self.finish_region(ctx);
|
||||
}
|
||||
|
||||
match self.rows[y] {
|
||||
TrackSizing::Auto => self.layout_auto_row(ctx, y),
|
||||
TrackSizing::Linear(v) => self.layout_linear_row(ctx, v, y),
|
||||
TrackSizing::Fractional(v) => {
|
||||
self.cts.exact.y = Some(self.full);
|
||||
self.lrows.push(Row::Fr(v, y));
|
||||
self.fr += v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.finish_region(ctx);
|
||||
self.finished
|
||||
}
|
||||
|
||||
/// Layout a row with automatic height. Such a row may break across multiple
|
||||
/// regions.
|
||||
fn layout_auto_row(&mut self, ctx: &mut LayoutContext, y: usize) {
|
||||
@ -599,7 +613,7 @@ impl<'a> GridLayouter<'a> {
|
||||
// Even columns and rows are children, odd ones are gutter.
|
||||
if x % 2 == 0 && y % 2 == 0 {
|
||||
let c = 1 + self.cols.len() / 2;
|
||||
self.children.get((y / 2) * c + x / 2)
|
||||
self.cells.get((y / 2) * c + x / 2)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -32,7 +32,29 @@ impl Layout for StackNode {
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
) -> Vec<Constrained<Arc<Frame>>> {
|
||||
StackLayouter::new(self, regions.clone(), styles).layout(ctx)
|
||||
let mut layouter = StackLayouter::new(self.dir, regions);
|
||||
|
||||
// Spacing to insert before the next node.
|
||||
let mut deferred = None;
|
||||
|
||||
for child in &self.children {
|
||||
match child {
|
||||
StackChild::Spacing(kind) => {
|
||||
layouter.layout_spacing(*kind);
|
||||
deferred = None;
|
||||
}
|
||||
StackChild::Node(node) => {
|
||||
if let Some(kind) = deferred {
|
||||
layouter.layout_spacing(kind);
|
||||
}
|
||||
|
||||
layouter.layout_node(ctx, node, styles);
|
||||
deferred = self.spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layouter.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,29 +87,23 @@ castable! {
|
||||
}
|
||||
|
||||
/// Performs stack layout.
|
||||
struct StackLayouter<'a> {
|
||||
/// The children of the stack.
|
||||
children: &'a [StackChild],
|
||||
pub struct StackLayouter {
|
||||
/// The stacking direction.
|
||||
dir: Dir,
|
||||
/// The axis of the stacking direction.
|
||||
axis: SpecAxis,
|
||||
/// The spacing between non-spacing children.
|
||||
spacing: Option<SpacingKind>,
|
||||
/// The regions to layout children into.
|
||||
regions: Regions,
|
||||
/// The inherited styles.
|
||||
styles: StyleChain<'a>,
|
||||
/// Whether the stack should expand to fill the region.
|
||||
/// Whether the stack itself should expand to fill the region.
|
||||
expand: Spec<bool>,
|
||||
/// The full size of `regions.current` that was available before we started
|
||||
/// subtracting.
|
||||
/// The full size of the current region that was available at the start.
|
||||
full: Size,
|
||||
/// The generic size used by the frames for the current region.
|
||||
used: Gen<Length>,
|
||||
/// The sum of fractional ratios in the current region.
|
||||
fr: Fractional,
|
||||
/// Spacing and layouted nodes.
|
||||
/// Already layouted items whose exact positions are not yet known due to
|
||||
/// fractional spacing.
|
||||
items: Vec<StackItem>,
|
||||
/// Finished frames for previous regions.
|
||||
finished: Vec<Constrained<Arc<Frame>>>,
|
||||
@ -103,24 +119,21 @@ enum StackItem {
|
||||
Frame(Arc<Frame>, Align),
|
||||
}
|
||||
|
||||
impl<'a> StackLayouter<'a> {
|
||||
impl StackLayouter {
|
||||
/// Create a new stack layouter.
|
||||
fn new(stack: &'a StackNode, mut regions: Regions, styles: StyleChain<'a>) -> Self {
|
||||
let dir = stack.dir;
|
||||
pub fn new(dir: Dir, regions: &Regions) -> Self {
|
||||
let axis = dir.axis();
|
||||
let expand = regions.expand;
|
||||
let full = regions.current;
|
||||
|
||||
// Disable expansion along the block axis for children.
|
||||
let mut regions = regions.clone();
|
||||
regions.expand.set(axis, false);
|
||||
|
||||
Self {
|
||||
children: &stack.children,
|
||||
dir,
|
||||
axis,
|
||||
spacing: stack.spacing,
|
||||
regions,
|
||||
styles,
|
||||
expand,
|
||||
full,
|
||||
used: Gen::zero(),
|
||||
@ -130,67 +143,43 @@ impl<'a> StackLayouter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout all children.
|
||||
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Arc<Frame>>> {
|
||||
// Spacing to insert before the next node.
|
||||
let mut deferred = None;
|
||||
|
||||
for child in self.children {
|
||||
match *child {
|
||||
StackChild::Spacing(kind) => {
|
||||
self.layout_spacing(kind);
|
||||
deferred = None;
|
||||
}
|
||||
StackChild::Node(ref node) => {
|
||||
if let Some(kind) = deferred {
|
||||
self.layout_spacing(kind);
|
||||
}
|
||||
|
||||
if self.regions.is_full() {
|
||||
self.finish_region();
|
||||
}
|
||||
|
||||
self.layout_node(ctx, node);
|
||||
deferred = self.spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.finish_region();
|
||||
self.finished
|
||||
}
|
||||
|
||||
/// Layout spacing.
|
||||
fn layout_spacing(&mut self, spacing: SpacingKind) {
|
||||
/// Add spacing along the spacing direction.
|
||||
pub fn layout_spacing(&mut self, spacing: SpacingKind) {
|
||||
match spacing {
|
||||
SpacingKind::Linear(v) => self.layout_absolute(v),
|
||||
SpacingKind::Linear(v) => {
|
||||
// Resolve the linear and limit it to the remaining space.
|
||||
let resolved = v.resolve(self.regions.base.get(self.axis));
|
||||
let remaining = self.regions.current.get_mut(self.axis);
|
||||
let limited = resolved.min(*remaining);
|
||||
*remaining -= limited;
|
||||
self.used.main += limited;
|
||||
self.items.push(StackItem::Absolute(resolved));
|
||||
}
|
||||
SpacingKind::Fractional(v) => {
|
||||
self.items.push(StackItem::Fractional(v));
|
||||
self.fr += v;
|
||||
self.items.push(StackItem::Fractional(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout absolute spacing.
|
||||
fn layout_absolute(&mut self, amount: Linear) {
|
||||
// Resolve the linear, limiting it to the remaining available space.
|
||||
let remaining = self.regions.current.get_mut(self.axis);
|
||||
let resolved = amount.resolve(self.regions.base.get(self.axis));
|
||||
let limited = resolved.min(*remaining);
|
||||
*remaining -= limited;
|
||||
self.used.main += limited;
|
||||
self.items.push(StackItem::Absolute(resolved));
|
||||
}
|
||||
/// Layout an arbitrary node.
|
||||
pub fn layout_node(
|
||||
&mut self,
|
||||
ctx: &mut LayoutContext,
|
||||
node: &PackedNode,
|
||||
styles: StyleChain,
|
||||
) {
|
||||
if self.regions.is_full() {
|
||||
self.finish_region();
|
||||
}
|
||||
|
||||
/// Layout a node.
|
||||
fn layout_node(&mut self, ctx: &mut LayoutContext, node: &PackedNode) {
|
||||
// Align nodes' block-axis alignment is respected by the stack node.
|
||||
let align = node
|
||||
.downcast::<AlignNode>()
|
||||
.and_then(|node| node.aligns.get(self.axis))
|
||||
.unwrap_or(self.dir.start().into());
|
||||
|
||||
let frames = node.layout(ctx, &self.regions, self.styles);
|
||||
let frames = node.layout(ctx, &self.regions, styles);
|
||||
let len = frames.len();
|
||||
for (i, frame) in frames.into_iter().enumerate() {
|
||||
// Grow our size, shrink the region and save the frame for later.
|
||||
@ -206,8 +195,8 @@ impl<'a> StackLayouter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Finish the frame for one region.
|
||||
fn finish_region(&mut self) {
|
||||
/// Advance to the next region.
|
||||
pub fn finish_region(&mut self) {
|
||||
// Determine the size of the stack in this region dependening on whether
|
||||
// the region expands.
|
||||
let used = self.used.to_spec(self.axis);
|
||||
@ -228,12 +217,8 @@ impl<'a> StackLayouter<'a> {
|
||||
// Place all frames.
|
||||
for item in self.items.drain(..) {
|
||||
match item {
|
||||
StackItem::Absolute(v) => {
|
||||
cursor += v;
|
||||
}
|
||||
StackItem::Fractional(v) => {
|
||||
cursor += v.resolve(self.fr, remaining);
|
||||
}
|
||||
StackItem::Absolute(v) => cursor += v,
|
||||
StackItem::Fractional(v) => cursor += v.resolve(self.fr, remaining),
|
||||
StackItem::Frame(frame, align) => {
|
||||
if self.dir.is_positive() {
|
||||
ruler = ruler.max(align);
|
||||
@ -270,4 +255,10 @@ impl<'a> StackLayouter<'a> {
|
||||
self.fr = Fractional::zero();
|
||||
self.finished.push(output.constrain(cts));
|
||||
}
|
||||
|
||||
/// Finish layouting and return the resulting frames.
|
||||
pub fn finish(mut self) -> Vec<Constrained<Arc<Frame>>> {
|
||||
self.finish_region();
|
||||
self.finished
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user