From 5e8984db53707f3d1452c67791259b8dbc55c981 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Mon, 26 Aug 2024 12:13:24 -0700 Subject: [PATCH 1/7] Simplify simple template --- Cargo.lock | 2 + simple-generated/Cargo.toml | 2 + simple-generated/src/app.rs | 49 ----------- simple-generated/src/event.rs | 145 +++++++++++++++++++------------- simple-generated/src/handler.rs | 28 ------ simple-generated/src/main.rs | 140 ++++++++++++++++++++++-------- simple-generated/src/tui.rs | 76 ----------------- simple-generated/src/ui.rs | 34 -------- simple/Cargo.toml | 2 + simple/src/app.rs | 49 ----------- simple/src/event.rs | 145 +++++++++++++++++++------------- simple/src/handler.rs | 28 ------ simple/src/main.rs | 140 ++++++++++++++++++++++-------- simple/src/tui.rs | 76 ----------------- simple/src/ui.rs | 34 -------- 15 files changed, 386 insertions(+), 564 deletions(-) delete mode 100644 simple-generated/src/app.rs delete mode 100644 simple-generated/src/handler.rs delete mode 100644 simple-generated/src/tui.rs delete mode 100644 simple-generated/src/ui.rs delete mode 100644 simple/src/app.rs delete mode 100644 simple/src/handler.rs delete mode 100644 simple/src/tui.rs delete mode 100644 simple/src/ui.rs diff --git a/Cargo.lock b/Cargo.lock index 4d07d2d..b9734b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2137,6 +2137,8 @@ dependencies = [ name = "simple-generated" version = "0.1.0" dependencies = [ + "color-eyre", + "crossterm", "ratatui", ] diff --git a/simple-generated/Cargo.toml b/simple-generated/Cargo.toml index 8211d4c..8675d4e 100644 --- a/simple-generated/Cargo.toml +++ b/simple-generated/Cargo.toml @@ -6,4 +6,6 @@ license = "MIT" edition = "2021" [dependencies] +crossterm = "0.28.1" ratatui = "0.28.1" +color-eyre = "0.6.3" diff --git a/simple-generated/src/app.rs b/simple-generated/src/app.rs deleted file mode 100644 index e99d547..0000000 --- a/simple-generated/src/app.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::error; - -/// Application result type. -pub type AppResult = std::result::Result>; - -/// Application. -#[derive(Debug)] -pub struct App { - /// Is the application running? - pub running: bool, - /// counter - pub counter: u8, -} - -impl Default for App { - fn default() -> Self { - Self { - running: true, - counter: 0, - } - } -} - -impl App { - /// Constructs a new instance of [`App`]. - pub fn new() -> Self { - Self::default() - } - - /// Handles the tick event of the terminal. - pub fn tick(&self) {} - - /// Set running to false to quit the application. - pub fn quit(&mut self) { - self.running = false; - } - - pub fn increment_counter(&mut self) { - if let Some(res) = self.counter.checked_add(1) { - self.counter = res; - } - } - - pub fn decrement_counter(&mut self) { - if let Some(res) = self.counter.checked_sub(1) { - self.counter = res; - } - } -} diff --git a/simple-generated/src/event.rs b/simple-generated/src/event.rs index 5bf9d35..c9fbbce 100644 --- a/simple-generated/src/event.rs +++ b/simple-generated/src/event.rs @@ -1,87 +1,116 @@ -use crate::app::AppResult; -use ratatui::crossterm::event::{ - self, Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent, -}; +use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent}; use std::sync::mpsc; use std::thread; use std::time::{Duration, Instant}; /// Terminal events. -#[derive(Clone, Copy, Debug)] +#[derive(Debug)] pub enum Event { - /// Terminal tick. + /// The tick event is sent at a regular interval and can be used to trigger application state + /// updates or animations. Tick, + /// Key press. Key(KeyEvent), + /// Mouse click/scroll. Mouse(MouseEvent), + /// Terminal resize. + /// + /// This event may be fired multiple times in quick succession, so it is recommended not to + /// render the UI on every resize event. Resize(u16, u16), + + /// An error occurred. + Error(std::io::Error), } /// Terminal event handler. -#[allow(dead_code)] +/// +/// This struct is responsible for listening to terminal events and sending them to the main thread. +/// +/// It sends a tick event at a regular interval. The tick rate is specified by the `tick_rate` +/// parameter. If an error occurs, it sends an error event to the main thread and then stops +/// running. #[derive(Debug)] -pub struct EventHandler { - /// Event sender channel. - sender: mpsc::Sender, +pub struct EventSource { /// Event receiver channel. receiver: mpsc::Receiver, - /// Event handler thread. - handler: thread::JoinHandle<()>, } -impl EventHandler { +impl EventSource { /// Constructs a new instance of [`EventHandler`]. - pub fn new(tick_rate: u64) -> Self { - let tick_rate = Duration::from_millis(tick_rate); + pub fn new(tick_rate: Duration) -> Self { let (sender, receiver) = mpsc::channel(); - let handler = { - let sender = sender.clone(); - thread::spawn(move || { - let mut last_tick = Instant::now(); - loop { - let timeout = tick_rate - .checked_sub(last_tick.elapsed()) - .unwrap_or(tick_rate); - - if event::poll(timeout).expect("failed to poll new events") { - match event::read().expect("unable to read event") { - CrosstermEvent::Key(e) => { - if e.kind == KeyEventKind::Press { - sender.send(Event::Key(e)) - } else { - Ok(()) - } - } - CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)), - CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)), - CrosstermEvent::FocusGained => Ok(()), - CrosstermEvent::FocusLost => Ok(()), - CrosstermEvent::Paste(_) => unimplemented!(), - } - .expect("failed to send terminal event") - } - - if last_tick.elapsed() >= tick_rate { - sender.send(Event::Tick).expect("failed to send tick event"); - last_tick = Instant::now(); - } - } - }) - }; - Self { - sender, - receiver, - handler, - } + thread::spawn(move || event_thread(sender, tick_rate)); + Self { receiver } } /// Receive the next event from the handler thread. /// - /// This function will always block the current thread if - /// there is no data available and it's possible for more data to be sent. - pub fn next(&self) -> AppResult { - Ok(self.receiver.recv()?) + /// This function will always block the current thread if there is no data available unless the + /// event source has been closed. + pub fn next(&self) -> color_eyre::Result { + let event = self.receiver.recv()?; + Ok(event) + } +} + +/// An event thread that listens for terminal events. +/// +/// This function is responsible for listening to terminal events and sending them to the main +/// thread. It sends a tick event at a regular interval. The tick rate is specified by the +/// `tick_rate` parameter. If an error occurs, it sends an error event to the main thread and then +/// stops running. +fn event_thread(sender: mpsc::Sender, tick_rate: Duration) { + let mut last_tick: Option = None; + loop { + if last_tick + .map(|tick| tick.elapsed() >= tick_rate) + .unwrap_or(true) + { + sender.send(Event::Tick).expect("failed to send tick event"); + last_tick = Some(Instant::now()); + } + + let timeout = last_tick.map_or(Duration::ZERO, |tick| { + tick_rate.saturating_sub(tick.elapsed()) + }); + + match event::poll(timeout) { + Ok(false) => { + // no new events waiting + continue; + } + Ok(true) => {} + Err(e) => { + let _ = sender.send(Event::Error(e)); + break; + } + } + + match event::read() { + Err(err) => { + let _ = sender.send(Event::Error(err)); + break; + } + Ok(event) => match event { + CrosstermEvent::Key(e) if e.kind == KeyEventKind::Press => { + // ignore key release / repeat events + let _ = sender.send(Event::Key(e)); + } + CrosstermEvent::Key(_) => {} + CrosstermEvent::Mouse(e) => { + let _ = sender.send(Event::Mouse(e)); + } + CrosstermEvent::Resize(w, h) => { + let _ = sender.send(Event::Resize(w, h)); + } + CrosstermEvent::FocusGained => {} + CrosstermEvent::FocusLost => {} + CrosstermEvent::Paste(_) => {} + }, + } } } diff --git a/simple-generated/src/handler.rs b/simple-generated/src/handler.rs deleted file mode 100644 index 6b66656..0000000 --- a/simple-generated/src/handler.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::app::{App, AppResult}; -use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; - -/// Handles the key events and updates the state of [`App`]. -pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> { - match key_event.code { - // Exit application on `ESC` or `q` - KeyCode::Esc | KeyCode::Char('q') => { - app.quit(); - } - // Exit application on `Ctrl-C` - KeyCode::Char('c') | KeyCode::Char('C') => { - if key_event.modifiers == KeyModifiers::CONTROL { - app.quit(); - } - } - // Counter handlers - KeyCode::Right => { - app.increment_counter(); - } - KeyCode::Left => { - app.decrement_counter(); - } - // Other handlers you could add here. - _ => {} - } - Ok(()) -} diff --git a/simple-generated/src/main.rs b/simple-generated/src/main.rs index 0825249..e866b47 100644 --- a/simple-generated/src/main.rs +++ b/simple-generated/src/main.rs @@ -1,45 +1,111 @@ -use std::io; +use std::time::Duration; -use ratatui::{backend::CrosstermBackend, Terminal}; - -use crate::{ - app::{App, AppResult}, - event::{Event, EventHandler}, - handler::handle_key_events, - tui::Tui, +use color_eyre::Result; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use event::{Event, EventSource}; +use ratatui::{ + layout::Alignment, + style::{Style, Stylize}, + widgets::{Block, BorderType, Paragraph}, + DefaultTerminal, Frame, }; -pub mod app; pub mod event; -pub mod handler; -pub mod tui; -pub mod ui; - -fn main() -> AppResult<()> { - // Create an application. - let mut app = App::new(); - - // Initialize the terminal user interface. - let backend = CrosstermBackend::new(io::stdout()); - let terminal = Terminal::new(backend)?; - let events = EventHandler::new(250); - let mut tui = Tui::new(terminal, events); - tui.init()?; - - // Start the main loop. - while app.running { - // Render the user interface. - tui.draw(&mut app)?; - // Handle events. - match tui.events.next()? { - Event::Tick => app.tick(), - Event::Key(key_event) => handle_key_events(key_event, &mut app)?, - Event::Mouse(_) => {} - Event::Resize(_, _) => {} + +fn main() -> Result<()> { + color_eyre::install()?; + let terminal = ratatui::init(); + let result = App::new().run(terminal); + ratatui::restore(); + result +} + +#[derive(Debug, Default)] +pub struct App { + /// Is the application running? + pub running: bool, + /// counter + pub counter: u8, +} + +impl App { + /// Construct a new instance of [`App`]. + pub fn new() -> Self { + Self::default() + } + + pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { + let tick_interval = Duration::from_secs_f64(1.0 / 10.0); // 10 ticks per second + let events = EventSource::new(tick_interval); + self.running = true; + while self.running { + terminal.draw(|frame| self.draw(frame))?; + match events.next()? { + Event::Tick => self.on_tick(), + Event::Key(key_event) => self.on_key_event(key_event)?, + Event::Mouse(_) => {} + Event::Resize(_, _) => {} + Event::Error(err) => { + return Err(err.into()); + } + } + } + Ok(()) + } + + /// Handles the tick event of the terminal. + pub fn on_tick(&self) {} + + /// Handles the key events and updates the state of [`App`]. + pub fn on_key_event(&mut self, key: KeyEvent) -> Result<()> { + match (key.modifiers, key.code) { + (_, KeyCode::Esc | KeyCode::Char('q')) + | (KeyModifiers::CONTROL, KeyCode::Char('c') | KeyCode::Char('C')) => self.quit(), + (_, KeyCode::Right) => self.increment_counter(), + (_, KeyCode::Left) => self.decrement_counter(), + // Add other key handlers here. + _ => {} } + Ok(()) + } + + /// Set running to false to quit the application. + pub fn quit(&mut self) { + self.running = false; + } + + pub fn increment_counter(&mut self) { + self.counter = self.counter.saturating_add(1); + } + + pub fn decrement_counter(&mut self) { + self.counter = self.counter.saturating_sub(1); } - // Exit the user interface. - tui.exit()?; - Ok(()) + /// Renders the user interface widgets. + fn draw(&mut self, frame: &mut Frame) { + // This is where you add new widgets. + // See the following resources: + // - https://docs.rs/ratatui/latest/ratatui/widgets/index.html + // - https://github.com/ratatui/ratatui/tree/master/examples + + let text = format!( + "This is a ratatui simple template.\n\ + Press `Esc`, `Ctrl-C` or `q` to stop running.\n\ + Press left and right to increment and decrement the counter respectively.\n\ + Counter: {}", + self.counter + ); + let block = Block::bordered() + .border_type(BorderType::Rounded) + .title("Ratatui Simple Template") + .title_alignment(Alignment::Center); + frame.render_widget( + Paragraph::new(text) + .block(block) + .style(Style::new().cyan().on_black()) + .centered(), + frame.area(), + ) + } } diff --git a/simple-generated/src/tui.rs b/simple-generated/src/tui.rs deleted file mode 100644 index 4c92a8e..0000000 --- a/simple-generated/src/tui.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::app::{App, AppResult}; -use crate::event::EventHandler; -use crate::ui; -use ratatui::backend::Backend; -use ratatui::crossterm::event::{DisableMouseCapture, EnableMouseCapture}; -use ratatui::crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}; -use ratatui::Terminal; -use std::io; -use std::panic; - -/// Representation of a terminal user interface. -/// -/// It is responsible for setting up the terminal, -/// initializing the interface and handling the draw events. -#[derive(Debug)] -pub struct Tui { - /// Interface to the Terminal. - terminal: Terminal, - /// Terminal event handler. - pub events: EventHandler, -} - -impl Tui { - /// Constructs a new instance of [`Tui`]. - pub fn new(terminal: Terminal, events: EventHandler) -> Self { - Self { terminal, events } - } - - /// Initializes the terminal interface. - /// - /// It enables the raw mode and sets terminal properties. - pub fn init(&mut self) -> AppResult<()> { - terminal::enable_raw_mode()?; - ratatui::crossterm::execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture)?; - - // Define a custom panic hook to reset the terminal properties. - // This way, you won't have your terminal messed up if an unexpected error happens. - let panic_hook = panic::take_hook(); - panic::set_hook(Box::new(move |panic| { - Self::reset().expect("failed to reset the terminal"); - panic_hook(panic); - })); - - self.terminal.hide_cursor()?; - self.terminal.clear()?; - Ok(()) - } - - /// [`Draw`] the terminal interface by [`rendering`] the widgets. - /// - /// [`Draw`]: ratatui::Terminal::draw - /// [`rendering`]: crate::ui::render - pub fn draw(&mut self, app: &mut App) -> AppResult<()> { - self.terminal.draw(|frame| ui::render(app, frame))?; - Ok(()) - } - - /// Resets the terminal interface. - /// - /// This function is also used for the panic hook to revert - /// the terminal properties if unexpected errors occur. - fn reset() -> AppResult<()> { - terminal::disable_raw_mode()?; - ratatui::crossterm::execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?; - Ok(()) - } - - /// Exits the terminal interface. - /// - /// It disables the raw mode and reverts back the terminal properties. - pub fn exit(&mut self) -> AppResult<()> { - Self::reset()?; - self.terminal.show_cursor()?; - Ok(()) - } -} diff --git a/simple-generated/src/ui.rs b/simple-generated/src/ui.rs deleted file mode 100644 index defd047..0000000 --- a/simple-generated/src/ui.rs +++ /dev/null @@ -1,34 +0,0 @@ -use ratatui::{ - layout::Alignment, - style::{Color, Style}, - widgets::{Block, BorderType, Paragraph}, - Frame, -}; - -use crate::app::App; - -/// Renders the user interface widgets. -pub fn render(app: &mut App, frame: &mut Frame) { - // This is where you add new widgets. - // See the following resources: - // - https://docs.rs/ratatui/latest/ratatui/widgets/index.html - // - https://github.com/ratatui/ratatui/tree/master/examples - frame.render_widget( - Paragraph::new(format!( - "This is a tui template.\n\ - Press `Esc`, `Ctrl-C` or `q` to stop running.\n\ - Press left and right to increment and decrement the counter respectively.\n\ - Counter: {}", - app.counter - )) - .block( - Block::bordered() - .title("Template") - .title_alignment(Alignment::Center) - .border_type(BorderType::Rounded), - ) - .style(Style::default().fg(Color::Cyan).bg(Color::Black)) - .centered(), - frame.area(), - ) -} diff --git a/simple/Cargo.toml b/simple/Cargo.toml index 0465ca4..dbebf6d 100644 --- a/simple/Cargo.toml +++ b/simple/Cargo.toml @@ -6,4 +6,6 @@ license = "MIT" edition = "2021" [dependencies] +crossterm = "0.28.1" ratatui = "0.28.1" +color-eyre = "0.6.3" diff --git a/simple/src/app.rs b/simple/src/app.rs deleted file mode 100644 index e99d547..0000000 --- a/simple/src/app.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::error; - -/// Application result type. -pub type AppResult = std::result::Result>; - -/// Application. -#[derive(Debug)] -pub struct App { - /// Is the application running? - pub running: bool, - /// counter - pub counter: u8, -} - -impl Default for App { - fn default() -> Self { - Self { - running: true, - counter: 0, - } - } -} - -impl App { - /// Constructs a new instance of [`App`]. - pub fn new() -> Self { - Self::default() - } - - /// Handles the tick event of the terminal. - pub fn tick(&self) {} - - /// Set running to false to quit the application. - pub fn quit(&mut self) { - self.running = false; - } - - pub fn increment_counter(&mut self) { - if let Some(res) = self.counter.checked_add(1) { - self.counter = res; - } - } - - pub fn decrement_counter(&mut self) { - if let Some(res) = self.counter.checked_sub(1) { - self.counter = res; - } - } -} diff --git a/simple/src/event.rs b/simple/src/event.rs index 5bf9d35..c9fbbce 100644 --- a/simple/src/event.rs +++ b/simple/src/event.rs @@ -1,87 +1,116 @@ -use crate::app::AppResult; -use ratatui::crossterm::event::{ - self, Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent, -}; +use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent}; use std::sync::mpsc; use std::thread; use std::time::{Duration, Instant}; /// Terminal events. -#[derive(Clone, Copy, Debug)] +#[derive(Debug)] pub enum Event { - /// Terminal tick. + /// The tick event is sent at a regular interval and can be used to trigger application state + /// updates or animations. Tick, + /// Key press. Key(KeyEvent), + /// Mouse click/scroll. Mouse(MouseEvent), + /// Terminal resize. + /// + /// This event may be fired multiple times in quick succession, so it is recommended not to + /// render the UI on every resize event. Resize(u16, u16), + + /// An error occurred. + Error(std::io::Error), } /// Terminal event handler. -#[allow(dead_code)] +/// +/// This struct is responsible for listening to terminal events and sending them to the main thread. +/// +/// It sends a tick event at a regular interval. The tick rate is specified by the `tick_rate` +/// parameter. If an error occurs, it sends an error event to the main thread and then stops +/// running. #[derive(Debug)] -pub struct EventHandler { - /// Event sender channel. - sender: mpsc::Sender, +pub struct EventSource { /// Event receiver channel. receiver: mpsc::Receiver, - /// Event handler thread. - handler: thread::JoinHandle<()>, } -impl EventHandler { +impl EventSource { /// Constructs a new instance of [`EventHandler`]. - pub fn new(tick_rate: u64) -> Self { - let tick_rate = Duration::from_millis(tick_rate); + pub fn new(tick_rate: Duration) -> Self { let (sender, receiver) = mpsc::channel(); - let handler = { - let sender = sender.clone(); - thread::spawn(move || { - let mut last_tick = Instant::now(); - loop { - let timeout = tick_rate - .checked_sub(last_tick.elapsed()) - .unwrap_or(tick_rate); - - if event::poll(timeout).expect("failed to poll new events") { - match event::read().expect("unable to read event") { - CrosstermEvent::Key(e) => { - if e.kind == KeyEventKind::Press { - sender.send(Event::Key(e)) - } else { - Ok(()) - } - } - CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)), - CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)), - CrosstermEvent::FocusGained => Ok(()), - CrosstermEvent::FocusLost => Ok(()), - CrosstermEvent::Paste(_) => unimplemented!(), - } - .expect("failed to send terminal event") - } - - if last_tick.elapsed() >= tick_rate { - sender.send(Event::Tick).expect("failed to send tick event"); - last_tick = Instant::now(); - } - } - }) - }; - Self { - sender, - receiver, - handler, - } + thread::spawn(move || event_thread(sender, tick_rate)); + Self { receiver } } /// Receive the next event from the handler thread. /// - /// This function will always block the current thread if - /// there is no data available and it's possible for more data to be sent. - pub fn next(&self) -> AppResult { - Ok(self.receiver.recv()?) + /// This function will always block the current thread if there is no data available unless the + /// event source has been closed. + pub fn next(&self) -> color_eyre::Result { + let event = self.receiver.recv()?; + Ok(event) + } +} + +/// An event thread that listens for terminal events. +/// +/// This function is responsible for listening to terminal events and sending them to the main +/// thread. It sends a tick event at a regular interval. The tick rate is specified by the +/// `tick_rate` parameter. If an error occurs, it sends an error event to the main thread and then +/// stops running. +fn event_thread(sender: mpsc::Sender, tick_rate: Duration) { + let mut last_tick: Option = None; + loop { + if last_tick + .map(|tick| tick.elapsed() >= tick_rate) + .unwrap_or(true) + { + sender.send(Event::Tick).expect("failed to send tick event"); + last_tick = Some(Instant::now()); + } + + let timeout = last_tick.map_or(Duration::ZERO, |tick| { + tick_rate.saturating_sub(tick.elapsed()) + }); + + match event::poll(timeout) { + Ok(false) => { + // no new events waiting + continue; + } + Ok(true) => {} + Err(e) => { + let _ = sender.send(Event::Error(e)); + break; + } + } + + match event::read() { + Err(err) => { + let _ = sender.send(Event::Error(err)); + break; + } + Ok(event) => match event { + CrosstermEvent::Key(e) if e.kind == KeyEventKind::Press => { + // ignore key release / repeat events + let _ = sender.send(Event::Key(e)); + } + CrosstermEvent::Key(_) => {} + CrosstermEvent::Mouse(e) => { + let _ = sender.send(Event::Mouse(e)); + } + CrosstermEvent::Resize(w, h) => { + let _ = sender.send(Event::Resize(w, h)); + } + CrosstermEvent::FocusGained => {} + CrosstermEvent::FocusLost => {} + CrosstermEvent::Paste(_) => {} + }, + } } } diff --git a/simple/src/handler.rs b/simple/src/handler.rs deleted file mode 100644 index 6b66656..0000000 --- a/simple/src/handler.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::app::{App, AppResult}; -use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; - -/// Handles the key events and updates the state of [`App`]. -pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> { - match key_event.code { - // Exit application on `ESC` or `q` - KeyCode::Esc | KeyCode::Char('q') => { - app.quit(); - } - // Exit application on `Ctrl-C` - KeyCode::Char('c') | KeyCode::Char('C') => { - if key_event.modifiers == KeyModifiers::CONTROL { - app.quit(); - } - } - // Counter handlers - KeyCode::Right => { - app.increment_counter(); - } - KeyCode::Left => { - app.decrement_counter(); - } - // Other handlers you could add here. - _ => {} - } - Ok(()) -} diff --git a/simple/src/main.rs b/simple/src/main.rs index 0825249..e866b47 100644 --- a/simple/src/main.rs +++ b/simple/src/main.rs @@ -1,45 +1,111 @@ -use std::io; +use std::time::Duration; -use ratatui::{backend::CrosstermBackend, Terminal}; - -use crate::{ - app::{App, AppResult}, - event::{Event, EventHandler}, - handler::handle_key_events, - tui::Tui, +use color_eyre::Result; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use event::{Event, EventSource}; +use ratatui::{ + layout::Alignment, + style::{Style, Stylize}, + widgets::{Block, BorderType, Paragraph}, + DefaultTerminal, Frame, }; -pub mod app; pub mod event; -pub mod handler; -pub mod tui; -pub mod ui; - -fn main() -> AppResult<()> { - // Create an application. - let mut app = App::new(); - - // Initialize the terminal user interface. - let backend = CrosstermBackend::new(io::stdout()); - let terminal = Terminal::new(backend)?; - let events = EventHandler::new(250); - let mut tui = Tui::new(terminal, events); - tui.init()?; - - // Start the main loop. - while app.running { - // Render the user interface. - tui.draw(&mut app)?; - // Handle events. - match tui.events.next()? { - Event::Tick => app.tick(), - Event::Key(key_event) => handle_key_events(key_event, &mut app)?, - Event::Mouse(_) => {} - Event::Resize(_, _) => {} + +fn main() -> Result<()> { + color_eyre::install()?; + let terminal = ratatui::init(); + let result = App::new().run(terminal); + ratatui::restore(); + result +} + +#[derive(Debug, Default)] +pub struct App { + /// Is the application running? + pub running: bool, + /// counter + pub counter: u8, +} + +impl App { + /// Construct a new instance of [`App`]. + pub fn new() -> Self { + Self::default() + } + + pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { + let tick_interval = Duration::from_secs_f64(1.0 / 10.0); // 10 ticks per second + let events = EventSource::new(tick_interval); + self.running = true; + while self.running { + terminal.draw(|frame| self.draw(frame))?; + match events.next()? { + Event::Tick => self.on_tick(), + Event::Key(key_event) => self.on_key_event(key_event)?, + Event::Mouse(_) => {} + Event::Resize(_, _) => {} + Event::Error(err) => { + return Err(err.into()); + } + } + } + Ok(()) + } + + /// Handles the tick event of the terminal. + pub fn on_tick(&self) {} + + /// Handles the key events and updates the state of [`App`]. + pub fn on_key_event(&mut self, key: KeyEvent) -> Result<()> { + match (key.modifiers, key.code) { + (_, KeyCode::Esc | KeyCode::Char('q')) + | (KeyModifiers::CONTROL, KeyCode::Char('c') | KeyCode::Char('C')) => self.quit(), + (_, KeyCode::Right) => self.increment_counter(), + (_, KeyCode::Left) => self.decrement_counter(), + // Add other key handlers here. + _ => {} } + Ok(()) + } + + /// Set running to false to quit the application. + pub fn quit(&mut self) { + self.running = false; + } + + pub fn increment_counter(&mut self) { + self.counter = self.counter.saturating_add(1); + } + + pub fn decrement_counter(&mut self) { + self.counter = self.counter.saturating_sub(1); } - // Exit the user interface. - tui.exit()?; - Ok(()) + /// Renders the user interface widgets. + fn draw(&mut self, frame: &mut Frame) { + // This is where you add new widgets. + // See the following resources: + // - https://docs.rs/ratatui/latest/ratatui/widgets/index.html + // - https://github.com/ratatui/ratatui/tree/master/examples + + let text = format!( + "This is a ratatui simple template.\n\ + Press `Esc`, `Ctrl-C` or `q` to stop running.\n\ + Press left and right to increment and decrement the counter respectively.\n\ + Counter: {}", + self.counter + ); + let block = Block::bordered() + .border_type(BorderType::Rounded) + .title("Ratatui Simple Template") + .title_alignment(Alignment::Center); + frame.render_widget( + Paragraph::new(text) + .block(block) + .style(Style::new().cyan().on_black()) + .centered(), + frame.area(), + ) + } } diff --git a/simple/src/tui.rs b/simple/src/tui.rs deleted file mode 100644 index 4c92a8e..0000000 --- a/simple/src/tui.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::app::{App, AppResult}; -use crate::event::EventHandler; -use crate::ui; -use ratatui::backend::Backend; -use ratatui::crossterm::event::{DisableMouseCapture, EnableMouseCapture}; -use ratatui::crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}; -use ratatui::Terminal; -use std::io; -use std::panic; - -/// Representation of a terminal user interface. -/// -/// It is responsible for setting up the terminal, -/// initializing the interface and handling the draw events. -#[derive(Debug)] -pub struct Tui { - /// Interface to the Terminal. - terminal: Terminal, - /// Terminal event handler. - pub events: EventHandler, -} - -impl Tui { - /// Constructs a new instance of [`Tui`]. - pub fn new(terminal: Terminal, events: EventHandler) -> Self { - Self { terminal, events } - } - - /// Initializes the terminal interface. - /// - /// It enables the raw mode and sets terminal properties. - pub fn init(&mut self) -> AppResult<()> { - terminal::enable_raw_mode()?; - ratatui::crossterm::execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture)?; - - // Define a custom panic hook to reset the terminal properties. - // This way, you won't have your terminal messed up if an unexpected error happens. - let panic_hook = panic::take_hook(); - panic::set_hook(Box::new(move |panic| { - Self::reset().expect("failed to reset the terminal"); - panic_hook(panic); - })); - - self.terminal.hide_cursor()?; - self.terminal.clear()?; - Ok(()) - } - - /// [`Draw`] the terminal interface by [`rendering`] the widgets. - /// - /// [`Draw`]: ratatui::Terminal::draw - /// [`rendering`]: crate::ui::render - pub fn draw(&mut self, app: &mut App) -> AppResult<()> { - self.terminal.draw(|frame| ui::render(app, frame))?; - Ok(()) - } - - /// Resets the terminal interface. - /// - /// This function is also used for the panic hook to revert - /// the terminal properties if unexpected errors occur. - fn reset() -> AppResult<()> { - terminal::disable_raw_mode()?; - ratatui::crossterm::execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?; - Ok(()) - } - - /// Exits the terminal interface. - /// - /// It disables the raw mode and reverts back the terminal properties. - pub fn exit(&mut self) -> AppResult<()> { - Self::reset()?; - self.terminal.show_cursor()?; - Ok(()) - } -} diff --git a/simple/src/ui.rs b/simple/src/ui.rs deleted file mode 100644 index defd047..0000000 --- a/simple/src/ui.rs +++ /dev/null @@ -1,34 +0,0 @@ -use ratatui::{ - layout::Alignment, - style::{Color, Style}, - widgets::{Block, BorderType, Paragraph}, - Frame, -}; - -use crate::app::App; - -/// Renders the user interface widgets. -pub fn render(app: &mut App, frame: &mut Frame) { - // This is where you add new widgets. - // See the following resources: - // - https://docs.rs/ratatui/latest/ratatui/widgets/index.html - // - https://github.com/ratatui/ratatui/tree/master/examples - frame.render_widget( - Paragraph::new(format!( - "This is a tui template.\n\ - Press `Esc`, `Ctrl-C` or `q` to stop running.\n\ - Press left and right to increment and decrement the counter respectively.\n\ - Counter: {}", - app.counter - )) - .block( - Block::bordered() - .title("Template") - .title_alignment(Alignment::Center) - .border_type(BorderType::Rounded), - ) - .style(Style::default().fg(Color::Cyan).bg(Color::Black)) - .centered(), - frame.area(), - ) -} From ddb5fe32df7228a96c709cbdec252725ca8e7985 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Tue, 27 Aug 2024 15:46:14 -0700 Subject: [PATCH 2/7] Move App back to app module --- simple-generated/src/app.rs | 102 ++++++++++++++++++++++++++++++++++ simple-generated/src/main.rs | 105 +---------------------------------- simple/src/app.rs | 102 ++++++++++++++++++++++++++++++++++ simple/src/main.rs | 105 +---------------------------------- 4 files changed, 210 insertions(+), 204 deletions(-) create mode 100644 simple-generated/src/app.rs create mode 100644 simple/src/app.rs diff --git a/simple-generated/src/app.rs b/simple-generated/src/app.rs new file mode 100644 index 0000000..7c749aa --- /dev/null +++ b/simple-generated/src/app.rs @@ -0,0 +1,102 @@ +use std::time::Duration; + +use color_eyre::Result; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use ratatui::{ + layout::Alignment, + style::{Style, Stylize}, + widgets::{Block, BorderType, Paragraph}, + DefaultTerminal, Frame, +}; + +use crate::event::{Event, EventSource}; + +#[derive(Debug, Default)] +pub struct App { + /// Is the application running? + pub running: bool, + /// counter + pub counter: u8, +} + +impl App { + /// Construct a new instance of [`App`]. + pub fn new() -> Self { + Self::default() + } + + pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { + let tick_interval = Duration::from_secs_f64(1.0 / 10.0); // 10 ticks per second + let events = EventSource::new(tick_interval); + self.running = true; + while self.running { + terminal.draw(|frame| self.draw(frame))?; + match events.next()? { + Event::Tick => self.on_tick(), + Event::Key(key_event) => self.on_key_event(key_event)?, + Event::Mouse(_) => {} + Event::Resize(_, _) => {} + Event::Error(err) => { + return Err(err.into()); + } + } + } + Ok(()) + } + + /// Handles the tick event of the terminal. + pub fn on_tick(&self) {} + + /// Handles the key events and updates the state of [`App`]. + pub fn on_key_event(&mut self, key: KeyEvent) -> Result<()> { + match (key.modifiers, key.code) { + (_, KeyCode::Esc | KeyCode::Char('q')) + | (KeyModifiers::CONTROL, KeyCode::Char('c') | KeyCode::Char('C')) => self.quit(), + (_, KeyCode::Right) => self.increment_counter(), + (_, KeyCode::Left) => self.decrement_counter(), + // Add other key handlers here. + _ => {} + } + Ok(()) + } + + /// Set running to false to quit the application. + pub fn quit(&mut self) { + self.running = false; + } + + pub fn increment_counter(&mut self) { + self.counter = self.counter.saturating_add(1); + } + + pub fn decrement_counter(&mut self) { + self.counter = self.counter.saturating_sub(1); + } + + /// Renders the user interface widgets. + pub(crate) fn draw(&mut self, frame: &mut Frame) { + // This is where you add new widgets. + // See the following resources: + // - https://docs.rs/ratatui/latest/ratatui/widgets/index.html + // - https://github.com/ratatui/ratatui/tree/master/examples + + let text = format!( + "This is a ratatui simple template.\n\ + Press `Esc`, `Ctrl-C` or `q` to stop running.\n\ + Press left and right to increment and decrement the counter respectively.\n\ + Counter: {}", + self.counter + ); + let block = Block::bordered() + .border_type(BorderType::Rounded) + .title("Ratatui Simple Template") + .title_alignment(Alignment::Center); + frame.render_widget( + Paragraph::new(text) + .block(block) + .style(Style::new().cyan().on_black()) + .centered(), + frame.area(), + ) + } +} diff --git a/simple-generated/src/main.rs b/simple-generated/src/main.rs index e866b47..cdf4998 100644 --- a/simple-generated/src/main.rs +++ b/simple-generated/src/main.rs @@ -1,111 +1,12 @@ -use std::time::Duration; - -use color_eyre::Result; -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use event::{Event, EventSource}; -use ratatui::{ - layout::Alignment, - style::{Style, Stylize}, - widgets::{Block, BorderType, Paragraph}, - DefaultTerminal, Frame, -}; +pub use app::App; +pub mod app; pub mod event; -fn main() -> Result<()> { +fn main() -> color_eyre::Result<()> { color_eyre::install()?; let terminal = ratatui::init(); let result = App::new().run(terminal); ratatui::restore(); result } - -#[derive(Debug, Default)] -pub struct App { - /// Is the application running? - pub running: bool, - /// counter - pub counter: u8, -} - -impl App { - /// Construct a new instance of [`App`]. - pub fn new() -> Self { - Self::default() - } - - pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { - let tick_interval = Duration::from_secs_f64(1.0 / 10.0); // 10 ticks per second - let events = EventSource::new(tick_interval); - self.running = true; - while self.running { - terminal.draw(|frame| self.draw(frame))?; - match events.next()? { - Event::Tick => self.on_tick(), - Event::Key(key_event) => self.on_key_event(key_event)?, - Event::Mouse(_) => {} - Event::Resize(_, _) => {} - Event::Error(err) => { - return Err(err.into()); - } - } - } - Ok(()) - } - - /// Handles the tick event of the terminal. - pub fn on_tick(&self) {} - - /// Handles the key events and updates the state of [`App`]. - pub fn on_key_event(&mut self, key: KeyEvent) -> Result<()> { - match (key.modifiers, key.code) { - (_, KeyCode::Esc | KeyCode::Char('q')) - | (KeyModifiers::CONTROL, KeyCode::Char('c') | KeyCode::Char('C')) => self.quit(), - (_, KeyCode::Right) => self.increment_counter(), - (_, KeyCode::Left) => self.decrement_counter(), - // Add other key handlers here. - _ => {} - } - Ok(()) - } - - /// Set running to false to quit the application. - pub fn quit(&mut self) { - self.running = false; - } - - pub fn increment_counter(&mut self) { - self.counter = self.counter.saturating_add(1); - } - - pub fn decrement_counter(&mut self) { - self.counter = self.counter.saturating_sub(1); - } - - /// Renders the user interface widgets. - fn draw(&mut self, frame: &mut Frame) { - // This is where you add new widgets. - // See the following resources: - // - https://docs.rs/ratatui/latest/ratatui/widgets/index.html - // - https://github.com/ratatui/ratatui/tree/master/examples - - let text = format!( - "This is a ratatui simple template.\n\ - Press `Esc`, `Ctrl-C` or `q` to stop running.\n\ - Press left and right to increment and decrement the counter respectively.\n\ - Counter: {}", - self.counter - ); - let block = Block::bordered() - .border_type(BorderType::Rounded) - .title("Ratatui Simple Template") - .title_alignment(Alignment::Center); - frame.render_widget( - Paragraph::new(text) - .block(block) - .style(Style::new().cyan().on_black()) - .centered(), - frame.area(), - ) - } -} diff --git a/simple/src/app.rs b/simple/src/app.rs new file mode 100644 index 0000000..7c749aa --- /dev/null +++ b/simple/src/app.rs @@ -0,0 +1,102 @@ +use std::time::Duration; + +use color_eyre::Result; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use ratatui::{ + layout::Alignment, + style::{Style, Stylize}, + widgets::{Block, BorderType, Paragraph}, + DefaultTerminal, Frame, +}; + +use crate::event::{Event, EventSource}; + +#[derive(Debug, Default)] +pub struct App { + /// Is the application running? + pub running: bool, + /// counter + pub counter: u8, +} + +impl App { + /// Construct a new instance of [`App`]. + pub fn new() -> Self { + Self::default() + } + + pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { + let tick_interval = Duration::from_secs_f64(1.0 / 10.0); // 10 ticks per second + let events = EventSource::new(tick_interval); + self.running = true; + while self.running { + terminal.draw(|frame| self.draw(frame))?; + match events.next()? { + Event::Tick => self.on_tick(), + Event::Key(key_event) => self.on_key_event(key_event)?, + Event::Mouse(_) => {} + Event::Resize(_, _) => {} + Event::Error(err) => { + return Err(err.into()); + } + } + } + Ok(()) + } + + /// Handles the tick event of the terminal. + pub fn on_tick(&self) {} + + /// Handles the key events and updates the state of [`App`]. + pub fn on_key_event(&mut self, key: KeyEvent) -> Result<()> { + match (key.modifiers, key.code) { + (_, KeyCode::Esc | KeyCode::Char('q')) + | (KeyModifiers::CONTROL, KeyCode::Char('c') | KeyCode::Char('C')) => self.quit(), + (_, KeyCode::Right) => self.increment_counter(), + (_, KeyCode::Left) => self.decrement_counter(), + // Add other key handlers here. + _ => {} + } + Ok(()) + } + + /// Set running to false to quit the application. + pub fn quit(&mut self) { + self.running = false; + } + + pub fn increment_counter(&mut self) { + self.counter = self.counter.saturating_add(1); + } + + pub fn decrement_counter(&mut self) { + self.counter = self.counter.saturating_sub(1); + } + + /// Renders the user interface widgets. + pub(crate) fn draw(&mut self, frame: &mut Frame) { + // This is where you add new widgets. + // See the following resources: + // - https://docs.rs/ratatui/latest/ratatui/widgets/index.html + // - https://github.com/ratatui/ratatui/tree/master/examples + + let text = format!( + "This is a ratatui simple template.\n\ + Press `Esc`, `Ctrl-C` or `q` to stop running.\n\ + Press left and right to increment and decrement the counter respectively.\n\ + Counter: {}", + self.counter + ); + let block = Block::bordered() + .border_type(BorderType::Rounded) + .title("Ratatui Simple Template") + .title_alignment(Alignment::Center); + frame.render_widget( + Paragraph::new(text) + .block(block) + .style(Style::new().cyan().on_black()) + .centered(), + frame.area(), + ) + } +} diff --git a/simple/src/main.rs b/simple/src/main.rs index e866b47..cdf4998 100644 --- a/simple/src/main.rs +++ b/simple/src/main.rs @@ -1,111 +1,12 @@ -use std::time::Duration; - -use color_eyre::Result; -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; -use event::{Event, EventSource}; -use ratatui::{ - layout::Alignment, - style::{Style, Stylize}, - widgets::{Block, BorderType, Paragraph}, - DefaultTerminal, Frame, -}; +pub use app::App; +pub mod app; pub mod event; -fn main() -> Result<()> { +fn main() -> color_eyre::Result<()> { color_eyre::install()?; let terminal = ratatui::init(); let result = App::new().run(terminal); ratatui::restore(); result } - -#[derive(Debug, Default)] -pub struct App { - /// Is the application running? - pub running: bool, - /// counter - pub counter: u8, -} - -impl App { - /// Construct a new instance of [`App`]. - pub fn new() -> Self { - Self::default() - } - - pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { - let tick_interval = Duration::from_secs_f64(1.0 / 10.0); // 10 ticks per second - let events = EventSource::new(tick_interval); - self.running = true; - while self.running { - terminal.draw(|frame| self.draw(frame))?; - match events.next()? { - Event::Tick => self.on_tick(), - Event::Key(key_event) => self.on_key_event(key_event)?, - Event::Mouse(_) => {} - Event::Resize(_, _) => {} - Event::Error(err) => { - return Err(err.into()); - } - } - } - Ok(()) - } - - /// Handles the tick event of the terminal. - pub fn on_tick(&self) {} - - /// Handles the key events and updates the state of [`App`]. - pub fn on_key_event(&mut self, key: KeyEvent) -> Result<()> { - match (key.modifiers, key.code) { - (_, KeyCode::Esc | KeyCode::Char('q')) - | (KeyModifiers::CONTROL, KeyCode::Char('c') | KeyCode::Char('C')) => self.quit(), - (_, KeyCode::Right) => self.increment_counter(), - (_, KeyCode::Left) => self.decrement_counter(), - // Add other key handlers here. - _ => {} - } - Ok(()) - } - - /// Set running to false to quit the application. - pub fn quit(&mut self) { - self.running = false; - } - - pub fn increment_counter(&mut self) { - self.counter = self.counter.saturating_add(1); - } - - pub fn decrement_counter(&mut self) { - self.counter = self.counter.saturating_sub(1); - } - - /// Renders the user interface widgets. - fn draw(&mut self, frame: &mut Frame) { - // This is where you add new widgets. - // See the following resources: - // - https://docs.rs/ratatui/latest/ratatui/widgets/index.html - // - https://github.com/ratatui/ratatui/tree/master/examples - - let text = format!( - "This is a ratatui simple template.\n\ - Press `Esc`, `Ctrl-C` or `q` to stop running.\n\ - Press left and right to increment and decrement the counter respectively.\n\ - Counter: {}", - self.counter - ); - let block = Block::bordered() - .border_type(BorderType::Rounded) - .title("Ratatui Simple Template") - .title_alignment(Alignment::Center); - frame.render_widget( - Paragraph::new(text) - .block(block) - .style(Style::new().cyan().on_black()) - .centered(), - frame.area(), - ) - } -} From c6d2b6a3e38decfedcc34cc8dcee9fcae3cb90fc Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Tue, 27 Aug 2024 16:06:49 -0700 Subject: [PATCH 3/7] Ignore tick send error --- simple-generated/src/event.rs | 5 ++++- simple/src/event.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/simple-generated/src/event.rs b/simple-generated/src/event.rs index c9fbbce..175bf64 100644 --- a/simple-generated/src/event.rs +++ b/simple-generated/src/event.rs @@ -63,6 +63,9 @@ impl EventSource { /// thread. It sends a tick event at a regular interval. The tick rate is specified by the /// `tick_rate` parameter. If an error occurs, it sends an error event to the main thread and then /// stops running. +/// +/// Errors sending an event are ignored as this generally indicates that the main thread has exited +/// and is no longer listening for events. fn event_thread(sender: mpsc::Sender, tick_rate: Duration) { let mut last_tick: Option = None; loop { @@ -70,7 +73,7 @@ fn event_thread(sender: mpsc::Sender, tick_rate: Duration) { .map(|tick| tick.elapsed() >= tick_rate) .unwrap_or(true) { - sender.send(Event::Tick).expect("failed to send tick event"); + let _ = sender.send(Event::Tick); last_tick = Some(Instant::now()); } diff --git a/simple/src/event.rs b/simple/src/event.rs index c9fbbce..175bf64 100644 --- a/simple/src/event.rs +++ b/simple/src/event.rs @@ -63,6 +63,9 @@ impl EventSource { /// thread. It sends a tick event at a regular interval. The tick rate is specified by the /// `tick_rate` parameter. If an error occurs, it sends an error event to the main thread and then /// stops running. +/// +/// Errors sending an event are ignored as this generally indicates that the main thread has exited +/// and is no longer listening for events. fn event_thread(sender: mpsc::Sender, tick_rate: Duration) { let mut last_tick: Option = None; loop { @@ -70,7 +73,7 @@ fn event_thread(sender: mpsc::Sender, tick_rate: Duration) { .map(|tick| tick.elapsed() >= tick_rate) .unwrap_or(true) { - sender.send(Event::Tick).expect("failed to send tick event"); + let _ = sender.send(Event::Tick); last_tick = Some(Instant::now()); } From 6ba914b2121d1523b2122dffb15f4ba77d128387 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Sun, 1 Sep 2024 15:53:25 -0700 Subject: [PATCH 4/7] add ticks per second const --- simple-generated/src/app.rs | 3 ++- simple/src/app.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/simple-generated/src/app.rs b/simple-generated/src/app.rs index 7c749aa..bd56445 100644 --- a/simple-generated/src/app.rs +++ b/simple-generated/src/app.rs @@ -26,7 +26,8 @@ impl App { } pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { - let tick_interval = Duration::from_secs_f64(1.0 / 10.0); // 10 ticks per second + const TICKS_PER_SECOND: f64 = 10.0; + let tick_interval = Duration::from_secs_f64(1.0 / TICKS_PER_SECOND); let events = EventSource::new(tick_interval); self.running = true; while self.running { diff --git a/simple/src/app.rs b/simple/src/app.rs index 7c749aa..bd56445 100644 --- a/simple/src/app.rs +++ b/simple/src/app.rs @@ -26,7 +26,8 @@ impl App { } pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { - let tick_interval = Duration::from_secs_f64(1.0 / 10.0); // 10 ticks per second + const TICKS_PER_SECOND: f64 = 10.0; + let tick_interval = Duration::from_secs_f64(1.0 / TICKS_PER_SECOND); let events = EventSource::new(tick_interval); self.running = true; while self.running { From b01284136e7722b52d7eedf00c56d5476dfbcce9 Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Sun, 1 Sep 2024 16:39:40 -0700 Subject: [PATCH 5/7] Add README, LICENSE, github workflows Remove cargo.lock as this will get out of sync with any dependency bump --- simple-generated/.github/dependabot.yml | 17 + simple-generated/.github/workflows/ci.yml | 89 ++++ simple-generated/Cargo.lock | 526 ---------------------- simple-generated/LICENSE | 22 + simple-generated/README.md | 28 +- simple/Cargo.lock | 526 ---------------------- simple/README.md | 20 +- simple/template/.github/dependabot.yml | 17 + simple/template/.github/workflows/ci.yml | 89 ++++ simple/{ => template}/.gitignore | 0 simple/{ => template}/Cargo.toml | 0 simple/template/LICENSE | 22 + simple/template/README.md | 14 + simple/template/cargo-generate.toml | 2 + simple/{ => template}/src/app.rs | 0 simple/{ => template}/src/event.rs | 0 simple/{ => template}/src/main.rs | 0 17 files changed, 301 insertions(+), 1071 deletions(-) create mode 100644 simple-generated/.github/dependabot.yml create mode 100644 simple-generated/.github/workflows/ci.yml delete mode 100644 simple-generated/Cargo.lock create mode 100644 simple-generated/LICENSE delete mode 100644 simple/Cargo.lock create mode 100644 simple/template/.github/dependabot.yml create mode 100644 simple/template/.github/workflows/ci.yml rename simple/{ => template}/.gitignore (100%) rename simple/{ => template}/Cargo.toml (100%) create mode 100644 simple/template/LICENSE create mode 100644 simple/template/README.md create mode 100644 simple/template/cargo-generate.toml rename simple/{ => template}/src/app.rs (100%) rename simple/{ => template}/src/event.rs (100%) rename simple/{ => template}/src/main.rs (100%) diff --git a/simple-generated/.github/dependabot.yml b/simple-generated/.github/dependabot.yml new file mode 100644 index 0000000..c2fabe1 --- /dev/null +++ b/simple-generated/.github/dependabot.yml @@ -0,0 +1,17 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + # Maintain dependencies for Cargo + - package-ecosystem: "cargo" + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + # Maintain dependencies for GitHub Actions + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly diff --git a/simple-generated/.github/workflows/ci.yml b/simple-generated/.github/workflows/ci.yml new file mode 100644 index 0000000..eebd111 --- /dev/null +++ b/simple-generated/.github/workflows/ci.yml @@ -0,0 +1,89 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + - master + - develop + +env: + CARGO_TERM_COLOR: always + +# ensure that the workflow is only triggered once per PR, subsequent pushes to the PR will cancel +# and restart the workflow. See https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + fmt: + name: fmt + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: check formatting + run: cargo fmt -- --check + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 + clippy: + name: clippy + runs-on: ubuntu-latest + permissions: + checks: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - name: Run clippy action + uses: clechasseur/rs-clippy-check@v3 + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 + doc: + # run docs generation on nightly rather than stable. This enables features like + # https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an + # API be documented as only available in some specific platforms. + name: doc + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust nightly + uses: dtolnay/rust-toolchain@nightly + - name: Run cargo doc + run: cargo doc --no-deps --all-features + env: + RUSTDOCFLAGS: --cfg docsrs + test: + runs-on: ${{ matrix.os }} + name: test ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest] + steps: + # if your project needs OpenSSL, uncomment this to fix Windows builds. + # it's commented out by default as the install command takes 5-10m. + # - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append + # if: runner.os == 'Windows' + # - run: vcpkg install openssl:x64-windows-static-md + # if: runner.os == 'Windows' + - uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + # enable this ci template to run regardless of whether the lockfile is checked in or not + - name: cargo generate-lockfile + if: hashFiles('Cargo.lock') == '' + run: cargo generate-lockfile + - name: cargo test --locked + run: cargo test --locked --all-features --all-targets + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 diff --git a/simple-generated/Cargo.lock b/simple-generated/Cargo.lock deleted file mode 100644 index 3195199..0000000 --- a/simple-generated/Cargo.lock +++ /dev/null @@ -1,526 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "allocator-api2" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" - -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - -[[package]] -name = "castaway" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" -dependencies = [ - "rustversion", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "compact_str" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "ryu", - "static_assertions", -] - -[[package]] -name = "crossterm" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" -dependencies = [ - "bitflags 2.4.2", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - -[[package]] -name = "either" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" - -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "indoc" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" - -[[package]] -name = "libc" -version = "0.2.153" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "lock_api" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" - -[[package]] -name = "lru" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2c024b41519440580066ba82aab04092b333e09066a5eb86c7c4890df31f22" -dependencies = [ - "hashbrown", -] - -[[package]] -name = "mio" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - -[[package]] -name = "paste" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" - -[[package]] -name = "proc-macro2" -version = "1.0.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "ratatui" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "154b85ef15a5d1719bcaa193c3c81fe645cd120c156874cd660fe49fd21d1373" -dependencies = [ - "bitflags 2.4.2", - "cassowary", - "compact_str", - "crossterm", - "indoc", - "itertools", - "lru", - "paste", - "stability", - "strum", - "unicode-segmentation", - "unicode-width", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - -[[package]] -name = "ryu" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "signal-hook" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - -[[package]] -name = "smallvec" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" - -[[package]] -name = "stability" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd1b177894da2a2d9120208c3386066af06a488255caabc5de8ddca22dbc3ce" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strum" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.48", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "toto" -version = "0.1.0" -dependencies = [ - "crossterm", - "ratatui", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-segmentation" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" - -[[package]] -name = "unicode-width" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "zerocopy" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] diff --git a/simple-generated/LICENSE b/simple-generated/LICENSE new file mode 100644 index 0000000..4953c71 --- /dev/null +++ b/simple-generated/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2021-2022 Orhun Parmaksiz +Copyright (c) 2023 The Ratatui Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/simple-generated/README.md b/simple-generated/README.md index 824d912..a83deb9 100644 --- a/simple-generated/README.md +++ b/simple-generated/README.md @@ -1,14 +1,14 @@ -## Simple template - -The simple template will create the following project structure: - -```text -src/ -├── app.rs -> holds the state and application logic -├── event.rs -> handles the terminal events (key press, mouse click, resize, etc.) -├── handler.rs -> handles the key press events and updates the application -├── lib.rs -> module definitions -├── main.rs -> entry-point -├── tui.rs -> initializes/exits the terminal interface -└── ui.rs -> renders the widgets / UI -``` +# simple-generated + +This is a [Ratatui] app generated by the [Simple template]. + +[Ratatui]: https://ratatui.rs +[Simple Template]: https://github.com/ratatui/templates/tree/main/simple + +## License + +Copyright (c) Josh McKinney + +This project is licensed under the MIT license ([LICENSE] or ) + +[LICENSE]: ./LICENSE diff --git a/simple/Cargo.lock b/simple/Cargo.lock deleted file mode 100644 index 3195199..0000000 --- a/simple/Cargo.lock +++ /dev/null @@ -1,526 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "allocator-api2" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" - -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - -[[package]] -name = "castaway" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" -dependencies = [ - "rustversion", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "compact_str" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "ryu", - "static_assertions", -] - -[[package]] -name = "crossterm" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" -dependencies = [ - "bitflags 2.4.2", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - -[[package]] -name = "either" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" - -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "indoc" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" - -[[package]] -name = "libc" -version = "0.2.153" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "lock_api" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" - -[[package]] -name = "lru" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2c024b41519440580066ba82aab04092b333e09066a5eb86c7c4890df31f22" -dependencies = [ - "hashbrown", -] - -[[package]] -name = "mio" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - -[[package]] -name = "paste" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" - -[[package]] -name = "proc-macro2" -version = "1.0.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "ratatui" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "154b85ef15a5d1719bcaa193c3c81fe645cd120c156874cd660fe49fd21d1373" -dependencies = [ - "bitflags 2.4.2", - "cassowary", - "compact_str", - "crossterm", - "indoc", - "itertools", - "lru", - "paste", - "stability", - "strum", - "unicode-segmentation", - "unicode-width", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - -[[package]] -name = "ryu" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "signal-hook" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - -[[package]] -name = "smallvec" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" - -[[package]] -name = "stability" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd1b177894da2a2d9120208c3386066af06a488255caabc5de8ddca22dbc3ce" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strum" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.48", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "toto" -version = "0.1.0" -dependencies = [ - "crossterm", - "ratatui", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-segmentation" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" - -[[package]] -name = "unicode-width" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "zerocopy" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] diff --git a/simple/README.md b/simple/README.md index 824d912..a667497 100644 --- a/simple/README.md +++ b/simple/README.md @@ -1,4 +1,4 @@ -## Simple template +# Ratatui Simple template The simple template will create the following project structure: @@ -6,9 +6,19 @@ The simple template will create the following project structure: src/ ├── app.rs -> holds the state and application logic ├── event.rs -> handles the terminal events (key press, mouse click, resize, etc.) -├── handler.rs -> handles the key press events and updates the application -├── lib.rs -> module definitions ├── main.rs -> entry-point -├── tui.rs -> initializes/exits the terminal interface -└── ui.rs -> renders the widgets / UI ``` + +## Design choices + +We use [color-eyre](https://docs.rs/color-eyre/latest/color_eyre/) for simplifying any errors that +need to be reported to the console. + +We have a small `App` struct that has a main loop that calls methods to handle events and draw the +ui. + +Events are read on a secondary thread and collated into a single mpsc channel. This allows a tick +event to be interspersed with other events like keyboard and mouse. The Tick event is a useful place +to perform updates to animations or to do external polling. + +The app can be quit by pressing any of Q/Esc/Ctrl+C. diff --git a/simple/template/.github/dependabot.yml b/simple/template/.github/dependabot.yml new file mode 100644 index 0000000..c2fabe1 --- /dev/null +++ b/simple/template/.github/dependabot.yml @@ -0,0 +1,17 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + # Maintain dependencies for Cargo + - package-ecosystem: "cargo" + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + # Maintain dependencies for GitHub Actions + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly diff --git a/simple/template/.github/workflows/ci.yml b/simple/template/.github/workflows/ci.yml new file mode 100644 index 0000000..eebd111 --- /dev/null +++ b/simple/template/.github/workflows/ci.yml @@ -0,0 +1,89 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + - master + - develop + +env: + CARGO_TERM_COLOR: always + +# ensure that the workflow is only triggered once per PR, subsequent pushes to the PR will cancel +# and restart the workflow. See https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + fmt: + name: fmt + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: check formatting + run: cargo fmt -- --check + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 + clippy: + name: clippy + runs-on: ubuntu-latest + permissions: + checks: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - name: Run clippy action + uses: clechasseur/rs-clippy-check@v3 + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 + doc: + # run docs generation on nightly rather than stable. This enables features like + # https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an + # API be documented as only available in some specific platforms. + name: doc + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust nightly + uses: dtolnay/rust-toolchain@nightly + - name: Run cargo doc + run: cargo doc --no-deps --all-features + env: + RUSTDOCFLAGS: --cfg docsrs + test: + runs-on: ${{ matrix.os }} + name: test ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest] + steps: + # if your project needs OpenSSL, uncomment this to fix Windows builds. + # it's commented out by default as the install command takes 5-10m. + # - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append + # if: runner.os == 'Windows' + # - run: vcpkg install openssl:x64-windows-static-md + # if: runner.os == 'Windows' + - uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + # enable this ci template to run regardless of whether the lockfile is checked in or not + - name: cargo generate-lockfile + if: hashFiles('Cargo.lock') == '' + run: cargo generate-lockfile + - name: cargo test --locked + run: cargo test --locked --all-features --all-targets + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 diff --git a/simple/.gitignore b/simple/template/.gitignore similarity index 100% rename from simple/.gitignore rename to simple/template/.gitignore diff --git a/simple/Cargo.toml b/simple/template/Cargo.toml similarity index 100% rename from simple/Cargo.toml rename to simple/template/Cargo.toml diff --git a/simple/template/LICENSE b/simple/template/LICENSE new file mode 100644 index 0000000..4953c71 --- /dev/null +++ b/simple/template/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2021-2022 Orhun Parmaksiz +Copyright (c) 2023 The Ratatui Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/simple/template/README.md b/simple/template/README.md new file mode 100644 index 0000000..88b1f57 --- /dev/null +++ b/simple/template/README.md @@ -0,0 +1,14 @@ +# {{project-name}} + +This is a [Ratatui] app generated by the [Simple template]. + +[Ratatui]: https://ratatui.rs +[Simple Template]: https://github.com/ratatui/templates/tree/main/simple + +## License + +Copyright (c) {{authors}} + +This project is licensed under the MIT license ([LICENSE] or ) + +[LICENSE]: ./LICENSE diff --git a/simple/template/cargo-generate.toml b/simple/template/cargo-generate.toml new file mode 100644 index 0000000..2589766 --- /dev/null +++ b/simple/template/cargo-generate.toml @@ -0,0 +1,2 @@ +[template] +cargo_generate_version = ">=0.10.0" diff --git a/simple/src/app.rs b/simple/template/src/app.rs similarity index 100% rename from simple/src/app.rs rename to simple/template/src/app.rs diff --git a/simple/src/event.rs b/simple/template/src/event.rs similarity index 100% rename from simple/src/event.rs rename to simple/template/src/event.rs diff --git a/simple/src/main.rs b/simple/template/src/main.rs similarity index 100% rename from simple/src/main.rs rename to simple/template/src/main.rs From a808d3bc7726e9c338f4554303972116a240cdba Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Wed, 4 Sep 2024 10:24:07 -0700 Subject: [PATCH 6/7] Add the thread handle back as an ignored field --- simple-generated/src/event.rs | 6 ++++-- simple/template/src/event.rs | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/simple-generated/src/event.rs b/simple-generated/src/event.rs index 175bf64..275b346 100644 --- a/simple-generated/src/event.rs +++ b/simple-generated/src/event.rs @@ -37,14 +37,16 @@ pub enum Event { pub struct EventSource { /// Event receiver channel. receiver: mpsc::Receiver, + /// Handle to the event thread. + _handle: thread::JoinHandle<()>, } impl EventSource { /// Constructs a new instance of [`EventHandler`]. pub fn new(tick_rate: Duration) -> Self { let (sender, receiver) = mpsc::channel(); - thread::spawn(move || event_thread(sender, tick_rate)); - Self { receiver } + let _handle = thread::spawn(move || event_thread(sender, tick_rate)); + Self { receiver, _handle } } /// Receive the next event from the handler thread. diff --git a/simple/template/src/event.rs b/simple/template/src/event.rs index 175bf64..275b346 100644 --- a/simple/template/src/event.rs +++ b/simple/template/src/event.rs @@ -37,14 +37,16 @@ pub enum Event { pub struct EventSource { /// Event receiver channel. receiver: mpsc::Receiver, + /// Handle to the event thread. + _handle: thread::JoinHandle<()>, } impl EventSource { /// Constructs a new instance of [`EventHandler`]. pub fn new(tick_rate: Duration) -> Self { let (sender, receiver) = mpsc::channel(); - thread::spawn(move || event_thread(sender, tick_rate)); - Self { receiver } + let _handle = thread::spawn(move || event_thread(sender, tick_rate)); + Self { receiver, _handle } } /// Receive the next event from the handler thread. From 6a769404c021b1944afd4b306445fa9e4e6e712b Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Wed, 4 Sep 2024 11:03:27 -0700 Subject: [PATCH 7/7] Remove event module, tick, etc, counter behavior --- simple-generated/src/app.rs | 78 ++++++++-------------- simple-generated/src/event.rs | 121 ---------------------------------- simple-generated/src/main.rs | 1 - simple/template/src/app.rs | 78 ++++++++-------------- simple/template/src/event.rs | 121 ---------------------------------- simple/template/src/main.rs | 1 - 6 files changed, 52 insertions(+), 348 deletions(-) delete mode 100644 simple-generated/src/event.rs delete mode 100644 simple/template/src/event.rs diff --git a/simple-generated/src/app.rs b/simple-generated/src/app.rs index bd56445..0f599cd 100644 --- a/simple-generated/src/app.rs +++ b/simple-generated/src/app.rs @@ -1,22 +1,16 @@ -use std::time::Duration; - use color_eyre::Result; -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use ratatui::{ - layout::Alignment, - style::{Style, Stylize}, - widgets::{Block, BorderType, Paragraph}, + style::Stylize, + text::Line, + widgets::{Block, Paragraph}, DefaultTerminal, Frame, }; -use crate::event::{Event, EventSource}; - #[derive(Debug, Default)] pub struct App { /// Is the application running? - pub running: bool, - /// counter - pub counter: u8, + running: bool, } impl App { @@ -26,76 +20,56 @@ impl App { } pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { - const TICKS_PER_SECOND: f64 = 10.0; - let tick_interval = Duration::from_secs_f64(1.0 / TICKS_PER_SECOND); - let events = EventSource::new(tick_interval); self.running = true; while self.running { terminal.draw(|frame| self.draw(frame))?; - match events.next()? { - Event::Tick => self.on_tick(), - Event::Key(key_event) => self.on_key_event(key_event)?, - Event::Mouse(_) => {} - Event::Resize(_, _) => {} - Event::Error(err) => { - return Err(err.into()); - } - } + self.handle_crossterm_events()?; } Ok(()) } - /// Handles the tick event of the terminal. - pub fn on_tick(&self) {} + fn handle_crossterm_events(&mut self) -> Result<()> { + match event::read()? { + Event::Key(key) if key.kind == KeyEventKind::Press => self.on_key_event(key), + Event::Mouse(_) => {} + Event::Resize(_, _) => {} + _ => {} + } + Ok(()) + } /// Handles the key events and updates the state of [`App`]. - pub fn on_key_event(&mut self, key: KeyEvent) -> Result<()> { + fn on_key_event(&mut self, key: KeyEvent) { match (key.modifiers, key.code) { (_, KeyCode::Esc | KeyCode::Char('q')) | (KeyModifiers::CONTROL, KeyCode::Char('c') | KeyCode::Char('C')) => self.quit(), - (_, KeyCode::Right) => self.increment_counter(), - (_, KeyCode::Left) => self.decrement_counter(), // Add other key handlers here. _ => {} } - Ok(()) } /// Set running to false to quit the application. - pub fn quit(&mut self) { + fn quit(&mut self) { self.running = false; } - pub fn increment_counter(&mut self) { - self.counter = self.counter.saturating_add(1); - } - - pub fn decrement_counter(&mut self) { - self.counter = self.counter.saturating_sub(1); - } - /// Renders the user interface widgets. - pub(crate) fn draw(&mut self, frame: &mut Frame) { + fn draw(&mut self, frame: &mut Frame) { // This is where you add new widgets. // See the following resources: // - https://docs.rs/ratatui/latest/ratatui/widgets/index.html // - https://github.com/ratatui/ratatui/tree/master/examples - let text = format!( - "This is a ratatui simple template.\n\ - Press `Esc`, `Ctrl-C` or `q` to stop running.\n\ - Press left and right to increment and decrement the counter respectively.\n\ - Counter: {}", - self.counter - ); - let block = Block::bordered() - .border_type(BorderType::Rounded) - .title("Ratatui Simple Template") - .title_alignment(Alignment::Center); + let title = Line::from("Ratatui Simple Template") + .bold() + .blue() + .centered(); + let text = "Hello, Ratatui!\n\n\ + Created using https://github.com/ratatui/templates\n\ + Press `Esc`, `Ctrl-C` or `q` to stop running."; frame.render_widget( Paragraph::new(text) - .block(block) - .style(Style::new().cyan().on_black()) + .block(Block::bordered().title(title)) .centered(), frame.area(), ) diff --git a/simple-generated/src/event.rs b/simple-generated/src/event.rs deleted file mode 100644 index 275b346..0000000 --- a/simple-generated/src/event.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent}; -use std::sync::mpsc; -use std::thread; -use std::time::{Duration, Instant}; - -/// Terminal events. -#[derive(Debug)] -pub enum Event { - /// The tick event is sent at a regular interval and can be used to trigger application state - /// updates or animations. - Tick, - - /// Key press. - Key(KeyEvent), - - /// Mouse click/scroll. - Mouse(MouseEvent), - - /// Terminal resize. - /// - /// This event may be fired multiple times in quick succession, so it is recommended not to - /// render the UI on every resize event. - Resize(u16, u16), - - /// An error occurred. - Error(std::io::Error), -} - -/// Terminal event handler. -/// -/// This struct is responsible for listening to terminal events and sending them to the main thread. -/// -/// It sends a tick event at a regular interval. The tick rate is specified by the `tick_rate` -/// parameter. If an error occurs, it sends an error event to the main thread and then stops -/// running. -#[derive(Debug)] -pub struct EventSource { - /// Event receiver channel. - receiver: mpsc::Receiver, - /// Handle to the event thread. - _handle: thread::JoinHandle<()>, -} - -impl EventSource { - /// Constructs a new instance of [`EventHandler`]. - pub fn new(tick_rate: Duration) -> Self { - let (sender, receiver) = mpsc::channel(); - let _handle = thread::spawn(move || event_thread(sender, tick_rate)); - Self { receiver, _handle } - } - - /// Receive the next event from the handler thread. - /// - /// This function will always block the current thread if there is no data available unless the - /// event source has been closed. - pub fn next(&self) -> color_eyre::Result { - let event = self.receiver.recv()?; - Ok(event) - } -} - -/// An event thread that listens for terminal events. -/// -/// This function is responsible for listening to terminal events and sending them to the main -/// thread. It sends a tick event at a regular interval. The tick rate is specified by the -/// `tick_rate` parameter. If an error occurs, it sends an error event to the main thread and then -/// stops running. -/// -/// Errors sending an event are ignored as this generally indicates that the main thread has exited -/// and is no longer listening for events. -fn event_thread(sender: mpsc::Sender, tick_rate: Duration) { - let mut last_tick: Option = None; - loop { - if last_tick - .map(|tick| tick.elapsed() >= tick_rate) - .unwrap_or(true) - { - let _ = sender.send(Event::Tick); - last_tick = Some(Instant::now()); - } - - let timeout = last_tick.map_or(Duration::ZERO, |tick| { - tick_rate.saturating_sub(tick.elapsed()) - }); - - match event::poll(timeout) { - Ok(false) => { - // no new events waiting - continue; - } - Ok(true) => {} - Err(e) => { - let _ = sender.send(Event::Error(e)); - break; - } - } - - match event::read() { - Err(err) => { - let _ = sender.send(Event::Error(err)); - break; - } - Ok(event) => match event { - CrosstermEvent::Key(e) if e.kind == KeyEventKind::Press => { - // ignore key release / repeat events - let _ = sender.send(Event::Key(e)); - } - CrosstermEvent::Key(_) => {} - CrosstermEvent::Mouse(e) => { - let _ = sender.send(Event::Mouse(e)); - } - CrosstermEvent::Resize(w, h) => { - let _ = sender.send(Event::Resize(w, h)); - } - CrosstermEvent::FocusGained => {} - CrosstermEvent::FocusLost => {} - CrosstermEvent::Paste(_) => {} - }, - } - } -} diff --git a/simple-generated/src/main.rs b/simple-generated/src/main.rs index cdf4998..95d10a5 100644 --- a/simple-generated/src/main.rs +++ b/simple-generated/src/main.rs @@ -1,7 +1,6 @@ pub use app::App; pub mod app; -pub mod event; fn main() -> color_eyre::Result<()> { color_eyre::install()?; diff --git a/simple/template/src/app.rs b/simple/template/src/app.rs index bd56445..0f599cd 100644 --- a/simple/template/src/app.rs +++ b/simple/template/src/app.rs @@ -1,22 +1,16 @@ -use std::time::Duration; - use color_eyre::Result; -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use ratatui::{ - layout::Alignment, - style::{Style, Stylize}, - widgets::{Block, BorderType, Paragraph}, + style::Stylize, + text::Line, + widgets::{Block, Paragraph}, DefaultTerminal, Frame, }; -use crate::event::{Event, EventSource}; - #[derive(Debug, Default)] pub struct App { /// Is the application running? - pub running: bool, - /// counter - pub counter: u8, + running: bool, } impl App { @@ -26,76 +20,56 @@ impl App { } pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { - const TICKS_PER_SECOND: f64 = 10.0; - let tick_interval = Duration::from_secs_f64(1.0 / TICKS_PER_SECOND); - let events = EventSource::new(tick_interval); self.running = true; while self.running { terminal.draw(|frame| self.draw(frame))?; - match events.next()? { - Event::Tick => self.on_tick(), - Event::Key(key_event) => self.on_key_event(key_event)?, - Event::Mouse(_) => {} - Event::Resize(_, _) => {} - Event::Error(err) => { - return Err(err.into()); - } - } + self.handle_crossterm_events()?; } Ok(()) } - /// Handles the tick event of the terminal. - pub fn on_tick(&self) {} + fn handle_crossterm_events(&mut self) -> Result<()> { + match event::read()? { + Event::Key(key) if key.kind == KeyEventKind::Press => self.on_key_event(key), + Event::Mouse(_) => {} + Event::Resize(_, _) => {} + _ => {} + } + Ok(()) + } /// Handles the key events and updates the state of [`App`]. - pub fn on_key_event(&mut self, key: KeyEvent) -> Result<()> { + fn on_key_event(&mut self, key: KeyEvent) { match (key.modifiers, key.code) { (_, KeyCode::Esc | KeyCode::Char('q')) | (KeyModifiers::CONTROL, KeyCode::Char('c') | KeyCode::Char('C')) => self.quit(), - (_, KeyCode::Right) => self.increment_counter(), - (_, KeyCode::Left) => self.decrement_counter(), // Add other key handlers here. _ => {} } - Ok(()) } /// Set running to false to quit the application. - pub fn quit(&mut self) { + fn quit(&mut self) { self.running = false; } - pub fn increment_counter(&mut self) { - self.counter = self.counter.saturating_add(1); - } - - pub fn decrement_counter(&mut self) { - self.counter = self.counter.saturating_sub(1); - } - /// Renders the user interface widgets. - pub(crate) fn draw(&mut self, frame: &mut Frame) { + fn draw(&mut self, frame: &mut Frame) { // This is where you add new widgets. // See the following resources: // - https://docs.rs/ratatui/latest/ratatui/widgets/index.html // - https://github.com/ratatui/ratatui/tree/master/examples - let text = format!( - "This is a ratatui simple template.\n\ - Press `Esc`, `Ctrl-C` or `q` to stop running.\n\ - Press left and right to increment and decrement the counter respectively.\n\ - Counter: {}", - self.counter - ); - let block = Block::bordered() - .border_type(BorderType::Rounded) - .title("Ratatui Simple Template") - .title_alignment(Alignment::Center); + let title = Line::from("Ratatui Simple Template") + .bold() + .blue() + .centered(); + let text = "Hello, Ratatui!\n\n\ + Created using https://github.com/ratatui/templates\n\ + Press `Esc`, `Ctrl-C` or `q` to stop running."; frame.render_widget( Paragraph::new(text) - .block(block) - .style(Style::new().cyan().on_black()) + .block(Block::bordered().title(title)) .centered(), frame.area(), ) diff --git a/simple/template/src/event.rs b/simple/template/src/event.rs deleted file mode 100644 index 275b346..0000000 --- a/simple/template/src/event.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent}; -use std::sync::mpsc; -use std::thread; -use std::time::{Duration, Instant}; - -/// Terminal events. -#[derive(Debug)] -pub enum Event { - /// The tick event is sent at a regular interval and can be used to trigger application state - /// updates or animations. - Tick, - - /// Key press. - Key(KeyEvent), - - /// Mouse click/scroll. - Mouse(MouseEvent), - - /// Terminal resize. - /// - /// This event may be fired multiple times in quick succession, so it is recommended not to - /// render the UI on every resize event. - Resize(u16, u16), - - /// An error occurred. - Error(std::io::Error), -} - -/// Terminal event handler. -/// -/// This struct is responsible for listening to terminal events and sending them to the main thread. -/// -/// It sends a tick event at a regular interval. The tick rate is specified by the `tick_rate` -/// parameter. If an error occurs, it sends an error event to the main thread and then stops -/// running. -#[derive(Debug)] -pub struct EventSource { - /// Event receiver channel. - receiver: mpsc::Receiver, - /// Handle to the event thread. - _handle: thread::JoinHandle<()>, -} - -impl EventSource { - /// Constructs a new instance of [`EventHandler`]. - pub fn new(tick_rate: Duration) -> Self { - let (sender, receiver) = mpsc::channel(); - let _handle = thread::spawn(move || event_thread(sender, tick_rate)); - Self { receiver, _handle } - } - - /// Receive the next event from the handler thread. - /// - /// This function will always block the current thread if there is no data available unless the - /// event source has been closed. - pub fn next(&self) -> color_eyre::Result { - let event = self.receiver.recv()?; - Ok(event) - } -} - -/// An event thread that listens for terminal events. -/// -/// This function is responsible for listening to terminal events and sending them to the main -/// thread. It sends a tick event at a regular interval. The tick rate is specified by the -/// `tick_rate` parameter. If an error occurs, it sends an error event to the main thread and then -/// stops running. -/// -/// Errors sending an event are ignored as this generally indicates that the main thread has exited -/// and is no longer listening for events. -fn event_thread(sender: mpsc::Sender, tick_rate: Duration) { - let mut last_tick: Option = None; - loop { - if last_tick - .map(|tick| tick.elapsed() >= tick_rate) - .unwrap_or(true) - { - let _ = sender.send(Event::Tick); - last_tick = Some(Instant::now()); - } - - let timeout = last_tick.map_or(Duration::ZERO, |tick| { - tick_rate.saturating_sub(tick.elapsed()) - }); - - match event::poll(timeout) { - Ok(false) => { - // no new events waiting - continue; - } - Ok(true) => {} - Err(e) => { - let _ = sender.send(Event::Error(e)); - break; - } - } - - match event::read() { - Err(err) => { - let _ = sender.send(Event::Error(err)); - break; - } - Ok(event) => match event { - CrosstermEvent::Key(e) if e.kind == KeyEventKind::Press => { - // ignore key release / repeat events - let _ = sender.send(Event::Key(e)); - } - CrosstermEvent::Key(_) => {} - CrosstermEvent::Mouse(e) => { - let _ = sender.send(Event::Mouse(e)); - } - CrosstermEvent::Resize(w, h) => { - let _ = sender.send(Event::Resize(w, h)); - } - CrosstermEvent::FocusGained => {} - CrosstermEvent::FocusLost => {} - CrosstermEvent::Paste(_) => {} - }, - } - } -} diff --git a/simple/template/src/main.rs b/simple/template/src/main.rs index cdf4998..95d10a5 100644 --- a/simple/template/src/main.rs +++ b/simple/template/src/main.rs @@ -1,7 +1,6 @@ pub use app::App; pub mod app; -pub mod event; fn main() -> color_eyre::Result<()> { color_eyre::install()?;