use crate::console::{ConsoleOptions, DynRenderable, RenderResult, Renderable};
use crate::segment::Segment;
use crate::style::Style;
#[derive(Debug, Clone, Copy)]
pub struct PaddingDimensions {
pub top: usize,
pub right: usize,
pub bottom: usize,
pub left: usize,
}
impl PaddingDimensions {
pub fn all(pad: usize) -> Self {
Self { top: pad, right: pad, bottom: pad, left: pad }
}
pub fn symmetric(vertical: usize, horizontal: usize) -> Self {
Self { top: vertical, right: horizontal, bottom: vertical, left: horizontal }
}
pub fn new(top: usize, right: usize, bottom: usize, left: usize) -> Self {
Self { top, right, bottom, left }
}
}
#[derive(Clone)]
pub struct Padding {
pub renderable: DynRenderable,
pub pad: PaddingDimensions,
pub style: Style,
pub expand: bool,
}
impl Padding {
pub fn new(renderable: impl Renderable + Send + Sync + 'static) -> Self {
Self {
renderable: DynRenderable::new(renderable),
pad: PaddingDimensions::all(0),
style: Style::new(),
expand: true,
}
}
pub fn pad(mut self, top: usize, right: usize, bottom: usize, left: usize) -> Self {
self.pad = PaddingDimensions::new(top, right, bottom, left);
self
}
pub fn pad_all(mut self, pad: usize) -> Self {
self.pad = PaddingDimensions::all(pad);
self
}
pub fn indent(mut self, level: usize) -> Self {
self.pad = PaddingDimensions::new(0, 0, 0, level);
self.expand = false;
self
}
pub fn style(mut self, style: Style) -> Self { self.style = style; self }
}
impl std::fmt::Debug for Padding {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Padding")
.field("pad", &self.pad)
.finish()
}
}
impl Renderable for Padding {
fn render(&self, options: &ConsoleOptions) -> RenderResult {
let pad = &self.pad;
let width = if self.expand {
options.max_width
} else {
(crate::measure::Measurement::fixed(0).maximum + pad.left + pad.right)
.min(options.max_width)
};
let inner_width = width.saturating_sub(pad.left + pad.right);
let inner_opts = options.update_width(inner_width.max(1));
let inner_height = options.height.map(|h| h.saturating_sub(pad.top + pad.bottom));
let inner_opts = if let Some(h) = inner_height {
inner_opts.update_height(h)
} else {
inner_opts
};
let content = self.renderable.render(&inner_opts);
let mut lines: Vec<Vec<Segment>> = Vec::new();
for _ in 0..pad.top {
lines.push(vec![
Segment::new(" ".repeat(width)),
Segment::line(),
]);
}
for content_line in &content.lines {
let mut line = Vec::new();
if pad.left > 0 {
line.push(Segment::new(" ".repeat(pad.left)));
}
line.extend(content_line.iter().cloned());
if pad.right > 0 {
line.push(Segment::new(" ".repeat(pad.right)));
line.push(Segment::line());
}
lines.push(line);
}
for _ in 0..pad.bottom {
lines.push(vec![
Segment::new(" ".repeat(width)),
Segment::line(),
]);
}
RenderResult { lines, items: Vec::new() }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::console::ConsoleOptions;
#[test]
fn test_padding() {
let p = Padding::new("Hello").pad_all(2);
let opts = ConsoleOptions::default();
let result = p.render(&opts);
let ansi = result.to_ansi();
assert!(ansi.contains("Hello"));
}
}