Compare commits

...

5 Commits

Author SHA1 Message Date
mars 63b985d230 Lots of axis logic + min-sized based calc_sizes() 2022-10-23 23:15:55 -06:00
mars 8477e4c1f1 Add horizontal box demo 2022-10-23 22:27:41 -06:00
mars 0ec3b49eae Better box building 2022-10-23 22:24:20 -06:00
mars 847de268aa Calculate individual child block sizes 2022-10-23 20:37:53 -06:00
mars d7b465b438 Dimensions + ParseScoped 2022-10-23 19:54:49 -06:00
7 changed files with 295 additions and 159 deletions

7
assets/horizontal.tml Normal file
View File

@ -0,0 +1,7 @@
(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 "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

@ -44,6 +44,11 @@ pub trait ParseValue: Sized {
fn parse_value(val: &Value) -> ParseResult<Self>;
}
pub trait ParseScoped: Sized {
type Default;
fn parse_scoped(args: &mut Args, scope: &str, default: Self::Default) -> ParseResult<Self>;
}
impl ParseValue for usize {
fn parse_value(val: &Value) -> ParseResult<Self> {
match val.inner {

View File

@ -1,10 +1,10 @@
use strum::EnumString;
use crate::args::{Args, ParseArgs};
use crate::args::{Args, ParseArgs, ParseScoped};
use crate::ast::ParseResult;
use crate::border::BorderKind;
use crate::layout::{Margin, Rect, Size};
use crate::text::Color;
use crate::layout::{AxisSize, Dimensions, Margin, Rect, Axis};
use crate::text::{Color, TextLayout};
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
@ -31,6 +31,19 @@ impl BlockTagKind {
Box => AllowedChildren::Block,
}
}
pub fn make_text_layout(&self, text: String) -> TextLayout {
use BlockTagKind::*;
match self {
Figlet => {
let font = figlet_rs::FIGfont::standard().unwrap();
let figure = font.convert(&text).expect("Failed to FIG-ify text");
TextLayout::from_plain(&figure.to_string())
}
Code => TextLayout::from_plain(&text),
_ => unimplemented!("Text layout for {:?} tag", self),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -62,7 +75,7 @@ impl AllowedChildren {
#[derive(Clone, Debug)]
pub struct Block {
pub size: Size,
pub size: Dimensions<AxisSize>,
pub margin: Rect<Margin>,
pub padding: Rect<usize>,
pub border: Rect<Option<BorderKind>>,
@ -71,11 +84,11 @@ pub struct Block {
impl ParseArgs for Block {
fn parse_args(args: &mut Args) -> ParseResult<Self> {
let size = Size::parse_args(args)?;
let margin = Rect::parse_rect(args, "margin", Margin::Fixed(1))?;
let padding = Rect::parse_rect(args, "padding", 0)?;
let border = Rect::parse_rect(args, "border", None)?;
let border_color = Rect::parse_rect(args, "border-color", Color::White)?;
let size = Dimensions::parse(args, 1)?;
let margin = Rect::parse_scoped(args, "margin", Margin::Fixed(1))?;
let padding = Rect::parse_scoped(args, "padding", 0)?;
let border = Rect::parse_scoped(args, "border", None)?;
let border_color = Rect::parse_scoped(args, "border-color", Color::White)?;
Ok(Self {
size,
@ -86,3 +99,19 @@ impl ParseArgs for Block {
})
}
}
impl Block {
pub fn required_axis_size(&self, axis: Axis) -> AxisSize {
let mut size = self.size.axis(axis).to_owned();
let (ps, pe) = self.padding.axis_sides(axis);
size.add_size(*ps);
size.add_size(*pe);
let (ms, me) = self.margin.axis_sides(axis);
size.add_margin(ms);
size.add_margin(me);
size
}
}

View File

@ -4,10 +4,10 @@ use std::str::FromStr;
use crate::args::ParseArgs;
use crate::ast::{self, ParseErrorKind, TagBody};
use crate::block::{AllowedChildren, Block, BlockTagKind};
use crate::layout::{BoxChild, BoxLayout, RenderedBlock};
use crate::layout::{Axis, BoxLayout, Dimensions, RenderedBlock};
use crate::node::{NodeId, NodeStore};
use crate::source::WithSource;
use crate::text::{TaggedText, TextBlock, TextLayout, TextParseErrorKind, TextStyle, TextTagKind};
use crate::text::{TaggedText, TextBlock, TextLayout, TextParseErrorKind, TextStyle};
#[derive(Clone, Debug)]
pub struct Dom {
@ -19,12 +19,12 @@ impl Dom {
pub fn parse(body: &TagBody) -> DomResult<Self> {
let mut builder = DomBuilder::default();
let root = builder.new_node();
let mut block = BlockBuilder::new(BlockTagKind::Box, root);
let mut block = BoxBuilder::new(root);
let mut text_style = TextStyle::default();
let mut state = DomWalkState {
builder: &mut builder,
block: &mut block,
box_builder: &mut block,
text_style: &mut text_style,
};
@ -44,33 +44,55 @@ impl Dom {
pub fn print_from(&self, node: NodeId, indent: usize) {
let margin: String = std::iter::repeat(" ").take(indent * 4).collect();
let kind: &NodeKind = self.nodes.get(node).unwrap();
let children: &NodeChildren = self.nodes.get(node).unwrap();
let text = self.nodes.get::<TextBlock>(node);
println!("{}{:?}: {:?} {:?}", margin, kind, children, text);
if let Some(NodeChildren(children)) = self.nodes.get(node) {
println!("{}{}: {:?}", margin, node, children);
for child in children.0.iter() {
self.print_from(*child, indent + 1);
for child in children.iter() {
self.print_from(*child, indent + 1);
}
} else {
let text = self.nodes.get::<TextBlock>(node);
println!("{}{}: {:?}", margin, node, text);
}
}
pub fn render(&mut self, width: usize) {
self.render_from(self.root, width);
let rendered: &RenderedBlock = self.nodes.get(self.root).unwrap();
for line in rendered.lines.iter() {
println!("{}", line);
}
pub fn render(&mut self, size: Dimensions<usize>) -> &RenderedBlock {
self.render_from(self.root, size);
self.nodes.get(self.root).unwrap()
}
pub fn render_from(&mut self, node: NodeId, width: usize) {
let rendered = if let NodeKind::Text = self.nodes.get(node).unwrap() {
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 layout = BoxLayout {
axis: Axis::Vertical,
children: self.get_many::<Block>(&children),
};
let sizes = layout.calc_sizes(size).to_owned();
drop(layout);
for (size, child) in sizes.into_iter().zip(children) {
self.render_from(child, size);
}
let children = self.get_children(node);
let rendered = self.get_many::<RenderedBlock>(&children);
let layout = BoxLayout {
axis: Axis::Vertical,
children: self.get_many::<Block>(&children),
};
layout.render(size, &rendered)
} else {
let style = TextStyle::default();
let layout: TextLayout = if let Some(layout) = self.nodes.get::<TextLayout>(node) {
layout.to_owned()
} else if let Some(block) = self.nodes.get::<TextBlock>(node) {
block.layout(&style, 40)
block.layout(&style, size.width)
} else {
panic!("Couldn't find text info on node {}", node);
};
@ -78,29 +100,20 @@ impl Dom {
let rendered = layout.render(&style);
let block: &Block = self.nodes.get(node).unwrap();
rendered.add_padding(&block.padding)
} else {
let children = self.nodes.get::<NodeChildren>(node).unwrap().0.clone();
for child in children.iter() {
self.render_from(*child, width);
}
let layout = BoxLayout {
children: children
.iter()
.map(|child| self.get_box_child(*child))
.collect(),
};
layout.render(width)
};
self.nodes.insert(node, rendered);
}
pub fn get_box_child(&self, node: NodeId) -> BoxChild {
let block: &Block = self.nodes.get(node).unwrap();
let rendered: &RenderedBlock = self.nodes.get(node).unwrap();
BoxChild { rendered, block }
pub fn get_children(&self, node: NodeId) -> Vec<NodeId> {
self.nodes.get::<NodeChildren>(node).unwrap().0.to_owned()
}
pub fn get_many<T: 'static>(&self, children: &[NodeId]) -> Vec<&T> {
children
.iter()
.map(|child| self.nodes.get(*child).unwrap())
.collect()
}
}
@ -110,12 +123,6 @@ pub struct NodeChildren(Vec<NodeId>);
#[derive(Copy, Clone, Debug, Default)]
pub struct NodeParent(NodeId);
#[derive(Copy, Clone, Debug)]
pub enum NodeKind {
Block,
Text,
}
#[derive(Debug, Default)]
pub struct DomBuilder {
pub node_store: NodeStore,
@ -144,20 +151,16 @@ impl DomBuilder {
}
#[derive(Debug)]
pub struct BlockBuilder {
pub struct BoxBuilder {
pub id: NodeId,
pub children: Vec<NodeId>,
pub allowed_children: AllowedChildren,
pub kind: BlockTagKind,
}
impl BlockBuilder {
pub fn new(kind: BlockTagKind, id: NodeId) -> Self {
impl BoxBuilder {
pub fn new(id: NodeId) -> Self {
Self {
id,
children: Vec::new(),
allowed_children: kind.children(),
kind,
}
}
@ -169,14 +172,6 @@ impl BlockBuilder {
let children = NodeChildren(self.children);
dom.insert(self.id, children);
use BlockTagKind::*;
let kind = match self.kind {
P | Figlet | Code | Li => NodeKind::Text,
Ul | Ol | Box | Section => NodeKind::Block,
};
dom.insert(self.id, kind);
self.id
}
}
@ -184,7 +179,7 @@ impl BlockBuilder {
#[derive(Debug)]
pub struct DomWalkState<'a> {
pub builder: &'a mut DomBuilder,
pub block: &'a mut BlockBuilder,
pub box_builder: &'a mut BoxBuilder,
pub text_style: &'a mut TextStyle,
}
@ -192,101 +187,69 @@ impl<'a> DomWalkState<'a> {
pub fn fork(&mut self) -> DomWalkState {
DomWalkState {
builder: &mut *self.builder,
block: &mut *self.block,
box_builder: &mut *self.box_builder,
text_style: &mut *self.text_style,
}
}
pub fn make_text_block(&mut self, text: TaggedText) -> TextBlock {
use BlockTagKind::*;
match self.block.kind {
P => TextBlock {
text,
initial_indent: None,
subsequent_indent: None,
},
_ => unimplemented!("Text block for {:?} tag", self.block.kind),
}
}
pub fn add_text_block(&mut self, text: TaggedText) -> NodeId {
pub fn add_text_block(&mut self, kind: BlockTagKind, text: TaggedText) -> NodeId {
let node = self.builder.new_node();
let block = self.make_text_block(text);
let block = text.make_text_block(kind);
self.builder.insert(node, block);
node
}
pub fn make_text_layout(&self, text: String) -> TextLayout {
use BlockTagKind::*;
match self.block.kind {
Figlet => {
let font = figlet_rs::FIGfont::standard().unwrap();
let figure = font.convert(&text).expect("Failed to FIG-ify text");
TextLayout::from_plain(&figure.to_string())
}
Code => TextLayout::from_plain(&text),
_ => unimplemented!("Text layout for {:?} tag", self.block.kind),
}
}
pub fn add_block_tag(&mut self, kind: BlockTagKind, body: &ast::TagBody) -> DomResult<NodeId> {
let node = self.builder.new_node();
self.block.children.push(node);
let mut args = body.args.clone();
let block = Block::parse_args(&mut args).map_err(|e| e.map(e.inner.clone().into()))?;
println!("{:#?}", block);
self.builder.insert(node, block);
let mut block = BlockBuilder::new(kind, node);
let mut state = DomWalkState {
block: &mut block,
..self.fork()
};
if let BlockTagKind::Box = kind {
self.box_builder.children.push(node);
let mut box_builder = BoxBuilder::new(node);
let mut state = DomWalkState {
box_builder: &mut box_builder,
..self.fork()
};
state.add_children(&body.children)?;
box_builder.build(&mut self.builder);
return Ok(node);
}
match kind.children() {
AllowedChildren::TaggedText => {
let text = TaggedText::parse(&state.text_style, &body.children)
let text = TaggedText::parse(&self.text_style, &body.children)
.map_err(|e| e.map(e.inner.clone().into()))?;
let block = state.make_text_block(text);
let block = text.make_text_block(kind);
self.builder.insert(node, block);
self.box_builder.children.push(node);
}
AllowedChildren::PlainText => {
let text = crate::text::parse_plain(&body.children)
.map_err(|e| e.map(e.inner.clone().into()))?;
let layout = state.make_text_layout(text);
let layout = kind.make_text_layout(text);
self.builder.insert(node, layout);
self.box_builder.children.push(node);
}
_ => {
state.add_children(&body.children)?;
self.add_children(&body.children)?;
}
}
block.build(&mut self.builder);
Ok(node)
}
pub fn add_tag(mut self, tag: &ast::Tag) -> DomResult<()> {
if let Ok(kind) = TextTagKind::from_str(&tag.id) {
if !self.block.allowed_children.allows_styled_text() {
return Err(tag.id.map(DomErrorKind::InvalidChild));
}
let style = &self.text_style;
let styled = TaggedText::parse_tag_body(style, &kind, &tag.body)
.map_err(|e| e.map(e.inner.clone().into()))?;
self.add_text_block(styled);
} else if let Ok(kind) = BlockTagKind::from_str(&tag.id) {
if !self.block.allowed_children.allows_block(&kind) {
return Err(tag.id.map(DomErrorKind::InvalidChild));
}
if let Ok(kind) = BlockTagKind::from_str(&tag.id) {
self.add_block_tag(kind, &tag.body)?;
Ok(())
} else {
return Err(tag.id.map(DomErrorKind::InvalidTag));
Err(tag.id.map(DomErrorKind::InvalidTag))
}
Ok(())
}
pub fn add_children(&mut self, vals: &[ast::Value]) -> DomResult<()> {

View File

@ -40,28 +40,61 @@ impl RenderedBlock {
}
}
#[derive(Clone, Debug, EnumString)]
#[derive(Copy, Clone, Debug, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum Direction {
Left,
Up,
Right,
Down,
pub enum Axis {
Horizontal,
Vertical,
}
#[derive(Clone, Debug)]
pub struct BoxChild<'a> {
pub block: &'a Block,
pub rendered: &'a RenderedBlock,
impl Axis {
pub fn cross(&self) -> Self {
match self {
Axis::Horizontal => Axis::Vertical,
Axis::Vertical => Axis::Horizontal,
}
}
}
#[derive(Clone, Debug)]
pub struct BoxLayout<'a> {
pub children: Vec<BoxChild<'a>>,
pub axis: Axis,
pub children: Vec<&'a Block>,
}
impl<'a> BoxLayout<'a> {
pub fn render(&self, width: usize) -> RenderedBlock {
pub fn calc_sizes(&self, available: Dimensions<usize>) -> Vec<Dimensions<usize>> {
let cross = self.axis.cross();
let sizes = self
.children
.iter()
.map(|block| {
let available = *available.axis(cross);
let required = block.required_axis_size(cross);
let min_size = required.min_size();
let remaining = available - min_size.min(available);
let cross_size = block.size.axis(cross).clamp(remaining);
let axis_size = block.size.axis(self.axis).min_size();
match self.axis {
Axis::Horizontal => Dimensions {
width: axis_size,
height: cross_size,
},
Axis::Vertical => Dimensions {
width: cross_size,
height: axis_size,
},
}
})
.collect();
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;
@ -73,7 +106,7 @@ impl<'a> BoxLayout<'a> {
std::iter::repeat(blank_line.clone()).take(num).collect()
};
for child in self.children.iter() {
for (block, rendered) in self.children.iter().zip(blocks) {
let unwrap_margin = |margin: &Margin| -> usize {
match margin {
Margin::Auto => unimplemented!("length-wise auto margins"),
@ -81,24 +114,24 @@ impl<'a> BoxLayout<'a> {
}
};
let start = unwrap_margin(&child.block.margin.t);
let start = unwrap_margin(&block.margin.t);
let start = start.max(last_margin);
lines.extend(make_blank_lines(start).into_iter());
let left = &child.block.margin.l;
let right = &child.block.margin.r;
let available = width - child.rendered.width.min(width);
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 child.rendered.lines.iter() {
for line in rendered.lines.iter() {
let line = format!("{}{}{}", left, line, right);
lines.push(line);
}
last_margin = unwrap_margin(&child.block.margin.b);
last_margin = unwrap_margin(&block.margin.b);
}
lines.extend(make_blank_lines(last_margin).into_iter());
@ -140,8 +173,10 @@ pub enum AxisSize {
Bounded(usize, Option<usize>),
}
impl AxisSize {
pub fn parse(args: &mut Args, name: &str, mut min: usize) -> ParseResult<Self> {
impl ParseScoped for AxisSize {
type Default = usize;
fn parse_scoped(args: &mut Args, name: &str, mut min: usize) -> ParseResult<Self> {
if let Some(arg) = args.get(name) {
Ok(AxisSize::Fixed(ParseValue::parse_value(&arg.val)?))
} else {
@ -162,17 +197,38 @@ impl AxisSize {
}
}
#[derive(Clone, Debug)]
pub struct Size {
pub w: AxisSize,
pub h: AxisSize,
}
impl AxisSize {
pub fn add_size(&mut self, size: usize) {
match self {
AxisSize::Fixed(fixed) => *fixed += size,
AxisSize::Bounded(min, None) => *min += size,
AxisSize::Bounded(min, Some(max)) => {
*min += size;
*max += size;
}
}
}
impl ParseArgs for Size {
fn parse_args(args: &mut Args) -> ParseResult<Self> {
let w = AxisSize::parse(args, "width", 1)?;
let h = AxisSize::parse(args, "height", 1)?;
Ok(Self { w, h })
pub fn add_margin(&mut self, margin: &Margin) {
match margin {
Margin::Fixed(margin) => self.add_size(*margin),
Margin::Auto => {}
}
}
pub fn clamp(&self, size: usize) -> usize {
match *self {
AxisSize::Fixed(fixed) => fixed,
AxisSize::Bounded(min, None) => size.max(min),
AxisSize::Bounded(min, Some(max)) => size.max(min).min(max),
}
}
pub fn min_size(&self) -> usize {
match self {
AxisSize::Fixed(fixed) => *fixed,
AxisSize::Bounded(min, _) => *min,
}
}
}
@ -191,6 +247,50 @@ impl ParseValue for Margin {
}
}
impl Margin {
pub fn required_size(&self) -> usize {
match self {
Margin::Auto => 0,
Margin::Fixed(fixed) => *fixed,
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct Dimensions<T> {
pub width: T,
pub height: T,
}
impl<T: ParseScoped> Dimensions<T>
where
T::Default: Clone,
{
pub fn parse(args: &mut Args, default: T::Default) -> ParseResult<Self> {
let width = T::parse_scoped(args, "width", default.clone())?;
let height = T::parse_scoped(args, "height", default)?;
Ok(Self { width, height })
}
}
impl<T> Dimensions<T> {
pub fn axis(&self, axis: Axis) -> &T {
match axis {
Axis::Horizontal => &self.width,
Axis::Vertical => &self.height,
}
}
}
impl Dimensions<AxisSize> {
pub fn min_size(&self) -> Dimensions<usize> {
Dimensions {
width: self.width.min_size(),
height: self.height.min_size(),
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct Rect<T> {
pub l: T,
@ -208,6 +308,13 @@ impl<T> Rect<T> {
b: f(&self.b),
}
}
pub fn axis_sides(&self, axis: Axis) -> (&T, &T) {
match axis {
Axis::Horizontal => (&self.l, &self.r),
Axis::Vertical => (&self.t, &self.b),
}
}
}
impl<T: Clone + ParseValue> Rect<T> {
@ -240,8 +347,12 @@ impl<T: Clone + ParseValue> Rect<T> {
_ => Err(parsed.map(ParseErrorKind::InvalidValue)),
}
}
}
pub fn parse_rect(args: &mut Args, name: &str, default: T) -> ParseResult<Self> {
impl<T: Clone + ParseValue> ParseScoped for Rect<T> {
type Default = T;
fn parse_scoped(args: &mut Args, name: &str, default: T) -> ParseResult<Self> {
let mut get_side = |side: &str| -> Option<Value> {
let name = format!("{}-{}", name, side);
args.get(&name).map(|arg| arg.val)

View File

@ -26,7 +26,15 @@ fn main() {
Err(e) => panic!("DOM parse error:\n{}", e),
};
let width = 80;
let size = layout::Dimensions {
width: 80,
height: 20,
};
dom.print();
dom.render(width);
let rendered = dom.render(size);
for line in rendered.lines.iter() {
println!("{}", line);
}
}

View File

@ -3,6 +3,7 @@ use std::str::FromStr;
use strum::EnumString;
use crate::ast;
use crate::block::BlockTagKind;
use crate::layout::RenderedBlock;
use crate::source::WithSource;
@ -220,6 +221,18 @@ impl TaggedText {
rendered
}
pub fn make_text_block(&self, kind: BlockTagKind) -> TextBlock {
use BlockTagKind::*;
match kind {
P => TextBlock {
text: self.to_owned(),
initial_indent: None,
subsequent_indent: None,
},
_ => unimplemented!("Text block for {:?} tag", kind),
}
}
}
pub fn parse_plain(vals: &[ast::Value]) -> Result<String, TextParseError> {