use std::collections::HashMap; use flume::Sender; use ratatui::{ layout::Rect, style::{Modifier, Style}, text::Span, widgets::{Block, BorderType, Borders, List, ListItem, ListState}, Frame, }; use crabidy_core::proto::crabidy::LibraryNode; use super::{ MessageFromUi, StatefulList, UiItem, UiItemKind, COLOR_GREEN, COLOR_PRIMARY, COLOR_PRIMARY_DARK, }; pub struct Library { title: String, path: String, list: Vec, list_state: ListState, parent: Option, positions: HashMap, tx: Sender, } impl Library { pub fn new(tx: Sender) -> Self { Self { title: "Library".to_string(), path: crabidy_core::ROOT_PATH.to_string(), list: Vec::new(), list_state: ListState::default(), positions: HashMap::new(), parent: None, tx, } } pub fn get_selected(&self) -> Option> { if self.list.iter().any(|i| i.marked) { return Some( self.list .iter() .filter(|i| i.marked) .map(|i| i.path.to_string()) .collect(), ); } if let Some(idx) = self.list_state.selected() { return Some(vec![self.list[idx].path.to_string()]); } None } pub fn ascend(&mut self) { if let Some(parent) = self.parent.as_ref() { let _ = self.tx.send(MessageFromUi::GetLibraryNode(parent.clone())); } } pub fn dive(&mut self) { if let Some(idx) = self.list_state.selected() { let item = &self.list[idx]; if let UiItemKind::Node = item.kind { let _ = self .tx .send(MessageFromUi::GetLibraryNode(item.path.clone())); } } } pub fn queue_append(&mut self) { if let Some(items) = self.get_selected() { match self.tx.send(MessageFromUi::AppendTracks(items)) { Ok(_) => self.remove_marks(), Err(_) => { /* FIXME: warn */ } } } } pub fn queue_queue(&mut self) { if let Some(items) = self.get_selected() { match self.tx.send(MessageFromUi::QueueTracks(items)) { Ok(_) => self.remove_marks(), Err(_) => { /* FIXME: warn */ } } } } pub fn queue_replace(&mut self) { if let Some(items) = self.get_selected() { match self.tx.send(MessageFromUi::ReplaceQueue(items)) { Ok(_) => self.remove_marks(), Err(_) => { /* FIXME: warn */ } } } } pub fn queue_insert(&mut self, pos: usize) { if let Some(items) = self.get_selected() { match self.tx.send(MessageFromUi::InsertTracks(items, pos)) { Ok(_) => self.remove_marks(), Err(_) => { /* FIXME: warn */ } } } } pub fn prev_selected(&self) -> usize { *self.positions.get(&self.path).unwrap_or(&0) } pub fn toggle_mark(&mut self) { if let Some(idx) = self.list_state.selected() { let item = &mut self.list[idx]; if !item.is_queable { return; } item.marked = !item.marked; } } pub fn remove_marks(&mut self) { if self.list.iter().any(|i| i.marked) { self.list .iter_mut() .filter(|i| i.marked) .for_each(|i| i.marked = false); } } pub fn update(&mut self, node: LibraryNode) { if node.tracks.is_empty() && node.children.is_empty() { return; } // if children empty and tracks empty return self.path = node.path; self.title = node.title; self.parent = node.parent; self.select(Some(self.prev_selected())); if !node.tracks.is_empty() { self.list = node .tracks .iter() .map(|t| UiItem { path: t.path.clone(), title: format!("{} - {}", t.artist, t.title), kind: UiItemKind::Track, marked: false, is_queable: true, }) .collect(); } else { // if tracks not empty use tracks instead self.list = node .children .iter() .map(|c| UiItem { path: c.path.clone(), title: c.title.clone(), kind: UiItemKind::Node, marked: false, is_queable: c.is_queable, }) .collect(); } self.update_selection(); } pub fn render(&mut self, f: &mut Frame, area: Rect, focused: bool) { let library_items: Vec = self .list .iter() .map(|i| { let text = if i.marked { format!("* {}", i.title) } else { i.title.to_string() }; let style = if i.marked { Style::default() .fg(COLOR_GREEN) .add_modifier(Modifier::BOLD) } else { Style::default() }; ListItem::new(Span::from(text)).style(style) }) .collect(); let library_list = List::new(library_items) .block( Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) .border_style(Style::default().fg(if focused { COLOR_PRIMARY } else { COLOR_PRIMARY_DARK })) .title(self.title.clone()), ) .highlight_style( Style::default() .bg(if focused { COLOR_PRIMARY } else { COLOR_PRIMARY_DARK }) .add_modifier(Modifier::BOLD), ); f.render_stateful_widget(library_list, area, &mut self.list_state); } } impl StatefulList for Library { fn get_size(&self) -> usize { self.list.len() } fn select(&mut self, idx: Option) { if let Some(pos) = idx { self.positions .entry(self.path.clone()) .and_modify(|e| *e = pos) .or_insert(pos); } self.list_state.select(idx); } fn selected(&self) -> Option { self.list_state.selected() } }