use std::convert::TryInto;
use std::str::FromStr;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScrollDirection {
Up(u16),
Right(u16),
Down(u16),
Left(u16),
Top,
Bottom,
}
impl FromStr for ScrollDirection {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.split_whitespace().collect::<Vec<&str>>();
let value = s.get(1).cloned().unwrap_or_default().parse().unwrap_or(1);
match s.first().cloned() {
Some("up") | Some("u") => Ok(Self::Up(value)),
Some("right") | Some("r") => Ok(Self::Right(value)),
Some("down") | Some("d") => Ok(Self::Down(value)),
Some("left") | Some("l") => Ok(Self::Left(value)),
Some("top") | Some("t") => Ok(Self::Top),
Some("bottom") | Some("b") => Ok(Self::Bottom),
_ => Err(()),
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct ScrollAmount {
pub vertical: u16,
pub horizontal: u16,
}
#[derive(Clone, Debug)]
pub struct RowItem {
pub data: Vec<String>,
max_width: Option<u16>,
max_height: u16,
height_overflow: u16,
scroll: ScrollAmount,
}
impl RowItem {
pub fn new(
data: Vec<String>,
max_width: Option<u16>,
max_height: u16,
scroll: ScrollAmount,
) -> Self {
let mut item = Self {
max_width,
max_height,
height_overflow: (data.len().saturating_sub(max_height.into()) + 1)
.try_into()
.unwrap_or_default(),
scroll,
data,
};
item.process();
item
}
fn process(&mut self) {
if self.height_overflow != 1 {
if self.scroll.vertical != 0 {
self.scroll_vertical();
}
if self.scroll.vertical < self.height_overflow {
self.limit_height(self.max_height);
}
}
if let Some(width) = self.max_width {
if self.scroll.horizontal != 0
&& match self.data.iter().max_by(|x, y| x.len().cmp(&y.len())) {
Some(line) => line.len() >= width.into(),
None => false,
} {
self.scroll_horizontal();
}
self.limit_width(width);
}
}
fn scroll_vertical(&mut self) {
self.data = self
.data
.iter()
.skip(if self.scroll.vertical <= self.height_overflow {
self.scroll.vertical.into()
} else {
self.height_overflow.into()
})
.enumerate()
.map(|(i, line)| {
if i == 0 {
String::from("...")
} else {
line.to_string()
}
})
.collect::<Vec<String>>()
}
fn scroll_horizontal(&mut self) {
self.data = self
.data
.iter()
.map(|line| {
match line
.char_indices()
.nth((self.scroll.horizontal + 1).into())
{
Some((pos, _)) => {
format!(".{}", &line[pos..])
}
None => String::new(),
}
})
.collect::<Vec<String>>();
}
fn limit_width(&mut self, width: u16) {
self.data = self
.data
.iter()
.map(|line| match line.char_indices().nth(width.into()) {
Some((pos, _)) => format!("{}..", &line[0..pos]),
None => line.to_string(),
})
.collect::<Vec<String>>()
}
fn limit_height(&mut self, height: u16) {
self.data = self
.data
.drain(0..(height).into())
.enumerate()
.map(|(i, line)| {
if i == (height - 1) as usize {
String::from("...")
} else {
line
}
})
.collect::<Vec<String>>()
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_widget_row() -> Result<(), ()> {
assert_eq!(
vec!["..", ".ne3", ".ne4", ".."],
RowItem::new(
vec![
String::from("line1"),
String::from("line2"),
String::from("line3"),
String::from("line4"),
String::from("line5"),
],
Some(4),
4,
ScrollAmount {
vertical: 1,
horizontal: 1,
},
)
.data
);
assert_eq!(
ScrollDirection::Right(5),
ScrollDirection::from_str("right 5")?
);
assert_eq!(
ScrollDirection::Left(9),
ScrollDirection::from_str("left 9")?
);
assert_eq!(ScrollDirection::Down(1), ScrollDirection::from_str("d")?);
assert_eq!(ScrollDirection::Top, ScrollDirection::from_str("top")?);
assert_eq!(
ScrollDirection::Bottom,
ScrollDirection::from_str("bottom")?
);
assert!(ScrollDirection::from_str("xyz").is_err());
Ok(())
}
}