Add initial UI sections from previous build
This commit is contained in:
parent
4ef4096930
commit
b999c1ee90
4 changed files with 96 additions and 16 deletions
84
src/app.rs
84
src/app.rs
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue