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
60impl<N: Network> Display<N> {
61 pub fn start(node: Node<N>, log_receiver: Receiver<Vec<u8>>) -> Result<()> {
63 enable_raw_mode()?;
65 let mut stdout = io::stdout();
66 execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
67 let backend = CrosstermBackend::new(stdout);
68 let mut terminal = Terminal::new(backend)?;
69
70 let mut display = Self {
72 node,
73 tick_rate: Duration::from_secs(1),
74 tabs: Tabs::new(PAGES.to_vec()),
75 logs: Logs::new(log_receiver),
76 };
77
78 let res = display.render(&mut terminal);
80
81 disable_raw_mode()?;
83 execute!(terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture)?;
84 terminal.show_cursor()?;
85
86 if let Err(err) = res {
88 println!("{err:?}")
89 }
90
91 Ok(())
92 }
93}
94
95impl<N: Network> Display<N> {
96 fn render<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> io::Result<()> {
98 let mut last_tick = Instant::now();
99 loop {
100 terminal.draw(|f| self.draw(f))?;
101
102 let timeout = self.tick_rate.checked_sub(last_tick.elapsed()).unwrap_or_else(|| Duration::from_secs(0));
104
105 if event::poll(timeout)? {
106 if let Event::Key(key) = event::read()? {
107 match key.code {
108 KeyCode::Esc => {
109 return Ok(());
115 }
116 KeyCode::Left => self.tabs.previous(),
117 KeyCode::Right => self.tabs.next(),
118 _ => {}
119 }
120 }
121 }
122
123 if last_tick.elapsed() >= self.tick_rate {
124 thread::sleep(Duration::from_millis(50));
125 last_tick = Instant::now();
126 }
127 }
128 }
129
130 fn draw(&mut self, f: &mut Frame) {
132 let chunks = Layout::default()
136 .margin(1)
137 .direction(Direction::Vertical)
138 .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
139 .split(f.size());
140
141 let block = Block::default().style(Style::default().bg(Color::Black).fg(Color::White));
145 f.render_widget(block, f.size());
146 let titles = self
147 .tabs
148 .titles
149 .iter()
150 .map(|t| {
151 let (first, rest) = t.split_at(1);
152 Line::from(vec![
153 Span::styled(first, Style::default().fg(Color::Yellow)),
154 Span::styled(rest, Style::default().fg(Color::Green)),
155 ])
156 })
157 .collect();
158 let tabs = TabsTui::new(titles)
159 .block(
160 Block::default()
161 .borders(Borders::ALL)
162 .title("Welcome to Aleo.")
163 .style(Style::default().add_modifier(Modifier::BOLD)),
164 )
165 .select(self.tabs.index)
166 .style(Style::default().fg(Color::Cyan))
167 .highlight_style(Style::default().add_modifier(Modifier::BOLD).bg(Color::White));
168 f.render_widget(tabs, chunks[0]);
169
170 match self.tabs.index {
174 0 => Overview.draw(f, chunks[1], &self.node),
175 1 => self.logs.draw(f, chunks[1]),
176 _ => unreachable!(),
177 };
178 }
179}