harmony/harmony_tui/src/widget/score.rs
Ian Letourneau 440c1bce12
Some checks failed
Run Check Script / check (pull_request) Has been cancelled
Run Check Script / check (push) Has been cancelled
Compile and package harmony_composer / package_harmony_composer (push) Has been cancelled
chore: reformat & clippy cleanup (#96)
Clippy is now added to the `check` in the pipeline

Co-authored-by: Ian Letourneau <letourneau.ian@gmail.com>
Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/96
2025-08-06 15:57:14 +00:00

159 lines
4.8 KiB
Rust

use std::sync::{Arc, RwLock};
use crate::HarmonyTuiEvent;
use crossterm::event::{Event, KeyCode, KeyEventKind};
use harmony::{score::Score, topology::Topology};
use log::{info, warn};
use ratatui::{
Frame,
layout::Rect,
style::{Style, Stylize},
widgets::{List, ListItem, ListState, StatefulWidget, Widget},
};
use tokio::sync::mpsc;
#[derive(Debug)]
enum ExecutionState {
Initiated,
Running,
Canceled,
}
struct Execution<T: Topology> {
state: ExecutionState,
score: Box<dyn Score<T>>,
}
impl<T: Topology> std::fmt::Display for Execution<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"Execution of {} status {:?}",
self.score.name(),
self.state
))
}
}
pub(crate) struct ScoreListWidget<T: Topology> {
list_state: Arc<RwLock<ListState>>,
scores: Vec<Box<dyn Score<T>>>,
execution: Option<Execution<T>>,
execution_history: Vec<Execution<T>>,
sender: mpsc::Sender<HarmonyTuiEvent<T>>,
}
impl<T: Topology> ScoreListWidget<T> {
pub(crate) fn new(
scores: Vec<Box<dyn Score<T>>>,
sender: mpsc::Sender<HarmonyTuiEvent<T>>,
) -> 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) {
if let Some(score) = self.get_selected_score() {
self.execution = Some(Execution {
state: ExecutionState::Initiated,
score: score.clone_box(),
});
info!("{}\n\nConfirm Execution (Press y/n)", score.name());
info!("{}", score.print_score_details());
} else {
warn!("No Score selected, nothing to launch");
}
}
pub(crate) fn get_selected_score(&self) -> Option<Box<dyn Score<T>>> {
let list_read = self.list_state.read().unwrap();
if let Some(index) = list_read.selected() {
self.scores.get(index).map(|s| s.clone_box())
} else {
None
}
}
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_box()))
.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<T: Topology> Widget for &ScoreListWidget<T> {
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 scores_items: Vec<ListItem<'_>> = self
.scores
.iter()
.map(|score| ListItem::new(score.name()))
.collect();
let list = List::new(scores_items)
.highlight_style(Style::new().bold().italic())
.highlight_symbol("🠊 ");
StatefulWidget::render(list, area, buf, &mut list_state)
}
}