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