feat: allow configuring alignment when max columns is hit

This commit is contained in:
Matias Fontanini 2025-03-03 05:26:40 -08:00
parent ec1be93a06
commit 24e6ea8386
6 changed files with 90 additions and 12 deletions

View File

@ -50,6 +50,14 @@
"format": "uint16", "format": "uint16",
"minimum": 0.0 "minimum": 0.0
}, },
"max_columns_alignment": {
"description": "The alignment the presentation should have if `max_columns` is set and the terminal is larger than that.",
"allOf": [
{
"$ref": "#/definitions/MaxColumnsAlignment"
}
]
},
"terminal_font_size": { "terminal_font_size": {
"description": "Override the terminal font size when in windows or when using sixel.", "description": "Override the terminal font size when in windows or when using sixel.",
"default": 16, "default": 16,
@ -267,6 +275,32 @@
} }
} }
}, },
"MaxColumnsAlignment": {
"description": "The alignment to use when `defaults.max_columns` is set.",
"oneOf": [
{
"description": "Align the presentation to the left.",
"type": "string",
"enum": [
"left"
]
},
{
"description": "Align the presentation on the center.",
"type": "string",
"enum": [
"center"
]
},
{
"description": "Align the presentation to the right.",
"type": "string",
"enum": [
"right"
]
}
]
},
"MermaidConfig": { "MermaidConfig": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -74,7 +74,7 @@ pub struct DefaultsConfig {
pub theme: Option<String>, pub theme: Option<String>,
/// Override the terminal font size when in windows or when using sixel. /// Override the terminal font size when in windows or when using sixel.
#[serde(default = "default_font_size")] #[serde(default = "default_terminal_font_size")]
#[validate(range(min = 1))] #[validate(range(min = 1))]
pub terminal_font_size: u8, pub terminal_font_size: u8,
@ -89,24 +89,45 @@ pub struct DefaultsConfig {
/// A max width in columns that the presentation must always be capped to. /// A max width in columns that the presentation must always be capped to.
#[serde(default = "default_max_columns")] #[serde(default = "default_max_columns")]
pub max_columns: u16, pub max_columns: u16,
/// The alignment the presentation should have if `max_columns` is set and the terminal is
/// larger than that.
#[serde(default)]
pub max_columns_alignment: MaxColumnsAlignment,
} }
impl Default for DefaultsConfig { impl Default for DefaultsConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
theme: Default::default(), theme: Default::default(),
terminal_font_size: default_font_size(), terminal_font_size: default_terminal_font_size(),
image_protocol: Default::default(), image_protocol: Default::default(),
validate_overflows: Default::default(), validate_overflows: Default::default(),
max_columns: default_max_columns(), max_columns: default_max_columns(),
max_columns_alignment: Default::default(),
} }
} }
} }
fn default_font_size() -> u8 { fn default_terminal_font_size() -> u8 {
16 16
} }
/// The alignment to use when `defaults.max_columns` is set.
#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum MaxColumnsAlignment {
/// Align the presentation to the left.
Left,
/// Align the presentation on the center.
#[default]
Center,
/// Align the presentation to the right.
Right,
}
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)] #[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum ValidateOverflows { pub enum ValidateOverflows {

View File

@ -418,6 +418,7 @@ fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
bindings: config.bindings, bindings: config.bindings,
validate_overflows, validate_overflows,
max_columns: config.defaults.max_columns, max_columns: config.defaults.max_columns,
max_columns_alignment: config.defaults.max_columns_alignment,
}; };
let presenter = Presenter::new( let presenter = Presenter::new(
&default_theme, &default_theme,

View File

@ -4,7 +4,7 @@ use crate::{
listener::{Command, CommandListener}, listener::{Command, CommandListener},
speaker_notes::{SpeakerNotesEvent, SpeakerNotesEventPublisher}, speaker_notes::{SpeakerNotesEvent, SpeakerNotesEventPublisher},
}, },
config::KeyBindingsConfig, config::{KeyBindingsConfig, MaxColumnsAlignment},
export::ImageReplacer, export::ImageReplacer,
markdown::parse::{MarkdownParser, ParseError}, markdown::parse::{MarkdownParser, ParseError},
presentation::{ presentation::{
@ -43,6 +43,7 @@ pub struct PresenterOptions {
pub bindings: KeyBindingsConfig, pub bindings: KeyBindingsConfig,
pub validate_overflows: bool, pub validate_overflows: bool,
pub max_columns: u16, pub max_columns: u16,
pub max_columns_alignment: MaxColumnsAlignment,
} }
/// A slideshow presenter. /// A slideshow presenter.
@ -105,6 +106,7 @@ impl<'a> Presenter<'a> {
let drawer_options = TerminalDrawerOptions { let drawer_options = TerminalDrawerOptions {
font_size_fallback: self.options.font_size_fallback, font_size_fallback: self.options.font_size_fallback,
max_columns: self.options.max_columns, max_columns: self.options.max_columns,
max_columns_alignment: self.options.max_columns_alignment,
}; };
let mut drawer = TerminalDrawer::new(self.image_printer.clone(), drawer_options)?; let mut drawer = TerminalDrawer::new(self.image_printer.clone(), drawer_options)?;
loop { loop {

View File

@ -1,5 +1,6 @@
use super::{RenderError, RenderResult, layout::Layout, properties::CursorPosition, text::TextDrawer}; use super::{RenderError, RenderResult, layout::Layout, properties::CursorPosition, text::TextDrawer};
use crate::{ use crate::{
config::MaxColumnsAlignment,
markdown::{text::WeightedLine, text_style::Colors}, markdown::{text::WeightedLine, text_style::Colors},
render::{ render::{
layout::Positioning, layout::Positioning,
@ -27,12 +28,18 @@ const MINIMUM_LINE_LENGTH: u16 = 10;
pub(crate) struct RenderEngineOptions { pub(crate) struct RenderEngineOptions {
pub(crate) validate_overflows: bool, pub(crate) validate_overflows: bool,
pub(crate) max_columns: u16, pub(crate) max_columns: u16,
pub(crate) max_columns_alignment: MaxColumnsAlignment,
pub(crate) column_layout_margin: u16, pub(crate) column_layout_margin: u16,
} }
impl Default for RenderEngineOptions { impl Default for RenderEngineOptions {
fn default() -> Self { fn default() -> Self {
Self { validate_overflows: false, max_columns: u16::MAX, column_layout_margin: 4 } Self {
validate_overflows: false,
max_columns: u16::MAX,
max_columns_alignment: Default::default(),
column_layout_margin: 4,
}
} }
} }
@ -71,7 +78,12 @@ where
if window_dimensions.columns > options.max_columns { if window_dimensions.columns > options.max_columns {
let extra_width = window_dimensions.columns - options.max_columns; let extra_width = window_dimensions.columns - options.max_columns;
let dimensions = window_dimensions.shrink_columns(extra_width); let dimensions = window_dimensions.shrink_columns(extra_width);
WindowRect { dimensions, start_column: extra_width / 2, start_row } let start_column = match options.max_columns_alignment {
MaxColumnsAlignment::Left => 0,
MaxColumnsAlignment::Center => extra_width / 2,
MaxColumnsAlignment::Right => extra_width,
};
WindowRect { dimensions, start_column, start_row }
} else { } else {
WindowRect { dimensions: window_dimensions, start_column: 0, start_row } WindowRect { dimensions: window_dimensions, start_column: 0, start_row }
} }
@ -549,7 +561,12 @@ mod tests {
fn render(operations: &[RenderOperation]) -> Vec<Instruction> { fn render(operations: &[RenderOperation]) -> Vec<Instruction> {
let mut buf = TerminalBuf::default(); let mut buf = TerminalBuf::default();
let dimensions = WindowSize { rows: 100, columns: 100, height: 200, width: 200 }; let dimensions = WindowSize { rows: 100, columns: 100, height: 200, width: 200 };
let options = RenderEngineOptions { validate_overflows: false, max_columns: u16::MAX, column_layout_margin: 0 }; let options = RenderEngineOptions {
validate_overflows: false,
max_columns: u16::MAX,
max_columns_alignment: Default::default(),
column_layout_margin: 0,
};
let engine = RenderEngine::new(&mut buf, dimensions, options); let engine = RenderEngine::new(&mut buf, dimensions, options);
engine.render(operations.iter()).expect("render failed"); engine.render(operations.iter()).expect("render failed");
buf.instructions buf.instructions

View File

@ -6,6 +6,7 @@ pub(crate) mod text;
pub(crate) mod validate; pub(crate) mod validate;
use crate::{ use crate::{
config::MaxColumnsAlignment,
markdown::{ markdown::{
elements::Text, elements::Text,
text::WeightedLine, text::WeightedLine,
@ -28,16 +29,14 @@ use std::{
pub(crate) type RenderResult = Result<(), RenderError>; pub(crate) type RenderResult = Result<(), RenderError>;
pub(crate) struct TerminalDrawerOptions { pub(crate) struct TerminalDrawerOptions {
/// The font size to fall back to if we can't find the window size in pixels.
pub(crate) font_size_fallback: u8, pub(crate) font_size_fallback: u8,
/// The max width in columns that the presentation should be capped to.
pub(crate) max_columns: u16, pub(crate) max_columns: u16,
pub(crate) max_columns_alignment: MaxColumnsAlignment,
} }
impl Default for TerminalDrawerOptions { impl Default for TerminalDrawerOptions {
fn default() -> Self { fn default() -> Self {
Self { font_size_fallback: 1, max_columns: u16::MAX } Self { font_size_fallback: 1, max_columns: u16::MAX, max_columns_alignment: Default::default() }
} }
} }
@ -98,7 +97,11 @@ impl TerminalDrawer {
} }
fn create_engine(&mut self, dimensions: WindowSize) -> RenderEngine<Terminal<Stdout>> { fn create_engine(&mut self, dimensions: WindowSize) -> RenderEngine<Terminal<Stdout>> {
let options = RenderEngineOptions { max_columns: self.options.max_columns, ..Default::default() }; let options = RenderEngineOptions {
max_columns: self.options.max_columns,
max_columns_alignment: self.options.max_columns_alignment,
..Default::default()
};
RenderEngine::new(&mut self.terminal, dimensions, options) RenderEngine::new(&mut self.terminal, dimensions, options)
} }
} }