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"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[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() {
|
pub mod termi;
|
||||||
println!("Hello, world!");
|
|
||||||
|
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