lib.rs
1 // Copyright (c) 2025 ADnet Contributors 2 // This file is part of the AlphaOS library. 3 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at: 7 8 // http://www.apache.org/licenses/LICENSE-2.0 9 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 #![forbid(unsafe_code)] 17 18 mod pages; 19 use pages::*; 20 21 mod tabs; 22 use tabs::Tabs; 23 24 use alphaos_node::Node; 25 use alphaos_utilities::Stoppable; 26 27 use alphavm::prelude::Network; 28 29 use anyhow::Result; 30 use crossterm::{ 31 event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}, 32 execute, 33 terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, 34 }; 35 use 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 }; 44 use std::{ 45 io, 46 sync::Arc, 47 thread, 48 time::{Duration, Instant}, 49 }; 50 use tokio::sync::mpsc::Receiver; 51 52 pub struct Display<N: Network> { 53 /// An instance of the node. 54 node: Node<N>, 55 /// The tick rate of the display. 56 tick_rate: Duration, 57 /// The state of the tabs. 58 tabs: Tabs, 59 /// The logs tab. 60 logs: Logs, 61 } 62 63 fn header_style() -> Style { 64 Style::default().fg(Color::Cyan) 65 } 66 67 fn content_style() -> Style { 68 Style::default().fg(Color::White) 69 } 70 71 impl<N: Network> Display<N> { 72 /// Initializes a new display. 73 pub fn start(node: Node<N>, log_receiver: Receiver<Vec<u8>>, stoppable: Arc<dyn Stoppable>) -> Result<()> { 74 // Initialize the display. 75 enable_raw_mode()?; 76 let mut stdout = io::stdout(); 77 execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; 78 let backend = CrosstermBackend::new(stdout); 79 let mut terminal = Terminal::new(backend)?; 80 81 // Initialize the display. 82 let mut display = Self { 83 node, 84 tick_rate: Duration::from_secs(1), 85 tabs: Tabs::new(PAGES.to_vec()), 86 logs: Logs::new(log_receiver), 87 }; 88 89 // Render the display. 90 let res = display.render(&mut terminal, stoppable); 91 92 // Terminate the display. 93 disable_raw_mode()?; 94 execute!(terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture)?; 95 terminal.show_cursor()?; 96 97 // Exit. 98 if let Err(err) = res { 99 println!("{err:?}") 100 } 101 102 Ok(()) 103 } 104 } 105 106 impl<N: Network> Display<N> { 107 /// Renders the display. 108 fn render<B: Backend>(&mut self, terminal: &mut Terminal<B>, stoppable: Arc<dyn Stoppable>) -> io::Result<()> { 109 let mut last_tick = Instant::now(); 110 loop { 111 terminal.draw(|f| self.draw(f))?; 112 113 // Set the timeout duration. 114 let timeout = self.tick_rate.checked_sub(last_tick.elapsed()).unwrap_or_else(|| Duration::from_secs(0)); 115 116 if event::poll(timeout)? { 117 if let Event::Key(key) = event::read()? { 118 match key.code { 119 KeyCode::Esc => { 120 stoppable.stop(); 121 return Ok(()); 122 } 123 KeyCode::Left => self.tabs.previous(), 124 KeyCode::Right => self.tabs.next(), 125 _ => {} 126 } 127 } 128 } 129 130 if last_tick.elapsed() >= self.tick_rate { 131 thread::sleep(Duration::from_millis(50)); 132 last_tick = Instant::now(); 133 } 134 } 135 } 136 137 /// Draws the display. 138 fn draw(&mut self, f: &mut Frame) { 139 /* Layout */ 140 141 // Initialize the layout of the page. 142 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 /* Tabs */ 149 150 // Initialize the tabs. 151 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 /* Pages */ 178 179 // Initialize the page. 180 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 }