booktyping_core/
ui.rs

1use ratatui::{
2	layout::Alignment,
3	prelude::*,
4	style::{Color, Style},
5	widgets::{Block, Borders, Paragraph, *},
6	Frame,
7};
8
9use crate::app::App;
10
11// BUG: panic on end of short input
12impl App {
13	pub fn render(&self, frame: &mut Frame) {
14		let &(start_line, start_offset) = self.line_index.get(self.sample_start_index).unwrap();
15		let &(cur_line, cur_offset) = self.line_index.get(self.sample_start_index + self.text.cur_char).unwrap();
16		let &(end_line, end_offset) = self.line_index.get(self.sample_start_index + self.sample_len).unwrap();
17		let mut lines: Vec<String> = self.book_lines.clone();
18		let num_rows = frame.area().height as usize - 2; // TODO fix crash
19		let rows_to_center = num_rows / 2 - 2; // TODO fix crash
20
21		let first_row = usize::checked_sub(rows_to_center, self.display_line).unwrap_or(0);
22
23		let num_skipped_lines = usize::checked_sub(self.display_line, rows_to_center).unwrap_or(0);
24		lines = lines.split_off(usize::min(num_skipped_lines, lines.len()));
25		lines.truncate(num_rows - first_row);
26
27		let mut display_lines: Vec<Line> = Vec::new();
28		for (mut i, s) in lines.iter().enumerate() {
29			i += num_skipped_lines;
30			match i {
31				_ if i == cur_line => match (i == start_line, i == end_line) {
32					(true, true) => {
33						display_lines.push(Line::from(vec![
34							s.chars().take(start_offset).collect::<String>().dim(),
35							s.chars().take(cur_offset).skip(start_offset).collect::<String>().white(),
36							s.chars().nth(cur_offset).unwrap().to_string().black().bg(Color::White),
37							s.chars().take(end_offset).skip(cur_offset + 1).collect::<String>().blue(),
38							s.chars().skip(end_offset).collect::<String>().dim(),
39						]));
40					}
41					(true, false) => {
42						display_lines.push(Line::from(vec![
43							s.chars().take(start_offset).collect::<String>().dim(),
44							s.chars().take(cur_offset).skip(start_offset).collect::<String>().white(),
45							s.chars().nth(cur_offset).unwrap().to_string().black().bg(Color::White),
46							s.chars().skip(cur_offset + 1).collect::<String>().blue(),
47						]));
48					}
49					(false, true) => {
50						display_lines.push(Line::from(vec![
51							s.chars().take(cur_offset).collect::<String>().white(),
52							s.chars().nth(cur_offset).unwrap().to_string().black().bg(Color::White),
53							s.chars().take(end_offset).skip(cur_offset + 1).collect::<String>().blue(),
54							s.chars().skip(end_offset).collect::<String>().dim(),
55						]));
56					}
57					(false, false) => {
58						display_lines.push(Line::from(vec![
59							s.chars().take(cur_offset).collect::<String>().white(),
60							s.chars().nth(cur_offset).unwrap().to_string().black().bg(Color::White),
61							s.chars().skip(cur_offset + 1).collect::<String>().blue(),
62						]));
63					}
64				},
65				_ if i < cur_line => match i {
66					_ if i == start_line => {
67						display_lines.push(Line::from(vec![
68							s.chars().take(start_offset).collect::<String>().dim(),
69							s.chars().skip(start_offset).collect::<String>().white(),
70						]));
71					}
72					_ if i < start_line => {
73						display_lines.push(s.clone().dim().into());
74					}
75					_ => {
76						display_lines.push(s.clone().white().into());
77					}
78				},
79				_ if i == end_line => {
80					display_lines.push(Line::from(vec![
81						s.chars().take(end_offset).collect::<String>().blue(),
82						s.chars().skip(end_offset).collect::<String>().dim(),
83					]));
84				}
85				_ if i < end_line => {
86					display_lines.push(s.clone().blue().into());
87				}
88				_ => {
89					display_lines.push(s.clone().dim().into());
90				}
91			}
92		}
93
94		let graph = Paragraph::new::<Text>(display_lines.into()).style(Style::default());
95
96		let screen = Rect::new(0, 0, frame.area().width, frame.area().height);
97
98		let vert = Layout::default()
99			.direction(Direction::Vertical)
100			.constraints([Constraint::Length(first_row as u16 + 1), Constraint::Percentage(100)])
101			.split(screen);
102		let horiz = Layout::default()
103			.direction(Direction::Horizontal)
104			.constraints([
105				Constraint::Percentage((100 - self.text_width_percent) / 2),
106				Constraint::Percentage(self.text_width_percent),
107				Constraint::Percentage((100 - self.text_width_percent) / 2),
108			])
109			.split(vert[1])[1];
110
111		// Render into the second chunk of the layout.
112		frame.render_widget(graph, horiz);
113		frame.render_widget(
114			Block::default()
115				.title("Booktyping")
116				.title(block::Title::from(format!("{}", self.get_rolling_average().unwrap())).alignment(Alignment::Right))
117				.borders(Borders::ALL)
118				.border_style(Style::new().white()),
119			screen,
120		);
121	}
122}