godwit/tui/
mod.rs

1//! TUI Splash Core
2//!
3//! TUI splash procedure core. Dictates the TUI windows and operations.
4use crate::iohandler::scanner;
5use crossterm::{
6	event::{poll, read, Event, KeyCode},
7	terminal,
8};
9use log::debug;
10use std::{io, process, time::Duration};
11use tui::{
12	backend::CrosstermBackend,
13	layout::{Alignment, Constraint, Direction, Layout},
14	style::{Color, Modifier, Style},
15	widgets::{Block, Borders, Paragraph, SelectableList, Text, Widget},
16	Terminal,
17};
18
19// TODO: Refactor
20
21/// Global-state for TUI
22pub struct DrawState<'a> {
23	list_items: Vec<&'a str>,
24	selected_item: usize,
25}
26/// TUI bootstrap
27pub fn run() -> Result<(), crossterm::ErrorKind> {
28	terminal::enable_raw_mode()?;
29	let backend = CrosstermBackend::new(io::stdout());
30	let mut term = Terminal::new(backend).unwrap();
31
32	term.clear()?;
33
34	let mut draw_state = DrawState {
35		list_items: vec!["Init", "Add", "Remove", "Switch"],
36		selected_item: 0,
37	};
38
39	draw(&mut term, &draw_state).unwrap();
40
41	loop {
42		check_update(&mut term, &mut draw_state)?;
43	}
44}
45
46/// Up key-press subroutine.
47fn up_pressed(state: &mut DrawState) {
48	if state.selected_item != 0 {
49		state.selected_item -= 1;
50	}
51}
52
53/// Down key-press subroutine.
54fn down_pressed(state: &mut DrawState) {
55	if state.selected_item + 1 != state.list_items.len() {
56		state.selected_item += 1;
57	}
58}
59
60/// Enter key-press subroutine.
61fn enter_pressed(_state: &mut DrawState) {
62	// Move to next window
63}
64
65/// Exit subroutine.
66fn exit_routine(
67	term: &mut Terminal<CrosstermBackend<io::Stdout>>,
68	_state: &mut DrawState,
69) -> Result<(), crossterm::ErrorKind> {
70	// Add exit coroutines and subwindows
71	term.clear()?;
72	term.show_cursor()?;
73	terminal::disable_raw_mode()?;
74	debug!("Nice chirping... Bye!");
75	process::exit(1);
76}
77
78/// Poll for events and call draw on capture.
79pub fn check_update(
80	mut term: &mut Terminal<CrosstermBackend<io::Stdout>>,
81	mut draw_state: &mut DrawState,
82) -> Result<(), crossterm::ErrorKind> {
83	if poll(Duration::from_millis(100))? {
84		match read()? {
85			Event::Key(event) => {
86				match event.code {
87					KeyCode::Up => up_pressed(&mut draw_state),
88					KeyCode::Down => down_pressed(&mut draw_state),
89					KeyCode::Enter => enter_pressed(&mut draw_state),
90					KeyCode::Esc => exit_routine(&mut term, &mut draw_state)?,
91					_ => (),
92				}
93				draw(&mut term, &mut draw_state)?;
94			}
95			Event::Mouse(event) => debug!("{:?}", event),
96			Event::Resize(width, _) => {
97				debug!("{:?}", width);
98				draw(&mut term, &mut draw_state)?;
99			}
100		}
101	}
102	Ok(())
103}
104
105/// Redraw canvas using global-state.
106pub fn draw(
107	term: &mut Terminal<CrosstermBackend<io::Stdout>>,
108	state: &DrawState,
109) -> Result<(), crossterm::ErrorKind> {
110	term.hide_cursor()?;
111
112	term.draw(|mut f| {
113		let size = f.size();
114
115		Block::default().borders(Borders::ALL).render(&mut f, size);
116
117		let chunks = Layout::default()
118			.direction(Direction::Vertical)
119			.margin(1)
120			.constraints([Constraint::Percentage(80), Constraint::Percentage(20)].as_ref())
121			.split(f.size());
122
123		let art_para =
124			scanner::parse_art("assets/ansi/art.ans", f.size().width as usize).unwrap_or_default();
125
126		Paragraph::new(
127			art_para
128				.into_iter()
129				.map(|line| Text::raw(line))
130				.collect::<Vec<Text>>()
131				.iter(),
132		)
133		.style(Style::default().fg(Color::White).bg(Color::Black))
134		.alignment(Alignment::Center)
135		.wrap(true)
136		.render(&mut f, chunks[0]);
137
138		SelectableList::default()
139			.block(Block::default().title("Operation").borders(Borders::ALL))
140			.items(&state.list_items)
141			.select(Some(state.selected_item))
142			.style(Style::default().fg(Color::White))
143			.highlight_style(Style::default().modifier(Modifier::ITALIC))
144			.highlight_symbol(">>")
145			.render(&mut f, chunks[1]);
146	})?;
147	Ok(())
148}