2022-01-05 20:42:12 +00:00
|
|
|
use iced::Command;
|
|
|
|
use core::time::Duration;
|
2022-01-03 23:19:10 +00:00
|
|
|
use iced::Length;
|
|
|
|
use iced::Color;
|
2022-01-07 22:39:24 +00:00
|
|
|
use iced::Point;
|
2022-01-03 23:19:10 +00:00
|
|
|
use symphonia::core::formats::FormatReader;
|
|
|
|
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;
|
2022-01-05 20:42:12 +00:00
|
|
|
use blocking::unblock;
|
2022-01-03 23:19:10 +00:00
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
pub enum ControlsEvent {
|
|
|
|
SeekPosition(f32),
|
|
|
|
TogglePlay,
|
2022-01-05 20:42:12 +00:00
|
|
|
DurationDiscovered(Duration),
|
2022-01-03 23:19:10 +00:00
|
|
|
}
|
|
|
|
|
2022-01-22 15:39:18 +00:00
|
|
|
pub enum ErrorState {
|
2022-01-03 23:19:10 +00:00
|
|
|
Error(PlayerError),
|
|
|
|
NoError {
|
|
|
|
player: Player,
|
|
|
|
has_device: bool
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
use ErrorState::*;
|
|
|
|
|
2022-01-22 15:39:18 +00:00
|
|
|
pub struct Controls(pub ErrorState);
|
2022-01-03 23:19:10 +00:00
|
|
|
|
|
|
|
impl Controls {
|
2022-01-05 20:42:12 +00:00
|
|
|
pub fn new(song: Box<dyn FormatReader>) -> (Self, Command<Message>) {
|
2022-01-03 23:19:10 +00:00
|
|
|
match Player::new(song) {
|
|
|
|
Ok(player) => {
|
2022-01-05 20:42:12 +00:00
|
|
|
let duration_task = unblock(player.compute_duration());
|
|
|
|
let duration_cmd = Command::perform(
|
|
|
|
duration_task,
|
|
|
|
|d| Message::ControlsEvent(ControlsEvent::DurationDiscovered(d)),
|
|
|
|
);
|
|
|
|
|
2022-01-22 15:39:18 +00:00
|
|
|
(
|
|
|
|
Controls(NoError {
|
2022-01-03 23:19:10 +00:00
|
|
|
has_device: player.has_output_device(),
|
|
|
|
player,
|
2022-01-22 15:39:18 +00:00
|
|
|
}),
|
|
|
|
duration_cmd
|
|
|
|
)
|
2022-01-03 23:19:10 +00:00
|
|
|
},
|
|
|
|
Err(e) => {
|
2022-01-22 15:39:18 +00:00
|
|
|
(Controls(Error(e)), Command::none())
|
2022-01-03 23:19:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn view_progress(&mut self, theme: Theme) -> Canvas<Message, (&Controls, Theme)> {
|
|
|
|
Canvas::new((&*self, theme))
|
|
|
|
.width(Length::Units(50))
|
|
|
|
.height(Length::Fill)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_event(&mut self, event: ControlsEvent) {
|
2022-01-22 15:39:18 +00:00
|
|
|
if let NoError { player, has_device } = &mut self.0 {
|
2022-01-03 23:19:10 +00:00
|
|
|
let result = match event {
|
|
|
|
ControlsEvent::SeekPosition(pos) => player.seek_percentage(pos),
|
|
|
|
ControlsEvent::TogglePlay => player.toggle_play(),
|
2022-01-05 20:42:12 +00:00
|
|
|
ControlsEvent::DurationDiscovered(d) => {
|
|
|
|
player.set_duration(d);
|
|
|
|
println!("Found duration! {:?}", d);
|
|
|
|
Ok(player.has_output_device())
|
|
|
|
},
|
2022-01-03 23:19:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
match result {
|
|
|
|
Ok(now_has_device) => {
|
|
|
|
*has_device = now_has_device;
|
|
|
|
},
|
|
|
|
Err(e) => {
|
2022-01-22 15:39:18 +00:00
|
|
|
self.0 = Error(e);
|
2022-01-03 23:19:10 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_playing(&self) -> bool {
|
2022-01-22 15:39:18 +00:00
|
|
|
if let NoError { player, has_device: true } = &self.0 {
|
2022-01-03 23:19:10 +00:00
|
|
|
player.is_playing()
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2022-01-09 22:10:12 +00:00
|
|
|
|
|
|
|
/// 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<Duration> {
|
2022-01-22 15:39:18 +00:00
|
|
|
if let NoError { player, .. } = &self.0 {
|
2022-01-09 22:10:12 +00:00
|
|
|
Some(player.position())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
2022-01-03 23:19:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Program<Message> for (&Controls, Theme) {
|
|
|
|
fn draw(&self, bounds: Rectangle<f32>, _cursor: Cursor) -> Vec<Geometry> {
|
|
|
|
let mut frame = Frame::new(bounds.size());
|
|
|
|
|
2022-01-22 15:39:18 +00:00
|
|
|
match &self.0.0 {
|
2022-01-03 23:19:10 +00:00
|
|
|
NoError { player, has_device: true } => {
|
|
|
|
let mut background = self.1.text_color;
|
|
|
|
background.a = 0.2;
|
|
|
|
|
2022-01-07 22:39:24 +00:00
|
|
|
frame.fill_rectangle(Point::ORIGIN, bounds.size(), background);
|
2022-01-03 23:19:10 +00:00
|
|
|
|
|
|
|
let mut played_size = bounds.size();
|
|
|
|
played_size.height *= player.position_percent();
|
|
|
|
|
2022-01-07 22:39:24 +00:00
|
|
|
frame.fill_rectangle(Point::ORIGIN, played_size, self.1.text_color);
|
2022-01-03 23:19:10 +00:00
|
|
|
},
|
|
|
|
NoError { player: _, has_device: false } => {
|
|
|
|
let mut background = self.1.text_color;
|
|
|
|
background.a = 0.1;
|
|
|
|
|
2022-01-07 22:39:24 +00:00
|
|
|
frame.fill_rectangle(Point::ORIGIN, bounds.size(), background);
|
2022-01-03 23:19:10 +00:00
|
|
|
},
|
|
|
|
Error(e) => {
|
|
|
|
let background = Color {r: 1., g: 0.1, b: 0.1, a: 1.};
|
|
|
|
|
2022-01-07 22:39:24 +00:00
|
|
|
frame.fill_rectangle(Point::ORIGIN, bounds.size(), background);
|
2022-01-03 23:19:10 +00:00
|
|
|
eprintln!("Error!!! {}", e.to_string());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
vec![frame.into_geometry()]
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update(&mut self, event: Event, bounds: Rectangle<f32>, cursor: Cursor) -> (Status, Option<Message>) {
|
|
|
|
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),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|