Basic TUI and other bits
This commit is contained in:
parent
0280ea3b85
commit
2f489d5466
4 changed files with 2160 additions and 2 deletions
1961
Cargo.lock
generated
1961
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -4,3 +4,10 @@ version = "0.1.0"
|
|||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
color-eyre = "0.6.5"
|
||||
crossterm = "0.29.0"
|
||||
directories = "6.0.0"
|
||||
rand = { version = "0.10.2", features = ["serde"] }
|
||||
ratatui = "0.30.2"
|
||||
serde = "1.0.229"
|
||||
serde_json = "1.0.151"
|
||||
|
|
|
|||
147
src/main.rs
147
src/main.rs
|
|
@ -1,3 +1,146 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
pub mod termi;
|
||||
|
||||
use std::{io, path::Path};
|
||||
|
||||
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
|
||||
use directories::BaseDirs;
|
||||
use ratatui::{
|
||||
DefaultTerminal, Frame,
|
||||
layout::{Constraint, Direction, Layout, Margin},
|
||||
style::Stylize,
|
||||
widgets::{Clear, Fill},
|
||||
};
|
||||
|
||||
use crate::termi::Termi;
|
||||
|
||||
fn main() -> color_eyre::Result<()> {
|
||||
color_eyre::install()?;
|
||||
let mut app = TermiApp::default();
|
||||
ratatui::run(|term| app.run(term))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub enum TermiAppState {
|
||||
#[default]
|
||||
Start,
|
||||
Exiting,
|
||||
}
|
||||
|
||||
impl TermiAppState {
|
||||
pub fn is_exiting(&self) -> bool {
|
||||
match self {
|
||||
Self::Exiting => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
!self.is_exiting()
|
||||
}
|
||||
|
||||
pub fn is_starting(&self) -> bool {
|
||||
match self {
|
||||
Self::Start => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TermiApp {
|
||||
termi: Option<Termi>,
|
||||
state: TermiAppState,
|
||||
}
|
||||
|
||||
impl TermiApp {
|
||||
pub fn try_load(&mut self) {
|
||||
// currently use PWD
|
||||
// ./termi.json
|
||||
let garden_path = Path::new("./garden.json");
|
||||
if garden_path.exists() {
|
||||
if garden_path.is_file() {
|
||||
todo!()
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
// create new termi
|
||||
self.termi = {
|
||||
let mut _new_termi = Termi::default();
|
||||
_new_termi.name = "john termi".to_string();
|
||||
Some(_new_termi)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn run(&mut self, terminal: &mut DefaultTerminal) -> std::io::Result<()> {
|
||||
if self.state.is_starting() {
|
||||
self.try_load();
|
||||
}
|
||||
|
||||
while self.state.is_running() {
|
||||
terminal.draw(|frame| self.draw(frame))?;
|
||||
self.handle_events()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw(&self, frame: &mut Frame) {
|
||||
// background: the void
|
||||
frame.render_widget(Fill::new("·"), frame.area());
|
||||
|
||||
let cols = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(vec![
|
||||
Constraint::Fill(1),
|
||||
Constraint::Min(24),
|
||||
Constraint::Fill(1),
|
||||
])
|
||||
.split(frame.area());
|
||||
|
||||
let rows = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(vec![
|
||||
Constraint::Fill(1),
|
||||
Constraint::Min(14),
|
||||
Constraint::Fill(1),
|
||||
])
|
||||
.split(frame.area());
|
||||
|
||||
let centre_stage_area = rows[1].intersection(cols[1]);
|
||||
let [sprite_area, status_area] = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(vec![Constraint::Fill(1), Constraint::Length(1)])
|
||||
.areas(centre_stage_area.inner(Margin::new(2, 2)));
|
||||
|
||||
frame.render_widget(Fill::new(" "), centre_stage_area);
|
||||
|
||||
let termi: &Termi = self.termi.as_ref().unwrap();
|
||||
frame.render_widget(termi.get_sprite(), sprite_area);
|
||||
frame.render_widget(termi.get_status_line(), status_area);
|
||||
}
|
||||
|
||||
pub fn handle_events(&mut self) -> io::Result<()> {
|
||||
match event::read()? {
|
||||
// it's important to check that the event is a key press event as
|
||||
// crossterm also emits key release and repeat events on Windows.
|
||||
Event::Key(key_event) if key_event.kind == KeyEventKind::Press => {
|
||||
self.handle_key_event(key_event)
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_key_event(&mut self, key_event: KeyEvent) {
|
||||
match key_event.code {
|
||||
KeyCode::Char('q') => self.exit(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exit(&mut self) {
|
||||
self.state = TermiAppState::Exiting;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
47
src/termi.rs
Normal file
47
src/termi.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use ratatui::{
|
||||
style::{Color, Style, Stylize},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, BorderType, Borders, Widget},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct Termi {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl Termi {
|
||||
pub fn new(name: String) -> Termi {
|
||||
Termi { name }
|
||||
}
|
||||
|
||||
pub fn get_sprite(&self) -> TermiSprite {
|
||||
TermiSprite {
|
||||
name: self.name.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_status_line(&self) -> Line<'_> {
|
||||
Line::from(vec![
|
||||
Span::styled(self.name.clone(), Style::new().yellow()),
|
||||
Span::raw(" is "),
|
||||
Span::styled("happy", Style::new().green().bold()),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TermiSprite {
|
||||
// TODO should this be a reference to Termi/Termi's name instead?
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl Widget for TermiSprite {
|
||||
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) {
|
||||
// placeholder
|
||||
let placeholder_block = Block::<'_>::bordered()
|
||||
.border_type(BorderType::Rounded)
|
||||
.title(self.name);
|
||||
|
||||
placeholder_block.render(area, buf);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue