From b999c1ee908bbc492698c5581f11032e7faf0f33 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Tue, 29 Oct 2024 23:46:23 -0700 Subject: [PATCH] Add initial UI sections from previous build --- src/app.rs | 84 +++++++++++++++++++++++++--- src/components.rs | 2 +- src/components/fps.rs | 11 +++- src/components/{home.rs => title.rs} | 15 +++-- 4 files changed, 96 insertions(+), 16 deletions(-) rename src/components/{home.rs => title.rs} (73%) diff --git a/src/app.rs b/src/app.rs index 951ca7b..b0e2f47 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,13 +1,18 @@ +use std::iter::zip; + use color_eyre::Result; use crossterm::event::KeyEvent; -use ratatui::prelude::Rect; +use ratatui::{ + layout::{Constraint, Direction, Layout}, + prelude::Rect, +}; use serde::{Deserialize, Serialize}; use tokio::sync::mpsc; use tracing::{debug, info}; use crate::{ action::Action, - components::{fps::FpsCounter, home::Home, Component}, + components::{fps::FpsCounter, title::Title, Component}, config::Config, tui::{Event, Tui}, }; @@ -37,7 +42,7 @@ impl App { Ok(Self { tick_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_suspend: false, config: Config::new()?, @@ -164,14 +169,75 @@ impl App { fn render(&mut self, tui: &mut Tui) -> Result<()> { tui.draw(|frame| { - for component in self.components.iter_mut() { - if let Err(err) = component.draw(frame, frame.area()) { - let _ = self - .action_tx - .send(Action::Error(format!("Failed to draw: {:?}", err))); + 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, + ]; + 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(()) } } + +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 { + let mut chunks: Vec = 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 +} diff --git a/src/components.rs b/src/components.rs index 84c12c9..1852794 100644 --- a/src/components.rs +++ b/src/components.rs @@ -9,7 +9,7 @@ use tokio::sync::mpsc::UnboundedSender; use crate::{action::Action, config::Config, tui::Event}; pub mod fps; -pub mod home; +pub mod title; /// `Component` is a trait that represents a visual and interactive element of the user interface. /// diff --git a/src/components/fps.rs b/src/components/fps.rs index a79c4b4..3323dd2 100644 --- a/src/components/fps.rs +++ b/src/components/fps.rs @@ -78,14 +78,21 @@ impl Component for FpsCounter { } 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!( "{:.2} ticks/sec, {:.2} FPS", self.ticks_per_second, self.frames_per_second ); let span = Span::styled(message, Style::new().dim()); let paragraph = Paragraph::new(span).right_aligned(); - frame.render_widget(paragraph, top); + frame.render_widget(paragraph, row); Ok(()) } } diff --git a/src/components/home.rs b/src/components/title.rs similarity index 73% rename from src/components/home.rs rename to src/components/title.rs index f6033da..3f71568 100644 --- a/src/components/home.rs +++ b/src/components/title.rs @@ -6,18 +6,18 @@ use super::Component; use crate::{action::Action, config::Config}; #[derive(Default)] -pub struct Home { +pub struct Title { command_tx: Option>, config: Config, } -impl Home { +impl Title { pub fn new() -> Self { Self::default() } } -impl Component for Home { +impl Component for Title { fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { self.command_tx = Some(tx); Ok(()) @@ -42,7 +42,14 @@ impl Component for Home { } 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(()) } }