diff --git a/src/app.rs b/src/app.rs index efcf1d6..a4abef8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,38 +1,64 @@ +use std::sync::Arc; + use color_eyre::eyre::Result; use crossterm::event::KeyEvent; -use ratatui::prelude::Rect; +use ratatui::{ + layout::{Constraint, Direction, Layout}, + prelude::Rect, + widgets::{Block, Borders, Paragraph}, +}; use serde::{Deserialize, Serialize}; use tokio::sync::mpsc; use crate::{ action::Action, - components::{fps::FpsCounter, home::Home, Component}, + components::{data::Data, ide::IDE, menu::Menu, Component}, config::Config, mode::Mode, tui, }; +pub struct AppState { + pub connection_string: String, +} + +pub struct Components { + pub menu: Box, + pub ide: Box, + pub data: Box, +} + +impl Components { + pub fn to_array(&mut self) -> [&mut Box; 3] { + [&mut self.menu, &mut self.ide, &mut self.data] + } +} + pub struct App { pub config: Config, - pub tick_rate: f64, - pub frame_rate: f64, - pub components: Vec>, + pub tick_rate: Option, + pub frame_rate: Option, + pub components: Components, pub should_quit: bool, pub should_suspend: bool, pub mode: Mode, pub last_tick_key_events: Vec, + pub state: Arc, } impl App { - pub fn new(tick_rate: f64, frame_rate: f64) -> Result { - let home = Home::new(); - let fps = FpsCounter::default(); + pub fn new(connection_string: String, tick_rate: Option, frame_rate: Option) -> Result { + let state = Arc::new(AppState { connection_string }); + let menu = Menu::new(Arc::clone(&state)); + let ide = IDE::new(Arc::clone(&state)); + let data = Data::new(Arc::clone(&state)); let config = Config::new()?; let mode = Mode::Home; Ok(Self { + state: Arc::clone(&state), tick_rate, frame_rate, - components: vec![Box::new(home), Box::new(fps)], + components: Components { menu: Box::new(menu), ide: Box::new(ide), data: Box::new(data) }, should_quit: false, should_suspend: false, config, @@ -48,15 +74,15 @@ impl App { // tui.mouse(true); tui.enter()?; - for component in self.components.iter_mut() { + for component in self.components.to_array().iter_mut() { component.register_action_handler(action_tx.clone())?; } - for component in self.components.iter_mut() { + for component in self.components.to_array().iter_mut() { component.register_config_handler(self.config.clone())?; } - for component in self.components.iter_mut() { + for component in self.components.to_array().iter_mut() { component.init(tui.size()?)?; } @@ -87,7 +113,7 @@ impl App { }, _ => {}, } - for component in self.components.iter_mut() { + for component in self.components.to_array().iter_mut() { if let Some(action) = component.handle_events(Some(e.clone()))? { action_tx.send(action)?; } @@ -108,7 +134,7 @@ impl App { Action::Resize(w, h) => { tui.resize(Rect::new(0, 0, w, h))?; tui.draw(|f| { - for component in self.components.iter_mut() { + for component in self.components.to_array().iter_mut() { let r = component.draw(f, f.size()); if let Err(e) = r { action_tx.send(Action::Error(format!("Failed to draw: {:?}", e))).unwrap(); @@ -118,17 +144,23 @@ impl App { }, Action::Render => { tui.draw(|f| { - for component in self.components.iter_mut() { - let r = component.draw(f, f.size()); - if let Err(e) = r { - action_tx.send(Action::Error(format!("Failed to draw: {:?}", e))).unwrap(); - } - } + let root_layout = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(25), Constraint::Percentage(75)]) + .split(f.size()); + let right_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(root_layout[1]); + + self.components.menu.draw(f, root_layout[0]).unwrap(); + self.components.ide.draw(f, right_layout[0]).unwrap(); + self.components.data.draw(f, right_layout[1]).unwrap(); })?; }, _ => {}, } - for component in self.components.iter_mut() { + for component in self.components.to_array().iter_mut() { if let Some(action) = component.update(action.clone())? { action_tx.send(action)? }; diff --git a/src/cli.rs b/src/cli.rs index e1cc94e..9d51774 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -7,15 +7,17 @@ use crate::utils::version; #[derive(Parser, Debug)] #[command(author, version = version(), about)] pub struct Cli { - #[arg(short, long, value_name = "FLOAT", help = "Tick rate, i.e. number of ticks per second", default_value_t = 1.0)] - pub tick_rate: f64, + #[arg(short, long, value_name = "FLOAT", help = "Tick rate, i.e. number of ticks per second")] + pub tick_rate: Option, + + #[arg(short, long, value_name = "FLOAT", help = "Frame rate, i.e. number of frames per second")] + pub frame_rate: Option, #[arg( - short, - long, - value_name = "FLOAT", - help = "Frame rate, i.e. number of frames per second", - default_value_t = 4.0 + short = 'u', + long = "url", + value_name = "URL", + help = "Connection URL for the database, e.g. postgres://username::password@localhost:5432/dbname" )] - pub frame_rate: f64, + pub connection_url: String, } diff --git a/src/components.rs b/src/components.rs index 861df56..5cb7f67 100644 --- a/src/components.rs +++ b/src/components.rs @@ -9,8 +9,9 @@ use crate::{ tui::{Event, Frame}, }; -pub mod fps; -pub mod home; +pub mod data; +pub mod ide; +pub mod menu; /// `Component` is a trait that represents a visual and interactive element of the user interface. /// Implementors of this trait can be registered with the main application loop and will be able to receive events, diff --git a/src/components/data.rs b/src/components/data.rs new file mode 100644 index 0000000..399c533 --- /dev/null +++ b/src/components/data.rs @@ -0,0 +1,53 @@ +use std::{collections::HashMap, sync::Arc, time::Duration}; + +use color_eyre::eyre::Result; +use crossterm::event::{KeyCode, KeyEvent}; +use ratatui::{prelude::*, widgets::*}; +use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc::UnboundedSender; + +use super::{Component, Frame}; +use crate::{ + action::Action, + app::{App, AppState}, + config::{Config, KeyBindings}, +}; + +pub struct Data { + command_tx: Option>, + config: Config, + state: Arc, +} + +impl Data { + pub fn new(state: Arc) -> Self { + Data { command_tx: None, config: Config::default(), state } + } +} + +impl Component for Data { + fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { + self.command_tx = Some(tx); + Ok(()) + } + + fn register_config_handler(&mut self, config: Config) -> Result<()> { + self.config = config; + Ok(()) + } + + fn update(&mut self, action: Action) -> Result> { + match action { + Action::Tick => {}, + _ => {}, + } + + Ok(None) + } + + fn draw(&mut self, f: &mut Frame<'_>, area: Rect) -> Result<()> { + let state = self.state.clone(); + f.render_widget(Block::default().title("bottom").borders(Borders::ALL), area); + Ok(()) + } +} diff --git a/src/components/home.rs b/src/components/ide.rs similarity index 66% rename from src/components/home.rs rename to src/components/ide.rs index a1e9cc6..bdabf0b 100644 --- a/src/components/home.rs +++ b/src/components/ide.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, time::Duration}; +use std::{collections::HashMap, sync::Arc, time::Duration}; use color_eyre::eyre::Result; use crossterm::event::{KeyCode, KeyEvent}; @@ -9,22 +9,23 @@ use tokio::sync::mpsc::UnboundedSender; use super::{Component, Frame}; use crate::{ action::Action, + app::{App, AppState}, config::{Config, KeyBindings}, }; -#[derive(Default)] -pub struct Home { +pub struct IDE { command_tx: Option>, config: Config, + state: Arc, } -impl Home { - pub fn new() -> Self { - Self::default() +impl IDE { + pub fn new(state: Arc) -> Self { + IDE { command_tx: None, config: Config::default(), state } } } -impl Component for Home { +impl Component for IDE { fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { self.command_tx = Some(tx); Ok(()) @@ -37,16 +38,16 @@ impl Component for Home { fn update(&mut self, action: Action) -> Result> { match action { - Action::Tick => { - }, + Action::Tick => {}, _ => {}, } + Ok(None) } fn draw(&mut self, f: &mut Frame<'_>, area: Rect) -> Result<()> { - f.render_widget(Paragraph::new("hello world"), area); + let state = self.state.clone(); + f.render_widget(Block::default().title("top").borders(Borders::ALL), area); Ok(()) } } - diff --git a/src/components/menu.rs b/src/components/menu.rs new file mode 100644 index 0000000..c8efa2a --- /dev/null +++ b/src/components/menu.rs @@ -0,0 +1,53 @@ +use std::{collections::HashMap, sync::Arc, time::Duration}; + +use color_eyre::eyre::Result; +use crossterm::event::{KeyCode, KeyEvent}; +use ratatui::{prelude::*, widgets::*}; +use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc::UnboundedSender; + +use super::{Component, Frame}; +use crate::{ + action::Action, + app::{App, AppState}, + config::{Config, KeyBindings}, +}; + +pub struct Menu { + command_tx: Option>, + config: Config, + state: Arc, +} + +impl Menu { + pub fn new(state: Arc) -> Self { + Menu { command_tx: None, config: Config::default(), state } + } +} + +impl Component for Menu { + fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { + self.command_tx = Some(tx); + Ok(()) + } + + fn register_config_handler(&mut self, config: Config) -> Result<()> { + self.config = config; + Ok(()) + } + + fn update(&mut self, action: Action) -> Result> { + match action { + Action::Tick => {}, + _ => {}, + } + Ok(None) + } + + fn draw(&mut self, f: &mut Frame<'_>, area: Rect) -> Result<()> { + let state = self.state.clone(); + + f.render_widget(Block::default().title(state.connection_string.to_string()).borders(Borders::ALL), area); + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index cd3303b..fcfe0c6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,7 @@ async fn tokio_main() -> Result<()> { initialize_panic_handler()?; let args = Cli::parse(); - let mut app = App::new(args.tick_rate, args.frame_rate)?; + let mut app = App::new(args.connection_url, args.tick_rate, args.frame_rate)?; app.run().await?; Ok(()) diff --git a/src/tui.rs b/src/tui.rs index 6a0589b..5c17bb6 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -68,13 +68,17 @@ impl Tui { Ok(Self { terminal, task, cancellation_token, event_rx, event_tx, frame_rate, tick_rate, mouse, paste }) } - pub fn tick_rate(mut self, tick_rate: f64) -> Self { - self.tick_rate = tick_rate; + pub fn tick_rate(mut self, tick_rate: Option) -> Self { + if tick_rate.is_some() { + self.tick_rate = tick_rate.unwrap() + }; self } - pub fn frame_rate(mut self, frame_rate: f64) -> Self { - self.frame_rate = frame_rate; + pub fn frame_rate(mut self, frame_rate: Option) -> Self { + if frame_rate.is_some() { + self.frame_rate = frame_rate.unwrap(); + } self }