140 lines
3.4 KiB
Rust
140 lines
3.4 KiB
Rust
|
use iced::Length;
|
||
|
use iced::Color;
|
||
|
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;
|
||
|
|
||
|
#[derive(Debug, Clone, Copy)]
|
||
|
pub enum ControlsEvent {
|
||
|
SeekPosition(f32),
|
||
|
TogglePlay,
|
||
|
}
|
||
|
|
||
|
enum ErrorState {
|
||
|
Error(PlayerError),
|
||
|
NoError {
|
||
|
player: Player,
|
||
|
has_device: bool
|
||
|
}
|
||
|
}
|
||
|
|
||
|
use ErrorState::*;
|
||
|
|
||
|
pub struct Controls {
|
||
|
error_state: ErrorState,
|
||
|
}
|
||
|
|
||
|
impl Controls {
|
||
|
pub fn new(song: Box<dyn FormatReader>) -> Self {
|
||
|
match Player::new(song) {
|
||
|
Ok(player) => {
|
||
|
Controls {
|
||
|
error_state: NoError {
|
||
|
has_device: player.has_output_device(),
|
||
|
player,
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
Err(e) => {
|
||
|
Controls {
|
||
|
error_state: Error(e)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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) {
|
||
|
if let NoError { player, has_device } = &mut self.error_state {
|
||
|
let result = match event {
|
||
|
ControlsEvent::SeekPosition(pos) => player.seek_percentage(pos),
|
||
|
ControlsEvent::TogglePlay => player.toggle_play(),
|
||
|
};
|
||
|
|
||
|
match result {
|
||
|
Ok(now_has_device) => {
|
||
|
*has_device = now_has_device;
|
||
|
},
|
||
|
Err(e) => {
|
||
|
self.error_state = Error(e);
|
||
|
},
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn is_playing(&self) -> bool {
|
||
|
if let NoError { player, has_device: true } = &self.error_state {
|
||
|
player.is_playing()
|
||
|
} else {
|
||
|
false
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Program<Message> for (&Controls, Theme) {
|
||
|
fn draw(&self, bounds: Rectangle<f32>, _cursor: Cursor) -> Vec<Geometry> {
|
||
|
let mut frame = Frame::new(bounds.size());
|
||
|
|
||
|
match &self.0.error_state {
|
||
|
NoError { player, has_device: true } => {
|
||
|
let mut background = self.1.text_color;
|
||
|
background.a = 0.2;
|
||
|
|
||
|
frame.fill_rectangle(bounds.position(), bounds.size(), background);
|
||
|
|
||
|
let mut played_size = bounds.size();
|
||
|
played_size.height *= player.position_percent();
|
||
|
|
||
|
frame.fill_rectangle(bounds.position(), 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(bounds.position(), bounds.size(), background);
|
||
|
},
|
||
|
Error(e) => {
|
||
|
let background = Color {r: 1., g: 0.1, b: 0.1, a: 1.};
|
||
|
|
||
|
frame.fill_rectangle(bounds.position(), bounds.size(), background);
|
||
|
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),
|
||
|
}
|
||
|
}
|
||
|
}
|