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