Add remaining UI section templates

Actual logic needs re-added
This commit is contained in:
2Shirt 2024-10-30 00:54:44 -07:00
parent b999c1ee90
commit 7458699859
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C
6 changed files with 281 additions and 4 deletions

View file

@ -12,7 +12,10 @@ use tracing::{debug, info};
use crate::{
action::Action,
components::{fps::FpsCounter, title::Title, Component},
components::{
footer::Footer, fps::FpsCounter, left::Left, popup::Popup, right::Right, title::Title,
Component,
},
config::Config,
tui::{Event, Tui},
};
@ -42,7 +45,14 @@ impl App {
Ok(Self {
tick_rate,
frame_rate,
components: vec![Box::new(Title::new()), Box::new(FpsCounter::default())],
components: vec![
Box::new(Title::new()),
Box::new(FpsCounter::default()),
Box::new(Left::new()),
Box::new(Right::new()),
Box::new(Footer::new()),
Box::new(Popup::new()),
],
should_quit: false,
should_suspend: false,
config: Config::new()?,
@ -169,11 +179,11 @@ impl App {
fn render(&mut self, tui: &mut Tui) -> Result<()> {
tui.draw(|frame| {
if let [header, _body, _footer, _left, _right, _popup] = get_chunks(frame.area())[..] {
if let [header, _body, footer, left, right, popup] = get_chunks(frame.area())[..] {
let component_areas = vec![
header, // Title Bar
header, // FPS Counter
// left, right, footer, popup,
left, right, footer, popup,
];
for (component, area) in zip(self.components.iter_mut(), component_areas) {
if let Err(err) = component.draw(frame, area) {

View file

@ -8,7 +8,11 @@ use tokio::sync::mpsc::UnboundedSender;
use crate::{action::Action, config::Config, tui::Event};
pub mod footer;
pub mod fps;
pub mod left;
pub mod popup;
pub mod right;
pub mod title;
/// `Component` is a trait that represents a visual and interactive element of the user interface.

59
src/components/footer.rs Normal file
View file

@ -0,0 +1,59 @@
use color_eyre::Result;
use ratatui::{prelude::*, widgets::*};
use tokio::sync::mpsc::UnboundedSender;
use super::Component;
use crate::{action::Action, config::Config};
#[derive(Default)]
pub struct Footer {
command_tx: Option<UnboundedSender<Action>>,
config: Config,
show_install_driver: bool,
}
impl Footer {
pub fn new() -> Self {
Self::default()
}
}
impl Component for Footer {
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> 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<Option<Action>> {
match action {
Action::Tick => {
// add any logic here that should run on every tick
}
Action::Render => {
// add any logic here that should run on every render
}
_ => {}
}
Ok(None)
}
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let footer_text = Span::styled(
if self.show_install_driver {
"(Enter) to select / (b) to go back / (i) to install driver / (q) to quit"
} else {
"(Enter) to select / (b) to go back / (q) to quit"
},
Style::default().fg(Color::DarkGray),
);
let footer = Paragraph::new(Line::from(footer_text).centered())
.block(Block::default().borders(Borders::ALL));
frame.render_widget(footer, area);
Ok(())
}
}

74
src/components/left.rs Normal file
View file

@ -0,0 +1,74 @@
use color_eyre::Result;
use crossterm::event::KeyEvent;
use ratatui::{prelude::*, widgets::*};
use tokio::sync::mpsc::UnboundedSender;
use super::Component;
use crate::{action::Action, config::Config};
#[derive(Default)]
pub struct Left {
command_tx: Option<UnboundedSender<Action>>,
config: Config,
}
impl Left {
pub fn new() -> Self {
Self::default()
}
}
impl Component for Left {
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
// TODO
let _ = key; // to appease clippy
Ok(None)
}
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> 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<Option<Action>> {
match action {
Action::Tick => {
// add any logic here that should run on every tick
}
Action::Render => {
// add any logic here that should run on every render
}
_ => {}
}
Ok(None)
}
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [title_area, body_area] = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(1), Constraint::Min(1)])
.areas(area);
// Title
let title_text = String::from("Hello world!");
let title = Paragraph::new(Line::from(title_text).centered())
.block(Block::default().borders(Borders::NONE));
frame.render_widget(title, title_area);
// Body
let paragraph = Paragraph::new("Body text...").block(
Block::default()
.borders(Borders::ALL)
.padding(Padding::new(1, 1, 1, 1)),
);
frame.render_widget(paragraph, body_area);
// Done
Ok(())
}
}

56
src/components/popup.rs Normal file
View file

@ -0,0 +1,56 @@
use color_eyre::Result;
use ratatui::{prelude::*, widgets::*};
use tokio::sync::mpsc::UnboundedSender;
use super::Component;
use crate::{action::Action, config::Config};
#[derive(Default)]
pub struct Popup {
command_tx: Option<UnboundedSender<Action>>,
config: Config,
}
impl Popup {
pub fn new() -> Self {
Self::default()
}
}
impl Component for Popup {
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> 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<Option<Action>> {
match action {
Action::Tick => {
// add any logic here that should run on every tick
}
Action::Render => {
// add any logic here that should run on every render
}
_ => {}
}
Ok(None)
}
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let popup_block = Block::default()
.borders(Borders::ALL)
.style(Style::default().cyan().bold());
let popup = Paragraph::new(vec![Line::from(Span::raw("Popup text..."))])
.block(popup_block)
.centered()
.wrap(Wrap { trim: false });
frame.render_widget(Clear, area);
frame.render_widget(popup, area);
Ok(())
}
}

74
src/components/right.rs Normal file
View file

@ -0,0 +1,74 @@
use color_eyre::Result;
use crossterm::event::KeyEvent;
use ratatui::{prelude::*, widgets::*};
use tokio::sync::mpsc::UnboundedSender;
use super::Component;
use crate::{action::Action, config::Config};
#[derive(Default)]
pub struct Right {
command_tx: Option<UnboundedSender<Action>>,
config: Config,
}
impl Right {
pub fn new() -> Self {
Self::default()
}
}
impl Component for Right {
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
// TODO ??
let _ = key; // to appease clippy
Ok(None)
}
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> 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<Option<Action>> {
match action {
Action::Tick => {
// add any logic here that should run on every tick
}
Action::Render => {
// add any logic here that should run on every render
}
_ => {}
}
Ok(None)
}
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [title_area, body_area] = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(1), Constraint::Min(1)])
.areas(area);
// Title
let title_text = String::from("Info?");
let title = Paragraph::new(Line::from(title_text).centered())
.block(Block::default().borders(Borders::NONE));
frame.render_widget(title, title_area);
// Body
let paragraph = Paragraph::new("Some info...").block(
Block::default()
.borders(Borders::ALL)
.padding(Padding::new(1, 1, 1, 1)),
);
frame.render_widget(paragraph, body_area);
// Done
Ok(())
}
}