use std::sync::{ Arc, RwLock}; use crossterm::event::{Event, KeyCode, KeyEventKind}; use log::{debug, error, info, trace, warn}; use ratatui::{ layout::{Constraint, Rect}, style::{Style, Stylize}, widgets::{Block, Clear, List, ListState, Paragraph, StatefulWidget, Widget}, Frame, }; use tokio::sync::mpsc; use crate::{HarmonyTuiEvent, ScoreItem}; use crate::ratatui_utils::center; #[derive(Debug)] enum ExecutionState { INITIATED, CONFIRMED, CANCELED, RUNNING, COMPLETED, } #[derive(Debug)] struct Execution { state: ExecutionState, score: ScoreItem, } #[derive(Debug)] pub(crate) struct ScoreListWidget { list_state: Arc>, scores: Vec, execution: Option, execution_history: Vec, sender: mpsc::Sender, } impl ScoreListWidget { pub(crate) fn new(scores: Vec, sender : mpsc::Sender) -> 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); self.render_execution(frame); } pub(crate) fn render_execution(&self, frame: &mut Frame) { if let None = self.execution { return; } let execution = self.execution.as_ref().unwrap(); } 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::CONFIRMED; info!("Launch execution {:?}", execution); warn!("Launch execution"); error!("Launch execution"); trace!("Launch execution"); debug!("Launch 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) } }