1#![forbid(unsafe_code)]
17
18mod pages;
19use pages::*;
20
21mod tabs;
22use tabs::Tabs;
23
24use snarkos_node::Node;
25use snarkos_utilities::Stoppable;
26
27use snarkvm::prelude::Network;
28
29use anyhow::{Result, anyhow};
30use crossterm::{
31 event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
32 execute,
33 terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
34};
35use ratatui::{
36 Frame,
37 Terminal,
38 backend::{Backend, CrosstermBackend},
39 layout::{Constraint, Direction, Layout},
40 style::{Color, Modifier, Style},
41 text::{Line, Span},
42 widgets::{Block, Borders, Tabs as TabsTui},
43};
44use std::{
45 io,
46 sync::Arc,
47 thread,
48 time::{Duration, Instant},
49};
50use tokio::sync::mpsc::Receiver;
51
52pub struct Display<N: Network> {
53 node: Node<N>,
55 tick_rate: Duration,
58 tabs: Tabs,
60 logs: Logs,
62}
63
64fn header_style() -> Style {
65 Style::default().fg(Color::Cyan)
66}
67
68fn content_style() -> Style {
69 Style::default().fg(Color::White)
70}
71
72impl<N: Network> Display<N> {
73 pub fn start(node: Node<N>, log_receiver: Receiver<Vec<u8>>, stoppable: Arc<dyn Stoppable>) -> Result<()> {
75 enable_raw_mode()?;
77 let mut stdout = io::stdout();
78 execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
79 let backend = CrosstermBackend::new(stdout);
80 let mut terminal = Terminal::new(backend)?;
81
82 let mut display = Self {
84 node,
85 tick_rate: Duration::from_secs(1),
86 tabs: Tabs::new(PAGES.to_vec()),
87 logs: Logs::new(log_receiver),
88 };
89
90 let result = display.render(&mut terminal, stoppable);
92
93 disable_raw_mode()?;
95 execute!(terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture)?;
96 terminal.show_cursor()?;
97
98 result
100 }
101}
102
103impl<N: Network> Display<N> {
104 fn render<B: Backend>(&mut self, terminal: &mut Terminal<B>, stoppable: Arc<dyn Stoppable>) -> Result<()> {
106 let mut last_tick = Instant::now();
107 loop {
108 if let Err(err) = terminal.draw(|f| self.draw(f)) {
109 return Err(anyhow!("{err}").context("Failed to draw terminal UI"));
110 }
111
112 let timeout = self.tick_rate.saturating_sub(last_tick.elapsed());
114
115 if event::poll(timeout)? {
116 if let Event::Key(key) = event::read()? {
117 match key.code {
118 KeyCode::Esc => {
119 stoppable.stop();
120 return Ok(());
121 }
122 KeyCode::Left => self.tabs.previous(),
123 KeyCode::Right => self.tabs.next(),
124 _ => {}
125 }
126 }
127 }
128
129 if last_tick.elapsed() >= self.tick_rate {
131 thread::sleep(Duration::from_millis(50));
132 last_tick = Instant::now();
133 }
134 }
135 }
136
137 fn draw(&mut self, f: &mut Frame) {
139 let chunks = Layout::default()
143 .margin(1)
144 .direction(Direction::Vertical)
145 .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
146 .split(f.area());
147
148 let block = Block::default().style(Style::default().bg(Color::Black).fg(Color::White));
152 f.render_widget(block, f.area());
153 let titles: Vec<_> = self
154 .tabs
155 .titles
156 .iter()
157 .map(|t| {
158 let (first, rest) = t.split_at(1);
159 Line::from(vec![
160 Span::styled(first, Style::default().fg(Color::Yellow)),
161 Span::styled(rest, Style::default().fg(Color::Green)),
162 ])
163 })
164 .collect();
165 let tabs = TabsTui::new(titles)
166 .block(
167 Block::default()
168 .borders(Borders::ALL)
169 .title("Welcome to Aleo.")
170 .style(Style::default().add_modifier(Modifier::BOLD)),
171 )
172 .select(self.tabs.index)
173 .style(header_style())
174 .highlight_style(Style::default().add_modifier(Modifier::BOLD).bg(Color::White));
175 f.render_widget(tabs, chunks[0]);
176
177 match self.tabs.index {
181 0 => Overview.draw(f, chunks[1], &self.node),
182 1 => self.logs.draw(f, chunks[1]),
183 _ => unreachable!(),
184 };
185 }
186}