better more fun things

This commit is contained in:
nora 2021-12-17 18:10:24 +01:00
parent 4f5b34a154
commit 48304dce33
4 changed files with 104 additions and 75 deletions

View file

@ -1,4 +1,4 @@
use crate::model::{AppState, AppStateTable}; use crate::model::{AppState, AppStateFullView};
use crate::{view, App}; use crate::{view, App};
use crossterm::event; use crossterm::event;
use crossterm::event::{Event, KeyCode}; use crossterm::event::{Event, KeyCode};
@ -9,14 +9,18 @@ use tui::Terminal;
pub fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<()> { pub fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<()> {
loop { loop {
terminal.draw(|f| view::ui(f, &mut app))?; terminal.draw(|f| view::render_ui(f, &mut app))?;
if let Event::Key(key) = event::read()? { if let Event::Key(key) = event::read()? {
match key.code { match key.code {
KeyCode::Char('q') => return Ok(()), KeyCode::Char('q') => match app.selected {
Some(_) => app.leave_service(),
None => return Ok(()),
},
KeyCode::Down => app.next(), KeyCode::Down => app.next(),
KeyCode::Up => app.previous(), KeyCode::Up => app.previous(),
KeyCode::Enter => app.select(), KeyCode::Enter => app.select_service(),
KeyCode::Esc => app.leave_service(),
_ => {} _ => {}
} }
} }
@ -26,47 +30,61 @@ pub fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Resu
impl App { impl App {
pub fn new() -> App { pub fn new() -> App {
App { App {
state: AppState::Table(AppStateTable { table: AppState {
table_state: TableState::default(), table_state: TableState::default(),
items: vec![ items: vec![
vec!["backend".to_string(), "running".to_string()], vec!["backend".to_string(), "running".to_string()],
vec!["frontend".to_string(), "exited (0)".to_string()], vec!["frontend".to_string(), "exited (0)".to_string()],
vec!["frontend".to_string(), "failed (1)".to_string()], vec!["database".to_string(), "failed (1)".to_string()],
], ],
}), },
} selected: None,
}
pub fn next(&mut self) {
if let AppState::Table(AppStateTable { table_state, items }) = &mut self.state {
let i = match table_state.selected() {
Some(i) => {
if i >= items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
table_state.select(Some(i));
} }
} }
pub fn previous(&mut self) { pub fn is_table(&self) -> bool {
if let AppState::Table(AppStateTable { table_state, items }) = &mut self.state { self.selected.is_none()
let i = match table_state.selected() { }
Some(i) => {
if i == 0 { fn select_service(&mut self) {
items.len() - 1 if self.is_table() {
} else { if let Some(selected) = self.table.table_state.selected() {
i - 1 self.selected = Some(AppStateFullView {
} name: self.table.items[selected][0].to_string(),
} });
None => 0, }
};
table_state.select(Some(i));
} }
} }
pub fn select(&mut self) {} fn leave_service(&mut self) {
self.selected = None;
}
fn next(&mut self) {
let i = match self.table.table_state.selected() {
Some(i) => {
if i >= self.table.items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.table.table_state.select(Some(i));
}
fn previous(&mut self) {
let i = match self.table.table_state.selected() {
Some(i) => {
if i == 0 {
self.table.items.len() - 1
} else {
i - 1
}
}
None => 0,
};
self.table.table_state.select(Some(i));
}
} }

View file

@ -2,13 +2,13 @@ mod controller;
mod model; mod model;
mod view; mod view;
use crossterm::event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}; use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
use crossterm::execute; use crossterm::execute;
use crossterm::terminal::{ use crossterm::terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
}; };
use std::{error::Error, io}; use std::{error::Error, io};
use tui::backend::{Backend, CrosstermBackend}; use tui::backend::CrosstermBackend;
use tui::Terminal; use tui::Terminal;
use crate::model::App; use crate::model::App;

View file

@ -2,24 +2,17 @@ use tui::widgets::TableState;
#[derive(Debug)] #[derive(Debug)]
pub struct App { pub struct App {
pub state: AppState pub table: AppState,
pub selected: Option<AppStateFullView>
} }
#[derive(Debug)] #[derive(Debug)]
pub enum AppState { pub struct AppState {
Table(AppStateTable),
FullView {
name: String,
}
}
#[derive(Debug)]
pub struct AppStateTable {
pub table_state: TableState, pub table_state: TableState,
pub items: Vec<Vec<String>>, pub items: Vec<Vec<String>>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct AppStateFullView { pub struct AppStateFullView {
name: String, pub name: String,
} }

View file

@ -1,35 +1,45 @@
use tui::backend::Backend; use tui::backend::Backend;
use tui::layout::{Constraint, Layout}; use tui::layout::{Constraint, Layout, Rect};
use tui::style::{Color, Modifier, Style}; use tui::style::{Color, Modifier, Style};
use tui::widgets::{Block, Borders, Cell, Row, Table}; use tui::text::Spans;
use tui::widgets::{Block, Borders, Cell, Paragraph, Row, Table};
use tui::Frame; use tui::Frame;
use crate::model::{AppState, AppStateTable}; use crate::model::{AppState, AppStateFullView};
use crate::App; use crate::App;
pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) { pub fn render_ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
match &mut app.state { let chunks = if f.size().height < 22 {
AppState::Table(state) => { Layout::default()
table_ui(f, state); .constraints(vec![Constraint::Percentage(100)])
.split(f.size())
} else {
Layout::default()
.constraints(vec![Constraint::Percentage(90), Constraint::Max(3)])
.split(f.size())
};
match app.selected {
None => {
render_table(f, &mut app.table, chunks[0]);
} }
AppState::FullView { name } => f.render_widget( Some(AppStateFullView { ref name }) => f.render_widget(
Block::default().borders(Borders::ALL).title(name.as_ref()), Block::default().borders(Borders::ALL).title(name.as_ref()),
f.size(), chunks[0],
), ),
} }
if let Some(footer_chunk) = chunks.get(1) {
render_help_footer(f, app, *footer_chunk);
}
} }
pub fn table_ui<B: Backend>(f: &mut Frame<B>, state: &mut AppStateTable) { fn render_table<B: Backend>(f: &mut Frame<B>, state: &mut AppState, area: Rect) {
let rects = Layout::default()
.constraints(vec![Constraint::Percentage(100)])
.margin(5)
.split(f.size());
let selected_style = Style::default().add_modifier(Modifier::REVERSED); let selected_style = Style::default().add_modifier(Modifier::REVERSED);
let normal_style = Style::default().bg(Color::Blue); let normal_style = Style::default().bg(Color::Blue);
let header_cells = ["Header1", "Header2", "Header3"] let header_cells = ["name", "status"]
.iter() .iter()
.map(|h| Cell::from(*h).style(Style::default().fg(Color::Red))); .map(|h| Cell::from(*h).style(Style::default()));
let header = Row::new(header_cells) let header = Row::new(header_cells)
.style(normal_style) .style(normal_style)
@ -49,14 +59,22 @@ pub fn table_ui<B: Backend>(f: &mut Frame<B>, state: &mut AppStateTable) {
let t = Table::new(rows) let t = Table::new(rows)
.header(header) .header(header)
.block(Block::default().borders(Borders::ALL).title("Table")) .block(Block::default().borders(Borders::ALL).title("services"))
.highlight_style(selected_style) .highlight_style(selected_style)
.highlight_symbol(">> ") .widths(&[Constraint::Percentage(50), Constraint::Length(30)]);
.widths(&[
Constraint::Percentage(50),
Constraint::Length(30),
Constraint::Min(10),
]);
f.render_stateful_widget(t, rects[0], &mut state.table_state); f.render_stateful_widget(t, area, &mut state.table_state);
}
pub fn render_help_footer<B: Backend>(f: &mut Frame<B>, app: &App, area: Rect) {
let block = Block::default().title("help").borders(Borders::ALL);
let paragraph = Paragraph::new(if app.is_table() {
vec![Spans::from("q-quit down-down up-up enter-select")]
} else {
vec![Spans::from("q-back esc-back")]
})
.block(block);
f.render_widget(paragraph, area);
} }