Skip to main content

gpg_tui/widget/
row.rs

1use std::convert::TryInto;
2use std::str::FromStr;
3
4/// Scrolling direction and offset.
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub 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
21impl FromStr for ScrollDirection {
22	type 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)]
40pub 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)]
49pub 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
62impl RowItem {
63	/// Constructs a new instance of `RowItem`.
64	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.len().saturating_sub(max_height.into()) + 1)
74				.try_into()
75				.unwrap_or_default(),
76			scroll,
77			data,
78		};
79		item.process();
80		item
81	}
82
83	/// Processes the row data.
84	///
85	/// It involves scrolling vertically/horizontally
86	/// and limiting the row width/height.
87	fn process(&mut self) {
88		if self.height_overflow != 1 {
89			if self.scroll.vertical != 0 {
90				self.scroll_vertical();
91			}
92			if self.scroll.vertical < self.height_overflow {
93				self.limit_height(self.max_height);
94			}
95		}
96		if let Some(width) = self.max_width {
97			if self.scroll.horizontal != 0
98				&& match self.data.iter().max_by(|x, y| x.len().cmp(&y.len())) {
99					Some(line) => line.len() >= width.into(),
100					None => false,
101				} {
102				self.scroll_horizontal();
103			}
104			self.limit_width(width);
105		}
106	}
107
108	/// Scrolls the row vertically.
109	fn scroll_vertical(&mut self) {
110		self.data = self
111			.data
112			.iter()
113			.skip(if self.scroll.vertical <= self.height_overflow {
114				self.scroll.vertical.into()
115			} else {
116				self.height_overflow.into()
117			})
118			.enumerate()
119			.map(|(i, line)| {
120				if i == 0 {
121					String::from("...")
122				} else {
123					line.to_string()
124				}
125			})
126			.collect::<Vec<String>>()
127	}
128
129	/// Scrolls the row horizontally.
130	fn scroll_horizontal(&mut self) {
131		self.data = self
132			.data
133			.iter()
134			.map(|line| {
135				match line
136					.char_indices()
137					.nth((self.scroll.horizontal + 1).into())
138				{
139					Some((pos, _)) => {
140						format!(".{}", &line[pos..])
141					}
142					None => String::new(),
143				}
144			})
145			.collect::<Vec<String>>();
146	}
147
148	/// Limits the row width to match the maximum width.
149	fn limit_width(&mut self, width: u16) {
150		self.data = self
151			.data
152			.iter()
153			.map(|line| match line.char_indices().nth(width.into()) {
154				Some((pos, _)) => format!("{}..", &line[0..pos]),
155				None => line.to_string(),
156			})
157			.collect::<Vec<String>>()
158	}
159
160	/// Limits the row height to match the maximum height.
161	fn limit_height(&mut self, height: u16) {
162		self.data = self
163			.data
164			.drain(0..(height).into())
165			.enumerate()
166			.map(|(i, line)| {
167				if i == (height - 1) as usize {
168					String::from("...")
169				} else {
170					line
171				}
172			})
173			.collect::<Vec<String>>()
174	}
175}
176
177#[cfg(test)]
178mod tests {
179	use super::*;
180	use pretty_assertions::assert_eq;
181	#[test]
182	fn test_widget_row() -> Result<(), ()> {
183		assert_eq!(
184			vec!["..", ".ne3", ".ne4", ".."],
185			RowItem::new(
186				vec![
187					String::from("line1"),
188					String::from("line2"),
189					String::from("line3"),
190					String::from("line4"),
191					String::from("line5"),
192				],
193				Some(4),
194				4,
195				ScrollAmount {
196					vertical: 1,
197					horizontal: 1,
198				},
199			)
200			.data
201		);
202		assert_eq!(
203			ScrollDirection::Right(5),
204			ScrollDirection::from_str("right 5")?
205		);
206		assert_eq!(
207			ScrollDirection::Left(9),
208			ScrollDirection::from_str("left 9")?
209		);
210		assert_eq!(ScrollDirection::Down(1), ScrollDirection::from_str("d")?);
211		assert_eq!(ScrollDirection::Top, ScrollDirection::from_str("top")?);
212		assert_eq!(
213			ScrollDirection::Bottom,
214			ScrollDirection::from_str("bottom")?
215		);
216		assert!(ScrollDirection::from_str("xyz").is_err());
217		Ok(())
218	}
219}