library_ui.rs (8694B)
1 use crossterm::event::{KeyCode, KeyEvent}; 2 use ratatui::{ 3 Frame, 4 layout::{Constraint, Direction, Layout, Rect}, 5 style::{Color, Modifier, Style}, 6 widgets::{Block, Borders, List, ListItem, ListState}, 7 }; 8 9 use crate::{app::App, audio::AudioPlayer, ui::Focus}; 10 11 pub fn render(f: &mut Frame, app: &mut App) { 12 let chunks = Layout::default() 13 .direction(Direction::Horizontal) 14 .constraints([ 15 Constraint::Percentage(33), 16 Constraint::Percentage(33), 17 Constraint::Percentage(34), 18 ]) 19 .split(f.area()); 20 21 let artists: Vec<String> = app.library.artists.keys().cloned().collect(); 22 let artist_items: Vec<ListItem> = artists 23 .iter() 24 .map(|name| ListItem::new(name.as_str())) 25 .collect(); 26 render_list( 27 f, 28 "Artist", 29 artist_items, 30 chunks[0], 31 app.state.focus == Focus::Artist, 32 &mut app.state.artist_state, 33 ); 34 35 let selected_artist = app 36 .state 37 .artist_state 38 .selected() 39 .and_then(|i| artists.get(i)); 40 let mut albums: Vec<String> = Vec::new(); 41 if let Some(artist) = selected_artist { 42 if let Some(artist_data) = app.library.artists.get(artist) { 43 albums = artist_data.keys().cloned().collect(); 44 } 45 } 46 let album_items: Vec<ListItem> = albums 47 .iter() 48 .map(|name| ListItem::new(name.as_str())) 49 .collect(); 50 render_list( 51 f, 52 "Album", 53 album_items, 54 chunks[1], 55 app.state.focus == Focus::Album, 56 &mut app.state.album_state, 57 ); 58 59 let selected_album = app.state.album_state.selected().and_then(|i| albums.get(i)); 60 let mut tracks: Vec<String> = Vec::new(); 61 if let (Some(artist), Some(album)) = (selected_artist, selected_album) { 62 if let Some(album_data) = app.library.artists.get(artist).and_then(|a| a.get(album)) { 63 tracks = album_data.iter().map(|t| t.title.clone()).collect(); 64 } 65 } 66 let track_items: Vec<ListItem> = tracks 67 .iter() 68 .map(|name| ListItem::new(name.as_str())) 69 .collect(); 70 render_list( 71 f, 72 "Track", 73 track_items, 74 chunks[2], 75 app.state.focus == Focus::Track, 76 &mut app.state.track_state, 77 ); 78 } 79 80 fn render_list( 81 frame: &mut Frame, 82 title: &str, 83 items: Vec<ListItem>, 84 area: Rect, 85 focused: bool, 86 state: &mut ListState, 87 ) { 88 let border_style = if focused { 89 Style::default().fg(Color::Yellow) 90 } else { 91 Style::default() 92 }; 93 let list = List::new(items) 94 .block( 95 Block::default() 96 .title(title) 97 .borders(Borders::ALL) 98 .border_style(border_style), 99 ) 100 .highlight_style( 101 Style::default() 102 .bg(Color::Blue) 103 .add_modifier(Modifier::BOLD), 104 ) 105 .highlight_symbol(">> "); 106 frame.render_stateful_widget(list, area, state); 107 } 108 109 pub fn handle_key_event(app: &mut App, key: KeyEvent) -> Result<bool, Box<dyn std::error::Error>> { 110 match key.code { 111 KeyCode::Left => { 112 app.state.focus = match app.state.focus { 113 Focus::Artist => Focus::Artist, 114 Focus::Album => Focus::Artist, 115 Focus::Track => Focus::Album, 116 Focus::Settings => Focus::Settings, 117 }; 118 } 119 KeyCode::Right => { 120 app.state.focus = match app.state.focus { 121 Focus::Artist => { 122 app.state.album_state.select(Some(0)); 123 Focus::Album 124 } 125 Focus::Album => { 126 app.state.track_state.select(Some(0)); 127 Focus::Track 128 } 129 Focus::Track => Focus::Track, 130 Focus::Settings => Focus::Settings, 131 }; 132 } 133 KeyCode::Up => { 134 let current_state = match app.state.focus { 135 Focus::Artist => &mut app.state.artist_state, 136 Focus::Album => &mut app.state.album_state, 137 Focus::Track => &mut app.state.track_state, 138 Focus::Settings => &mut app.state.artist_state, 139 }; 140 let i = current_state.selected().unwrap_or(0); 141 if i > 0 { 142 current_state.select(Some(i - 1)); 143 } 144 } 145 KeyCode::Down => { 146 let max_len = match app.state.focus { 147 Focus::Artist => app.library.artists.len(), 148 Focus::Album => { 149 let artists: Vec<_> = app.library.artists.keys().collect(); 150 app.state 151 .artist_state 152 .selected() 153 .and_then(|i| artists.get(i)) 154 .and_then(|a| app.library.artists.get(*a)) 155 .map(|m| m.len()) 156 .unwrap_or(0) 157 } 158 Focus::Track => { 159 let artists: Vec<_> = app.library.artists.keys().collect(); 160 let artist = app 161 .state 162 .artist_state 163 .selected() 164 .and_then(|i| artists.get(i)); 165 let albums: Vec<_> = artist 166 .and_then(|a| app.library.artists.get(*a)) 167 .map(|m| m.keys().collect()) 168 .unwrap_or_default(); 169 let album = app.state.album_state.selected().and_then(|i| albums.get(i)); 170 artist 171 .and_then(|a| app.library.artists.get(*a)) 172 .and_then(|m| album.and_then(|al| m.get(*al))) 173 .map(|v| v.len()) 174 .unwrap_or(0) 175 } 176 Focus::Settings => 0, 177 }; 178 179 let current_state = match app.state.focus { 180 Focus::Artist => &mut app.state.artist_state, 181 Focus::Album => &mut app.state.album_state, 182 Focus::Track => &mut app.state.track_state, 183 Focus::Settings => &mut app.state.artist_state, 184 }; 185 let i = current_state.selected().unwrap_or(0); 186 if max_len > 0 && i < max_len - 1 { 187 current_state.select(Some(i + 1)); 188 } 189 } 190 KeyCode::Enter => { 191 let artists: Vec<_> = app.library.artists.keys().collect(); 192 if let Some(artist_name) = app 193 .state 194 .artist_state 195 .selected() 196 .and_then(|i| artists.get(i)) 197 { 198 let artist_data = app.library.artists.get(*artist_name).unwrap(); 199 let albums: Vec<_> = artist_data.keys().collect(); 200 201 match app.state.focus { 202 Focus::Track => { 203 if let Some(album_name) = 204 app.state.album_state.selected().and_then(|i| albums.get(i)) 205 { 206 let tracks = artist_data.get(*album_name).unwrap(); 207 if let Some(track) = 208 app.state.track_state.selected().and_then(|i| tracks.get(i)) 209 { 210 app.audioengine.stop(); 211 app.audioengine 212 .append_a_track(track.path.to_str().unwrap())?; 213 app.audioengine.play(); 214 } 215 } 216 } 217 Focus::Album => { 218 if let Some(album_name) = 219 app.state.album_state.selected().and_then(|i| albums.get(i)) 220 { 221 let tracks = artist_data.get(*album_name).unwrap(); 222 app.audioengine.stop(); 223 for track in tracks { 224 app.audioengine 225 .append_a_track(track.path.to_str().unwrap())?; 226 } 227 app.audioengine.play(); 228 } 229 } 230 _ => {} 231 } 232 } 233 } 234 _ => {} 235 } 236 Ok(false) 237 }