Add initial UI sections from previous build

This commit is contained in:
2Shirt 2024-10-29 23:46:23 -07:00
parent 4ef4096930
commit b999c1ee90
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C
4 changed files with 96 additions and 16 deletions

View file

@ -1,13 +1,18 @@
use std::iter::zip;
use color_eyre::Result; use color_eyre::Result;
use crossterm::event::KeyEvent; use crossterm::event::KeyEvent;
use ratatui::prelude::Rect; use ratatui::{
layout::{Constraint, Direction, Layout},
prelude::Rect,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::{debug, info}; use tracing::{debug, info};
use crate::{ use crate::{
action::Action, action::Action,
components::{fps::FpsCounter, home::Home, Component}, components::{fps::FpsCounter, title::Title, Component},
config::Config, config::Config,
tui::{Event, Tui}, tui::{Event, Tui},
}; };
@ -37,7 +42,7 @@ impl App {
Ok(Self { Ok(Self {
tick_rate, tick_rate,
frame_rate, frame_rate,
components: vec![Box::new(Home::new()), Box::new(FpsCounter::default())], components: vec![Box::new(Title::new()), Box::new(FpsCounter::default())],
should_quit: false, should_quit: false,
should_suspend: false, should_suspend: false,
config: Config::new()?, config: Config::new()?,
@ -164,14 +169,75 @@ impl App {
fn render(&mut self, tui: &mut Tui) -> Result<()> { fn render(&mut self, tui: &mut Tui) -> Result<()> {
tui.draw(|frame| { tui.draw(|frame| {
for component in self.components.iter_mut() { if let [header, _body, _footer, _left, _right, _popup] = get_chunks(frame.area())[..] {
if let Err(err) = component.draw(frame, frame.area()) { let component_areas = vec![
let _ = self header, // Title Bar
.action_tx header, // FPS Counter
.send(Action::Error(format!("Failed to draw: {:?}", err))); // left, right, footer, popup,
];
for (component, area) in zip(self.components.iter_mut(), component_areas) {
if let Err(err) = component.draw(frame, area) {
let _ = self
.action_tx
.send(Action::Error(format!("Failed to draw: {:?}", err)));
}
} }
} };
})?; })?;
Ok(()) Ok(())
} }
} }
fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
// Cut the given rectangle into three vertical pieces
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
])
.split(r);
// Then cut the middle vertical piece into three width-wise pieces
Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
])
.split(popup_layout[1])[1] // Return the middle chunk
}
fn get_chunks(r: Rect) -> Vec<Rect> {
let mut chunks: Vec<Rect> = Vec::with_capacity(6);
// Main sections
chunks.extend(
Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3),
Constraint::Min(1),
Constraint::Length(3),
])
.split(r)
.to_vec(),
);
// Left/Right
chunks.extend(
Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(centered_rect(90, 90, chunks[1]))
.to_vec(),
);
// Popup
chunks.push(centered_rect(60, 25, r));
// Done
chunks
}

View file

@ -9,7 +9,7 @@ use tokio::sync::mpsc::UnboundedSender;
use crate::{action::Action, config::Config, tui::Event}; use crate::{action::Action, config::Config, tui::Event};
pub mod fps; pub mod fps;
pub mod home; 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.
/// ///

View file

@ -78,14 +78,21 @@ impl Component for FpsCounter {
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [top, _] = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]).areas(area); let [column, _] =
Layout::horizontal([Constraint::Min(1), Constraint::Length(2)]).areas(area);
let [_, row, _] = Layout::vertical([
Constraint::Length(1),
Constraint::Length(1),
Constraint::Min(0),
])
.areas(column);
let message = format!( let message = format!(
"{:.2} ticks/sec, {:.2} FPS", "{:.2} ticks/sec, {:.2} FPS",
self.ticks_per_second, self.frames_per_second self.ticks_per_second, self.frames_per_second
); );
let span = Span::styled(message, Style::new().dim()); let span = Span::styled(message, Style::new().dim());
let paragraph = Paragraph::new(span).right_aligned(); let paragraph = Paragraph::new(span).right_aligned();
frame.render_widget(paragraph, top); frame.render_widget(paragraph, row);
Ok(()) Ok(())
} }
} }

View file

@ -6,18 +6,18 @@ use super::Component;
use crate::{action::Action, config::Config}; use crate::{action::Action, config::Config};
#[derive(Default)] #[derive(Default)]
pub struct Home { pub struct Title {
command_tx: Option<UnboundedSender<Action>>, command_tx: Option<UnboundedSender<Action>>,
config: Config, config: Config,
} }
impl Home { impl Title {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
} }
impl Component for Home { impl Component for Title {
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> { fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
self.command_tx = Some(tx); self.command_tx = Some(tx);
Ok(()) Ok(())
@ -42,7 +42,14 @@ impl Component for Home {
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
frame.render_widget(Paragraph::new("hello world"), area); // Title Block
let title_text = Span::styled(
"WizardKit: Clone Tool",
Style::default().fg(Color::LightCyan),
);
let title = Paragraph::new(Line::from(title_text).centered())
.block(Block::default().borders(Borders::ALL));
frame.render_widget(title, area);
Ok(()) Ok(())
} }
} }