// TODO: Things that should be done. // // * Wherever there's a function that take 2 or more arguments of the same type, // eg: fn new(total_size: gtk::Label, local_size: gtk::Label ..) // Wrap the types into Struct-tuples and imple deref so it won't be possible to pass // the wrong argument to the wrong position. use chrono; use glib; use gtk; use gtk::prelude::*; use humansize::FileSize; use std::sync::{Arc, Mutex}; use manager::Progress as OtherProgress; use widgets::episode::SIZE_OPTS; #[derive(Debug, Clone)] pub struct UnInitialized; #[derive(Debug, Clone)] pub struct Shown; #[derive(Debug, Clone)] pub struct Hidden; pub trait Visibility {} impl Visibility for Shown {} impl Visibility for Hidden {} impl From for Shown { fn from(_: Hidden) -> Self { Shown {} } } impl From for Hidden { fn from(_: Shown) -> Self { Hidden {} } } impl Into for UnInitialized { fn into(self) -> Hidden { Hidden {} } } impl Into for UnInitialized { fn into(self) -> Shown { Shown {} } } #[derive(Debug, Clone)] pub struct Normal; #[derive(Debug, Clone)] pub struct GreyedOut; impl From for GreyedOut { fn from(_: Normal) -> Self { GreyedOut {} } } impl From for Normal { fn from(_: GreyedOut) -> Self { Normal {} } } #[derive(Debug, Clone)] pub struct Title { title: gtk::Label, state: S, } impl Title { #[allow(unused_must_use)] // This does not need to be &mut since gtk-rs does not model ownership // But I think it wouldn't heart if we treat it as a Rust api. fn set_title(&mut self, s: &str) { self.title.set_text(s); } } impl Title { fn new(title: gtk::Label) -> Self { Title { title, state: Normal {}, } } } impl From> for Title { fn from(f: Title) -> Self { f.title .get_style_context() .map(|c| c.add_class("dim-label")); Title { title: f.title, state: f.state.into(), } } } impl From> for Title { fn from(f: Title) -> Self { f.title .get_style_context() .map(|c| c.remove_class("dim-label")); Title { title: f.title, state: f.state.into(), } } } #[derive(Debug, Clone)] pub enum TitleMachine { Normal(Title), GreyedOut(Title), } impl TitleMachine { pub fn new(label: gtk::Label, is_played: bool) -> Self { let m = TitleMachine::Normal(Title::::new(label)); m.determine_state(is_played) } pub fn determine_state(self, is_played: bool) -> Self { use self::TitleMachine::*; match (self, is_played) { (title @ Normal(_), false) => title, (title @ GreyedOut(_), true) => title, (Normal(val), true) => GreyedOut(val.into()), (GreyedOut(val), false) => Normal(val.into()), } } pub fn set_title(&mut self, s: &str) { use self::TitleMachine::*; match *self { Normal(ref mut val) => val.set_title(s), GreyedOut(ref mut val) => val.set_title(s), } } } #[derive(Debug, Clone)] pub struct Duration { // TODO: make duration and separator diff types duration: gtk::Label, separator: gtk::Label, state: S, } impl Duration { // This needs a better name. // TODO: make me mut fn set_duration(&self, minutes: i64) { self.duration.set_text(&format!("{} min", minutes)); } } impl Duration { fn new(duration: gtk::Label, separator: gtk::Label) -> Self { duration.hide(); separator.hide(); Duration { duration, separator, state: Hidden {}, } } } impl From> for Duration { fn from(f: Duration) -> Self { f.duration.show(); f.separator.show(); Duration { duration: f.duration, separator: f.separator, state: f.state.into(), } } } impl From> for Duration { fn from(f: Duration) -> Self { f.duration.hide(); f.separator.hide(); Duration { duration: f.duration, separator: f.separator, state: f.state.into(), } } } #[derive(Debug, Clone)] pub enum DurationMachine { Hidden(Duration), Shown(Duration), } impl DurationMachine { pub fn new(duration: gtk::Label, separator: gtk::Label, seconds: Option) -> Self { let m = DurationMachine::Hidden(Duration::::new(duration, separator)); m.determine_state(seconds) } pub fn determine_state(self, seconds: Option) -> Self { match (self, seconds) { (DurationMachine::Hidden(val), None) => DurationMachine::Hidden(val.into()), (DurationMachine::Shown(val), None) => DurationMachine::Hidden(val.into()), (DurationMachine::Hidden(val), Some(s)) => { let minutes = chrono::Duration::seconds(s.into()).num_minutes(); if minutes == 0 { DurationMachine::Hidden(val.into()) } else { val.set_duration(minutes); DurationMachine::Shown(val.into()) } } (DurationMachine::Shown(val), Some(s)) => { let minutes = chrono::Duration::seconds(s.into()).num_minutes(); if minutes == 0 { DurationMachine::Hidden(val.into()) } else { val.set_duration(minutes); DurationMachine::Shown(val.into()) } } } } } #[derive(Debug, Clone)] pub struct Size { size: gtk::Label, separator: gtk::Label, state: S, } impl Size { fn set_size(self, s: &str) -> Size { self.size.set_text(s); self.size.show(); self.separator.show(); Size { size: self.size, separator: self.separator, state: Shown {}, } } // https://play.rust-lang.org/?gist=1acffaf62743eeb85be1ae6ecf474784&version=stable // It might be possible to make a generic definition with Specialization. // https://github.com/rust-lang/rust/issues/31844 fn into_shown(self) -> Size { self.size.show(); self.separator.show(); Size { size: self.size, separator: self.separator, state: Shown {}, } } fn into_hidden(self) -> Size { self.size.hide(); self.separator.hide(); Size { size: self.size, separator: self.separator, state: Hidden {}, } } } impl Size { fn new(size: gtk::Label, separator: gtk::Label) -> Self { size.hide(); separator.hide(); Size { size, separator, state: UnInitialized {}, } } } // pub trait Playable {} // impl Playable for Download {} // impl Playable for Play {} #[derive(Debug, Clone)] pub struct Download; #[derive(Debug, Clone)] pub struct Play; #[derive(Debug, Clone)] // FIXME: Needs better name. // Should each button also has it's own type and machine? pub struct DownloadPlay { play: gtk::Button, download: gtk::Button, state: S, } impl DownloadPlay { // https://play.rust-lang.org/?gist=1acffaf62743eeb85be1ae6ecf474784&version=stable // It might be possible to make a generic definition with Specialization. // https://github.com/rust-lang/rust/issues/31844 fn into_playable(self) -> DownloadPlay { self.play.show(); self.download.hide(); DownloadPlay { play: self.play, download: self.download, state: Play {}, } } fn into_fetchable(self) -> DownloadPlay { self.play.hide(); self.download.show(); DownloadPlay { play: self.play, download: self.download, state: Download {}, } } fn into_hidden(self) -> DownloadPlay { self.play.hide(); self.download.hide(); DownloadPlay { play: self.play, download: self.download, state: Hidden {}, } } fn download_connect_clicked( &self, f: F, ) -> glib::SignalHandlerId { self.download.connect_clicked(f) } fn play_connect_clicked(&self, f: F) -> glib::SignalHandlerId { self.play.connect_clicked(f) } } impl DownloadPlay { fn new(play: gtk::Button, download: gtk::Button) -> Self { play.hide(); download.hide(); DownloadPlay { play, download, state: UnInitialized {}, } } } #[derive(Debug, Clone)] pub struct Progress { bar: gtk::ProgressBar, cancel: gtk::Button, local_size: gtk::Label, prog_separator: gtk::Label, state: S, } impl Progress { fn into_shown(self) -> Progress { self.bar.show(); self.cancel.show(); self.local_size.show(); self.prog_separator.show(); Progress { bar: self.bar, cancel: self.cancel, local_size: self.local_size, prog_separator: self.prog_separator, state: Shown {}, } } fn into_hidden(self) -> Progress { self.bar.hide(); self.cancel.hide(); self.local_size.hide(); self.prog_separator.hide(); Progress { bar: self.bar, cancel: self.cancel, local_size: self.local_size, prog_separator: self.prog_separator, state: Hidden {}, } } fn cancel_connect_clicked(&self, prog: Arc>) -> glib::SignalHandlerId { self.cancel.connect_clicked(move |cancel| { if let Ok(mut m) = prog.lock() { m.cancel(); cancel.set_sensitive(false); } }) } } impl Progress { fn new( bar: gtk::ProgressBar, cancel: gtk::Button, local_size: gtk::Label, prog_separator: gtk::Label, ) -> Self { bar.hide(); cancel.hide(); local_size.hide(); prog_separator.hide(); Progress { bar, cancel, local_size, prog_separator, state: UnInitialized {}, } } } #[derive(Debug, Clone)] pub struct Media { dl: DownloadPlay, size: Size, progress: Progress, } type New = Media