use egui::{RichText, Sense, Stroke, Vec2}; use xmpd_manifest::store::BaseStore; use super::{song_list::SongList, CompGetter, CompUi}; #[derive(Debug)] pub struct Player { slider_progress: usize, old_slider_progress: usize, volume_slider: f64, } impl Default for Player { fn default() -> Self { Self { volume_slider: 1.0, old_slider_progress: 0, slider_progress: 0 } } } component_register!(Player); impl CompUi for Player { fn draw(ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> { let theme = xmpd_settings::Settings::get()?.theme.clone(); let full_avail = ui.available_size(); ui.horizontal_centered(|ui| { ui.add_space(10.0); let icon = egui::Image::new(crate::data::NOTE_ICON) .tint(theme.accent_color) .sense(Sense::click()) .fit_to_exact_size(Vec2::new(32.0, 32.0)); ui.add(icon); ui.vertical(|ui| { ui.add_space(5.0); let sid = &handle_error_ui!(SongList::get()).selected_sid; if let Some(song) = state.manifest.store().get_song(sid) { let mut name = song.name().to_string(); if name.len() > 16 { name = (&name)[..16].to_string(); name.push_str("..."); } ui.label( RichText::new(name) .size(12.0) ); ui.label( RichText::new(song.author()) .size(8.0) .monospace() ); } }); ui.vertical_centered_justified(|ui| { let avail = ui.available_size(); let song_info_w = full_avail.x - avail.x; ui.add_space(3.0); ui.horizontal(|ui| { { let slider_width = full_avail.x * 0.60; ui.add_space((((full_avail.x / 2.0) - song_info_w) - slider_width / 2.0).clamp(0.0, f32::MAX)); ui.style_mut().spacing.slider_width = avail.x * 0.75; let s = Stroke { color: theme.accent_color, width: 2.0 }; ui.style_mut().visuals.widgets.inactive.fg_stroke = s; ui.style_mut().visuals.widgets.active.fg_stroke = s; ui.style_mut().visuals.widgets.hovered.fg_stroke = s; let mut slf = handle_error_ui!(Player::get()); ui.add( egui::Slider::new(&mut slf.slider_progress, 0..=100) .show_value(false) ); if slf.slider_progress == slf.old_slider_progress { slf.slider_progress = (state.player.get_played_f() * 100.0) as usize; slf.old_slider_progress = slf.slider_progress; } else { handle_error_ui!(state.player.seek_to_f(slf.slider_progress as f64 / 100.0 )); slf.old_slider_progress = slf.slider_progress; } let secs_left = state.player.get_ms_left() as f64 / 1000.0; let h = (secs_left/60.0/60.0).floor(); let m = ((secs_left - h * 60.0)/60.0).floor(); let s = (secs_left - m * 60.0).floor(); ui.label(format!("{h:02}:{m:02}:{s:02}")); } }); ui.horizontal(|ui| { let icon_size = 16.0; ui.add_space(((full_avail.x / 2.0) - song_info_w) - icon_size * 1.5 - ui.spacing().item_spacing.x); let pp = if state.player.is_paused() { crate::data::PLAY_ICON } else { crate::data::PAUSE_ICON }; let prev = egui::Image::new(crate::data::PREV_ICON) .tint(theme.accent_color) .sense(Sense::click()) .max_size(Vec2::new(icon_size, icon_size)); let pp = egui::Image::new(pp) .tint(theme.accent_color) .sense(Sense::click()) .max_size(Vec2::new(icon_size, icon_size)); let next = egui::Image::new(crate::data::NEXT_ICON) .tint(theme.accent_color) .sense(Sense::click()) .max_size(Vec2::new(icon_size, icon_size)); if ui.add(prev).clicked() { handle_error_ui!(handle_error_ui!(SongList::get()).play_prev(state)); } if ui.add(pp).clicked() { if state.player.is_paused() { state.player.play(); } else { state.player.pause(); } } if ui.add(next).clicked() || state.player.just_stopped() { handle_error_ui!(handle_error_ui!(SongList::get()).play_next(state)); } ui.add_space(15.0); ui.style_mut().spacing.slider_width = avail.x * 0.15; let s = Stroke { color: theme.accent_color, width: 1.0 }; ui.style_mut().visuals.widgets.inactive.fg_stroke = s; ui.style_mut().visuals.widgets.active.fg_stroke = s; ui.style_mut().visuals.widgets.hovered.fg_stroke = s; let mut slf = handle_error_ui!(Player::get()); let slider =ui.add( egui::Slider::new(&mut slf.volume_slider, 0.0..=1.0) .show_value(false) ); if slider.changed() { state.player.set_volume(slf.volume_slider); } }); ui.add_space(3.0); }); }); Ok(()) } }