chore: Reorganize file tree for easier onboarding. Rust project now at the root for simple git clone && cargo run
This commit is contained in:
21
harmony_tui/src/widget/help.rs
Normal file
21
harmony_tui/src/widget/help.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use ratatui::widgets::{Paragraph, Widget, Wrap};
|
||||
|
||||
pub(crate) struct HelpWidget;
|
||||
impl HelpWidget {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for HelpWidget {
|
||||
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let text = Paragraph::new("Usage => q/Esc: Quit | j/↑ :Select UP | k/↓: Select Down | Enter: Launch Score\nPageUp/PageDown: Scroll Logs | Shift+G/End: Logs bottom")
|
||||
.centered()
|
||||
.wrap(Wrap { trim: false });
|
||||
|
||||
Widget::render(text, area, buf)
|
||||
}
|
||||
}
|
||||
2
harmony_tui/src/widget/mod.rs
Normal file
2
harmony_tui/src/widget/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod help;
|
||||
pub mod score;
|
||||
138
harmony_tui/src/widget/score.rs
Normal file
138
harmony_tui/src/widget/score.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use crossterm::event::{Event, KeyCode, KeyEventKind};
|
||||
use log::{info, warn};
|
||||
use ratatui::{
|
||||
Frame,
|
||||
layout::Rect,
|
||||
style::{Style, Stylize},
|
||||
widgets::{List, ListState, StatefulWidget, Widget},
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{HarmonyTuiEvent, ScoreItem};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ExecutionState {
|
||||
INITIATED,
|
||||
RUNNING,
|
||||
CANCELED,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Execution {
|
||||
state: ExecutionState,
|
||||
score: ScoreItem,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ScoreListWidget {
|
||||
list_state: Arc<RwLock<ListState>>,
|
||||
scores: Vec<ScoreItem>,
|
||||
execution: Option<Execution>,
|
||||
execution_history: Vec<Execution>,
|
||||
sender: mpsc::Sender<HarmonyTuiEvent>,
|
||||
}
|
||||
|
||||
impl ScoreListWidget {
|
||||
pub(crate) fn new(scores: Vec<ScoreItem>, sender: mpsc::Sender<HarmonyTuiEvent>) -> Self {
|
||||
let mut list_state = ListState::default();
|
||||
list_state.select_first();
|
||||
let list_state = Arc::new(RwLock::new(list_state));
|
||||
Self {
|
||||
scores,
|
||||
list_state,
|
||||
execution: None,
|
||||
execution_history: vec![],
|
||||
sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn launch_execution(&mut self) {
|
||||
let list_read = self.list_state.read().unwrap();
|
||||
if let Some(index) = list_read.selected() {
|
||||
let score = self
|
||||
.scores
|
||||
.get(index)
|
||||
.expect("List state should always match with internal Vec");
|
||||
|
||||
self.execution = Some(Execution {
|
||||
state: ExecutionState::INITIATED,
|
||||
score: score.clone(),
|
||||
});
|
||||
info!("{:#?}\n\nConfirm Execution (Press y/n)", score.0);
|
||||
} else {
|
||||
warn!("No Score selected, nothing to launch");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn scroll_down(&self) {
|
||||
self.list_state.write().unwrap().scroll_down_by(1);
|
||||
}
|
||||
|
||||
pub(crate) fn scroll_up(&self) {
|
||||
self.list_state.write().unwrap().scroll_up_by(1);
|
||||
}
|
||||
|
||||
pub(crate) fn render(&self, area: Rect, frame: &mut Frame) {
|
||||
frame.render_widget(self, area);
|
||||
}
|
||||
|
||||
fn clear_execution(&mut self) {
|
||||
match self.execution.take() {
|
||||
Some(execution) => {
|
||||
self.execution_history.push(execution);
|
||||
}
|
||||
None => warn!("Should never clear execution when no execution exists"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn confirm(&mut self, confirm: bool) {
|
||||
if let Some(execution) = &mut self.execution {
|
||||
match confirm {
|
||||
true => {
|
||||
execution.state = ExecutionState::RUNNING;
|
||||
info!("Launch execution {:?}", execution);
|
||||
self.sender
|
||||
.send(HarmonyTuiEvent::LaunchScore(execution.score.clone()))
|
||||
.await
|
||||
.expect("Should be able to send message");
|
||||
}
|
||||
false => {
|
||||
execution.state = ExecutionState::CANCELED;
|
||||
info!("Execution cancelled");
|
||||
self.clear_execution();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn handle_event(&mut self, event: &Event) {
|
||||
if let Event::Key(key) = event {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
match key.code {
|
||||
KeyCode::Char('j') | KeyCode::Down => self.scroll_down(),
|
||||
KeyCode::Char('k') | KeyCode::Up => self.scroll_up(),
|
||||
KeyCode::Enter => self.launch_execution(),
|
||||
KeyCode::Char('y') => self.confirm(true).await,
|
||||
KeyCode::Char('n') => self.confirm(false).await,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &ScoreListWidget {
|
||||
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut list_state = self.list_state.write().unwrap();
|
||||
let list = List::new(&self.scores)
|
||||
.highlight_style(Style::new().bold().italic())
|
||||
.highlight_symbol("🠊 ");
|
||||
|
||||
StatefulWidget::render(list, area, buf, &mut list_state)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user