use std::fs::File; use iced::Command; use core::time::Duration; use iced::Length; use iced::Color; use iced::Point; use crate::player::PlayerError; use iced::canvas::event::Status; use iced::canvas::Event; use crate::styles::Theme; use iced::canvas::Frame; use iced::canvas::Geometry; use iced::canvas::Cursor; use iced::Rectangle; use crate::app::Message; use iced::canvas::Program; use iced::Canvas; use iced::mouse::{self, Button}; use crate::player::Player; use blocking::unblock; #[derive(Debug, Clone, Copy)] pub enum ControlsEvent { SeekPosition(f32), TogglePlay, DurationDiscovered(Duration), } pub enum ErrorState { Error(PlayerError), NoError { player: Player, has_device: bool } } use ErrorState::*; pub struct Controls(pub ErrorState); impl Controls { pub fn new(song: File) -> (Self, Command) { match Player::new(song) { Ok(player) => { let duration_task = unblock(player.compute_duration()); let duration_cmd = Command::perform( duration_task, |d| Message::ControlsEvent(ControlsEvent::DurationDiscovered(d)), ); ( Self(NoError { has_device: player.has_output_device(), player, }), duration_cmd ) }, Err(e) => { (Self(Error(e)), Command::none()) } } } pub fn view_progress(&mut self, theme: Theme) -> Canvas { Canvas::new((&*self, theme)) .width(Length::Units(50)) .height(Length::Fill) } pub fn handle_event(&mut self, event: ControlsEvent) { if let NoError { player, has_device } = &mut self.0 { let result = match event { ControlsEvent::SeekPosition(pos) => player.seek_percentage(pos), ControlsEvent::TogglePlay => player.toggle_play(), ControlsEvent::DurationDiscovered(d) => { player.set_duration(d); println!("Found duration! {:?}", d); Ok(player.has_output_device()) }, }; match result { Ok(now_has_device) => { *has_device = now_has_device; }, Err(e) => { self.0 = Error(e); }, }; } } pub fn is_playing(&self) -> bool { if let NoError { player, has_device: true } = &self.0 { player.is_playing() } else { false } } /// Returns the current position of the playhead /// /// If there was an error, this will be `None`. In all other cases, this will return /// `Some`. pub fn position(&self) -> Option { if let NoError { player, .. } = &self.0 { Some(player.position()) } else { None } } } impl Program for (&Controls, Theme) { fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec { let mut frame = Frame::new(bounds.size()); match &self.0.0 { NoError { player, has_device: true } => { let mut background = self.1.text_color; background.a = 0.2; frame.fill_rectangle(Point::ORIGIN, bounds.size(), background); let mut played_size = bounds.size(); played_size.height *= player.position_percent(); frame.fill_rectangle(Point::ORIGIN, played_size, self.1.text_color); }, NoError { player: _, has_device: false } => { let mut background = self.1.text_color; background.a = 0.1; frame.fill_rectangle(Point::ORIGIN, bounds.size(), background); }, Error(e) => { let background = Color {r: 1., g: 0.1, b: 0.1, a: 1.}; frame.fill_rectangle(Point::ORIGIN, bounds.size(), background); eprintln!("Error!!! {}", e); } } vec![frame.into_geometry()] } fn update(&mut self, event: Event, bounds: Rectangle, cursor: Cursor) -> (Status, Option) { match (event, cursor) { (Event::Mouse(mouse::Event::ButtonReleased(Button::Left)), Cursor::Available(pos)) if bounds.contains(pos) => { let sought = (pos.y - bounds.position().y) / bounds.size().height; (Status::Captured, Some(Message::ControlsEvent(ControlsEvent::SeekPosition(sought)))) }, (Event::Mouse(mouse::Event::ButtonReleased(Button::Right)), Cursor::Available(pos)) if bounds.contains(pos) => { // TODO! This should be somewhere intuitive (Status::Captured, Some(Message::ControlsEvent(ControlsEvent::TogglePlay))) }, _ => (Status::Ignored, None), } } }