Add remaining UI section templates
Actual logic needs re-added
This commit is contained in:
parent
b999c1ee90
commit
7458699859
6 changed files with 281 additions and 4 deletions
18
src/app.rs
18
src/app.rs
|
|
@ -12,7 +12,10 @@ use tracing::{debug, info};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::Action,
|
action::Action,
|
||||||
components::{fps::FpsCounter, title::Title, Component},
|
components::{
|
||||||
|
footer::Footer, fps::FpsCounter, left::Left, popup::Popup, right::Right, title::Title,
|
||||||
|
Component,
|
||||||
|
},
|
||||||
config::Config,
|
config::Config,
|
||||||
tui::{Event, Tui},
|
tui::{Event, Tui},
|
||||||
};
|
};
|
||||||
|
|
@ -42,7 +45,14 @@ impl App {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
tick_rate,
|
tick_rate,
|
||||||
frame_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_quit: false,
|
||||||
should_suspend: false,
|
should_suspend: false,
|
||||||
config: Config::new()?,
|
config: Config::new()?,
|
||||||
|
|
@ -169,11 +179,11 @@ impl App {
|
||||||
|
|
||||||
fn render(&mut self, tui: &mut Tui) -> Result<()> {
|
fn render(&mut self, tui: &mut Tui) -> Result<()> {
|
||||||
tui.draw(|frame| {
|
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![
|
let component_areas = vec![
|
||||||
header, // Title Bar
|
header, // Title Bar
|
||||||
header, // FPS Counter
|
header, // FPS Counter
|
||||||
// left, right, footer, popup,
|
left, right, footer, popup,
|
||||||
];
|
];
|
||||||
for (component, area) in zip(self.components.iter_mut(), component_areas) {
|
for (component, area) in zip(self.components.iter_mut(), component_areas) {
|
||||||
if let Err(err) = component.draw(frame, area) {
|
if let Err(err) = component.draw(frame, area) {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,11 @@ use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
use crate::{action::Action, config::Config, tui::Event};
|
use crate::{action::Action, config::Config, tui::Event};
|
||||||
|
|
||||||
|
pub mod footer;
|
||||||
pub mod fps;
|
pub mod fps;
|
||||||
|
pub mod left;
|
||||||
|
pub mod popup;
|
||||||
|
pub mod right;
|
||||||
pub mod title;
|
pub mod title;
|
||||||
|
|
||||||
/// `Component` is a trait that represents a visual and interactive element of the user interface.
|
/// `Component` is a trait that represents a visual and interactive element of the user interface.
|
||||||
|
|
|
||||||
59
src/components/footer.rs
Normal file
59
src/components/footer.rs
Normal 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
74
src/components/left.rs
Normal 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
56
src/components/popup.rs
Normal 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
74
src/components/right.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue