mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
307 lines
8.4 KiB
Rust
307 lines
8.4 KiB
Rust
use super::*;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct FlexLayouter {
|
|
axes: LayoutAxes,
|
|
flex_spacing: Size,
|
|
stack: StackLayouter,
|
|
|
|
units: Vec<FlexUnit>,
|
|
line: FlexLine,
|
|
part: PartialLine,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
enum FlexUnit {
|
|
Boxed(Layout),
|
|
Space(Size, bool),
|
|
SetAxes(LayoutAxes),
|
|
Break,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct FlexLine {
|
|
usable: Size,
|
|
actions: LayoutActionList,
|
|
combined_dimensions: Size2D,
|
|
}
|
|
|
|
impl FlexLine {
|
|
fn new(usable: Size) -> FlexLine {
|
|
FlexLine {
|
|
usable,
|
|
actions: LayoutActionList::new(),
|
|
combined_dimensions: Size2D::zero(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct PartialLine {
|
|
usable: Size,
|
|
content: Vec<(Size, Layout)>,
|
|
dimensions: Size2D,
|
|
space: Option<Size>,
|
|
last_was_space: bool,
|
|
}
|
|
|
|
impl PartialLine {
|
|
fn new(usable: Size) -> PartialLine {
|
|
PartialLine {
|
|
usable,
|
|
content: vec![],
|
|
dimensions: Size2D::zero(),
|
|
space: None,
|
|
last_was_space: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The context for flex layouting.
|
|
///
|
|
/// See [`LayoutContext`] for details about the fields.
|
|
#[derive(Debug, Clone)]
|
|
pub struct FlexContext {
|
|
pub spaces: LayoutSpaces,
|
|
pub axes: LayoutAxes,
|
|
pub expand: bool,
|
|
pub flex_spacing: Size,
|
|
}
|
|
|
|
impl FlexLayouter {
|
|
/// Create a new flex layouter.
|
|
pub fn new(ctx: FlexContext) -> FlexLayouter {
|
|
let stack = StackLayouter::new(StackContext {
|
|
spaces: ctx.spaces,
|
|
axes: ctx.axes,
|
|
expand: ctx.expand,
|
|
});
|
|
|
|
let usable = stack.primary_usable();
|
|
|
|
FlexLayouter {
|
|
axes: ctx.axes,
|
|
flex_spacing: ctx.flex_spacing,
|
|
stack,
|
|
|
|
units: vec![],
|
|
line: FlexLine::new(usable),
|
|
part: PartialLine::new(usable),
|
|
}
|
|
}
|
|
|
|
pub fn add(&mut self, layout: Layout) {
|
|
self.units.push(FlexUnit::Boxed(layout));
|
|
}
|
|
|
|
pub fn add_multiple(&mut self, layouts: MultiLayout) {
|
|
for layout in layouts {
|
|
self.add(layout);
|
|
}
|
|
}
|
|
|
|
pub fn add_break(&mut self) {
|
|
self.units.push(FlexUnit::Break);
|
|
}
|
|
|
|
pub fn add_primary_space(&mut self, space: Size, soft: bool) {
|
|
self.units.push(FlexUnit::Space(space, soft))
|
|
}
|
|
|
|
pub fn add_secondary_space(&mut self, space: Size, soft: bool) -> LayoutResult<()> {
|
|
if !self.run_is_empty() {
|
|
self.finish_run()?;
|
|
}
|
|
Ok(self.stack.add_space(space, soft))
|
|
}
|
|
|
|
pub fn set_axes(&mut self, axes: LayoutAxes) {
|
|
self.units.push(FlexUnit::SetAxes(axes));
|
|
}
|
|
|
|
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
|
|
if replace_empty && self.run_is_empty() && self.stack.space_is_empty() {
|
|
self.stack.set_spaces(spaces, true);
|
|
self.start_line();
|
|
} else {
|
|
self.stack.set_spaces(spaces, false);
|
|
}
|
|
}
|
|
|
|
pub fn remaining(&self) -> LayoutResult<(LayoutSpaces, Option<LayoutSpaces>)> {
|
|
if self.run_is_empty() {
|
|
Ok((self.stack.remaining(), None))
|
|
} else {
|
|
let mut future = self.clone();
|
|
let remaining_run = future.finish_run()?;
|
|
|
|
let stack_spaces = future.stack.remaining();
|
|
let mut flex_spaces = stack_spaces.clone();
|
|
flex_spaces[0].dimensions.x = remaining_run.x;
|
|
flex_spaces[0].dimensions.y += remaining_run.y;
|
|
|
|
Ok((flex_spaces, Some(stack_spaces)))
|
|
}
|
|
}
|
|
|
|
pub fn run_is_empty(&self) -> bool {
|
|
!self.units.iter().any(|unit| matches!(unit, FlexUnit::Boxed(_)))
|
|
}
|
|
|
|
pub fn run_last_is_space(&self) -> bool {
|
|
matches!(self.units.last(), Some(FlexUnit::Space(_, _)))
|
|
}
|
|
|
|
pub fn finish(mut self) -> LayoutResult<MultiLayout> {
|
|
self.finish_space(false)?;
|
|
Ok(self.stack.finish())
|
|
}
|
|
|
|
pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> {
|
|
if !self.run_is_empty() {
|
|
self.finish_run()?;
|
|
}
|
|
|
|
self.stack.finish_space(hard);
|
|
Ok(self.start_line())
|
|
}
|
|
|
|
pub fn finish_run(&mut self) -> LayoutResult<Size2D> {
|
|
let units = std::mem::replace(&mut self.units, vec![]);
|
|
for unit in units {
|
|
match unit {
|
|
FlexUnit::Boxed(boxed) => self.layout_box(boxed)?,
|
|
FlexUnit::Space(space, soft) => self.layout_space(space, soft),
|
|
FlexUnit::SetAxes(axes) => self.layout_set_axes(axes),
|
|
FlexUnit::Break => { self.finish_line()?; },
|
|
}
|
|
}
|
|
|
|
self.finish_line()
|
|
}
|
|
|
|
fn finish_line(&mut self) -> LayoutResult<Size2D> {
|
|
self.finish_partial_line();
|
|
|
|
if self.axes.primary.needs_expansion() {
|
|
self.line.combined_dimensions.x = self.line.usable;
|
|
}
|
|
|
|
self.stack.add(Layout {
|
|
dimensions: self.axes.specialize(
|
|
self.line.combined_dimensions+ Size2D::with_y(self.flex_spacing)
|
|
),
|
|
actions: self.line.actions.to_vec(),
|
|
debug_render: false,
|
|
})?;
|
|
|
|
let remaining = self.axes.specialize(Size2D {
|
|
x: self.part.usable
|
|
- self.part.dimensions.x
|
|
- self.part.space.unwrap_or(Size::zero()),
|
|
y: self.line.combined_dimensions.y,
|
|
});
|
|
|
|
self.start_line();
|
|
|
|
Ok(remaining)
|
|
}
|
|
|
|
fn start_line(&mut self) {
|
|
let usable = self.stack.primary_usable();
|
|
self.line = FlexLine::new(usable);
|
|
self.part = PartialLine::new(usable);
|
|
}
|
|
|
|
fn finish_partial_line(&mut self) {
|
|
let factor = self.axes.primary.axis.factor();
|
|
let anchor =
|
|
self.axes.primary.anchor(self.line.usable)
|
|
- self.axes.primary.anchor(self.part.dimensions.x);
|
|
|
|
for (offset, layout) in self.part.content.drain(..) {
|
|
let pos = self.axes.specialize(Size2D::with_x(anchor + factor * offset));
|
|
self.line.actions.add_layout(pos, layout);
|
|
}
|
|
|
|
self.line.combined_dimensions.x = match self.axes.primary.alignment {
|
|
Alignment::Origin => self.part.dimensions.x,
|
|
Alignment::Center => self.part.usable / 2 + self.part.dimensions.x / 2,
|
|
Alignment::End => self.part.usable,
|
|
};
|
|
|
|
self.line.combined_dimensions.y.max_eq(self.part.dimensions.y);
|
|
}
|
|
|
|
fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> {
|
|
let size = self.axes.generalize(boxed.dimensions);
|
|
|
|
let new_dimension = self.part.dimensions.x
|
|
+ self.part.space.unwrap_or(Size::zero());
|
|
|
|
if new_dimension > self.part.usable {
|
|
self.finish_line()?;
|
|
|
|
while size.x > self.line.usable {
|
|
if self.stack.space_is_last() {
|
|
Err(LayoutError::NotEnoughSpace("failed to add box to flex run"))?;
|
|
}
|
|
|
|
self.stack.finish_space(true);
|
|
}
|
|
}
|
|
|
|
if let Some(space) = self.part.space.take() {
|
|
self.layout_space(space, false);
|
|
}
|
|
|
|
let offset = self.part.dimensions.x;
|
|
self.part.content.push((offset, boxed));
|
|
|
|
self.part.dimensions.x += size.x;
|
|
self.part.dimensions.y.max_eq(size.y);
|
|
self.part.last_was_space = false;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn layout_space(&mut self, space: Size, soft: bool) {
|
|
if soft {
|
|
if !self.part.last_was_space {
|
|
self.part.space = Some(space);
|
|
}
|
|
} else {
|
|
if self.part.dimensions.x + space > self.part.usable {
|
|
self.part.dimensions.x = self.part.usable;
|
|
} else {
|
|
self.part.dimensions.x += space;
|
|
}
|
|
self.part.last_was_space = true;
|
|
}
|
|
}
|
|
|
|
fn layout_set_axes(&mut self, axes: LayoutAxes) {
|
|
if axes.primary != self.axes.primary {
|
|
self.finish_partial_line();
|
|
|
|
let extent = self.line.combined_dimensions.x;
|
|
let usable = self.line.usable;
|
|
|
|
let new_usable = match axes.primary.alignment {
|
|
Alignment::Origin if extent == Size::zero() => usable,
|
|
Alignment::Center if extent < usable / 2 => usable - 2 * extent,
|
|
Alignment::End => usable - extent,
|
|
_ => Size::zero(),
|
|
};
|
|
|
|
self.part = PartialLine::new(new_usable);
|
|
}
|
|
|
|
if axes.secondary != self.axes.secondary {
|
|
self.stack.set_axes(axes);
|
|
}
|
|
|
|
self.axes = axes;
|
|
}
|
|
}
|