use crate::components::Blank;
use crate::Component;
use crate::Dimensions;
use crate::DrawMode;
use crate::Lines;
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum VerticalAlignmentKind {
Top,
Center,
Bottom,
}
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum HorizontalAlignmentKind {
Left(bool),
Center,
Right,
}
#[derive(Debug)]
pub struct Aligned<C: Component = Box<dyn Component>> {
pub child: C,
pub horizontal: HorizontalAlignmentKind,
pub vertical: VerticalAlignmentKind,
}
impl<C: Component> Aligned<C> {
pub fn new(
child: C,
horizontal: HorizontalAlignmentKind,
vertical: VerticalAlignmentKind,
) -> Self {
Self {
child,
horizontal,
vertical,
}
}
}
impl Default for Aligned {
fn default() -> Self {
Self {
child: Box::new(Blank),
horizontal: HorizontalAlignmentKind::Left(false),
vertical: VerticalAlignmentKind::Top,
}
}
}
impl<C: Component> Component for Aligned<C> {
fn draw_unchecked(&self, dimensions: Dimensions, mode: DrawMode) -> anyhow::Result<Lines> {
let Dimensions { width, height } = dimensions;
let mut output = self.child.draw(dimensions, mode)?;
let number_of_lines = output.len();
let padding_needed = height.saturating_sub(number_of_lines);
match self.vertical {
VerticalAlignmentKind::Top => {}
VerticalAlignmentKind::Center => {
let top_pad = padding_needed / 2;
output.pad_lines_top(top_pad);
output.pad_lines_bottom(padding_needed - top_pad);
}
VerticalAlignmentKind::Bottom => {
output.pad_lines_top(padding_needed);
}
}
match self.horizontal {
HorizontalAlignmentKind::Left(justified) => {
if justified {
output.justify();
}
}
HorizontalAlignmentKind::Center => {
for line in output.iter_mut() {
let output_len = line.len();
let padding_needed = width.saturating_sub(output_len);
let left_pad = padding_needed / 2;
line.pad_left(left_pad);
line.pad_right(padding_needed - left_pad);
}
}
HorizontalAlignmentKind::Right => {
for line in output.iter_mut() {
line.pad_left(width.saturating_sub(line.len()));
}
}
}
Ok(output)
}
}
#[cfg(test)]
mod tests {
use derive_more::AsRef;
use crate::components::alignment::HorizontalAlignmentKind;
use crate::components::alignment::VerticalAlignmentKind;
use crate::components::echo::Echo;
use crate::components::Aligned;
use crate::components::DrawMode;
use crate::Component;
use crate::Dimensions;
use crate::Line;
use crate::Lines;
#[derive(AsRef, Debug)]
struct Msg(Lines);
#[test]
fn test_align_left_unjustified() {
let original = Lines(vec![
vec!["hello world"].try_into().unwrap(),
vec!["pretty normal test"].try_into().unwrap(),
]);
let component = Aligned::new(
Echo(original.clone()),
HorizontalAlignmentKind::Left(false),
VerticalAlignmentKind::Top,
);
let dimensions = Dimensions::new(20, 20);
let actual = component.draw(dimensions, DrawMode::Normal).unwrap();
assert_eq!(actual, original);
}
#[test]
fn test_align_left_justified() {
let original = Lines(vec![
vec!["hello world"].try_into().unwrap(), vec!["pretty normal test"].try_into().unwrap(), vec!["short"].try_into().unwrap(), ]);
let component = Aligned::new(
Echo(original),
HorizontalAlignmentKind::Left(true),
VerticalAlignmentKind::Top,
);
let dimensions = Dimensions::new(20, 20);
let actual = component.draw(dimensions, DrawMode::Normal).unwrap();
let expected = Lines(vec![
vec!["hello world", &" ".repeat(18 - 11)]
.try_into()
.unwrap(),
vec!["pretty normal test"].try_into().unwrap(),
vec!["short", &" ".repeat(18 - 5)].try_into().unwrap(),
]);
assert_eq!(actual, expected,);
}
#[test]
fn test_align_col_center() {
let original = Lines(vec![
vec!["hello world"].try_into().unwrap(), vec!["pretty normal testss"].try_into().unwrap(), vec!["shorts"].try_into().unwrap(), ]);
let component = Aligned::new(
Echo(original),
HorizontalAlignmentKind::Center,
VerticalAlignmentKind::Top,
);
let dimensions = Dimensions::new(20, 20);
let actual = component.draw(dimensions, DrawMode::Normal).unwrap();
let expected = Lines(vec![
vec![" ".repeat(4).as_ref(), "hello world", &" ".repeat(5)]
.try_into()
.unwrap(),
vec!["pretty normal testss"].try_into().unwrap(),
vec![" ".repeat(7).as_ref(), "shorts", &" ".repeat(7)]
.try_into()
.unwrap(),
]);
assert_eq!(actual, expected);
}
#[test]
fn test_align_right() {
let original = Lines(vec![
vec!["hello world"].try_into().unwrap(), vec!["pretty normal testsss"].try_into().unwrap(), vec!["shorts"].try_into().unwrap(), ]);
let component = Aligned::new(
Echo(original),
HorizontalAlignmentKind::Right,
VerticalAlignmentKind::Top,
);
let dimensions = Dimensions::new(20, 20);
let actual = component.draw(dimensions, DrawMode::Normal).unwrap();
let expected = Lines(vec![
vec![" ".repeat(9).as_ref(), "hello world"]
.try_into()
.unwrap(),
vec!["pretty normal testss"].try_into().unwrap(),
vec![" ".repeat(14).as_ref(), "shorts"].try_into().unwrap(),
]);
assert_eq!(actual, expected);
}
#[test]
fn test_align_top() {
let original = Lines(vec![
vec!["hello world"].try_into().unwrap(), vec!["pretty normal testsss"].try_into().unwrap(), vec!["shorts"].try_into().unwrap(), ]);
let component = Aligned::new(
Echo(original),
HorizontalAlignmentKind::Left(false),
VerticalAlignmentKind::Top,
);
let dimensions = Dimensions::new(20, 20);
let actual = component.draw(dimensions, DrawMode::Normal).unwrap();
let expected = Lines(vec![
vec!["hello world"].try_into().unwrap(),
vec!["pretty normal testss"].try_into().unwrap(),
vec!["shorts"].try_into().unwrap(),
]);
assert_eq!(actual, expected);
}
#[test]
fn test_align_row_center() {
let original = Lines(vec![
vec!["hello world"].try_into().unwrap(), vec!["pretty normal testsss"].try_into().unwrap(), vec!["shorts"].try_into().unwrap(), ]);
let component = Aligned::new(
Echo(original),
HorizontalAlignmentKind::Left(false),
VerticalAlignmentKind::Center,
);
let dimensions = Dimensions::new(20, 10);
let actual = component.draw(dimensions, DrawMode::Normal).unwrap();
let expected = Lines(vec![
Line::default(),
Line::default(),
Line::default(),
vec!["hello world"].try_into().unwrap(),
vec!["pretty normal testss"].try_into().unwrap(),
vec!["shorts"].try_into().unwrap(),
Line::default(),
Line::default(),
Line::default(),
Line::default(),
]);
assert_eq!(actual, expected);
}
#[test]
fn test_align_bottom() {
let original = Lines(vec![
vec!["hello world"].try_into().unwrap(), vec!["pretty normal testsss"].try_into().unwrap(), vec!["shorts"].try_into().unwrap(), ]);
let component = Aligned::new(
Echo(original),
HorizontalAlignmentKind::Left(false),
VerticalAlignmentKind::Bottom,
);
let dimensions = Dimensions::new(20, 10);
let actual = component.draw(dimensions, DrawMode::Normal).unwrap();
let expected = Lines(vec![
Line::default(),
Line::default(),
Line::default(),
Line::default(),
Line::default(),
Line::default(),
Line::default(),
vec!["hello world"].try_into().unwrap(),
vec!["pretty normal testss"].try_into().unwrap(),
vec!["shorts"].try_into().unwrap(),
]);
assert_eq!(actual, expected);
}
}