1use crate::helper;
2use crate::helper::SadariEnvironment;
3use std::{collections::HashMap, error::Error, fmt};
4use tui::{
5 backend::Backend,
6 buffer::Buffer,
7 layout::{Alignment, Constraint, Direction, Layout, Rect},
8 style::{Color, Modifier, Style},
9 symbols,
10 symbols::line,
11 widgets::{Block, Borders, Paragraph, Text, Widget},
12 Frame, Terminal,
13};
14
15fn create_simple_block<'a>(borders: Borders, color: Color) -> Block<'a> {
16 Block::default()
17 .borders(borders)
18 .border_style(Style::default().fg(color))
19}
20
21#[derive(PartialEq)]
22pub enum RenderingState {
23 Idle,
24 Drawing,
25 Done,
26}
27
28pub enum LineDirection {
29 Left,
30 Right,
31 Down,
32}
33
34enum BorderKind {
35 Selected,
36 NotSelected,
37}
38
39#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
40pub struct Point {
41 pub x: i32,
42 pub y: i32,
43}
44
45impl Point {
46 pub fn new(x: i32, y: i32) -> Self {
47 Point { x, y }
48 }
49}
50
51impl fmt::Display for Point {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 write!(f, "(x: {}, y: {})", self.x, self.y)
54 }
55}
56
57impl BorderKind {
58 fn color(self) -> Color {
59 match self {
60 BorderKind::Selected => Color::Red,
61 BorderKind::NotSelected => Color::White,
62 }
63 }
64}
65
66#[derive(Clone, Copy)]
67struct LineWidget {
68 border_style: Style,
69 line_type: &'static str,
70}
71
72impl LineWidget {
73 fn new(border_style: Style, line_type: &'static str) -> Self {
74 Self {
75 border_style,
76 line_type,
77 }
78 }
79}
80
81impl Widget for LineWidget {
82 fn draw(&mut self, area: Rect, buf: &mut Buffer) {
83 buf.set_string(area.left(), area.top(), self.line_type, Style::default());
84
85 match self.line_type {
86 line::VERTICAL => {
87 for y in area.top()..area.bottom() {
88 buf.get_mut(area.left(), y)
89 .set_symbol(self.line_type)
90 .set_style(self.border_style);
91 }
92 }
93 line::HORIZONTAL => {
94 for x in area.left()..area.right() {
95 buf.get_mut(x, area.top())
96 .set_symbol(line::HORIZONTAL)
97 .set_style(self.border_style);
98 }
99 }
100 _ => {}
101 }
102 }
103}
104
105struct Label<'a> {
106 text: &'a str,
107 text_style: Style,
108}
109
110impl<'a> Default for Label<'a> {
111 fn default() -> Label<'a> {
112 Label {
113 text: "",
114 text_style: Style::default(),
115 }
116 }
117}
118
119impl<'a> Widget for Label<'a> {
120 fn draw(&mut self, area: Rect, buf: &mut Buffer) {
121 buf.set_string(area.left(), area.top(), self.text, self.text_style);
122 }
123}
124
125impl<'a> Label<'a> {
126 fn text(mut self, text: &'a str) -> Label<'a> {
127 self.text = text;
128 self
129 }
130
131 fn text_style(mut self, style: Style) -> Self {
132 self.text_style = style;
133
134 self
135 }
136}
137
138pub fn _draw_bridge_point<B>(point_hashmap: &HashMap<Point, Point>, f: &mut Frame<B>)
139where
140 B: Backend,
141{
142 for (_, value) in point_hashmap {
143 let mut point = Block::default()
144 .borders(Borders::TOP)
145 .border_style(Style::default().fg(Color::Red));
146
147 f.render(&mut point, Rect::new(value.x as u16, value.y as u16, 2, 2));
148 }
149}
150
151pub fn render_sadari<B>(
152 terminal: &mut Terminal<B>,
153 sadari_env: &SadariEnvironment,
154 selected_chunk: u8,
155 tick: i32,
156 rendering_state: &mut RenderingState,
157 bridge_hashmap: &HashMap<u16, Vec<u16>>,
158 path_hashmap: &HashMap<u8, Vec<Point>>,
159) -> Result<(), Box<dyn Error>>
160where
161 B: Backend,
162{
163 let number_of_blocks: u8 = sadari_env.number_of_blocks;
164
165 let y_coordinate = sadari_env.y_coordinate;
166 let name_vec = &sadari_env.name_vec;
167 let result_vec = &sadari_env.result_vec;
168
169 terminal.draw(|mut f| {
170 let chunks = Layout::default()
171 .direction(Direction::Vertical)
172 .constraints(
173 [
174 Constraint::Percentage(15), Constraint::Percentage(80), Constraint::Percentage(5),
177 ]
178 .as_ref(),
179 )
180 .split(f.size());
181
182 let guide_chunk = chunks[0];
184 let guide_chunk = Layout::default()
185 .direction(Direction::Horizontal)
186 .constraints([Constraint::Percentage(100)].as_ref())
187 .horizontal_margin(10)
188 .vertical_margin(1)
189 .split(guide_chunk);
190
191 let text = [Text::raw(
192 r#"
193ā, ā or h,l : Left, Right s, enter : Start path animation
194q : Quit r : Go to result
195 "#,
196 )];
197
198 let block = Block::default()
199 .borders(Borders::NONE)
200 .title_style(Style::default().modifier(Modifier::BOLD).fg(Color::Green))
201 .title("Rust-Sadari-Cli!");
202
203 let mut paragraph = Paragraph::new(text.iter())
204 .block(block)
205 .alignment(Alignment::Center);
206 f.render(&mut paragraph, guide_chunk[0]);
207
208 let footer_chunk = chunks[2];
210 let footer_chunk = Layout::default()
211 .direction(Direction::Horizontal)
212 .constraints([Constraint::Percentage(100)].as_ref())
213 .horizontal_margin(10)
214 .split(footer_chunk);
215
216 let text = [Text::styled(
217 "\n\nšŗ Github: 24seconds/rust-sadari-cli, powered by 24seconds",
218 Style::default().fg(Color::Yellow),
219 )];
220 let mut paragraph = Paragraph::new(text.iter()).alignment(Alignment::Center);
221 f.render(&mut paragraph, footer_chunk[0]);
222
223 let main_chunks = Layout::default()
225 .direction(Direction::Vertical)
226 .constraints(
227 [
228 Constraint::Percentage(10), Constraint::Percentage(80), Constraint::Percentage(10), ]
232 .as_ref(),
233 )
234 .horizontal_margin(10)
235 .split(chunks[1]);
236
237 let name_chunk = main_chunks[0];
238 let vec_names_layout = helper::calc_names_layout(number_of_blocks, 3, 1).unwrap();
239
240 let name_chunks = Layout::default()
242 .direction(Direction::Horizontal)
243 .constraints(
244 vec_names_layout
245 .iter()
246 .map(|x| Constraint::Percentage(*x))
247 .collect::<Vec<Constraint>>(),
248 )
249 .split(name_chunk);
250
251 for i in 0..number_of_blocks {
252 let mut block = create_simple_block(
253 Borders::ALL,
254 match i {
255 _ if i == selected_chunk => BorderKind::Selected.color(),
256 _ => BorderKind::NotSelected.color(),
257 },
258 );
259 f.render(&mut block, name_chunks[i as usize * 2 + 1]);
260
261 let text = [Text::raw(name_vec.get(i as usize).unwrap())];
263 let mut paragraph = Paragraph::new(text.iter())
264 .alignment(Alignment::Center)
265 .wrap(true);
266 f.render(&mut paragraph, block.inner(name_chunks[i as usize * 2 + 1]));
267 }
268
269 let result_chunk = main_chunks[2];
271 let result_chunks = Layout::default()
272 .direction(Direction::Horizontal)
273 .constraints(
274 vec_names_layout
275 .iter()
276 .map(|x| Constraint::Percentage(*x))
277 .collect::<Vec<Constraint>>(),
278 )
279 .split(result_chunk);
280
281 for i in 0..number_of_blocks {
282 let mut block = create_simple_block(Borders::ALL, Color::White);
283 f.render(&mut block, result_chunks[i as usize * 2 + 1]);
284
285 let text = [Text::raw(result_vec.get(i as usize).unwrap())];
287 let mut paragraph = Paragraph::new(text.iter())
288 .alignment(Alignment::Center)
289 .wrap(true);
290 f.render(
291 &mut paragraph,
292 block.inner(result_chunks[i as usize * 2 + 1]),
293 );
294 }
295
296 let mut bridge_point_hashmap: HashMap<Point, Point> = HashMap::new();
297
298 let bridge_chunks: Vec<Rect> = name_chunks
300 .iter()
301 .zip(result_chunks.iter())
302 .map(|z| {
303 let n = z.0;
304 let r = z.1;
305 Rect::new(
306 n.x + n.width / 2,
307 n.y + n.height,
308 n.width / 2,
309 r.y - (n.y + n.height),
310 )
311 })
312 .collect();
313
314 for i in 0..number_of_blocks {
315 let mut line = create_simple_block(Borders::LEFT, Color::LightBlue);
316 f.render(&mut line, bridge_chunks[i as usize * 2 + 1]);
317
318 let Rect {
320 x,
321 y,
322 width: _,
323 height,
324 } = bridge_chunks[i as usize * 2 + 1];
325
326 bridge_point_hashmap
327 .insert(Point::new(i as i32, -1), Point::new(x as i32, y as i32 - 1));
328 bridge_point_hashmap.insert(
329 Point::new(i as i32, y_coordinate as i32),
330 Point::new(x as i32, (y + height) as i32),
331 );
332 }
333 for i in 0..(number_of_blocks - 1) {
335 let chunk_i = i as usize * 2 + 1;
336 let bridge_chunk = Rect::new(
337 bridge_chunks[chunk_i].x + 1,
338 bridge_chunks[chunk_i].y + 1,
339 bridge_chunks[chunk_i + 2].x - bridge_chunks[chunk_i].x - 1,
340 bridge_chunks[chunk_i].height - 2,
341 );
342
343 let vec_indexes: &Vec<u16> = bridge_hashmap.get(&(i as u16)).unwrap();
344 let bridge_chunks = Layout::default()
345 .direction(Direction::Vertical)
346 .constraints(
347 helper::calc_distributed_height(y_coordinate + 1, bridge_chunk.height)
348 .iter()
349 .map(|x| Constraint::Length(*x))
350 .collect::<Vec<Constraint>>(),
351 )
352 .split(bridge_chunk);
353
354 let mut line = create_simple_block(Borders::BOTTOM, Color::Yellow);
355 vec_indexes.iter().for_each(|vec_index| {
356 f.render(&mut line, bridge_chunks[*vec_index as usize]);
357
358 let Rect {
360 x,
361 y,
362 width,
363 height,
364 } = bridge_chunks[*vec_index as usize];
365
366 bridge_point_hashmap.insert(
367 Point::new(i as i32, *vec_index as i32),
368 Point::new(x as i32 - 1, (y + height - 1) as i32),
369 );
370 bridge_point_hashmap.insert(
371 Point::new(i as i32 + 1, *vec_index as i32),
372 Point::new((x - 1 + width + 1) as i32, (y + height - 1) as i32),
373 );
374 });
375 }
376
377 let path = path_hashmap.get(&selected_chunk).unwrap();
379
380 let mut current_path_index = 0;
381 let mut left_tick = tick;
382 while left_tick > 0 && current_path_index < path.len() as usize {
383 let (tick, area, direction, next_path_index) = helper::calc_partial_line(
384 &bridge_point_hashmap,
385 &path,
386 left_tick,
387 current_path_index as i32,
388 selected_chunk,
389 );
390
391 left_tick = tick;
392 current_path_index = next_path_index as usize;
393
394 let mut line = LineWidget::new(
395 Style::default().fg(Color::Red),
396 match direction {
397 LineDirection::Down => symbols::line::VERTICAL,
398 LineDirection::Right | LineDirection::Left => symbols::line::HORIZONTAL,
399 },
400 );
401
402 f.render(&mut line, area);
403 }
404
405 if current_path_index == path.len() {
406 let Point {
408 x: result_index,
409 y: _,
410 } = path.last().unwrap();
411
412 let mut block = create_simple_block(Borders::ALL, Color::Red);
413 f.render(&mut block, result_chunks[*result_index as usize * 2 + 1]);
414
415 *rendering_state = RenderingState::Done;
416 }
417 })?;
418
419 Ok(())
420}
421
422pub fn render_result<B>(
423 terminal: &mut Terminal<B>,
424 sadari_env: &SadariEnvironment,
425 path_hashmap: &HashMap<u8, Vec<Point>>,
426) -> Result<(), Box<dyn Error>>
427where
428 B: Backend,
429{
430 terminal.draw(|mut f| {
431 let size = f.size();
432 let chunks = Layout::default()
433 .direction(Direction::Vertical)
434 .margin(5)
435 .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
436 .split(size);
437
438 let length = path_hashmap.len() as u8;
439
440 let vec_text: Vec<(&String, &String)> = (0..length)
441 .into_iter()
442 .map(|i| {
443 let path = path_hashmap.get(&i).unwrap();
444 let start = path.first().unwrap();
445 let end = path.last().unwrap();
446
447 let start = start.x;
448 let end = end.x;
449
450 let start = sadari_env.name_vec.get(start as usize).unwrap();
451 let end = sadari_env.result_vec.get(end as usize).unwrap();
452
453 (start, end)
454 })
455 .collect();
456
457 let mut block = create_simple_block(Borders::ALL, Color::White);
458 f.render(&mut block, chunks[1]);
459
460 let main_chunks = Layout::default()
461 .direction(Direction::Horizontal)
462 .margin(2)
463 .constraints(
464 [
465 Constraint::Percentage(45),
466 Constraint::Length(20),
467 Constraint::Percentage(45),
468 ]
469 .as_ref(),
470 )
471 .split(block.inner(chunks[1]));
472
473 let mut label = Label::default()
474 .text("Sadari Result")
475 .text_style(Style::default().modifier(Modifier::BOLD).fg(Color::Green));
476 f.render(&mut label, chunks[0]);
477
478 let vec_start_text: Vec<Text> = vec_text
479 .iter()
480 .map(|x| {
481 let (start, _) = *x;
482
483 Text::raw(format!("{}\n\n", start))
484 })
485 .collect();
486 let mut paragraph = Paragraph::new(vec_start_text.iter()).alignment(Alignment::Right);
487 f.render(&mut paragraph, main_chunks[0]);
488
489 let vec_line: Vec<Text> = (0..length)
490 .into_iter()
491 .map(|_| Text::raw("<āāāāāāāāāāā>\n\n"))
492 .collect();
493 let mut paragraph = Paragraph::new(vec_line.iter()).alignment(Alignment::Center);
494 f.render(&mut paragraph, main_chunks[1]);
495
496 let vec_end_text: Vec<Text> = vec_text
497 .iter()
498 .map(|x| {
499 let (_, end) = *x;
500
501 Text::raw(format!("{}\n\n", end))
502 })
503 .collect();
504 let mut paragraph = Paragraph::new(vec_end_text.iter()).alignment(Alignment::Left);
505 f.render(&mut paragraph, main_chunks[2]);
506 })?;
507
508 Ok(())
509}