/ display / src / lib.rs
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  }