use crate::errors::Result;
use crate::measure::{Measurable, MeasureOptions, Measurement};
use crate::segment::{Segment, Segments};
use crate::text::Text;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct PaddingDimensions {
pub top: usize,
pub right: usize,
pub bottom: usize,
pub left: usize,
}
impl PaddingDimensions {
#[inline]
#[must_use]
pub const fn all(padding: usize) -> Self {
Self {
top: padding,
right: padding,
bottom: padding,
left: padding,
}
}
#[inline]
#[must_use]
pub const fn symmetric(horizontal: usize, vertical: usize) -> Self {
Self {
top: vertical,
right: horizontal,
bottom: vertical,
left: horizontal,
}
}
#[inline]
#[must_use]
pub const fn new(top: usize, right: usize, bottom: usize, left: usize) -> Self {
Self {
top,
right,
bottom,
left,
}
}
#[inline]
#[must_use]
pub const fn horizontal(&self) -> usize {
self.left.saturating_add(self.right)
}
#[inline]
#[must_use]
pub const fn vertical(&self) -> usize {
self.top.saturating_add(self.bottom)
}
}
impl From<usize> for PaddingDimensions {
fn from(value: usize) -> Self {
Self::all(value)
}
}
impl From<(usize, usize)> for PaddingDimensions {
fn from((horizontal, vertical): (usize, usize)) -> Self {
Self::symmetric(horizontal, vertical)
}
}
impl From<(usize, usize, usize, usize)> for PaddingDimensions {
fn from((top, right, bottom, left): (usize, usize, usize, usize)) -> Self {
Self::new(top, right, bottom, left)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Padding {
content: Text,
dimensions: PaddingDimensions,
pad_char: char,
}
impl Padding {
#[inline]
#[must_use]
pub fn new(content: impl Into<Text>, padding: impl Into<PaddingDimensions>) -> Self {
Self {
content: content.into(),
dimensions: padding.into(),
pad_char: ' ',
}
}
#[inline]
#[must_use]
pub const fn pad_char(mut self, ch: char) -> Self {
self.pad_char = ch;
self
}
#[inline]
#[must_use]
pub const fn dimensions(&self) -> &PaddingDimensions {
&self.dimensions
}
#[must_use]
pub fn render(&self, max_width: usize) -> Segments {
let mut segments = Segments::new();
let content_segments = self.content.to_segments();
let lines = content_segments.split_lines();
let content_width = max_width.saturating_sub(self.dimensions.horizontal());
for _ in 0..self.dimensions.top {
segments.push(Segment::new(self.pad_char.to_string().repeat(max_width)));
segments.push(Segment::newline());
}
for line in lines {
if self.dimensions.left > 0 {
segments.push(Segment::new(
self.pad_char.to_string().repeat(self.dimensions.left),
));
}
let line_width = line.cell_length();
segments.extend(line);
let remaining = content_width.saturating_sub(line_width);
if remaining > 0 {
segments.push(Segment::new(self.pad_char.to_string().repeat(remaining)));
}
if self.dimensions.right > 0 {
segments.push(Segment::new(
self.pad_char.to_string().repeat(self.dimensions.right),
));
}
segments.push(Segment::newline());
}
for _ in 0..self.dimensions.bottom {
segments.push(Segment::new(self.pad_char.to_string().repeat(max_width)));
segments.push(Segment::newline());
}
segments
}
}
impl Measurable for Padding {
fn measure(&self, options: &MeasureOptions) -> Result<Measurement> {
let content_measurement = self.content.measure(options)?;
let overhead = self.dimensions.horizontal();
let min = content_measurement.minimum.saturating_add(overhead);
let max = content_measurement.maximum.saturating_add(overhead);
Ok(Measurement::new(min, max).clamp_max(options.max_width))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_padding_dimensions_all() {
let dims = PaddingDimensions::all(2);
assert_eq!(dims.top, 2);
assert_eq!(dims.right, 2);
assert_eq!(dims.bottom, 2);
assert_eq!(dims.left, 2);
}
#[test]
fn test_padding_dimensions_symmetric() {
let dims = PaddingDimensions::symmetric(2, 1);
assert_eq!(dims.horizontal(), 4);
assert_eq!(dims.vertical(), 2);
}
#[test]
fn test_padding_dimensions_new() {
let dims = PaddingDimensions::new(1, 2, 3, 4);
assert_eq!(dims.top, 1);
assert_eq!(dims.right, 2);
assert_eq!(dims.bottom, 3);
assert_eq!(dims.left, 4);
}
#[test]
fn test_padding_render() {
let padding = Padding::new("hello", PaddingDimensions::symmetric(2, 1));
let segments = padding.render(20);
let text = segments.plain_text();
assert!(text.lines().count() >= 3); }
#[test]
fn test_padding_from_usize() {
let dims: PaddingDimensions = 3.into();
assert_eq!(dims, PaddingDimensions::all(3));
}
#[test]
fn test_padding_from_tuple() {
let dims: PaddingDimensions = (2, 1).into();
assert_eq!(dims, PaddingDimensions::symmetric(2, 1));
let dims: PaddingDimensions = (1, 2, 3, 4).into();
assert_eq!(dims, PaddingDimensions::new(1, 2, 3, 4));
}
#[test]
fn test_padding_measure() {
let padding = Padding::new("hello", PaddingDimensions::symmetric(2, 1));
let options = MeasureOptions::new(80);
let measurement = padding.measure(&options).ok().unwrap_or_default();
assert!(measurement.minimum >= 9);
}
}