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