1 use std::convert::TryInto; 2 use std::str::FromStr; 3 4 /// Scrolling direction and offset. 5 #[derive(Clone, Copy, Debug, PartialEq)] 6 pub enum ScrollDirection { 7 /// Scroll up. 8 Up(u16), 9 /// Scroll right. 10 Right(u16), 11 /// Scroll down. 12 Down(u16), 13 /// Scroll left. 14 Left(u16), 15 /// Scroll to top. 16 Top, 17 /// Scroll to bottom. 18 Bottom, 19 } 20 21 impl FromStr for ScrollDirection { 22 type Err = (); from_str(s: &str) -> Result<Self, Self::Err>23 fn from_str(s: &str) -> Result<Self, Self::Err> { 24 let s = s.split_whitespace().collect::<Vec<&str>>(); 25 let value = s.get(1).cloned().unwrap_or_default().parse().unwrap_or(1); 26 match s.first().cloned() { 27 Some("up") | Some("u") => Ok(Self::Up(value)), 28 Some("right") | Some("r") => Ok(Self::Right(value)), 29 Some("down") | Some("d") => Ok(Self::Down(value)), 30 Some("left") | Some("l") => Ok(Self::Left(value)), 31 Some("top") | Some("t") => Ok(Self::Top), 32 Some("bottom") | Some("b") => Ok(Self::Bottom), 33 _ => Err(()), 34 } 35 } 36 } 37 38 /// Vertical/horizontal scroll values. 39 #[derive(Clone, Copy, Debug, Default)] 40 pub struct ScrollAmount { 41 /// Vertical scroll amount. 42 pub vertical: u16, 43 /// Horizontal scroll amount. 44 pub horizontal: u16, 45 } 46 47 /// Row item. 48 #[derive(Clone, Debug)] 49 pub struct RowItem { 50 /// Row data. 51 pub data: Vec<String>, 52 /// Maximum width of the row. 53 max_width: Option<u16>, 54 /// Maximum height of the row. 55 max_height: u16, 56 /// Overflow value of row height. 57 height_overflow: u16, 58 /// Scroll amount. 59 scroll: ScrollAmount, 60 } 61 62 impl RowItem { 63 /// Constructs a new instance of `RowItem`. new( data: Vec<String>, max_width: Option<u16>, max_height: u16, scroll: ScrollAmount, ) -> Self64 pub fn new( 65 data: Vec<String>, 66 max_width: Option<u16>, 67 max_height: u16, 68 scroll: ScrollAmount, 69 ) -> Self { 70 let mut item = Self { 71 max_width, 72 max_height, 73 height_overflow: (data 74 .len() 75 .checked_sub(max_height.into()) 76 .unwrap_or_default() 77 + 1) 78 .try_into() 79 .unwrap_or_default(), 80 scroll, 81 data, 82 }; 83 item.process(); 84 item 85 } 86 87 /// Processes the row data. 88 /// 89 /// It involves scrolling vertically/horizontally 90 /// and limiting the row width/height. process(&mut self)91 fn process(&mut self) { 92 if self.height_overflow != 1 { 93 if self.scroll.vertical != 0 { 94 self.scroll_vertical(); 95 } 96 if self.scroll.vertical < self.height_overflow { 97 self.limit_height(self.max_height); 98 } 99 } 100 if let Some(width) = self.max_width { 101 if self.scroll.horizontal != 0 102 && match self.data.iter().max_by(|x, y| x.len().cmp(&y.len())) { 103 Some(line) => line.len() >= width.into(), 104 None => false, 105 } { 106 self.scroll_horizontal(); 107 } 108 self.limit_width(width); 109 } 110 } 111 112 /// Scrolls the row vertically. scroll_vertical(&mut self)113 fn scroll_vertical(&mut self) { 114 self.data = self 115 .data 116 .iter() 117 .skip(if self.scroll.vertical <= self.height_overflow { 118 self.scroll.vertical.into() 119 } else { 120 self.height_overflow.into() 121 }) 122 .enumerate() 123 .map(|(i, line)| { 124 if i == 0 { 125 String::from("...") 126 } else { 127 line.to_string() 128 } 129 }) 130 .collect::<Vec<String>>() 131 } 132 133 /// Scrolls the row horizontally. scroll_horizontal(&mut self)134 fn scroll_horizontal(&mut self) { 135 self.data = self 136 .data 137 .iter() 138 .map(|line| { 139 match line 140 .char_indices() 141 .nth((self.scroll.horizontal + 1).into()) 142 { 143 Some((pos, _)) => { 144 format!(".{}", &line[pos..]) 145 } 146 None => String::new(), 147 } 148 }) 149 .collect::<Vec<String>>(); 150 } 151 152 /// Limits the row width to match the maximum width. limit_width(&mut self, width: u16)153 fn limit_width(&mut self, width: u16) { 154 self.data = self 155 .data 156 .iter() 157 .map(|line| match line.char_indices().nth(width.into()) { 158 Some((pos, _)) => format!("{}..", &line[0..pos]), 159 None => line.to_string(), 160 }) 161 .collect::<Vec<String>>() 162 } 163 164 /// Limits the row height to match the maximum height. limit_height(&mut self, height: u16)165 fn limit_height(&mut self, height: u16) { 166 self.data = self 167 .data 168 .drain(0..(height).into()) 169 .enumerate() 170 .map(|(i, line)| { 171 if i == (height - 1).into() { 172 String::from("...") 173 } else { 174 line 175 } 176 }) 177 .collect::<Vec<String>>() 178 } 179 } 180 181 #[cfg(test)] 182 mod tests { 183 use super::*; 184 use pretty_assertions::assert_eq; 185 #[test] test_widget_row()186 fn test_widget_row() { 187 assert_eq!( 188 vec!["..", ".ne3", ".ne4", ".."], 189 RowItem::new( 190 vec![ 191 String::from("line1"), 192 String::from("line2"), 193 String::from("line3"), 194 String::from("line4"), 195 String::from("line5"), 196 ], 197 Some(4), 198 4, 199 ScrollAmount { 200 vertical: 1, 201 horizontal: 1, 202 }, 203 ) 204 .data 205 ); 206 assert_eq!( 207 ScrollDirection::Right(5), 208 ScrollDirection::from_str("right 5").unwrap() 209 ); 210 assert_eq!( 211 ScrollDirection::Left(9), 212 ScrollDirection::from_str("left 9").unwrap() 213 ); 214 assert_eq!( 215 ScrollDirection::Down(1), 216 ScrollDirection::from_str("d").unwrap() 217 ); 218 assert_eq!( 219 ScrollDirection::Top, 220 ScrollDirection::from_str("top").unwrap() 221 ); 222 assert_eq!( 223 ScrollDirection::Bottom, 224 ScrollDirection::from_str("bottom").unwrap() 225 ); 226 assert!(ScrollDirection::from_str("xyz").is_err()); 227 } 228 } 229