mirror of
https://github.com/mfontanini/presenterm.git
synced 2026-09-01 23:33:21 -06:00
feat: add theme support for column layout margin
This commit is contained in:
parent
b2fbe59cdf
commit
0a15d502dc
@ -57,7 +57,11 @@ impl PresentationBuilder<'_, '_> {
|
||||
} else {
|
||||
LayoutGrid::None
|
||||
};
|
||||
self.chunk_operations.push(RenderOperation::InitColumnLayout { columns, grid });
|
||||
self.chunk_operations.push(RenderOperation::InitColumnLayout {
|
||||
columns,
|
||||
grid,
|
||||
margin: self.theme.column_layout.margin,
|
||||
});
|
||||
self.slide_state.needs_enter_column = true;
|
||||
}
|
||||
CommentCommand::ResetLayout => {
|
||||
@ -297,12 +301,11 @@ impl fmt::Display for CommandParseError {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{fs, io::BufWriter};
|
||||
|
||||
use super::*;
|
||||
use crate::presentation::builder::{PresentationBuilderOptions, utils::Test};
|
||||
use image::{DynamicImage, ImageEncoder, codecs::png::PngEncoder};
|
||||
use rstest::rstest;
|
||||
use std::{fs, io::BufWriter};
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[rstest]
|
||||
@ -416,7 +419,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn columns() {
|
||||
let input = "
|
||||
let input = "---
|
||||
theme:
|
||||
override:
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 2
|
||||
---
|
||||
|
||||
<!-- column_layout: [1, 1] -->
|
||||
<!-- column: 0 -->
|
||||
foo1
|
||||
@ -433,15 +443,15 @@ bar2
|
||||
|
||||
---
|
||||
";
|
||||
let lines = Test::new(input).render().rows(7).columns(24).into_lines();
|
||||
let lines = Test::new(input).render().rows(7).columns(20).into_lines();
|
||||
let expected = &[
|
||||
" ",
|
||||
"foo1 bar1 ",
|
||||
" ",
|
||||
"foo2 bar2 ",
|
||||
" ",
|
||||
"———————— ————————",
|
||||
" ",
|
||||
" ",
|
||||
"foo1 bar1 ",
|
||||
" ",
|
||||
"foo2 bar2 ",
|
||||
" ",
|
||||
"———————— ————————",
|
||||
" ",
|
||||
];
|
||||
assert_eq!(lines, expected);
|
||||
}
|
||||
@ -449,7 +459,14 @@ bar2
|
||||
#[test]
|
||||
fn columns_back_and_forth() {
|
||||
// this is the same as the above but we run back and forth between the columns
|
||||
let input = "
|
||||
let input = "---
|
||||
theme:
|
||||
override:
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 2
|
||||
---
|
||||
|
||||
<!-- column_layout: [1, 1] -->
|
||||
|
||||
<!-- column: 0 -->
|
||||
@ -472,22 +489,29 @@ bar2
|
||||
|
||||
---
|
||||
";
|
||||
let lines = Test::new(input).render().rows(7).columns(24).into_lines();
|
||||
let lines = Test::new(input).render().rows(7).columns(20).into_lines();
|
||||
let expected = &[
|
||||
" ",
|
||||
"foo1 bar1 ",
|
||||
" ",
|
||||
"foo2 bar2 ",
|
||||
" ",
|
||||
"———————— ————————",
|
||||
" ",
|
||||
" ",
|
||||
"foo1 bar1 ",
|
||||
" ",
|
||||
"foo2 bar2 ",
|
||||
" ",
|
||||
"———————— ————————",
|
||||
" ",
|
||||
];
|
||||
assert_eq!(lines, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uneven_columns() {
|
||||
let input = "
|
||||
let input = "---
|
||||
theme:
|
||||
override:
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
---
|
||||
|
||||
<!-- column_layout: [2, 1] -->
|
||||
<!-- column: 0 -->
|
||||
foo1
|
||||
@ -519,7 +543,14 @@ bar2
|
||||
|
||||
#[test]
|
||||
fn uneven_three_columns() {
|
||||
let input = "
|
||||
let input = "---
|
||||
theme:
|
||||
override:
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
---
|
||||
|
||||
<!-- column_layout: [1, 2, 1] -->
|
||||
<!-- column: 0 -->
|
||||
|
||||
@ -574,7 +605,14 @@ bye
|
||||
|
||||
#[test]
|
||||
fn pause_layout_new_slide() {
|
||||
let input = r"
|
||||
let input = r"---
|
||||
theme:
|
||||
override:
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
---
|
||||
|
||||
<!-- column_layout: [1, 1] -->
|
||||
<!-- column: 0 -->
|
||||
hi
|
||||
|
||||
@ -166,7 +166,7 @@ mod test {
|
||||
))]
|
||||
#[case(RenderOperation::RenderDynamic(Rc::new(Dynamic)))]
|
||||
#[case(RenderOperation::RenderAsync(Rc::new(Dynamic)))]
|
||||
#[case(RenderOperation::InitColumnLayout{ columns: vec![1, 2], grid: LayoutGrid::None })]
|
||||
#[case(RenderOperation::InitColumnLayout{ columns: vec![1, 2], grid: LayoutGrid::None, margin: Default::default() })]
|
||||
#[case(RenderOperation::EnterColumn{ column: 1 })]
|
||||
#[case(RenderOperation::ExitLayout)]
|
||||
fn same_not_modified(#[case] operation: RenderOperation) {
|
||||
@ -203,8 +203,16 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn different_column_layout() {
|
||||
let lhs = RenderOperation::InitColumnLayout { columns: vec![1, 2], grid: LayoutGrid::None };
|
||||
let rhs = RenderOperation::InitColumnLayout { columns: vec![1, 3], grid: LayoutGrid::None };
|
||||
let lhs = RenderOperation::InitColumnLayout {
|
||||
columns: vec![1, 2],
|
||||
grid: LayoutGrid::None,
|
||||
margin: Default::default(),
|
||||
};
|
||||
let rhs = RenderOperation::InitColumnLayout {
|
||||
columns: vec![1, 3],
|
||||
grid: LayoutGrid::None,
|
||||
margin: Default::default(),
|
||||
};
|
||||
assert!(lhs.is_content_different(&rhs));
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ use crate::{
|
||||
},
|
||||
printer::{TerminalCommand, TerminalIo},
|
||||
},
|
||||
theme::Alignment,
|
||||
theme::{Alignment, Margin},
|
||||
};
|
||||
use std::mem;
|
||||
|
||||
@ -44,17 +44,10 @@ impl Default for MaxSize {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub(crate) struct RenderEngineOptions {
|
||||
pub(crate) validate_overflows: bool,
|
||||
pub(crate) max_size: MaxSize,
|
||||
pub(crate) column_layout_margin: u16,
|
||||
}
|
||||
|
||||
impl Default for RenderEngineOptions {
|
||||
fn default() -> Self {
|
||||
Self { validate_overflows: false, max_size: Default::default(), column_layout_margin: 4 }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RenderEngine<'a, T>
|
||||
@ -151,7 +144,9 @@ where
|
||||
RenderOperation::RenderDynamic(generator) => self.render_dynamic(generator.as_ref()),
|
||||
RenderOperation::RenderDynamicTopLevel(generator) => self.render_dynamic_top_level(generator.as_ref()),
|
||||
RenderOperation::RenderAsync(generator) => self.render_async(generator.as_ref()),
|
||||
RenderOperation::InitColumnLayout { columns, grid } => self.init_column_layout(columns, *grid),
|
||||
RenderOperation::InitColumnLayout { columns, grid, margin } => {
|
||||
self.init_column_layout(columns, *grid, *margin)
|
||||
}
|
||||
RenderOperation::EnterColumn { column } => self.enter_column(*column),
|
||||
RenderOperation::ExitLayout => self.exit_layout(),
|
||||
}?;
|
||||
@ -375,7 +370,7 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_column_layout(&mut self, widths: &[u8], grid: LayoutGrid) -> RenderResult {
|
||||
fn init_column_layout(&mut self, widths: &[u8], grid: LayoutGrid, margin: Margin) -> RenderResult {
|
||||
if !matches!(self.layout, LayoutState::Default) {
|
||||
self.exit_layout()?;
|
||||
}
|
||||
@ -392,23 +387,23 @@ where
|
||||
columns.push(Column { start_column: current_column, end_column, current_row });
|
||||
current_column = end_column;
|
||||
}
|
||||
self.layout = LayoutState::InitializedColumn { columns, grid, start_row: current_row };
|
||||
self.layout = LayoutState::InitializedColumn { columns, grid, margin, start_row: current_row };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn enter_column(&mut self, column_index: usize) -> RenderResult {
|
||||
let (columns, grid, start_row) = match mem::take(&mut self.layout) {
|
||||
let (columns, margin, grid, start_row) = match mem::take(&mut self.layout) {
|
||||
LayoutState::Default => return Err(RenderError::InvalidLayoutEnter),
|
||||
LayoutState::InitializedColumn { columns, .. } | LayoutState::EnteredColumn { columns, .. }
|
||||
if column_index >= columns.len() =>
|
||||
{
|
||||
return Err(RenderError::InvalidLayoutEnter);
|
||||
}
|
||||
LayoutState::InitializedColumn { columns, grid, start_row } => (columns, grid, start_row),
|
||||
LayoutState::EnteredColumn { columns, grid, start_row, .. } => {
|
||||
LayoutState::InitializedColumn { columns, margin, grid, start_row } => (columns, margin, grid, start_row),
|
||||
LayoutState::EnteredColumn { columns, margin, grid, start_row, .. } => {
|
||||
// Pop this one and start clean
|
||||
self.pop_margin()?;
|
||||
(columns, grid, start_row)
|
||||
(columns, margin, grid, start_row)
|
||||
}
|
||||
};
|
||||
let column = &columns[column_index];
|
||||
@ -421,18 +416,19 @@ where
|
||||
let mut dimensions =
|
||||
WindowRect { dimensions: new_size, start_column: column.start_column, start_row: column.current_row };
|
||||
|
||||
let total_margin = margin.as_characters(self.window_rects[0].dimensions.columns);
|
||||
if column_index == 0 {
|
||||
dimensions = dimensions.shrink_right(self.options.column_layout_margin);
|
||||
dimensions = dimensions.shrink_right(total_margin);
|
||||
} else if column_index == columns.len() - 1 {
|
||||
dimensions = dimensions.shrink_left(self.options.column_layout_margin);
|
||||
dimensions = dimensions.shrink_left(total_margin);
|
||||
} else {
|
||||
let margin = self.options.column_layout_margin / 2;
|
||||
let margin = total_margin / 2;
|
||||
dimensions = dimensions.shrink_left(margin).shrink_right(margin);
|
||||
}
|
||||
|
||||
self.window_rects.push(dimensions);
|
||||
self.terminal.execute(&TerminalCommand::MoveToRow(column.current_row))?;
|
||||
self.layout = LayoutState::EnteredColumn { column: column_index, columns, grid, start_row };
|
||||
self.layout = LayoutState::EnteredColumn { column: column_index, columns, margin, grid, start_row };
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -440,7 +436,7 @@ where
|
||||
fn exit_layout(&mut self) -> RenderResult {
|
||||
match &self.layout {
|
||||
LayoutState::Default => return Ok(()),
|
||||
LayoutState::InitializedColumn { columns, grid, start_row }
|
||||
LayoutState::InitializedColumn { columns, grid, start_row, .. }
|
||||
| LayoutState::EnteredColumn { columns, grid, start_row, .. } => {
|
||||
if let LayoutGrid::Draw(style) = grid {
|
||||
let style = *style;
|
||||
@ -479,12 +475,14 @@ enum LayoutState {
|
||||
InitializedColumn {
|
||||
start_row: u16,
|
||||
columns: Vec<Column>,
|
||||
margin: Margin,
|
||||
grid: LayoutGrid,
|
||||
},
|
||||
EnteredColumn {
|
||||
start_row: u16,
|
||||
column: usize,
|
||||
columns: Vec<Column>,
|
||||
margin: Margin,
|
||||
grid: LayoutGrid,
|
||||
},
|
||||
}
|
||||
@ -699,7 +697,7 @@ mod tests {
|
||||
fn do_render(max_size: MaxSize, operations: &[RenderOperation]) -> Vec<Instruction> {
|
||||
let mut buf = TerminalBuf::default();
|
||||
let dimensions = WindowSize { rows: 100, columns: 100, height: 200, width: 200 };
|
||||
let options = RenderEngineOptions { validate_overflows: false, max_size, column_layout_margin: 0 };
|
||||
let options = RenderEngineOptions { validate_overflows: false, max_size };
|
||||
let mut engine = RenderEngine::new(&mut buf, dimensions, options);
|
||||
engine.image_scaler = Box::new(DummyImageScaler);
|
||||
engine.render(operations.iter()).expect("render failed");
|
||||
@ -723,7 +721,11 @@ mod tests {
|
||||
#[test]
|
||||
fn columns() {
|
||||
let ops = render(&[
|
||||
RenderOperation::InitColumnLayout { columns: vec![1, 1], grid: LayoutGrid::None },
|
||||
RenderOperation::InitColumnLayout {
|
||||
columns: vec![1, 1],
|
||||
grid: LayoutGrid::None,
|
||||
margin: Default::default(),
|
||||
},
|
||||
// print on column 0
|
||||
RenderOperation::EnterColumn { column: 0 },
|
||||
RenderOperation::RenderText { line: "A".into(), alignment: Alignment::Left { margin: Margin::Fixed(0) } },
|
||||
|
||||
@ -84,7 +84,7 @@ pub(crate) enum RenderOperation {
|
||||
///
|
||||
/// The value for each column is the width of the column in column-unit units, where the entire
|
||||
/// screen contains `columns.sum()` column-units.
|
||||
InitColumnLayout { columns: Vec<u8>, grid: LayoutGrid },
|
||||
InitColumnLayout { columns: Vec<u8>, grid: LayoutGrid, margin: Margin },
|
||||
|
||||
/// Enter a column in a column layout.
|
||||
///
|
||||
|
||||
@ -44,6 +44,7 @@ pub(crate) struct PresentationTheme {
|
||||
pub(crate) block_quote: BlockQuoteStyle,
|
||||
pub(crate) alert: AlertStyle,
|
||||
pub(crate) default_style: DefaultStyle,
|
||||
pub(crate) column_layout: ColumnLayoutStyle,
|
||||
pub(crate) headings: HeadingStyles,
|
||||
pub(crate) intro_slide: IntroSlideStyle,
|
||||
pub(crate) footer: FooterStyle,
|
||||
@ -73,6 +74,7 @@ impl PresentationTheme {
|
||||
block_quote,
|
||||
alert,
|
||||
default_style,
|
||||
column_layout,
|
||||
headings,
|
||||
intro_slide,
|
||||
footer,
|
||||
@ -99,6 +101,7 @@ impl PresentationTheme {
|
||||
block_quote: BlockQuoteStyle::new(block_quote, &palette)?,
|
||||
alert: AlertStyle::new(alert, &palette)?,
|
||||
default_style: default_style.clone(),
|
||||
column_layout: ColumnLayoutStyle::new(column_layout),
|
||||
headings: HeadingStyles::new(headings, &palette, options)?,
|
||||
intro_slide: IntroSlideStyle::new(intro_slide, &palette, options)?,
|
||||
footer: FooterStyle::new(&footer.clone().unwrap_or_default(), &palette, resources)?,
|
||||
@ -470,6 +473,19 @@ impl DefaultStyle {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct ColumnLayoutStyle {
|
||||
pub(crate) margin: Margin,
|
||||
}
|
||||
|
||||
impl ColumnLayoutStyle {
|
||||
fn new(raw: &raw::ColumnLayoutStyle) -> Self {
|
||||
let raw::ColumnLayoutStyle { margin } = raw;
|
||||
let margin = margin.unwrap_or_default();
|
||||
Self { margin }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub(crate) enum Alignment {
|
||||
Left { margin: Margin },
|
||||
|
||||
@ -63,6 +63,10 @@ pub struct PresentationTheme {
|
||||
#[serde(rename = "default", default)]
|
||||
pub(crate) default_style: DefaultStyle,
|
||||
|
||||
/// The style for column layouts.
|
||||
#[serde(default)]
|
||||
pub(crate) column_layout: ColumnLayoutStyle,
|
||||
|
||||
//// The style of all headings.
|
||||
#[serde(default)]
|
||||
pub(crate) headings: HeadingStyles,
|
||||
@ -356,6 +360,14 @@ pub(crate) struct DefaultStyle {
|
||||
pub(crate) alignment: Option<Alignment>,
|
||||
}
|
||||
|
||||
/// The column layout style.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub(crate) struct ColumnLayoutStyle {
|
||||
/// The margin in between two columns.
|
||||
#[serde(default, with = "serde_yaml::with::singleton_map")]
|
||||
pub(crate) margin: Option<Margin>,
|
||||
}
|
||||
|
||||
/// A simple style.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub(crate) struct BasicStyle {
|
||||
|
||||
@ -6,6 +6,10 @@ default:
|
||||
foreground: palette:text
|
||||
background: palette:base
|
||||
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
|
||||
slide_title:
|
||||
alignment: center
|
||||
padding_bottom: 1
|
||||
|
||||
@ -6,6 +6,10 @@ default:
|
||||
foreground: palette:text
|
||||
background: palette:base
|
||||
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
|
||||
slide_title:
|
||||
alignment: center
|
||||
padding_bottom: 1
|
||||
|
||||
@ -6,6 +6,10 @@ default:
|
||||
foreground: palette:text
|
||||
background: palette:base
|
||||
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
|
||||
slide_title:
|
||||
alignment: center
|
||||
padding_bottom: 1
|
||||
|
||||
@ -6,6 +6,10 @@ default:
|
||||
foreground: palette:text
|
||||
background: palette:base
|
||||
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
|
||||
slide_title:
|
||||
alignment: center
|
||||
padding_bottom: 1
|
||||
|
||||
@ -6,6 +6,10 @@ default:
|
||||
foreground: palette:white
|
||||
background: "040312"
|
||||
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
|
||||
slide_title:
|
||||
alignment: center
|
||||
padding_bottom: 1
|
||||
|
||||
@ -6,6 +6,10 @@ default:
|
||||
foreground: "ebdbb2"
|
||||
background: "282828"
|
||||
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
|
||||
slide_title:
|
||||
alignment: center
|
||||
padding_bottom: 1
|
||||
|
||||
@ -6,6 +6,10 @@ default:
|
||||
foreground: "212529"
|
||||
background: "f8f9fa"
|
||||
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
|
||||
slide_title:
|
||||
alignment: center
|
||||
padding_bottom: 1
|
||||
|
||||
@ -6,6 +6,10 @@ default:
|
||||
foreground: null
|
||||
background: null
|
||||
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
|
||||
slide_title:
|
||||
alignment: center
|
||||
padding_bottom: 1
|
||||
|
||||
@ -6,6 +6,10 @@ default:
|
||||
foreground: null
|
||||
background: null
|
||||
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
|
||||
slide_title:
|
||||
alignment: center
|
||||
padding_bottom: 1
|
||||
|
||||
@ -6,6 +6,10 @@ default:
|
||||
foreground: "3760bf"
|
||||
background: "e1e2e7"
|
||||
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
|
||||
slide_title:
|
||||
alignment: center
|
||||
padding_bottom: 1
|
||||
|
||||
@ -6,6 +6,10 @@ default:
|
||||
foreground: "c8d3f5"
|
||||
background: "222436"
|
||||
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
|
||||
slide_title:
|
||||
alignment: center
|
||||
padding_bottom: 1
|
||||
|
||||
@ -6,6 +6,10 @@ default:
|
||||
foreground: "c0caf5"
|
||||
background: "1a1b26"
|
||||
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
|
||||
slide_title:
|
||||
alignment: center
|
||||
padding_bottom: 1
|
||||
|
||||
@ -6,6 +6,10 @@ default:
|
||||
foreground: "c0caf5"
|
||||
background: "24283b"
|
||||
|
||||
column_layout:
|
||||
margin:
|
||||
fixed: 4
|
||||
|
||||
slide_title:
|
||||
alignment: center
|
||||
padding_bottom: 1
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user