Compare commits
9 Commits
abd69cf8cb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9afe1118db | |||
| 4b09a0fefb | |||
| 16ba6d8ad5 | |||
| 5c532197c8 | |||
| 5e649474d9 | |||
| 8e32cc7277 | |||
| 6314b16e57 | |||
| 1d1a4af630 | |||
| 31d23e078b |
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
Generated
+1841
File diff suppressed because it is too large
Load Diff
+12
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "klipper-tui"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ratatui = "0.30.0"
|
||||||
|
crossterm = "0.29.0"
|
||||||
|
clap = { version = "4.6.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0.149"
|
||||||
|
anyhow = "1.0.102"
|
||||||
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
+106
@@ -0,0 +1,106 @@
|
|||||||
|
|
||||||
|
pub struct TabsState<'a> {
|
||||||
|
pub titles: Vec<&'a str>,
|
||||||
|
pub index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TabsState<'a> {
|
||||||
|
pub const fn new(titles: Vec<&'a str>) -> Self {
|
||||||
|
Self { titles, index: 0 }
|
||||||
|
}
|
||||||
|
pub fn next(&mut self) {
|
||||||
|
self.index = (self.index + 1) % self.titles.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous(&mut self) {
|
||||||
|
if self.index > 0 {
|
||||||
|
self.index -= 1;
|
||||||
|
} else {
|
||||||
|
self.index = self.titles.len() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct Servers<'a> {
|
||||||
|
pub servers: Vec<Server<'a>>,
|
||||||
|
pub index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Servers<'a> {
|
||||||
|
pub const fn new( servers: Vec<Server<'a>>) -> Self {
|
||||||
|
Self { servers, index: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) {
|
||||||
|
self.index = (self.index + 1) % self.servers.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous(&mut self) {
|
||||||
|
if self.index > 0 {
|
||||||
|
self.index -= 1;
|
||||||
|
} else {
|
||||||
|
self.index = self.servers.len() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Server<'a> {
|
||||||
|
pub name: &'a str,
|
||||||
|
pub location: &'a str,
|
||||||
|
pub ip: &'a str,
|
||||||
|
pub connected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct App<'a> {
|
||||||
|
pub title: &'a str,
|
||||||
|
pub should_quit: bool,
|
||||||
|
pub tabs: TabsState<'a>,
|
||||||
|
pub servers: Vec<Server<'a>>,
|
||||||
|
pub tabkeystext: Vec<&'a str>,
|
||||||
|
pub enhanced_graphics: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> App<'a> {
|
||||||
|
pub fn new(title: &'a str, servers: Vec<Server<'a>>, enhanced_graphics: bool) -> Self {
|
||||||
|
App {
|
||||||
|
title,
|
||||||
|
should_quit: false,
|
||||||
|
tabs: TabsState::new(vec!["Dashboard","Machine info","Config"]),
|
||||||
|
servers,
|
||||||
|
tabkeystext: vec!["(H)otend (B)ed P(r)eset (P)rint (C)ancel (Q)uit","(Q)uit","(Q)uit"],
|
||||||
|
enhanced_graphics,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_up(&mut self) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_down(&mut self) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_right(&mut self) {
|
||||||
|
self.tabs.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_left(&mut self) {
|
||||||
|
self.tabs.previous();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_tab(&mut self) {
|
||||||
|
self.tabs.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_key(&mut self, c: char) {
|
||||||
|
match c {
|
||||||
|
'q' => {
|
||||||
|
self.should_quit = true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_tick(&mut self) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::io;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use crossterm::event::{self, DisableMouseCapture, EnableMouseCapture, KeyCode};
|
||||||
|
use crossterm::execute;
|
||||||
|
use crossterm::terminal::{
|
||||||
|
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||||
|
};
|
||||||
|
use ratatui::backend::{Backend, CrosstermBackend};
|
||||||
|
use ratatui::Terminal;
|
||||||
|
|
||||||
|
use crate::app::{App, Server};
|
||||||
|
use crate::ui;
|
||||||
|
|
||||||
|
pub fn run(tick_rate: Duration, enhanced_graphics: bool) -> Result<(), Box<dyn Error>> {
|
||||||
|
// setup terminal
|
||||||
|
enable_raw_mode()?;
|
||||||
|
let mut stdout = io::stdout();
|
||||||
|
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||||
|
let backend = CrosstermBackend::new(stdout);
|
||||||
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
|
// create app and run it
|
||||||
|
let app = App::new("Klipper-Tui", get_servers(), enhanced_graphics);
|
||||||
|
let app_result = run_app(&mut terminal, app, tick_rate);
|
||||||
|
|
||||||
|
// restore terminal
|
||||||
|
disable_raw_mode()?;
|
||||||
|
execute!(
|
||||||
|
terminal.backend_mut(),
|
||||||
|
LeaveAlternateScreen,
|
||||||
|
DisableMouseCapture
|
||||||
|
)?;
|
||||||
|
terminal.show_cursor()?;
|
||||||
|
|
||||||
|
if let Err(err) = app_result {
|
||||||
|
println!("{err:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_app<B: Backend>(
|
||||||
|
terminal: &mut Terminal<B>,
|
||||||
|
mut app: App,
|
||||||
|
tick_rate: Duration,
|
||||||
|
) -> Result<(), Box<dyn Error>>
|
||||||
|
where
|
||||||
|
B::Error: 'static,{
|
||||||
|
let mut last_tick = Instant::now();
|
||||||
|
loop {
|
||||||
|
terminal.draw(|frame| ui::render(frame, &mut app))?;
|
||||||
|
|
||||||
|
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
|
||||||
|
if !event::poll(timeout)? {
|
||||||
|
app.on_tick();
|
||||||
|
last_tick = Instant::now();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(key) = event::read()?.as_key_press_event() {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Char('h') | KeyCode::Left => app.on_left(),
|
||||||
|
KeyCode::Char('j') | KeyCode::Down => app.on_down(),
|
||||||
|
KeyCode::Char('k') | KeyCode::Up => app.on_up(),
|
||||||
|
KeyCode::Char('l') | KeyCode::Right => app.on_right(),
|
||||||
|
KeyCode::Tab => app.on_tab(),
|
||||||
|
KeyCode::Char(c) => app.on_key(c),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if app.should_quit {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_servers<'a>() -> Vec<Server<'a>> {
|
||||||
|
//todo: add printer ip addrs
|
||||||
|
let printers: Vec<Server<'a>> = vec![
|
||||||
|
crate::app::Server{
|
||||||
|
name: "Micron+",
|
||||||
|
location:"Office",
|
||||||
|
ip:"192.168.1.247",
|
||||||
|
connected:false,
|
||||||
|
},
|
||||||
|
crate::app::Server{
|
||||||
|
name: "LDO V0.2",
|
||||||
|
location: "Office",
|
||||||
|
ip: "192.168.1.217",
|
||||||
|
connected: false,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return crate::app::Servers::new(printers).servers;
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
use std::io;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::time::Duration;
|
||||||
|
use clap::Parser;
|
||||||
|
mod app;
|
||||||
|
mod ui;
|
||||||
|
mod crossterm;
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
struct Cli {
|
||||||
|
/// time in ms between two ticks.
|
||||||
|
#[arg(short, long, default_value_t = 200)]
|
||||||
|
tick_rate: u64,
|
||||||
|
|
||||||
|
/// whether unicode symbols are used to improve the overall look of the app
|
||||||
|
#[arg(short, long, default_value_t = true)]
|
||||||
|
unicode: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let tick_rate = Duration::from_millis(cli.tick_rate);
|
||||||
|
crate::crossterm::run(tick_rate, cli.unicode)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
use crossterm::style;
|
||||||
|
use ratatui::{
|
||||||
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
|
style::{Color, Style, Modifier},
|
||||||
|
text::{self, Line, Span, Text},
|
||||||
|
widgets::{
|
||||||
|
Block, Borders, Clear, List, ListItem,Tabs, Paragraph, Wrap,
|
||||||
|
canvas::{self, Canvas, Circle, Map, MapResolution, Rectangle},
|
||||||
|
|
||||||
|
},
|
||||||
|
symbols,
|
||||||
|
Frame,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
|
||||||
|
pub fn render(frame: &mut Frame, app: &mut App) {
|
||||||
|
let chunks = Layout::vertical([Constraint::Length(3), Constraint::Min(1), Constraint::Length(3)]).split(frame.area());
|
||||||
|
let tabs = app.tabs
|
||||||
|
.titles
|
||||||
|
.iter()
|
||||||
|
.map(|t| text::Line::from(Span::styled(*t, Style::default().fg(Color::Green))))
|
||||||
|
.collect::<Tabs>()
|
||||||
|
.block(Block::bordered().title(app.title))
|
||||||
|
.highlight_style(Style::default().fg(Color::Yellow))
|
||||||
|
.select(app.tabs.index);
|
||||||
|
frame.render_widget(tabs, chunks[0]);
|
||||||
|
match app.tabs.index {
|
||||||
|
0 => draw_dashboard_tab(frame, app, chunks[1]),
|
||||||
|
1 => draw_machine_tab(frame, app, chunks[1]),
|
||||||
|
2 => draw_conf_tab(frame, app, chunks[1]),
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
draw_statusbar(frame, app, chunks[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_dashboard_tab(frame: &mut Frame, app: &mut App, area: Rect) {
|
||||||
|
let chunks = Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]).split(area);
|
||||||
|
{
|
||||||
|
let chunks = Layout::horizontal([Constraint::Percentage(30), Constraint::Percentage(40), Constraint::Percentage(30)]).split(chunks[0]);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_machine_tab(frame: &mut Frame, app: &mut App, area: Rect) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_conf_tab(frame: &mut Frame, app: &mut App, area: Rect) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_statusbar(frame: &mut Frame, app: &mut App, area: Rect) {
|
||||||
|
let chunks = Layout::horizontal([Constraint::Length(25), Constraint::Min(0)]).split(area);
|
||||||
|
let status = Block::bordered().title(Span::styled("Status", Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD),));
|
||||||
|
let statustext = Paragraph::new("Todo...").block(status);
|
||||||
|
frame.render_widget(statustext, chunks[0]);
|
||||||
|
|
||||||
|
let keysblock = Block::bordered();
|
||||||
|
let keystext = Paragraph::new(app.tabkeystext[app.tabs.index]).block(keysblock);
|
||||||
|
frame.render_widget(keystext, chunks[1]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user