1 use std::sync::Arc; 2 3 use cursive::align::HAlign; 4 use cursive::event::{Event, EventResult, MouseButton, MouseEvent}; 5 use cursive::theme::{ColorStyle, ColorType, PaletteColor}; 6 use cursive::traits::View; 7 use cursive::vec::Vec2; 8 use cursive::Printer; 9 use unicode_width::UnicodeWidthStr; 10 11 use crate::library::Library; 12 use crate::queue::{Queue, RepeatSetting}; 13 use crate::spotify::{PlayerEvent, Spotify}; 14 15 pub struct StatusBar { 16 queue: Arc<Queue>, 17 spotify: Spotify, 18 library: Arc<Library>, 19 last_size: Vec2, 20 } 21 22 impl StatusBar { new(queue: Arc<Queue>, library: Arc<Library>) -> StatusBar23 pub fn new(queue: Arc<Queue>, library: Arc<Library>) -> StatusBar { 24 let spotify = queue.get_spotify(); 25 26 StatusBar { 27 queue, 28 spotify, 29 library, 30 last_size: Vec2::new(0, 0), 31 } 32 } 33 use_nerdfont(&self) -> bool34 fn use_nerdfont(&self) -> bool { 35 self.library.cfg.values().use_nerdfont.unwrap_or(false) 36 } 37 playback_indicator(&self) -> &str38 fn playback_indicator(&self) -> &str { 39 let status = self.spotify.get_current_status(); 40 let nerdfont = self.use_nerdfont(); 41 let flipped = self 42 .library 43 .cfg 44 .values() 45 .flip_status_indicators 46 .unwrap_or(false); 47 48 const NF_PLAY: &str = "\u{f909} "; 49 const NF_PAUSE: &str = "\u{f8e3} "; 50 const NF_STOP: &str = "\u{f9da} "; 51 let indicators = match (nerdfont, flipped) { 52 (false, false) => ("▶ ", "▮▮", "◼ "), 53 (false, true) => ("▮▮", "▶ ", "▶ "), 54 (true, false) => (NF_PLAY, NF_PAUSE, NF_STOP), 55 (true, true) => (NF_PAUSE, NF_PLAY, NF_PLAY), 56 }; 57 58 match status { 59 PlayerEvent::Playing(_) => indicators.0, 60 PlayerEvent::Paused(_) => indicators.1, 61 PlayerEvent::Stopped | PlayerEvent::FinishedTrack => indicators.2, 62 } 63 } 64 volume_display(&self) -> String65 fn volume_display(&self) -> String { 66 format!( 67 " [{}%]", 68 (self.spotify.volume() as f64 / 65535_f64 * 100.0).round() as u16 69 ) 70 } 71 } 72 73 impl View for StatusBar { draw(&self, printer: &Printer<'_, '_>)74 fn draw(&self, printer: &Printer<'_, '_>) { 75 if printer.size.x == 0 { 76 return; 77 } 78 79 let style_bar = ColorStyle::new( 80 ColorType::Color(*printer.theme.palette.custom("statusbar_progress").unwrap()), 81 ColorType::Palette(PaletteColor::Background), 82 ); 83 let style_bar_bg = ColorStyle::new( 84 ColorType::Color( 85 *printer 86 .theme 87 .palette 88 .custom("statusbar_progress_bg") 89 .unwrap(), 90 ), 91 ColorType::Palette(PaletteColor::Background), 92 ); 93 let style = ColorStyle::new( 94 ColorType::Color(*printer.theme.palette.custom("statusbar").unwrap()), 95 ColorType::Color(*printer.theme.palette.custom("statusbar_bg").unwrap()), 96 ); 97 98 printer.print( 99 (0, 0), 100 &vec![' '; printer.size.x].into_iter().collect::<String>(), 101 ); 102 printer.with_color(style, |printer| { 103 printer.print( 104 (0, 1), 105 &vec![' '; printer.size.x].into_iter().collect::<String>(), 106 ); 107 }); 108 109 printer.with_color(style, |printer| { 110 printer.print((1, 1), self.playback_indicator()); 111 }); 112 113 let updating = if !*self.library.is_done.read().unwrap() { 114 if self.use_nerdfont() { 115 "\u{f9e5} " 116 } else { 117 "[U] " 118 } 119 } else { 120 "" 121 }; 122 123 let repeat = if self.use_nerdfont() { 124 match self.queue.get_repeat() { 125 RepeatSetting::None => "", 126 RepeatSetting::RepeatPlaylist => "\u{f955} ", 127 RepeatSetting::RepeatTrack => "\u{f957} ", 128 } 129 } else { 130 match self.queue.get_repeat() { 131 RepeatSetting::None => "", 132 RepeatSetting::RepeatPlaylist => "[R] ", 133 RepeatSetting::RepeatTrack => "[R1] ", 134 } 135 }; 136 137 let shuffle = if self.queue.get_shuffle() { 138 if self.use_nerdfont() { 139 "\u{f99c} " 140 } else { 141 "[Z] " 142 } 143 } else { 144 "" 145 }; 146 147 let volume = self.volume_display(); 148 149 printer.with_color(style_bar_bg, |printer| { 150 printer.print((0, 0), &"┉".repeat(printer.size.x)); 151 }); 152 153 let elapsed = self.spotify.get_current_progress(); 154 let elapsed_ms = elapsed.as_millis() as u32; 155 156 let formatted_elapsed = format!( 157 "{:02}:{:02}", 158 elapsed.as_secs() / 60, 159 elapsed.as_secs() % 60 160 ); 161 162 let playback_duration_status = match self.queue.get_current() { 163 Some(ref t) => format!("{} / {}", formatted_elapsed, t.duration_str()), 164 None => "".to_string(), 165 }; 166 167 let right = updating.to_string() 168 + repeat 169 + shuffle 170 // + saved 171 + &playback_duration_status 172 + &volume; 173 let offset = HAlign::Right.get_offset(right.width(), printer.size.x); 174 175 printer.with_color(style, |printer| { 176 if let Some(ref t) = self.queue.get_current() { 177 printer.print((4, 1), &t.to_string()); 178 } 179 printer.print((offset, 1), &right); 180 }); 181 182 if let Some(t) = self.queue.get_current() { 183 printer.with_color(style_bar, |printer| { 184 let duration_width = 185 (((printer.size.x as u32) * elapsed_ms) / t.duration()) as usize; 186 printer.print((0, 0), &"━".repeat(duration_width + 1)); 187 }); 188 } 189 } 190 layout(&mut self, size: Vec2)191 fn layout(&mut self, size: Vec2) { 192 self.last_size = size; 193 } 194 required_size(&mut self, constraint: Vec2) -> Vec2195 fn required_size(&mut self, constraint: Vec2) -> Vec2 { 196 Vec2::new(constraint.x, 2) 197 } 198 on_event(&mut self, event: Event) -> EventResult199 fn on_event(&mut self, event: Event) -> EventResult { 200 if let Event::Mouse { 201 offset, 202 position, 203 event, 204 } = event 205 { 206 let position = position - offset; 207 let volume_len = self.volume_display().len(); 208 209 if position.y == 0 { 210 if event == MouseEvent::WheelUp { 211 self.spotify.seek_relative(-500); 212 } 213 214 if event == MouseEvent::WheelDown { 215 self.spotify.seek_relative(500); 216 } 217 218 if event == MouseEvent::Press(MouseButton::Left) 219 || event == MouseEvent::Hold(MouseButton::Left) 220 { 221 if let Some(playable) = self.queue.get_current() { 222 let f: f32 = position.x as f32 / self.last_size.x as f32; 223 let new = playable.duration() as f32 * f; 224 self.spotify.seek(new as u32); 225 } 226 } 227 } else if self.last_size.x - position.x < volume_len { 228 if event == MouseEvent::WheelUp { 229 let volume = self 230 .spotify 231 .volume() 232 .saturating_add(crate::spotify::VOLUME_PERCENT); 233 234 self.spotify.set_volume(volume); 235 } 236 237 if event == MouseEvent::WheelDown { 238 let volume = self 239 .spotify 240 .volume() 241 .saturating_sub(crate::spotify::VOLUME_PERCENT); 242 243 self.spotify.set_volume(volume); 244 } 245 } else if event == MouseEvent::Press(MouseButton::Left) { 246 self.queue.toggleplayback(); 247 } 248 249 EventResult::Consumed(None) 250 } else { 251 EventResult::Ignored 252 } 253 } 254 } 255