Rename secondary/primary to main/cross ✏
This commit is contained in:
parent
5a7a32a9ba
commit
c6a6870978
@ -4,7 +4,7 @@ use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, Font
|
||||
|
||||
use super::Scope;
|
||||
use crate::geom::{Insets, Linear, Size};
|
||||
use crate::layout::{Dir, GenAlign, LayoutAlign, LayoutSystem, Sides};
|
||||
use crate::layout::{Dir, Gen2, GenAlign, Sides};
|
||||
use crate::length::Length;
|
||||
use crate::paper::{Paper, PaperClass, PAPER_A4};
|
||||
|
||||
@ -17,10 +17,10 @@ pub struct State {
|
||||
pub text: TextState,
|
||||
/// The page state.
|
||||
pub page: PageState,
|
||||
/// The active layouting system.
|
||||
pub sys: LayoutSystem,
|
||||
/// The active layouting directions.
|
||||
pub dirs: Gen2<Dir>,
|
||||
/// The active alignments.
|
||||
pub align: LayoutAlign,
|
||||
pub aligns: Gen2<GenAlign>,
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
@ -29,8 +29,8 @@ impl Default for State {
|
||||
scope: crate::library::_std(),
|
||||
text: TextState::default(),
|
||||
page: PageState::default(),
|
||||
sys: LayoutSystem::new(Dir::LTR, Dir::TTB),
|
||||
align: LayoutAlign::new(GenAlign::Start, GenAlign::Start),
|
||||
dirs: Gen2::new(Dir::TTB, Dir::LTR),
|
||||
aligns: Gen2::new(GenAlign::Start, GenAlign::Start),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
83
src/geom.rs
83
src/geom.rs
@ -6,7 +6,7 @@ pub use kurbo::*;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::ops::*;
|
||||
|
||||
use crate::layout::primitive::{Dir, GenAlign, LayoutAlign, LayoutSystem, SpecAxis};
|
||||
use crate::layout::primitive::{Dir, Gen2, GenAlign, Side, SpecAxis};
|
||||
|
||||
/// Additional methods for [sizes].
|
||||
///
|
||||
@ -18,26 +18,28 @@ pub trait SizeExt {
|
||||
/// Borrow the component for the specified axis mutably.
|
||||
fn get_mut(&mut self, axis: SpecAxis) -> &mut f64;
|
||||
|
||||
/// Returns the generalized version of a `Size` based on the layouting
|
||||
/// system, that is:
|
||||
/// - `x` describes the primary axis instead of the horizontal one.
|
||||
/// - `y` describes the secondary axis instead of the vertical one.
|
||||
fn generalized(self, sys: LayoutSystem) -> Self;
|
||||
/// Returns the generalized version of a `Size` based on the current
|
||||
/// directions.
|
||||
///
|
||||
/// In the generalized version:
|
||||
/// - `x` describes the cross axis instead of the horizontal one.
|
||||
/// - `y` describes the main axis instead of the vertical one.
|
||||
fn generalized(self, dirs: Gen2<Dir>) -> Self;
|
||||
|
||||
/// Returns the specialized version of this generalized Size2D (inverse to
|
||||
/// Returns the specialized version of this generalized `Size` (inverse to
|
||||
/// `generalized`).
|
||||
fn specialized(self, sys: LayoutSystem) -> Self;
|
||||
fn specialized(self, dirs: Gen2<Dir>) -> Self;
|
||||
|
||||
/// Whether the given size fits into this one, that is, both coordinate
|
||||
/// values are smaller or equal.
|
||||
fn fits(self, other: Self) -> bool;
|
||||
|
||||
/// The anchor position for an object to be aligned according to `align` in
|
||||
/// a container with this size.
|
||||
/// The anchor position for an object to be aligned in a container with this
|
||||
/// size and the given directions.
|
||||
///
|
||||
/// This assumes the size to be generalized such that `width` corresponds to
|
||||
/// the primary and `height` to the secondary axis.
|
||||
fn anchor(self, align: LayoutAlign, sys: LayoutSystem) -> Point;
|
||||
/// the cross and `height` to the main axis.
|
||||
fn anchor(self, dirs: Gen2<Dir>, aligns: Gen2<GenAlign>) -> Point;
|
||||
}
|
||||
|
||||
impl SizeExt for Size {
|
||||
@ -55,25 +57,25 @@ impl SizeExt for Size {
|
||||
}
|
||||
}
|
||||
|
||||
fn generalized(self, sys: LayoutSystem) -> Self {
|
||||
match sys.primary.axis() {
|
||||
SpecAxis::Horizontal => self,
|
||||
SpecAxis::Vertical => Self::new(self.height, self.width),
|
||||
fn generalized(self, dirs: Gen2<Dir>) -> Self {
|
||||
match dirs.main.axis() {
|
||||
SpecAxis::Horizontal => Self::new(self.height, self.width),
|
||||
SpecAxis::Vertical => self,
|
||||
}
|
||||
}
|
||||
|
||||
fn specialized(self, sys: LayoutSystem) -> Self {
|
||||
fn specialized(self, dirs: Gen2<Dir>) -> Self {
|
||||
// Even though generalized is its own inverse, we still have this second
|
||||
// function, for clarity at the call-site.
|
||||
self.generalized(sys)
|
||||
self.generalized(dirs)
|
||||
}
|
||||
|
||||
fn fits(self, other: Self) -> bool {
|
||||
self.width >= other.width && self.height >= other.height
|
||||
}
|
||||
|
||||
fn anchor(self, align: LayoutAlign, sys: LayoutSystem) -> Point {
|
||||
fn anchor(length: f64, align: GenAlign, dir: Dir) -> f64 {
|
||||
fn anchor(self, dirs: Gen2<Dir>, aligns: Gen2<GenAlign>) -> Point {
|
||||
fn anchor(length: f64, dir: Dir, align: GenAlign) -> f64 {
|
||||
match (dir.is_positive(), align) {
|
||||
(true, GenAlign::Start) | (false, GenAlign::End) => 0.0,
|
||||
(_, GenAlign::Center) => length / 2.0,
|
||||
@ -82,8 +84,8 @@ impl SizeExt for Size {
|
||||
}
|
||||
|
||||
Point::new(
|
||||
anchor(self.width, align.primary, sys.primary),
|
||||
anchor(self.height, align.secondary, sys.secondary),
|
||||
anchor(self.width, dirs.cross, aligns.cross),
|
||||
anchor(self.height, dirs.main, aligns.main),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -92,34 +94,29 @@ impl SizeExt for Size {
|
||||
///
|
||||
/// [rectangles]: ../../kurbo/struct.Rect.html
|
||||
pub trait RectExt {
|
||||
/// Return the side identified by direction and alignment.
|
||||
///
|
||||
/// Center alignment is treated the same as origin alignment.
|
||||
fn get(&mut self, dir: Dir, align: GenAlign) -> f64;
|
||||
/// Return the value for the given side.
|
||||
fn get(self, side: Side) -> f64;
|
||||
|
||||
/// Get a mutable reference to the side identified by direction and
|
||||
/// alignment.
|
||||
///
|
||||
/// Center alignment is treated the same as origin alignment.
|
||||
fn get_mut(&mut self, dir: Dir, align: GenAlign) -> &mut f64;
|
||||
/// Borrow the value for the given side mutably.
|
||||
fn get_mut(&mut self, side: Side) -> &mut f64;
|
||||
}
|
||||
|
||||
impl RectExt for Rect {
|
||||
fn get(&mut self, dir: Dir, align: GenAlign) -> f64 {
|
||||
match if align == GenAlign::End { dir.inv() } else { dir } {
|
||||
Dir::LTR => self.x0,
|
||||
Dir::TTB => self.y0,
|
||||
Dir::RTL => self.x1,
|
||||
Dir::BTT => self.y1,
|
||||
fn get(self, side: Side) -> f64 {
|
||||
match side {
|
||||
Side::Left => self.x0,
|
||||
Side::Top => self.y0,
|
||||
Side::Right => self.x1,
|
||||
Side::Bottom => self.y1,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, dir: Dir, align: GenAlign) -> &mut f64 {
|
||||
match if align == GenAlign::End { dir.inv() } else { dir } {
|
||||
Dir::LTR => &mut self.x0,
|
||||
Dir::TTB => &mut self.y0,
|
||||
Dir::RTL => &mut self.x1,
|
||||
Dir::BTT => &mut self.y1,
|
||||
fn get_mut(&mut self, side: Side) -> &mut f64 {
|
||||
match side {
|
||||
Side::Left => &mut self.x0,
|
||||
Side::Top => &mut self.y0,
|
||||
Side::Right => &mut self.x1,
|
||||
Side::Bottom => &mut self.y1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
//! Arranging boxes into lines.
|
||||
//!
|
||||
//! Along the primary axis, the boxes are laid out next to each other as long as
|
||||
//! they fit into a line. When necessary, a line break is inserted and the new
|
||||
//! line is offset along the secondary axis by the height of the previous line
|
||||
//! plus extra line spacing.
|
||||
//! The boxes are laid out along the cross axis as long as they fit into a line.
|
||||
//! When necessary, a line break is inserted and the new line is offset along
|
||||
//! the main axis by the height of the previous line plus extra line spacing.
|
||||
//!
|
||||
//! Internally, the line layouter uses a stack layouter to stack the finished
|
||||
//! lines on top of each.
|
||||
@ -23,8 +22,8 @@ pub struct LineLayouter {
|
||||
/// The context for line layouting.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LineContext {
|
||||
/// The initial layouting system, which can be updated through `set_sys`.
|
||||
pub sys: LayoutSystem,
|
||||
/// The layout directions.
|
||||
pub dirs: Gen2<Dir>,
|
||||
/// The spaces to layout into.
|
||||
pub spaces: Vec<LayoutSpace>,
|
||||
/// Whether to spill over into copies of the last space or finish layouting
|
||||
@ -40,7 +39,7 @@ impl LineLayouter {
|
||||
Self {
|
||||
stack: StackLayouter::new(StackContext {
|
||||
spaces: ctx.spaces.clone(),
|
||||
sys: ctx.sys,
|
||||
dirs: ctx.dirs,
|
||||
repeat: ctx.repeat,
|
||||
}),
|
||||
ctx,
|
||||
@ -49,26 +48,24 @@ impl LineLayouter {
|
||||
}
|
||||
|
||||
/// Add a layout.
|
||||
pub fn add(&mut self, layout: BoxLayout, align: LayoutAlign) {
|
||||
let sys = self.ctx.sys;
|
||||
|
||||
if let Some(prev) = self.run.align {
|
||||
if align.secondary != prev.secondary {
|
||||
pub fn add(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) {
|
||||
if let Some(prev) = self.run.aligns {
|
||||
if aligns.main != prev.main {
|
||||
// TODO: Issue warning for non-fitting alignment in
|
||||
// non-repeating context.
|
||||
let fitting = self.stack.is_fitting_alignment(align);
|
||||
let fitting = self.stack.is_fitting_alignment(aligns);
|
||||
if !fitting && self.ctx.repeat {
|
||||
self.finish_space(true);
|
||||
} else {
|
||||
self.finish_line();
|
||||
}
|
||||
} else if align.primary < prev.primary {
|
||||
} else if aligns.cross < prev.cross {
|
||||
self.finish_line();
|
||||
} else if align.primary > prev.primary {
|
||||
} else if aligns.cross > prev.cross {
|
||||
let mut rest_run = LineRun::new();
|
||||
|
||||
let usable = self.stack.usable().get(sys.primary.axis());
|
||||
rest_run.usable = Some(match align.primary {
|
||||
let usable = self.stack.usable().get(self.ctx.dirs.cross.axis());
|
||||
rest_run.usable = Some(match aligns.cross {
|
||||
GenAlign::Start => unreachable!("start > x"),
|
||||
GenAlign::Center => usable - 2.0 * self.run.size.width,
|
||||
GenAlign::End => usable - self.run.size.width,
|
||||
@ -84,10 +81,10 @@ impl LineLayouter {
|
||||
}
|
||||
|
||||
if let LastSpacing::Soft(spacing, _) = self.run.last_spacing {
|
||||
self.add_primary_spacing(spacing, SpacingKind::Hard);
|
||||
self.add_cross_spacing(spacing, SpacingKind::Hard);
|
||||
}
|
||||
|
||||
let size = layout.size.generalized(sys);
|
||||
let size = layout.size.generalized(self.ctx.dirs);
|
||||
|
||||
if !self.usable().fits(size) {
|
||||
if !self.line_is_empty() {
|
||||
@ -100,7 +97,7 @@ impl LineLayouter {
|
||||
}
|
||||
}
|
||||
|
||||
self.run.align = Some(align);
|
||||
self.run.aligns = Some(aligns);
|
||||
self.run.layouts.push((self.run.size.width, layout));
|
||||
|
||||
self.run.size.width += size.width;
|
||||
@ -114,19 +111,25 @@ impl LineLayouter {
|
||||
/// needed.
|
||||
fn usable(&self) -> Size {
|
||||
// The base is the usable space of the stack layouter.
|
||||
let mut usable = self.stack.usable().generalized(self.ctx.sys);
|
||||
let mut usable = self.stack.usable().generalized(self.ctx.dirs);
|
||||
|
||||
// If there was another run already, override the stack's size.
|
||||
if let Some(primary) = self.run.usable {
|
||||
usable.width = primary;
|
||||
if let Some(cross) = self.run.usable {
|
||||
usable.width = cross;
|
||||
}
|
||||
|
||||
usable.width -= self.run.size.width;
|
||||
usable
|
||||
}
|
||||
|
||||
/// Finish the line and add spacing to the underlying stack.
|
||||
pub fn add_main_spacing(&mut self, spacing: f64, kind: SpacingKind) {
|
||||
self.finish_line_if_not_empty();
|
||||
self.stack.add_spacing(spacing, kind)
|
||||
}
|
||||
|
||||
/// Add spacing to the line.
|
||||
pub fn add_primary_spacing(&mut self, mut spacing: f64, kind: SpacingKind) {
|
||||
pub fn add_cross_spacing(&mut self, mut spacing: f64, kind: SpacingKind) {
|
||||
match kind {
|
||||
SpacingKind::Hard => {
|
||||
spacing = spacing.min(self.usable().width);
|
||||
@ -150,19 +153,6 @@ impl LineLayouter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Finish the line and add spacing to the underlying stack.
|
||||
pub fn add_secondary_spacing(&mut self, spacing: f64, kind: SpacingKind) {
|
||||
self.finish_line_if_not_empty();
|
||||
self.stack.add_spacing(spacing, kind)
|
||||
}
|
||||
|
||||
/// Update the layouting system.
|
||||
pub fn set_sys(&mut self, sys: LayoutSystem) {
|
||||
self.finish_line_if_not_empty();
|
||||
self.ctx.sys = sys;
|
||||
self.stack.set_sys(sys)
|
||||
}
|
||||
|
||||
/// Update the layouting spaces.
|
||||
///
|
||||
/// If `replace_empty` is true, the current space is replaced if there are
|
||||
@ -181,7 +171,7 @@ impl LineLayouter {
|
||||
/// it will fit into this layouter's underlying stack.
|
||||
pub fn remaining(&self) -> Vec<LayoutSpace> {
|
||||
let mut spaces = self.stack.remaining();
|
||||
*spaces[0].size.get_mut(self.ctx.sys.secondary.axis()) -= self.run.size.height;
|
||||
*spaces[0].size.get_mut(self.ctx.dirs.main.axis()) -= self.run.size.height;
|
||||
spaces
|
||||
}
|
||||
|
||||
@ -206,17 +196,17 @@ impl LineLayouter {
|
||||
|
||||
/// Finish the active line and start a new one.
|
||||
pub fn finish_line(&mut self) {
|
||||
let mut layout = BoxLayout::new(self.run.size.specialized(self.ctx.sys));
|
||||
let align = self.run.align.unwrap_or_default();
|
||||
let mut layout = BoxLayout::new(self.run.size.specialized(self.ctx.dirs));
|
||||
let aligns = self.run.aligns.unwrap_or_default();
|
||||
|
||||
let layouts = std::mem::take(&mut self.run.layouts);
|
||||
for (offset, child) in layouts {
|
||||
let x = match self.ctx.sys.primary.is_positive() {
|
||||
let x = match self.ctx.dirs.cross.is_positive() {
|
||||
true => offset,
|
||||
false => {
|
||||
self.run.size.width
|
||||
- offset
|
||||
- child.size.get(self.ctx.sys.primary.axis())
|
||||
- child.size.get(self.ctx.dirs.cross.axis())
|
||||
}
|
||||
};
|
||||
|
||||
@ -224,7 +214,7 @@ impl LineLayouter {
|
||||
layout.push_layout(pos, child);
|
||||
}
|
||||
|
||||
self.stack.add(layout, align);
|
||||
self.stack.add(layout, aligns);
|
||||
|
||||
self.run = LineRun::new();
|
||||
self.stack.add_spacing(self.ctx.line_spacing, SpacingKind::LINE);
|
||||
@ -249,7 +239,7 @@ struct LineRun {
|
||||
/// When a new run is created the alignment is yet to be determined and
|
||||
/// `None` as such. Once a layout is added, its alignment decides the
|
||||
/// alignment for the whole run.
|
||||
align: Option<LayoutAlign>,
|
||||
aligns: Option<Gen2<GenAlign>>,
|
||||
/// The amount of space left by another run on the same line or `None` if
|
||||
/// this is the only run so far.
|
||||
usable: Option<f64>,
|
||||
@ -263,7 +253,7 @@ impl LineRun {
|
||||
Self {
|
||||
layouts: vec![],
|
||||
size: Size::ZERO,
|
||||
align: None,
|
||||
aligns: None,
|
||||
usable: None,
|
||||
last_spacing: LastSpacing::Hard,
|
||||
}
|
||||
|
@ -11,11 +11,10 @@ pub use primitive::*;
|
||||
pub use stack::*;
|
||||
pub use tree::*;
|
||||
|
||||
use crate::geom::{Insets, Point, Rect, RectExt, Size, SizeExt};
|
||||
|
||||
use crate::diag::Diag;
|
||||
use crate::eval::{PageState, State, TextState};
|
||||
use crate::font::SharedFontLoader;
|
||||
use crate::geom::{Insets, Point, Rect, RectExt, Size, SizeExt};
|
||||
use crate::shaping::Shaped;
|
||||
use crate::syntax::{Deco, Spanned, SynTree};
|
||||
use crate::{Feedback, Pass};
|
||||
@ -29,7 +28,7 @@ pub async fn layout(
|
||||
let space = LayoutSpace {
|
||||
size: state.page.size,
|
||||
insets: state.page.insets(),
|
||||
expansion: LayoutExpansion::new(true, true),
|
||||
expansion: Spec2::new(true, true),
|
||||
};
|
||||
|
||||
let constraints = LayoutConstraints {
|
||||
@ -134,7 +133,7 @@ pub struct LayoutSpace {
|
||||
pub insets: Insets,
|
||||
/// Whether to expand the size of the resulting layout to the full size of
|
||||
/// this space or to shrink it to fit the content.
|
||||
pub expansion: LayoutExpansion,
|
||||
pub expansion: Spec2<bool>,
|
||||
}
|
||||
|
||||
impl LayoutSpace {
|
||||
@ -154,7 +153,7 @@ impl LayoutSpace {
|
||||
Self {
|
||||
size: self.usable(),
|
||||
insets: Insets::ZERO,
|
||||
expansion: LayoutExpansion::new(false, false),
|
||||
expansion: Spec2::new(false, false),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -172,8 +171,8 @@ pub enum Command {
|
||||
LayoutSyntaxTree(SynTree),
|
||||
|
||||
/// Add a finished layout.
|
||||
Add(BoxLayout, LayoutAlign),
|
||||
/// Add spacing of the given kind along the primary or secondary axis. The
|
||||
Add(BoxLayout, Gen2<GenAlign>),
|
||||
/// Add spacing of the given kind along the given axis. The
|
||||
/// kind defines how the spacing interacts with surrounding spacing.
|
||||
AddSpacing(f64, SpacingKind, GenAxis),
|
||||
|
||||
@ -187,11 +186,8 @@ pub enum Command {
|
||||
SetTextState(TextState),
|
||||
/// Update the page style.
|
||||
SetPageState(PageState),
|
||||
/// Update the layouting system along which future boxes will be laid
|
||||
/// out. This ends the current line.
|
||||
SetSystem(LayoutSystem),
|
||||
/// Update the alignment for future boxes added to this layouting process.
|
||||
SetAlignment(LayoutAlign),
|
||||
SetAlignment(Gen2<GenAlign>),
|
||||
}
|
||||
|
||||
/// Defines how spacing interacts with surrounding spacing.
|
||||
|
@ -2,30 +2,6 @@
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
/// Specifies the directions into which content is laid out.
|
||||
///
|
||||
/// The primary component defines into which direction text and lines flow and the
|
||||
/// secondary into which paragraphs and pages grow.
|
||||
pub type LayoutSystem = Gen2<Dir>;
|
||||
|
||||
impl Default for LayoutSystem {
|
||||
fn default() -> Self {
|
||||
Self::new(Dir::LTR, Dir::TTB)
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies where to align a layout in a parent container.
|
||||
pub type LayoutAlign = Gen2<GenAlign>;
|
||||
|
||||
impl Default for LayoutAlign {
|
||||
fn default() -> Self {
|
||||
Self::new(GenAlign::Start, GenAlign::Start)
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to expand a layout to an area's full size or shrink it to fit its content.
|
||||
pub type LayoutExpansion = Spec2<bool>;
|
||||
|
||||
/// The four directions into which content can be laid out.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Dir {
|
||||
@ -66,6 +42,23 @@ impl Dir {
|
||||
if self.is_positive() { 1.0 } else { -1.0 }
|
||||
}
|
||||
|
||||
/// The side of this direction the alignment identifies.
|
||||
///
|
||||
/// `Center` alignment is treated the same as `Start` alignment.
|
||||
pub fn side(self, align: GenAlign) -> Side {
|
||||
let start = match self {
|
||||
Self::LTR => Side::Left,
|
||||
Self::RTL => Side::Right,
|
||||
Self::TTB => Side::Top,
|
||||
Self::BTT => Side::Bottom,
|
||||
};
|
||||
|
||||
match align {
|
||||
GenAlign::Start | GenAlign::Center => start,
|
||||
GenAlign::End => start.inv(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The inverse direction.
|
||||
pub fn inv(self) -> Self {
|
||||
match self {
|
||||
@ -75,18 +68,6 @@ impl Dir {
|
||||
Self::BTT => Self::TTB,
|
||||
}
|
||||
}
|
||||
|
||||
/// The side of this direction the alignment identifies.
|
||||
///
|
||||
/// `Center` alignment is treated the same as `Start` alignment.
|
||||
pub fn side(self, align: GenAlign) -> Side {
|
||||
match if align == GenAlign::End { self.inv() } else { self } {
|
||||
Self::LTR => Side::Left,
|
||||
Self::RTL => Side::Right,
|
||||
Self::TTB => Side::Top,
|
||||
Self::BTT => Side::Bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Dir {
|
||||
@ -100,27 +81,159 @@ impl Display for Dir {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a type into its generic representation.
|
||||
///
|
||||
/// The generic representation deals with main and cross axes while the specific
|
||||
/// representation deals with horizontal and vertical axes.
|
||||
///
|
||||
/// See also [`ToSpec`] for the inverse conversion.
|
||||
///
|
||||
/// [`ToSpec`]: trait.ToSpec.html
|
||||
pub trait ToGen {
|
||||
/// The generic version of this type.
|
||||
type Output;
|
||||
|
||||
/// The generic version of this type based on the current directions.
|
||||
fn to_gen(self, dirs: Gen2<Dir>) -> Self::Output;
|
||||
}
|
||||
|
||||
/// Convert a type into its specific representation.
|
||||
///
|
||||
/// The specific representation deals with horizontal and vertical axes while
|
||||
/// the generic representation deals with main and cross axes.
|
||||
///
|
||||
/// See also [`ToGen`] for the inverse conversion.
|
||||
///
|
||||
/// [`ToGen`]: trait.ToGen.html
|
||||
pub trait ToSpec {
|
||||
/// The specific version of this type.
|
||||
type Output;
|
||||
|
||||
/// The specific version of this type based on the current directions.
|
||||
fn to_spec(self, dirs: Gen2<Dir>) -> Self::Output;
|
||||
}
|
||||
|
||||
/// A generic container with two components for the two generic axes.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Gen2<T> {
|
||||
/// The main component.
|
||||
pub main: T,
|
||||
/// The cross component.
|
||||
pub cross: T,
|
||||
}
|
||||
|
||||
impl<T> Gen2<T> {
|
||||
/// Create a new instance from the two components.
|
||||
pub fn new(main: T, cross: T) -> Self {
|
||||
Self { main, cross }
|
||||
}
|
||||
|
||||
/// Return the component for the specified generic axis.
|
||||
pub fn get(self, axis: GenAxis) -> T {
|
||||
match axis {
|
||||
GenAxis::Main => self.main,
|
||||
GenAxis::Cross => self.cross,
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrow the component for the specified generic axis mutably.
|
||||
pub fn get_mut(&mut self, axis: GenAxis) -> &mut T {
|
||||
match axis {
|
||||
GenAxis::Main => &mut self.main,
|
||||
GenAxis::Cross => &mut self.cross,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToSpec for Gen2<T> {
|
||||
type Output = Spec2<T>;
|
||||
|
||||
fn to_spec(self, dirs: Gen2<Dir>) -> Self::Output {
|
||||
match dirs.main.axis() {
|
||||
SpecAxis::Horizontal => Spec2::new(self.main, self.cross),
|
||||
SpecAxis::Vertical => Spec2::new(self.cross, self.main),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic container with two components for the two specific axes.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Spec2<T> {
|
||||
/// The horizontal component.
|
||||
pub horizontal: T,
|
||||
/// The vertical component.
|
||||
pub vertical: T,
|
||||
}
|
||||
|
||||
impl<T> Spec2<T> {
|
||||
/// Create a new instance from the two components.
|
||||
pub fn new(horizontal: T, vertical: T) -> Self {
|
||||
Self { horizontal, vertical }
|
||||
}
|
||||
|
||||
/// Return the component for the given specific axis.
|
||||
pub fn get(self, axis: SpecAxis) -> T {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => self.horizontal,
|
||||
SpecAxis::Vertical => self.vertical,
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrow the component for the given specific axis mutably.
|
||||
pub fn get_mut(&mut self, axis: SpecAxis) -> &mut T {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => &mut self.horizontal,
|
||||
SpecAxis::Vertical => &mut self.vertical,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ToGen for Spec2<T> {
|
||||
type Output = Gen2<T>;
|
||||
|
||||
fn to_gen(self, dirs: Gen2<Dir>) -> Self::Output {
|
||||
match dirs.main.axis() {
|
||||
SpecAxis::Horizontal => Gen2::new(self.horizontal, self.vertical),
|
||||
SpecAxis::Vertical => Gen2::new(self.vertical, self.horizontal),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The two generic layouting axes.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum GenAxis {
|
||||
/// The primary layouting direction into which text and lines flow.
|
||||
Primary,
|
||||
/// The secondary layouting direction into which paragraphs grow.
|
||||
Secondary,
|
||||
/// The axis pages and paragraphs are set along.
|
||||
Main,
|
||||
/// The axis words and lines are set along.
|
||||
Cross,
|
||||
}
|
||||
|
||||
impl GenAxis {
|
||||
/// The specific version of this axis in the given layout system.
|
||||
pub fn to_spec(self, sys: LayoutSystem) -> SpecAxis {
|
||||
sys.get(self).axis()
|
||||
/// The other axis.
|
||||
pub fn other(self) -> Self {
|
||||
match self {
|
||||
Self::Main => Self::Cross,
|
||||
Self::Cross => Self::Main,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSpec for GenAxis {
|
||||
type Output = SpecAxis;
|
||||
|
||||
fn to_spec(self, dirs: Gen2<Dir>) -> Self::Output {
|
||||
match self {
|
||||
Self::Main => dirs.main.axis(),
|
||||
Self::Cross => dirs.cross.axis(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GenAxis {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
Self::Primary => "primary",
|
||||
Self::Secondary => "secondary",
|
||||
Self::Main => "main",
|
||||
Self::Cross => "cross",
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -128,19 +241,31 @@ impl Display for GenAxis {
|
||||
/// The two specific layouting axes.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum SpecAxis {
|
||||
/// The horizontal layouting axis.
|
||||
Horizontal,
|
||||
/// The vertical layouting axis.
|
||||
Vertical,
|
||||
/// The horizontal layouting axis.
|
||||
Horizontal,
|
||||
}
|
||||
|
||||
impl SpecAxis {
|
||||
/// The generic version of this axis in the given layout system.
|
||||
pub fn to_gen(self, sys: LayoutSystem) -> GenAxis {
|
||||
if self == sys.primary.axis() {
|
||||
GenAxis::Primary
|
||||
/// The other axis.
|
||||
pub fn other(self) -> Self {
|
||||
match self {
|
||||
Self::Horizontal => Self::Vertical,
|
||||
Self::Vertical => Self::Horizontal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToGen for SpecAxis {
|
||||
type Output = GenAxis;
|
||||
|
||||
fn to_gen(self, dirs: Gen2<Dir>) -> Self::Output {
|
||||
if self == dirs.main.axis() {
|
||||
GenAxis::Main
|
||||
} else {
|
||||
GenAxis::Secondary
|
||||
debug_assert_eq!(self, dirs.cross.axis());
|
||||
GenAxis::Cross
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -148,8 +273,8 @@ impl SpecAxis {
|
||||
impl Display for SpecAxis {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
Self::Horizontal => "horizontal",
|
||||
Self::Vertical => "vertical",
|
||||
Self::Horizontal => "horizontal",
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -173,6 +298,12 @@ impl GenAlign {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GenAlign {
|
||||
fn default() -> Self {
|
||||
Self::Start
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GenAlign {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
@ -207,21 +338,36 @@ impl SpecAlign {
|
||||
}
|
||||
}
|
||||
|
||||
/// The generic version of this alignment in the given layout system.
|
||||
pub fn to_gen(self, sys: LayoutSystem) -> GenAlign {
|
||||
let get = |spec: SpecAxis, positive: GenAlign| {
|
||||
if sys.get(spec.to_gen(sys)).is_positive() {
|
||||
positive
|
||||
/// The inverse alignment.
|
||||
pub fn inv(self) -> Self {
|
||||
match self {
|
||||
Self::Left => Self::Right,
|
||||
Self::Right => Self::Left,
|
||||
Self::Top => Self::Bottom,
|
||||
Self::Bottom => Self::Top,
|
||||
Self::Center => Self::Center,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToGen for SpecAlign {
|
||||
type Output = GenAlign;
|
||||
|
||||
fn to_gen(self, dirs: Gen2<Dir>) -> Self::Output {
|
||||
let dirs = dirs.to_spec(dirs);
|
||||
let get = |dir: Dir, at_positive_start| {
|
||||
if dir.is_positive() == at_positive_start {
|
||||
GenAlign::Start
|
||||
} else {
|
||||
positive.inv()
|
||||
GenAlign::End
|
||||
}
|
||||
};
|
||||
|
||||
match self {
|
||||
Self::Left => get(SpecAxis::Horizontal, GenAlign::Start),
|
||||
Self::Right => get(SpecAxis::Horizontal, GenAlign::End),
|
||||
Self::Top => get(SpecAxis::Vertical, GenAlign::Start),
|
||||
Self::Bottom => get(SpecAxis::Vertical, GenAlign::End),
|
||||
Self::Left => get(dirs.horizontal, true),
|
||||
Self::Right => get(dirs.horizontal, false),
|
||||
Self::Top => get(dirs.vertical, true),
|
||||
Self::Bottom => get(dirs.vertical, false),
|
||||
Self::Center => GenAlign::Center,
|
||||
}
|
||||
}
|
||||
@ -239,91 +385,6 @@ impl Display for SpecAlign {
|
||||
}
|
||||
}
|
||||
|
||||
/// A side of a container.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Side {
|
||||
Left,
|
||||
Top,
|
||||
Right,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
impl Side {
|
||||
/// The opposite side.
|
||||
pub fn inv(self) -> Self {
|
||||
match self {
|
||||
Self::Left => Self::Right,
|
||||
Self::Top => Self::Bottom,
|
||||
Self::Right => Self::Left,
|
||||
Self::Bottom => Self::Top,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic container with two components for the two generic axes.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Gen2<T> {
|
||||
/// The primary component.
|
||||
pub primary: T,
|
||||
/// The secondary component.
|
||||
pub secondary: T,
|
||||
}
|
||||
|
||||
impl<T> Gen2<T> {
|
||||
/// Create a new instance from the two components.
|
||||
pub fn new(primary: T, secondary: T) -> Self {
|
||||
Self { primary, secondary }
|
||||
}
|
||||
|
||||
/// Return the component for the specified generic axis.
|
||||
pub fn get(self, axis: GenAxis) -> T {
|
||||
match axis {
|
||||
GenAxis::Primary => self.primary,
|
||||
GenAxis::Secondary => self.secondary,
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrow the component for the specified generic axis mutably.
|
||||
pub fn get_mut(&mut self, axis: GenAxis) -> &mut T {
|
||||
match axis {
|
||||
GenAxis::Primary => &mut self.primary,
|
||||
GenAxis::Secondary => &mut self.secondary,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic container with two components for the two specific axes.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Spec2<T> {
|
||||
/// The horizontal component.
|
||||
pub horizontal: T,
|
||||
/// The vertical component.
|
||||
pub vertical: T,
|
||||
}
|
||||
|
||||
impl<T> Spec2<T> {
|
||||
/// Create a new instance from the two components.
|
||||
pub fn new(horizontal: T, vertical: T) -> Self {
|
||||
Self { horizontal, vertical }
|
||||
}
|
||||
|
||||
/// Return the component for the given specific axis.
|
||||
pub fn get(self, axis: SpecAxis) -> T {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => self.horizontal,
|
||||
SpecAxis::Vertical => self.vertical,
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrow the component for the given specific axis mutably.
|
||||
pub fn get_mut(&mut self, axis: SpecAxis) -> &mut T {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => &mut self.horizontal,
|
||||
SpecAxis::Vertical => &mut self.vertical,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic container with left, top, right and bottom components.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Sides<T> {
|
||||
@ -360,8 +421,8 @@ impl<T> Sides<T> {
|
||||
pub fn get(self, side: Side) -> T {
|
||||
match side {
|
||||
Side::Left => self.left,
|
||||
Side::Right => self.right,
|
||||
Side::Top => self.top,
|
||||
Side::Right => self.right,
|
||||
Side::Bottom => self.bottom,
|
||||
}
|
||||
}
|
||||
@ -370,9 +431,30 @@ impl<T> Sides<T> {
|
||||
pub fn get_mut(&mut self, side: Side) -> &mut T {
|
||||
match side {
|
||||
Side::Left => &mut self.left,
|
||||
Side::Right => &mut self.right,
|
||||
Side::Top => &mut self.top,
|
||||
Side::Right => &mut self.right,
|
||||
Side::Bottom => &mut self.bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A side of a container.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Side {
|
||||
Left,
|
||||
Top,
|
||||
Right,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
impl Side {
|
||||
/// The opposite side.
|
||||
pub fn inv(self) -> Self {
|
||||
match self {
|
||||
Self::Left => Self::Right,
|
||||
Self::Top => Self::Bottom,
|
||||
Self::Right => Self::Left,
|
||||
Self::Bottom => Self::Top,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! Arranging boxes into a stack along the secondary axis.
|
||||
//! Arranging boxes into a stack along the main axis.
|
||||
//!
|
||||
//! Individual layouts can be aligned at `Start`, `Center` or `End` along both
|
||||
//! axes. These alignments are with respect to the size of the finished layout
|
||||
@ -34,8 +34,8 @@ pub struct StackLayouter {
|
||||
/// The context for stack layouting.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StackContext {
|
||||
/// The initial layouting system, which can be updated through `set_sys`.
|
||||
pub sys: LayoutSystem,
|
||||
/// The layouting directions.
|
||||
pub dirs: Gen2<Dir>,
|
||||
/// The spaces to layout into.
|
||||
pub spaces: Vec<LayoutSpace>,
|
||||
/// Whether to spill over into copies of the last space or finish layouting
|
||||
@ -55,15 +55,15 @@ impl StackLayouter {
|
||||
}
|
||||
|
||||
/// Add a layout to the stack.
|
||||
pub fn add(&mut self, layout: BoxLayout, align: LayoutAlign) {
|
||||
pub fn add(&mut self, layout: BoxLayout, aligns: Gen2<GenAlign>) {
|
||||
// If the alignment cannot be fitted in this space, finish it.
|
||||
// TODO: Issue warning for non-fitting alignment in non-repeating
|
||||
// context.
|
||||
if !self.update_rulers(align) && self.ctx.repeat {
|
||||
if !self.update_rulers(aligns) && self.ctx.repeat {
|
||||
self.finish_space(true);
|
||||
}
|
||||
|
||||
// Now, we add a possibly cached soft space. If the secondary alignment
|
||||
// Now, we add a possibly cached soft space. If the main alignment
|
||||
// changed before, a possibly cached space would have already been
|
||||
// discarded.
|
||||
if let LastSpacing::Soft(spacing, _) = self.space.last_spacing {
|
||||
@ -76,11 +76,11 @@ impl StackLayouter {
|
||||
}
|
||||
|
||||
// Change the usable space and size of the space.
|
||||
self.update_metrics(layout.size.generalized(self.ctx.sys));
|
||||
self.update_metrics(layout.size.generalized(self.ctx.dirs));
|
||||
|
||||
// Add the box to the vector and remember that spacings are allowed
|
||||
// again.
|
||||
self.space.layouts.push((self.ctx.sys, align, layout));
|
||||
self.space.layouts.push((self.ctx.dirs, aligns, layout));
|
||||
self.space.last_spacing = LastSpacing::None;
|
||||
}
|
||||
|
||||
@ -90,15 +90,15 @@ impl StackLayouter {
|
||||
// A hard space is simply an empty box.
|
||||
SpacingKind::Hard => {
|
||||
// Reduce the spacing such that it definitely fits.
|
||||
let axis = self.ctx.sys.secondary.axis();
|
||||
let axis = self.ctx.dirs.main.axis();
|
||||
spacing = spacing.min(self.space.usable.get(axis));
|
||||
|
||||
let size = Size::new(0.0, spacing);
|
||||
self.update_metrics(size);
|
||||
self.space.layouts.push((
|
||||
self.ctx.sys,
|
||||
LayoutAlign::default(),
|
||||
BoxLayout::new(size.specialized(self.ctx.sys)),
|
||||
self.ctx.dirs,
|
||||
Gen2::default(),
|
||||
BoxLayout::new(size.specialized(self.ctx.dirs)),
|
||||
));
|
||||
|
||||
self.space.last_spacing = LastSpacing::Hard;
|
||||
@ -121,37 +121,34 @@ impl StackLayouter {
|
||||
}
|
||||
|
||||
fn update_metrics(&mut self, added: Size) {
|
||||
let sys = self.ctx.sys;
|
||||
|
||||
let mut size = self.space.size.generalized(sys);
|
||||
let mut extra = self.space.extra.generalized(sys);
|
||||
let mut size = self.space.size.generalized(self.ctx.dirs);
|
||||
let mut extra = self.space.extra.generalized(self.ctx.dirs);
|
||||
|
||||
size.width += (added.width - extra.width).max(0.0);
|
||||
size.height += (added.height - extra.height).max(0.0);
|
||||
|
||||
extra.width = extra.width.max(added.width);
|
||||
extra.height = (extra.height - added.height).max(0.0);
|
||||
|
||||
self.space.size = size.specialized(sys);
|
||||
self.space.extra = extra.specialized(sys);
|
||||
*self.space.usable.get_mut(sys.secondary.axis()) -= added.height;
|
||||
self.space.size = size.specialized(self.ctx.dirs);
|
||||
self.space.extra = extra.specialized(self.ctx.dirs);
|
||||
*self.space.usable.get_mut(self.ctx.dirs.main.axis()) -= added.height;
|
||||
}
|
||||
|
||||
/// Returns true if a space break is necessary.
|
||||
fn update_rulers(&mut self, align: LayoutAlign) -> bool {
|
||||
let allowed = self.is_fitting_alignment(align);
|
||||
fn update_rulers(&mut self, aligns: Gen2<GenAlign>) -> bool {
|
||||
let allowed = self.is_fitting_alignment(aligns);
|
||||
if allowed {
|
||||
let side = self.ctx.sys.secondary.side(GenAlign::Start);
|
||||
*self.space.rulers.get_mut(side) = align.secondary;
|
||||
let side = self.ctx.dirs.main.side(GenAlign::Start);
|
||||
*self.space.rulers.get_mut(side) = aligns.main;
|
||||
}
|
||||
allowed
|
||||
}
|
||||
|
||||
/// Whether a layout with the given alignment can still be layouted into the
|
||||
/// active space or a space break is necessary.
|
||||
pub(crate) fn is_fitting_alignment(&self, align: LayoutAlign) -> bool {
|
||||
self.is_fitting_axis(self.ctx.sys.primary, align.primary)
|
||||
&& self.is_fitting_axis(self.ctx.sys.secondary, align.secondary)
|
||||
pub(crate) fn is_fitting_alignment(&self, aligns: Gen2<GenAlign>) -> bool {
|
||||
self.is_fitting_axis(self.ctx.dirs.main, aligns.main)
|
||||
&& self.is_fitting_axis(self.ctx.dirs.cross, aligns.cross)
|
||||
}
|
||||
|
||||
fn is_fitting_axis(&self, dir: Dir, align: GenAlign) -> bool {
|
||||
@ -159,16 +156,6 @@ impl StackLayouter {
|
||||
&& align <= self.space.rulers.get(dir.side(GenAlign::End)).inv()
|
||||
}
|
||||
|
||||
/// Update the layouting system.
|
||||
pub fn set_sys(&mut self, sys: LayoutSystem) {
|
||||
// Forget the spacing because it is not relevant anymore.
|
||||
if sys.secondary != self.ctx.sys.secondary {
|
||||
self.space.last_spacing = LastSpacing::Hard;
|
||||
}
|
||||
|
||||
self.ctx.sys = sys;
|
||||
}
|
||||
|
||||
/// Update the layouting spaces.
|
||||
///
|
||||
/// If `replace_empty` is true, the current space is replaced if there are
|
||||
@ -200,12 +187,10 @@ impl StackLayouter {
|
||||
/// The remaining inner spaces. If something is laid out into these spaces,
|
||||
/// it will fit into this stack.
|
||||
pub fn remaining(&self) -> Vec<LayoutSpace> {
|
||||
let size = self.usable();
|
||||
|
||||
let mut spaces = vec![LayoutSpace {
|
||||
size,
|
||||
size: self.usable(),
|
||||
insets: Insets::ZERO,
|
||||
expansion: LayoutExpansion::new(false, false),
|
||||
expansion: Spec2::new(false, false),
|
||||
}];
|
||||
|
||||
for space in &self.ctx.spaces[self.next_space() ..] {
|
||||
@ -219,7 +204,7 @@ impl StackLayouter {
|
||||
pub fn usable(&self) -> Size {
|
||||
self.space.usable
|
||||
- Size::new(0.0, self.space.last_spacing.soft_or_zero())
|
||||
.specialized(self.ctx.sys)
|
||||
.specialized(self.ctx.dirs)
|
||||
}
|
||||
|
||||
/// Whether the current layout space is empty.
|
||||
@ -274,7 +259,7 @@ impl StackLayouter {
|
||||
y1: start.y + self.space.size.height,
|
||||
};
|
||||
|
||||
for (sys, _, layout) in &self.space.layouts {
|
||||
for &(dirs, _, ref layout) in &self.space.layouts {
|
||||
// First, we store the bounds calculated so far (which were reduced
|
||||
// by the predecessors of this layout) as the initial bounding box
|
||||
// of this layout.
|
||||
@ -283,41 +268,42 @@ impl StackLayouter {
|
||||
// Then, we reduce the bounding box for the following layouts. This
|
||||
// layout uses up space from the origin to the end. Thus, it reduces
|
||||
// the usable space for following layouts at its origin by its
|
||||
// extent along the secondary axis.
|
||||
*bound.get_mut(sys.secondary, GenAlign::Start) +=
|
||||
sys.secondary.factor() * layout.size.get(sys.secondary.axis());
|
||||
// main-axis extent.
|
||||
*bound.get_mut(dirs.main.side(GenAlign::Start)) +=
|
||||
dirs.main.factor() * layout.size.get(dirs.main.axis());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Step 3: Backward pass. Reduce the bounding boxes from the previous
|
||||
// layouts by what is taken by the following ones.
|
||||
|
||||
// The `x` field stores the maximal primary extent in one axis-aligned
|
||||
// run, while the `y` fields stores the accumulated secondary extent.
|
||||
// The `x` field stores the maximal cross-axis extent in one
|
||||
// axis-aligned run, while the `y` fields stores the accumulated
|
||||
// main-axis extent.
|
||||
let mut extent = Size::ZERO;
|
||||
let mut rotation = SpecAxis::Vertical;
|
||||
|
||||
for (bound, entry) in bounds.iter_mut().zip(&self.space.layouts).rev() {
|
||||
let (sys, _, layout) = entry;
|
||||
let &(dirs, _, ref layout) = entry;
|
||||
|
||||
// When the axes are rotated, the maximal primary size (`extent.x`)
|
||||
// dictates how much secondary extent the whole run had. This value
|
||||
// is thus stored in `extent.y`. The primary extent is reset for
|
||||
// this new axis-aligned run.
|
||||
if rotation != sys.secondary.axis() {
|
||||
// When the axes are rotated, the maximal cross-axis size
|
||||
// (`extent.x`) dictates how much main-axis extent the whole run
|
||||
// had. This value is thus stored in `extent.y`. The cross-axis
|
||||
// extent is reset for this new axis-aligned run.
|
||||
if rotation != dirs.main.axis() {
|
||||
extent.height = extent.width;
|
||||
extent.width = 0.0;
|
||||
rotation = sys.secondary.axis();
|
||||
rotation = dirs.main.axis();
|
||||
}
|
||||
|
||||
// We reduce the bounding box of this layout at its end by the
|
||||
// accumulated secondary extent of all layouts we have seen so far,
|
||||
// accumulated main-axis extent of all layouts we have seen so far,
|
||||
// which are the layouts after this one since we iterate reversed.
|
||||
*bound.get_mut(sys.secondary, GenAlign::End) -=
|
||||
sys.secondary.factor() * extent.height;
|
||||
*bound.get_mut(dirs.main.side(GenAlign::End)) -=
|
||||
dirs.main.factor() * extent.height;
|
||||
|
||||
// Then, we add this layout's secondary extent to the accumulator.
|
||||
let size = layout.size.generalized(*sys);
|
||||
// Then, we add this layout's main-axis extent to the accumulator.
|
||||
let size = layout.size.generalized(dirs);
|
||||
extent.width = extent.width.max(size.width);
|
||||
extent.height += size.height;
|
||||
}
|
||||
@ -329,14 +315,14 @@ impl StackLayouter {
|
||||
let mut layout = BoxLayout::new(size);
|
||||
|
||||
let layouts = std::mem::take(&mut self.space.layouts);
|
||||
for ((sys, align, child), bound) in layouts.into_iter().zip(bounds) {
|
||||
let size = child.size.specialized(sys);
|
||||
for ((dirs, aligns, child), bound) in layouts.into_iter().zip(bounds) {
|
||||
let size = child.size.specialized(dirs);
|
||||
|
||||
// The space in which this layout is aligned is given by the
|
||||
// distances between the borders of its bounding box.
|
||||
let usable = bound.size().generalized(sys);
|
||||
let local = usable.anchor(align, sys) - size.anchor(align, sys);
|
||||
let pos = bound.origin() + local.to_size().specialized(sys).to_vec2();
|
||||
let usable = bound.size().generalized(dirs);
|
||||
let local = usable.anchor(dirs, aligns) - size.anchor(dirs, aligns);
|
||||
let pos = bound.origin() + local.to_size().specialized(dirs).to_vec2();
|
||||
|
||||
layout.push_layout(pos, child);
|
||||
}
|
||||
@ -359,7 +345,7 @@ impl StackLayouter {
|
||||
}
|
||||
}
|
||||
|
||||
/// A layout space composed of subspaces which can have different systems and
|
||||
/// A layout space composed of subspaces which can have different directions and
|
||||
/// alignments.
|
||||
struct Space {
|
||||
/// The index of this space in `ctx.spaces`.
|
||||
@ -367,7 +353,7 @@ struct Space {
|
||||
/// Whether to include a layout for this space even if it would be empty.
|
||||
hard: bool,
|
||||
/// The so-far accumulated layouts.
|
||||
layouts: Vec<(LayoutSystem, LayoutAlign, BoxLayout)>,
|
||||
layouts: Vec<(Gen2<Dir>, Gen2<GenAlign>, BoxLayout)>,
|
||||
/// The specialized size of this space.
|
||||
size: Size,
|
||||
/// The specialized remaining space.
|
||||
|
@ -26,7 +26,7 @@ impl<'a> TreeLayouter<'a> {
|
||||
fn new(ctx: &'a mut LayoutContext) -> Self {
|
||||
let layouter = LineLayouter::new(LineContext {
|
||||
spaces: ctx.constraints.spaces.clone(),
|
||||
sys: ctx.state.sys,
|
||||
dirs: ctx.state.dirs,
|
||||
repeat: ctx.constraints.repeat,
|
||||
line_spacing: ctx.state.text.line_spacing(),
|
||||
});
|
||||
@ -89,14 +89,12 @@ impl<'a> TreeLayouter<'a> {
|
||||
|
||||
fn layout_space(&mut self) {
|
||||
self.layouter
|
||||
.add_primary_spacing(self.ctx.state.text.word_spacing(), SpacingKind::WORD);
|
||||
.add_cross_spacing(self.ctx.state.text.word_spacing(), SpacingKind::WORD);
|
||||
}
|
||||
|
||||
fn layout_parbreak(&mut self) {
|
||||
self.layouter.add_secondary_spacing(
|
||||
self.ctx.state.text.par_spacing(),
|
||||
SpacingKind::PARAGRAPH,
|
||||
);
|
||||
self.layouter
|
||||
.add_main_spacing(self.ctx.state.text.par_spacing(), SpacingKind::PARAGRAPH);
|
||||
}
|
||||
|
||||
async fn layout_text(&mut self, text: &str) {
|
||||
@ -117,14 +115,14 @@ impl<'a> TreeLayouter<'a> {
|
||||
let boxed = shaping::shape(
|
||||
text,
|
||||
self.ctx.state.text.font_size(),
|
||||
self.ctx.state.sys.primary,
|
||||
self.ctx.state.dirs.cross,
|
||||
&mut self.ctx.loader.borrow_mut(),
|
||||
&self.ctx.state.text.fallback,
|
||||
variant,
|
||||
)
|
||||
.await;
|
||||
|
||||
self.layouter.add(boxed, self.ctx.state.align);
|
||||
self.layouter.add(boxed, self.ctx.state.aligns);
|
||||
}
|
||||
|
||||
async fn layout_heading(&mut self, heading: &NodeHeading) {
|
||||
@ -187,10 +185,10 @@ impl<'a> TreeLayouter<'a> {
|
||||
match command {
|
||||
LayoutSyntaxTree(tree) => self.layout_tree(&tree).await,
|
||||
|
||||
Add(layout, align) => self.layouter.add(layout, align),
|
||||
Add(layout, aligns) => self.layouter.add(layout, aligns),
|
||||
AddSpacing(space, kind, axis) => match axis {
|
||||
GenAxis::Primary => self.layouter.add_primary_spacing(space, kind),
|
||||
GenAxis::Secondary => self.layouter.add_secondary_spacing(space, kind),
|
||||
GenAxis::Main => self.layouter.add_main_spacing(space, kind),
|
||||
GenAxis::Cross => self.layouter.add_cross_spacing(space, kind),
|
||||
},
|
||||
|
||||
BreakLine => self.layouter.finish_line(),
|
||||
@ -219,7 +217,7 @@ impl<'a> TreeLayouter<'a> {
|
||||
let space = LayoutSpace {
|
||||
size: style.size,
|
||||
insets: style.insets(),
|
||||
expansion: LayoutExpansion::new(true, true),
|
||||
expansion: Spec2::new(true, true),
|
||||
};
|
||||
self.constraints.base = space.usable();
|
||||
self.layouter.set_spaces(vec![space], true);
|
||||
@ -230,12 +228,7 @@ impl<'a> TreeLayouter<'a> {
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
SetAlignment(align) => self.ctx.state.align = align,
|
||||
SetSystem(sys) => {
|
||||
self.layouter.set_sys(sys);
|
||||
self.ctx.state.sys = sys;
|
||||
}
|
||||
SetAlignment(aligns) => self.ctx.state.aligns = aligns,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ pub async fn align(mut args: Args, ctx: &mut LayoutContext) -> Value {
|
||||
Some(tree) => vec![
|
||||
SetAlignment(aligns),
|
||||
LayoutSyntaxTree(tree),
|
||||
SetAlignment(ctx.state.align),
|
||||
SetAlignment(ctx.state.aligns),
|
||||
],
|
||||
None => vec![SetAlignment(aligns)],
|
||||
})
|
||||
@ -45,8 +45,8 @@ pub async fn align(mut args: Args, ctx: &mut LayoutContext) -> Value {
|
||||
fn dedup_aligns(
|
||||
ctx: &mut LayoutContext,
|
||||
iter: impl Iterator<Item = (Option<SpecAxis>, Spanned<SpecAlign>)>,
|
||||
) -> LayoutAlign {
|
||||
let mut aligns = ctx.state.align;
|
||||
) -> Gen2<GenAlign> {
|
||||
let mut aligns = ctx.state.aligns;
|
||||
let mut had = Gen2::new(false, false);
|
||||
let mut had_center = false;
|
||||
|
||||
@ -54,8 +54,8 @@ fn dedup_aligns(
|
||||
// Check whether we know which axis this alignment belongs to.
|
||||
if let Some(axis) = axis {
|
||||
// We know the axis.
|
||||
let gen_axis = axis.to_gen(ctx.state.sys);
|
||||
let gen_align = align.to_gen(ctx.state.sys);
|
||||
let gen_axis = axis.to_gen(ctx.state.dirs);
|
||||
let gen_align = align.to_gen(ctx.state.dirs);
|
||||
|
||||
if align.axis().map_or(false, |a| a != axis) {
|
||||
ctx.diag(error!(
|
||||
@ -73,14 +73,13 @@ fn dedup_aligns(
|
||||
// positional argument.
|
||||
debug_assert_eq!(align, SpecAlign::Center);
|
||||
|
||||
if had.primary && had.secondary {
|
||||
if had.main && had.cross {
|
||||
ctx.diag(error!(span, "duplicate alignment"));
|
||||
} else if had_center {
|
||||
// Both this and the previous one are unspecified `center`
|
||||
// alignments. Both axes should be centered.
|
||||
aligns = LayoutAlign::new(GenAlign::Center, GenAlign::Center);
|
||||
had.primary = true;
|
||||
had.secondary = true;
|
||||
aligns = Gen2::new(GenAlign::Center, GenAlign::Center);
|
||||
had = Gen2::new(true, true);
|
||||
} else {
|
||||
had_center = true;
|
||||
}
|
||||
@ -88,22 +87,22 @@ fn dedup_aligns(
|
||||
|
||||
// If we we know one alignment, we can handle the unspecified `center`
|
||||
// alignment.
|
||||
if had_center && (had.primary || had.secondary) {
|
||||
if had.primary {
|
||||
aligns.secondary = GenAlign::Center;
|
||||
had.secondary = true;
|
||||
if had_center && (had.main || had.cross) {
|
||||
if had.main {
|
||||
aligns.cross = GenAlign::Center;
|
||||
had.cross = true;
|
||||
} else {
|
||||
aligns.primary = GenAlign::Center;
|
||||
had.primary = true;
|
||||
aligns.main = GenAlign::Center;
|
||||
had.main = true;
|
||||
}
|
||||
had_center = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If center has not been flushed by now, it is the only argument and then
|
||||
// we default to applying it to the primary axis.
|
||||
// we default to applying it to the cross axis.
|
||||
if had_center {
|
||||
aligns.primary = GenAlign::Center;
|
||||
aligns.cross = GenAlign::Center;
|
||||
}
|
||||
|
||||
aligns
|
||||
|
@ -12,7 +12,7 @@ pub async fn boxed(mut args: Args, ctx: &mut LayoutContext) -> Value {
|
||||
let height = args.get::<_, Linear>(ctx, "height");
|
||||
args.done(ctx);
|
||||
|
||||
let align = ctx.state.align;
|
||||
let aligns = ctx.state.aligns;
|
||||
let constraints = &mut ctx.constraints;
|
||||
constraints.base = constraints.spaces[0].size;
|
||||
constraints.spaces.truncate(1);
|
||||
@ -35,5 +35,5 @@ pub async fn boxed(mut args: Args, ctx: &mut LayoutContext) -> Value {
|
||||
let layouted = layout_tree(&body, ctx).await;
|
||||
let layout = layouted.into_iter().next().unwrap();
|
||||
|
||||
Value::Commands(vec![Add(layout, align)])
|
||||
Value::Commands(vec![Add(layout, aligns)])
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ fn spacing(mut args: Args, ctx: &mut LayoutContext, axis: SpecAxis) -> Value {
|
||||
|
||||
Value::Commands(if let Some(spacing) = spacing {
|
||||
let spacing = spacing.eval(ctx.state.text.font_size());
|
||||
let axis = axis.to_gen(ctx.state.sys);
|
||||
let axis = axis.to_gen(ctx.state.dirs);
|
||||
vec![AddSpacing(spacing, SpacingKind::Hard, axis)]
|
||||
} else {
|
||||
vec![]
|
||||
|
Loading…
Reference in New Issue
Block a user