Horizontal layout (woohoo!)

This commit is contained in:
mars 2022-10-24 00:31:34 -06:00
parent 63b985d230
commit 7c8e913f0d
7 changed files with 175 additions and 56 deletions

16
Cargo.lock generated
View File

@ -49,6 +49,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "figlet-rs"
version = "0.1.4"
@ -87,6 +93,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.134"
@ -227,6 +242,7 @@ dependencies = [
"bitflags",
"figlet-rs",
"fnv",
"itertools",
"logos",
"strum",
"textwrap",

View File

@ -8,6 +8,7 @@ ansi_term = "0.12"
bitflags = "1.3"
figlet-rs = "0.1"
fnv = "1.0"
itertools = "0.10"
logos = "0.12"
strum = { version = "0.24", features = ["derive"] }
textwrap = "0.15"

View File

@ -1,7 +1,8 @@
(p "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
(box :axis horizontal
(p "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
(p "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."))
(p :min-width 20 "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
(p :min-width 20 "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
(p :min-width 20 "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."))
(p "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")

View File

@ -49,6 +49,17 @@ pub trait ParseScoped: Sized {
fn parse_scoped(args: &mut Args, scope: &str, default: Self::Default) -> ParseResult<Self>;
}
impl<T: ParseValue> ParseScoped for T {
type Default = T;
fn parse_scoped(args: &mut Args, scope: &str, default: Self::Default) -> ParseResult<Self> {
if let Some(arg) = args.get(scope) {
T::parse_value(&arg.val)
} else {
Ok(default)
}
}
}
impl ParseValue for usize {
fn parse_value(val: &Value) -> ParseResult<Self> {
match val.inner {

View File

@ -1,8 +1,8 @@
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use crate::args::ParseArgs;
use crate::ast::{self, ParseErrorKind, TagBody};
use crate::args::{Args, ParseArgs, ParseScoped};
use crate::ast::{self, ParseErrorKind, ParseResult, TagBody};
use crate::block::{AllowedChildren, Block, BlockTagKind};
use crate::layout::{Axis, BoxLayout, Dimensions, RenderedBlock};
use crate::node::{NodeId, NodeStore};
@ -19,7 +19,7 @@ impl Dom {
pub fn parse(body: &TagBody) -> DomResult<Self> {
let mut builder = DomBuilder::default();
let root = builder.new_node();
let mut block = BoxBuilder::new(root);
let mut block = BoxBuilder::root(root);
let mut text_style = TextStyle::default();
let mut state = DomWalkState {
@ -64,9 +64,10 @@ impl Dom {
pub fn render_from(&mut self, node: NodeId, size: Dimensions<usize>) {
let rendered = if let Some(NodeChildren(children)) = self.nodes.get(node) {
let children = children.to_owned();
let axis: Axis = self.nodes.get::<Axis>(node).unwrap().to_owned();
let layout = BoxLayout {
axis: Axis::Vertical,
axis,
children: self.get_many::<Block>(&children),
};
@ -81,11 +82,11 @@ impl Dom {
let rendered = self.get_many::<RenderedBlock>(&children);
let layout = BoxLayout {
axis: Axis::Vertical,
axis,
children: self.get_many::<Block>(&children),
};
layout.render(size, &rendered)
layout.render(&rendered)
} else {
let style = TextStyle::default();
@ -153,13 +154,23 @@ impl DomBuilder {
#[derive(Debug)]
pub struct BoxBuilder {
pub id: NodeId,
pub axis: Axis,
pub children: Vec<NodeId>,
}
impl BoxBuilder {
pub fn new(id: NodeId) -> Self {
pub fn new(id: NodeId, args: &mut Args) -> ParseResult<Self> {
Ok(Self {
id,
axis: Axis::parse_scoped(args, "axis", Axis::Vertical)?,
children: Vec::new(),
})
}
pub fn root(id: NodeId) -> Self {
Self {
id,
axis: Axis::Vertical,
children: Vec::new(),
}
}
@ -171,6 +182,7 @@ impl BoxBuilder {
let children = NodeChildren(self.children);
dom.insert(self.id, children);
dom.insert(self.id, self.axis);
self.id
}
@ -209,7 +221,8 @@ impl<'a> DomWalkState<'a> {
if let BlockTagKind::Box = kind {
self.box_builder.children.push(node);
let mut box_builder = BoxBuilder::new(node);
let mut box_builder =
BoxBuilder::new(node, &mut args).map_err(|e| e.map(e.inner.clone().into()))?;
let mut state = DomWalkState {
box_builder: &mut box_builder,
..self.fork()

View File

@ -1,3 +1,4 @@
use itertools::Itertools;
use strum::EnumString;
use crate::args::*;
@ -8,14 +9,13 @@ use crate::source::WithSource;
#[derive(Clone, Debug)]
pub struct RenderedBlock {
pub lines: Vec<String>,
pub width: usize,
pub height: usize,
pub size: Dimensions<usize>,
}
impl RenderedBlock {
pub fn add_padding(&self, padding: &Rect<usize>) -> Self {
let make_spaces = |num: usize| -> String { std::iter::repeat(" ").take(num).collect() };
let new_width = self.width + padding.l + padding.r;
let new_width = self.size.width + padding.l + padding.r;
let left_pad = make_spaces(padding.l);
let right_pad = make_spaces(padding.r);
let vertical_pad = make_spaces(new_width);
@ -34,8 +34,64 @@ impl RenderedBlock {
Self {
lines,
width: new_width,
height: self.height + padding.t + padding.b,
size: Dimensions {
width: new_width,
height: self.size.height + padding.t + padding.b,
},
}
}
pub fn make_blank(size: Dimensions<usize>) -> Self {
let line: String = std::iter::repeat(" ").take(size.width).collect();
Self {
lines: std::iter::repeat(line).take(size.height).collect(),
size,
}
}
pub fn concat(mut self, axis: Axis, mut other: Self) -> Self {
match axis {
Axis::Vertical => Self {
size: Dimensions {
width: self.size.width.max(other.size.width),
height: self.size.height + other.size.height,
},
lines: self.lines.into_iter().chain(other.lines).collect(),
},
Axis::Horizontal => {
if self.size.height < other.size.height {
let padding = Dimensions {
width: self.size.width,
height: other.size.height - self.size.height,
};
let padding = Self::make_blank(padding);
self = self.concat(Axis::Vertical, padding);
}
if other.size.height < self.size.height {
let padding = Dimensions {
width: other.size.width,
height: self.size.height - other.size.height,
};
let padding = Self::make_blank(padding);
other = other.concat(Axis::Vertical, padding);
}
Self {
size: Dimensions {
width: self.size.width + other.size.width,
height: self.size.height.max(other.size.height),
},
lines: self
.lines
.into_iter()
.zip(other.lines)
.map(|(first, other)| format!("{}{}", first, other))
.collect(),
}
}
}
}
}
@ -47,6 +103,8 @@ pub enum Axis {
Vertical,
}
impl ParseSymbol for Axis {}
impl Axis {
pub fn cross(&self) -> Self {
match self {
@ -93,54 +151,71 @@ impl<'a> BoxLayout<'a> {
sizes
}
pub fn render(&self, available: Dimensions<usize>, blocks: &[&RenderedBlock]) -> RenderedBlock {
let width = available.width;
let mut lines = Vec::new();
let mut last_margin = 0;
pub fn render(&self, blocks: &[&RenderedBlock]) -> RenderedBlock {
let axis = self.axis;
let cross = axis.cross();
let make_padding = |num: usize| -> String { std::iter::repeat(" ").take(num).collect() };
let cross_size = blocks
.iter()
.zip(&self.children)
.map(|(rendered, block)| {
let (ms, me) = block.margin.axis_sides(cross);
ms.required_size() + rendered.size.axis(cross) + me.required_size()
})
.max()
.unwrap_or(0);
let blank_line = make_padding(width);
let make_blank_lines = |num: usize| -> Vec<String> {
std::iter::repeat(blank_line.clone()).take(num).collect()
};
for (block, rendered) in self.children.iter().zip(blocks) {
let unwrap_margin = |margin: &Margin| -> usize {
match margin {
Margin::Auto => unimplemented!("length-wise auto margins"),
Margin::Fixed(m) => *m,
}
let make_spacer = |size: usize| -> RenderedBlock {
let (width, height) = match axis {
Axis::Horizontal => (size, cross_size),
Axis::Vertical => (cross_size, size),
};
let start = unwrap_margin(&block.margin.t);
let start = start.max(last_margin);
RenderedBlock::make_blank(Dimensions { width, height })
};
lines.extend(make_blank_lines(start).into_iter());
let mut spacers = Vec::new();
let mut last_margin = 0;
let left = &block.margin.l;
let right = &block.margin.r;
let available = width - rendered.width.min(width);
let (left, right) = Self::calc_margins(left, right, available);
let left = make_padding(left);
let right = make_padding(right);
for line in rendered.lines.iter() {
let line = format!("{}{}{}", left, line, right);
lines.push(line);
}
last_margin = unwrap_margin(&block.margin.b);
for block in self.children.iter() {
let (ms, me) = block.margin.axis_sides(axis);
let size = ms.required_size().max(last_margin);
spacers.push(make_spacer(size));
last_margin = me.required_size();
}
lines.extend(make_blank_lines(last_margin).into_iter());
spacers.push(make_spacer(last_margin));
RenderedBlock {
height: lines.len(),
width,
lines,
let mut padded = Vec::new();
for (block, rendered) in self.children.iter().zip(blocks) {
let (ms, me) = block.margin.axis_sides(cross);
let available = cross_size - rendered.size.axis(cross).min(&cross_size);
let (ms, me) = Self::calc_margins(ms, me, available);
let padding = match axis {
Axis::Horizontal => Rect {
l: 0,
r: 0,
t: ms,
b: me,
},
Axis::Vertical => Rect {
t: 0,
b: 0,
l: ms,
r: me,
},
};
padded.push(rendered.add_padding(&padding));
}
spacers
.into_iter()
.interleave(padded)
.reduce(|r1, r2| r1.concat(axis, r2))
.unwrap()
}
pub fn calc_margins(start: &Margin, end: &Margin, available: usize) -> (usize, usize) {

View File

@ -4,7 +4,7 @@ use strum::EnumString;
use crate::ast;
use crate::block::BlockTagKind;
use crate::layout::RenderedBlock;
use crate::layout::{RenderedBlock, Dimensions};
use crate::source::WithSource;
#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumString)]
@ -342,8 +342,10 @@ impl TextLayout {
}
RenderedBlock {
height: lines.len(),
width: self.width,
size: Dimensions {
width: self.width,
height: lines.len(),
},
lines,
}
}