mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Refactor stack and flex layouter ♻
This commit is contained in:
parent
5e41672a91
commit
c768b8b61f
@ -17,14 +17,13 @@ pub enum LayoutAction {
|
|||||||
/// Write text starting at the current position.
|
/// Write text starting at the current position.
|
||||||
WriteText(String),
|
WriteText(String),
|
||||||
/// Visualize a box for debugging purposes.
|
/// Visualize a box for debugging purposes.
|
||||||
/// Arguments are position and size.
|
/// The arguments are position and size.
|
||||||
DebugBox(Size2D, Size2D),
|
DebugBox(Size2D, Size2D),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutAction {
|
impl LayoutAction {
|
||||||
/// Serialize this layout action into a string representation.
|
/// Serialize this layout action into an easy-to-parse string representation.
|
||||||
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
|
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
|
||||||
use LayoutAction::*;
|
|
||||||
match self {
|
match self {
|
||||||
MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()),
|
MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()),
|
||||||
SetFont(i, s) => write!(f, "f {} {}", i, s),
|
SetFont(i, s) => write!(f, "f {} {}", i, s),
|
||||||
@ -55,7 +54,17 @@ impl Display for LayoutAction {
|
|||||||
|
|
||||||
debug_display!(LayoutAction);
|
debug_display!(LayoutAction);
|
||||||
|
|
||||||
/// Unifies and otimizes lists of actions.
|
/// A sequence of layouting actions.
|
||||||
|
///
|
||||||
|
/// The sequence of actions is optimized as the actions are added. For example,
|
||||||
|
/// a font changing option will only be added if the selected font is not already active.
|
||||||
|
/// All configuration actions (like moving, setting fonts, ...) are only flushed when
|
||||||
|
/// content is written.
|
||||||
|
///
|
||||||
|
/// Furthermore, the action list can translate absolute position into a coordinate system
|
||||||
|
/// with a different. This is realized in the `add_box` method, which allows a layout to
|
||||||
|
/// be added at a position, effectively translating all movement actions inside the layout
|
||||||
|
/// by the position.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LayoutActionList {
|
pub struct LayoutActionList {
|
||||||
pub origin: Size2D,
|
pub origin: Size2D,
|
||||||
@ -77,8 +86,7 @@ impl LayoutActionList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an action to the list if it is not useless
|
/// Add an action to the list.
|
||||||
/// (like changing to a font that is already active).
|
|
||||||
pub fn add(&mut self, action: LayoutAction) {
|
pub fn add(&mut self, action: LayoutAction) {
|
||||||
match action {
|
match action {
|
||||||
MoveAbsolute(pos) => self.next_pos = Some(self.origin + pos),
|
MoveAbsolute(pos) => self.next_pos = Some(self.origin + pos),
|
||||||
@ -89,16 +97,8 @@ impl LayoutActionList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(target) = self.next_pos.take() {
|
self.flush_position();
|
||||||
self.actions.push(MoveAbsolute(target));
|
self.flush_font();
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((index, size)) = self.next_font.take() {
|
|
||||||
if (index, size) != self.active_font {
|
|
||||||
self.actions.push(SetFont(index, size));
|
|
||||||
self.active_font = (index, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.actions.push(action);
|
self.actions.push(action);
|
||||||
}
|
}
|
||||||
@ -113,20 +113,16 @@ impl LayoutActionList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add all actions from a box layout at a position. A move to the position
|
/// Add a layout at a position. All move actions inside the layout are translated
|
||||||
/// is generated and all moves inside the box layout are translated as
|
/// by the position.
|
||||||
/// necessary.
|
pub fn add_layout(&mut self, position: Size2D, layout: Layout) {
|
||||||
pub fn add_box(&mut self, position: Size2D, layout: Layout) {
|
self.flush_position();
|
||||||
if let Some(target) = self.next_pos.take() {
|
|
||||||
self.actions.push(MoveAbsolute(target));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.next_pos = Some(position);
|
|
||||||
self.origin = position;
|
self.origin = position;
|
||||||
|
self.next_pos = Some(position);
|
||||||
|
|
||||||
if layout.debug_render {
|
if layout.debug_render {
|
||||||
self.actions
|
self.actions.push(DebugBox(position, layout.dimensions));
|
||||||
.push(LayoutAction::DebugBox(position, layout.dimensions));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.extend(layout.actions);
|
self.extend(layout.actions);
|
||||||
@ -141,4 +137,21 @@ impl LayoutActionList {
|
|||||||
pub fn into_vec(self) -> Vec<LayoutAction> {
|
pub fn into_vec(self) -> Vec<LayoutAction> {
|
||||||
self.actions
|
self.actions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Append a cached move action if one is cached.
|
||||||
|
fn flush_position(&mut self) {
|
||||||
|
if let Some(target) = self.next_pos.take() {
|
||||||
|
self.actions.push(MoveAbsolute(target));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append a cached font-setting action if one is cached.
|
||||||
|
fn flush_font(&mut self) {
|
||||||
|
if let Some((index, size)) = self.next_font.take() {
|
||||||
|
if (index, size) != self.active_font {
|
||||||
|
self.actions.push(SetFont(index, size));
|
||||||
|
self.active_font = (index, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,33 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Finishes a flex layout by justifying the positions of the individual boxes.
|
/// Flex-layouting of boxes.
|
||||||
#[derive(Debug)]
|
///
|
||||||
|
/// The boxes are arranged in "lines", each line having the height of its
|
||||||
|
/// biggest box. When a box does not fit on a line anymore horizontally,
|
||||||
|
/// a new line is started.
|
||||||
|
///
|
||||||
|
/// The flex layouter does not actually compute anything until the `finish`
|
||||||
|
/// method is called. The reason for this is the flex layouter will have
|
||||||
|
/// the capability to justify its layouts, later. To find a good justification
|
||||||
|
/// it needs total information about the contents.
|
||||||
|
///
|
||||||
|
/// There are two different kinds units that can be added to a flex run:
|
||||||
|
/// Normal layouts and _glue_. _Glue_ layouts are only written if a normal
|
||||||
|
/// layout follows and a glue layout is omitted if the following layout
|
||||||
|
/// flows into a new line. A _glue_ layout is typically used for a space character
|
||||||
|
/// since it prevents a space from appearing in the beginning or end of a line.
|
||||||
|
/// However, it can be any layout.
|
||||||
pub struct FlexLayouter {
|
pub struct FlexLayouter {
|
||||||
ctx: FlexContext,
|
ctx: FlexContext,
|
||||||
units: Vec<FlexUnit>,
|
units: Vec<FlexUnit>,
|
||||||
|
|
||||||
actions: LayoutActionList,
|
actions: LayoutActionList,
|
||||||
dimensions: Size2D,
|
|
||||||
usable: Size2D,
|
usable: Size2D,
|
||||||
|
dimensions: Size2D,
|
||||||
cursor: Size2D,
|
cursor: Size2D,
|
||||||
|
|
||||||
line_content: Vec<(Size2D, Layout)>,
|
run: FlexRun,
|
||||||
line_metrics: Size2D,
|
next_glue: Option<Layout>,
|
||||||
last_glue: Option<Layout>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The context for flex layouting.
|
/// The context for flex layouting.
|
||||||
@ -21,12 +35,10 @@ pub struct FlexLayouter {
|
|||||||
pub struct FlexContext {
|
pub struct FlexContext {
|
||||||
/// The space to layout the boxes in.
|
/// The space to layout the boxes in.
|
||||||
pub space: LayoutSpace,
|
pub space: LayoutSpace,
|
||||||
/// The flex spacing between two lines of boxes.
|
/// The spacing between two lines of boxes.
|
||||||
pub flex_spacing: Size,
|
pub flex_spacing: Size,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A unit in a flex layout.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
enum FlexUnit {
|
enum FlexUnit {
|
||||||
/// A content unit to be arranged flexibly.
|
/// A content unit to be arranged flexibly.
|
||||||
Boxed(Layout),
|
Boxed(Layout),
|
||||||
@ -36,6 +48,11 @@ enum FlexUnit {
|
|||||||
Glue(Layout),
|
Glue(Layout),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FlexRun {
|
||||||
|
content: Vec<(Size2D, Layout)>,
|
||||||
|
size: Size2D,
|
||||||
|
}
|
||||||
|
|
||||||
impl FlexLayouter {
|
impl FlexLayouter {
|
||||||
/// Create a new flex layouter.
|
/// Create a new flex layouter.
|
||||||
pub fn new(ctx: FlexContext) -> FlexLayouter {
|
pub fn new(ctx: FlexContext) -> FlexLayouter {
|
||||||
@ -44,16 +61,16 @@ impl FlexLayouter {
|
|||||||
units: vec![],
|
units: vec![],
|
||||||
|
|
||||||
actions: LayoutActionList::new(),
|
actions: LayoutActionList::new(),
|
||||||
|
usable: ctx.space.usable(),
|
||||||
dimensions: match ctx.space.alignment {
|
dimensions: match ctx.space.alignment {
|
||||||
Alignment::Left => Size2D::zero(),
|
Alignment::Left => Size2D::zero(),
|
||||||
Alignment::Right => Size2D::with_x(ctx.space.usable().x),
|
Alignment::Right => Size2D::with_x(ctx.space.usable().x),
|
||||||
},
|
},
|
||||||
usable: ctx.space.usable(),
|
|
||||||
cursor: Size2D::new(ctx.space.padding.left, ctx.space.padding.top),
|
cursor: Size2D::new(ctx.space.padding.left, ctx.space.padding.top),
|
||||||
|
|
||||||
line_content: vec![],
|
run: FlexRun::new(),
|
||||||
line_metrics: Size2D::zero(),
|
next_glue: None,
|
||||||
last_glue: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,27 +89,22 @@ impl FlexLayouter {
|
|||||||
self.units.push(FlexUnit::Glue(glue));
|
self.units.push(FlexUnit::Glue(glue));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this layouter contains any items.
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.units.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute the justified layout.
|
/// Compute the justified layout.
|
||||||
pub fn finish(mut self) -> LayoutResult<Layout> {
|
pub fn finish(mut self) -> LayoutResult<Layout> {
|
||||||
// Move the units out of the layout.
|
// Move the units out of the layout because otherwise, we run into
|
||||||
|
// ownership problems.
|
||||||
let units = self.units;
|
let units = self.units;
|
||||||
self.units = vec![];
|
self.units = Vec::new();
|
||||||
|
|
||||||
// Arrange the units.
|
|
||||||
for unit in units {
|
for unit in units {
|
||||||
match unit {
|
match unit {
|
||||||
FlexUnit::Boxed(boxed) => self.boxed(boxed)?,
|
FlexUnit::Boxed(boxed) => self.layout_box(boxed)?,
|
||||||
FlexUnit::Glue(glue) => self.glue(glue),
|
FlexUnit::Glue(glue) => self.layout_glue(glue),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush everything to get the correct dimensions.
|
// Finish the last flex run.
|
||||||
self.newline();
|
self.finish_flex_run();
|
||||||
|
|
||||||
Ok(Layout {
|
Ok(Layout {
|
||||||
dimensions: if self.ctx.space.shrink_to_fit {
|
dimensions: if self.ctx.space.shrink_to_fit {
|
||||||
@ -105,82 +117,95 @@ impl FlexLayouter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the box.
|
/// Whether this layouter contains any items.
|
||||||
fn boxed(&mut self, boxed: Layout) -> LayoutResult<()> {
|
pub fn is_empty(&self) -> bool {
|
||||||
let last_glue_x = self
|
self.units.is_empty()
|
||||||
.last_glue
|
}
|
||||||
|
|
||||||
|
fn layout_box(&mut self, boxed: Layout) -> LayoutResult<()> {
|
||||||
|
let next_glue_width = self
|
||||||
|
.next_glue
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|g| g.dimensions.x)
|
.map(|g| g.dimensions.x)
|
||||||
.unwrap_or(Size::zero());
|
.unwrap_or(Size::zero());
|
||||||
|
|
||||||
// Move to the next line if necessary.
|
let new_line_width = self.run.size.x + next_glue_width + boxed.dimensions.x;
|
||||||
if self.line_metrics.x + boxed.dimensions.x + last_glue_x > self.usable.x {
|
|
||||||
// If it still does not fit, we stand no chance.
|
if self.overflows(new_line_width) {
|
||||||
if boxed.dimensions.x > self.usable.x {
|
// If the box does not even fit on its own line, then
|
||||||
|
// we can't do anything.
|
||||||
|
if self.overflows(boxed.dimensions.x) {
|
||||||
return Err(LayoutError::NotEnoughSpace);
|
return Err(LayoutError::NotEnoughSpace);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.newline();
|
self.finish_flex_run();
|
||||||
} else if let Some(glue) = self.last_glue.take() {
|
} else {
|
||||||
self.append(glue);
|
// Only add the glue if we did not move to a new line.
|
||||||
|
self.flush_glue();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.append(boxed);
|
self.add_to_flex_run(boxed);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the glue.
|
fn layout_glue(&mut self, glue: Layout) {
|
||||||
fn glue(&mut self, glue: Layout) {
|
self.flush_glue();
|
||||||
if let Some(glue) = self.last_glue.take() {
|
self.next_glue = Some(glue);
|
||||||
self.append(glue);
|
}
|
||||||
|
|
||||||
|
fn flush_glue(&mut self) {
|
||||||
|
if let Some(glue) = self.next_glue.take() {
|
||||||
|
self.add_to_flex_run(glue);
|
||||||
}
|
}
|
||||||
self.last_glue = Some(glue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append a box to the layout without checking anything.
|
fn add_to_flex_run(&mut self, layout: Layout) {
|
||||||
fn append(&mut self, layout: Layout) {
|
let position = self.cursor;
|
||||||
let dim = layout.dimensions;
|
|
||||||
self.line_content.push((self.cursor, layout));
|
|
||||||
|
|
||||||
self.line_metrics.x += dim.x;
|
self.cursor.x += layout.dimensions.x;
|
||||||
self.line_metrics.y = crate::size::max(self.line_metrics.y, dim.y);
|
self.run.size.x += layout.dimensions.x;
|
||||||
self.cursor.x += dim.x;
|
self.run.size.y = crate::size::max(self.run.size.y, layout.dimensions.y);
|
||||||
|
|
||||||
|
self.run.content.push((position, layout));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move to the next line.
|
fn finish_flex_run(&mut self) {
|
||||||
fn newline(&mut self) {
|
// Add all layouts from the current flex run at the correct positions.
|
||||||
// Move all actions into this layout and translate absolute positions.
|
match self.ctx.space.alignment {
|
||||||
let remaining_space = Size2D::with_x(self.ctx.space.usable().x - self.line_metrics.x);
|
Alignment::Left => {
|
||||||
for (cursor, layout) in self.line_content.drain(..) {
|
for (position, layout) in self.run.content.drain(..) {
|
||||||
let position = match self.ctx.space.alignment {
|
self.actions.add_layout(position, layout);
|
||||||
Alignment::Left => cursor,
|
|
||||||
Alignment::Right => {
|
|
||||||
// Right align everything by shifting it right by the
|
|
||||||
// amount of space left to the right of the line.
|
|
||||||
cursor + remaining_space
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
self.actions.add_box(position, layout);
|
Alignment::Right => {
|
||||||
|
let extra_space = Size2D::with_x(self.usable.x - self.run.size.x);
|
||||||
|
for (position, layout) in self.run.content.drain(..) {
|
||||||
|
self.actions.add_layout(position + extra_space, layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stretch the dimensions to at least the line width.
|
self.dimensions.x = crate::size::max(self.dimensions.x, self.run.size.x);
|
||||||
self.dimensions.x = crate::size::max(self.dimensions.x, self.line_metrics.x);
|
self.dimensions.y += self.ctx.flex_spacing;
|
||||||
|
self.dimensions.y += self.run.size.y;
|
||||||
|
|
||||||
// If we wrote a line previously add the inter-line spacing.
|
|
||||||
if self.dimensions.y > Size::zero() {
|
|
||||||
self.dimensions.y += self.ctx.flex_spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.dimensions.y += self.line_metrics.y;
|
|
||||||
|
|
||||||
// Reset the cursor the left and move down by the line and the inter-line
|
|
||||||
// spacing.
|
|
||||||
self.cursor.x = self.ctx.space.padding.left;
|
self.cursor.x = self.ctx.space.padding.left;
|
||||||
self.cursor.y += self.line_metrics.y + self.ctx.flex_spacing;
|
self.cursor.y += self.run.size.y + self.ctx.flex_spacing;
|
||||||
|
self.run.size = Size2D::zero();
|
||||||
|
}
|
||||||
|
|
||||||
// Reset the current line metrics.
|
fn overflows(&self, line: Size) -> bool {
|
||||||
self.line_metrics = Size2D::zero();
|
line > self.usable.x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlexRun {
|
||||||
|
fn new() -> FlexRun {
|
||||||
|
FlexRun {
|
||||||
|
content: vec![],
|
||||||
|
size: Size2D::zero()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,7 +267,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
|||||||
|
|
||||||
let boxed = layout.finish()?;
|
let boxed = layout.finish()?;
|
||||||
|
|
||||||
self.stack_layouter.add_box(boxed)
|
self.stack_layouter.add(boxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a function.
|
/// Layout a function.
|
||||||
@ -287,7 +287,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
|||||||
for command in commands {
|
for command in commands {
|
||||||
match command {
|
match command {
|
||||||
Command::Layout(tree) => self.layout(tree)?,
|
Command::Layout(tree) => self.layout(tree)?,
|
||||||
Command::Add(layout) => self.stack_layouter.add_box(layout)?,
|
Command::Add(layout) => self.stack_layouter.add(layout)?,
|
||||||
Command::AddMany(layouts) => self.stack_layouter.add_many(layouts)?,
|
Command::AddMany(layouts) => self.stack_layouter.add_many(layouts)?,
|
||||||
Command::ToggleStyleClass(class) => self.style.to_mut().toggle_class(class),
|
Command::ToggleStyleClass(class) => self.style.to_mut().toggle_class(class),
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,41 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Layouts boxes block-style.
|
/// Stack-like layouting of boxes.
|
||||||
#[derive(Debug)]
|
///
|
||||||
|
/// The boxes are arranged vertically, each layout gettings it's own "line".
|
||||||
pub struct StackLayouter {
|
pub struct StackLayouter {
|
||||||
ctx: StackContext,
|
ctx: StackContext,
|
||||||
actions: LayoutActionList,
|
actions: LayoutActionList,
|
||||||
dimensions: Size2D,
|
|
||||||
usable: Size2D,
|
usable: Size2D,
|
||||||
|
dimensions: Size2D,
|
||||||
cursor: Size2D,
|
cursor: Size2D,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The context for the [`StackLayouter`].
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct StackContext {
|
pub struct StackContext {
|
||||||
pub space: LayoutSpace,
|
pub space: LayoutSpace,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StackLayouter {
|
impl StackLayouter {
|
||||||
/// Create a new box layouter.
|
/// Create a new stack layouter.
|
||||||
pub fn new(ctx: StackContext) -> StackLayouter {
|
pub fn new(ctx: StackContext) -> StackLayouter {
|
||||||
let space = ctx.space;
|
let space = ctx.space;
|
||||||
|
|
||||||
StackLayouter {
|
StackLayouter {
|
||||||
ctx,
|
ctx,
|
||||||
actions: LayoutActionList::new(),
|
actions: LayoutActionList::new(),
|
||||||
|
|
||||||
|
usable: ctx.space.usable(),
|
||||||
dimensions: match ctx.space.alignment {
|
dimensions: match ctx.space.alignment {
|
||||||
Alignment::Left => Size2D::zero(),
|
Alignment::Left => Size2D::zero(),
|
||||||
Alignment::Right => Size2D::with_x(space.usable().x),
|
Alignment::Right => Size2D::with_x(space.usable().x),
|
||||||
},
|
},
|
||||||
usable: space.usable(),
|
|
||||||
cursor: Size2D::new(
|
cursor: Size2D::new(
|
||||||
|
// If left-align, the cursor points to the top-left corner of
|
||||||
|
// each box. If we right-align, it points to the top-right
|
||||||
|
// corner.
|
||||||
match ctx.space.alignment {
|
match ctx.space.alignment {
|
||||||
Alignment::Left => space.padding.left,
|
Alignment::Left => space.padding.left,
|
||||||
Alignment::Right => space.dimensions.x - space.padding.right,
|
Alignment::Right => space.dimensions.x - space.padding.right,
|
||||||
@ -43,22 +50,17 @@ impl StackLayouter {
|
|||||||
&self.ctx
|
&self.ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a sublayout.
|
/// Add a sublayout to the bottom.
|
||||||
pub fn add_box(&mut self, layout: Layout) -> LayoutResult<()> {
|
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
|
||||||
// In the flow direction (vertical) add the layout and in the second
|
let new_dimensions = Size2D {
|
||||||
// direction just consider the maximal size of any child layout.
|
|
||||||
let new_size = Size2D {
|
|
||||||
x: crate::size::max(self.dimensions.x, layout.dimensions.x),
|
x: crate::size::max(self.dimensions.x, layout.dimensions.x),
|
||||||
y: self.dimensions.y + layout.dimensions.y,
|
y: self.dimensions.y + layout.dimensions.y,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check whether this box fits.
|
if self.overflows(new_dimensions) {
|
||||||
if self.overflows(new_size) {
|
|
||||||
return Err(LayoutError::NotEnoughSpace);
|
return Err(LayoutError::NotEnoughSpace);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.dimensions = new_size;
|
|
||||||
|
|
||||||
// Determine where to put the box. When we right-align it, we want the
|
// Determine where to put the box. When we right-align it, we want the
|
||||||
// cursor to point to the top-right corner of the box. Therefore, the
|
// cursor to point to the top-right corner of the box. Therefore, the
|
||||||
// position has to be moved to the left by the width of the box.
|
// position has to be moved to the left by the width of the box.
|
||||||
@ -68,28 +70,23 @@ impl StackLayouter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.cursor.y += layout.dimensions.y;
|
self.cursor.y += layout.dimensions.y;
|
||||||
|
self.dimensions = new_dimensions;
|
||||||
|
|
||||||
self.add_box_absolute(position, layout);
|
self.actions.add_layout(position, layout);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add multiple sublayouts.
|
/// Add multiple sublayouts from a multi-layout.
|
||||||
pub fn add_many(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
|
pub fn add_many(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
|
||||||
for layout in layouts {
|
for layout in layouts {
|
||||||
self.add_box(layout)?;
|
self.add(layout)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a sublayout at an absolute position.
|
/// Add vertical space after the last layout.
|
||||||
pub fn add_box_absolute(&mut self, position: Size2D, layout: Layout) {
|
|
||||||
self.actions.add_box(position, layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add space in between two boxes.
|
|
||||||
pub fn add_space(&mut self, space: Size) -> LayoutResult<()> {
|
pub fn add_space(&mut self, space: Size) -> LayoutResult<()> {
|
||||||
// Check whether this space fits.
|
|
||||||
if self.overflows(self.dimensions + Size2D::with_y(space)) {
|
if self.overflows(self.dimensions + Size2D::with_y(space)) {
|
||||||
return Err(LayoutError::NotEnoughSpace);
|
return Err(LayoutError::NotEnoughSpace);
|
||||||
}
|
}
|
||||||
@ -100,20 +97,7 @@ impl StackLayouter {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The remaining space for new boxes.
|
/// Finish the layouting.
|
||||||
pub fn remaining(&self) -> Size2D {
|
|
||||||
Size2D {
|
|
||||||
x: self.usable.x,
|
|
||||||
y: self.usable.y - self.dimensions.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether this layouter contains any items.
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.actions.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finish the layouting and create a box layout from this.
|
|
||||||
pub fn finish(self) -> Layout {
|
pub fn finish(self) -> Layout {
|
||||||
Layout {
|
Layout {
|
||||||
dimensions: if self.ctx.space.shrink_to_fit {
|
dimensions: if self.ctx.space.shrink_to_fit {
|
||||||
@ -126,8 +110,20 @@ impl StackLayouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the given box is bigger than what we can hold.
|
/// The remaining space for new layouts.
|
||||||
|
pub fn remaining(&self) -> Size2D {
|
||||||
|
Size2D {
|
||||||
|
x: self.usable.x,
|
||||||
|
y: self.usable.y - self.dimensions.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this layouter contains any items.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.actions.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
fn overflows(&self, dimensions: Size2D) -> bool {
|
fn overflows(&self, dimensions: Size2D) -> bool {
|
||||||
dimensions.x > self.usable.x || dimensions.y > self.usable.y
|
!self.usable.fits(dimensions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,6 +130,13 @@ impl Size2D {
|
|||||||
y: self.y + padding.top + padding.bottom,
|
y: self.y + padding.top + padding.bottom,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the given [`Size2D`] fits into this one, that is,
|
||||||
|
/// both coordinate values are smaller.
|
||||||
|
#[inline]
|
||||||
|
pub fn fits(&self, other: Size2D) -> bool {
|
||||||
|
self.x >= other.x && self.y >= other.y
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SizeBox {
|
impl SizeBox {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user