// 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 chrono::prelude::*; use gtk::prelude::*; use humansize::{file_size_opts as size_opts, FileSize}; use std::sync::Arc; lazy_static! { pub static ref SIZE_OPTS: Arc = { // Declare a custom humansize option struct // See: https://docs.rs/humansize/1.0.2/humansize/file_size_opts/struct.FileSizeOpts.html Arc::new(size_opts::FileSizeOpts { divider: size_opts::Kilo::Binary, units: size_opts::Kilo::Decimal, decimal_places: 0, decimal_zeroes: 0, fixed_at: size_opts::FixedAt::No, long_units: false, space: true, suffix: "", allow_negative: false, }) }; static ref NOW: DateTime = Utc::now(); } #[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 {} #[derive(Debug, Clone)] pub struct Normal; #[derive(Debug, Clone)] pub struct GreyedOut; #[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 hurt 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: GreyedOut {}, } } } 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: Normal {}, } } } #[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 Usual; #[derive(Debug, Clone)] pub struct YearShown; #[derive(Debug, Clone)] pub struct Date { date: gtk::Label, epoch: i64, state: S, } impl Date { fn into_usual(self, epoch: i64) -> Date { let ts = Utc.timestamp(epoch, 0); self.date.set_text(ts.format("%e %b").to_string().trim()); Date { date: self.date, epoch: self.epoch, state: Usual {}, } } fn into_year_shown(self, epoch: i64) -> Date { let ts = Utc.timestamp(epoch, 0); self.date.set_text(ts.format("%e %b %Y").to_string().trim()); Date { date: self.date, epoch: self.epoch, state: YearShown {}, } } } impl Date { fn new(date: gtk::Label, epoch: i64) -> Self { let ts = Utc.timestamp(epoch, 0); date.set_text(ts.format("%e %b %Y").to_string().trim()); Date { date, epoch, state: UnInitialized {}, } } } #[derive(Debug, Clone)] pub enum DateMachine { UnInitialized(Date), Usual(Date), WithYear(Date), } impl DateMachine { pub fn new(label: gtk::Label, epoch: i64) -> Self { let m = DateMachine::UnInitialized(Date::::new(label, epoch)); m.determine_state(epoch) } pub fn determine_state(self, epoch: i64) -> Self { use self::DateMachine::*; let ts = Utc.timestamp(epoch, 0); let is_old = !(NOW.year() == ts.year()); match (self, is_old) { // Into Usual (Usual(val), false) => Usual(val.into_usual(epoch)), (WithYear(val), false) => Usual(val.into_usual(epoch)), (UnInitialized(val), false) => Usual(val.into_usual(epoch)), // Into Year Shown (Usual(val), true) => WithYear(val.into_year_shown(epoch)), (WithYear(val), true) => WithYear(val.into_year_shown(epoch)), (UnInitialized(val), true) => WithYear(val.into_year_shown(epoch)), } } } #[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: Shown {}, } } } impl From> for Duration { fn from(f: Duration) -> Self { f.duration.hide(); f.separator.hide(); Duration { duration: f.duration, separator: f.separator, state: Hidden {}, } } } #[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) { (d @ DurationMachine::Hidden(_), None) => d, (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) } 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) } } } } } #[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 {}, } } #[allow(unused_must_use)] // This does not need to be &mut since gtk-rs does not model ownership // But I think it wouldn't hurt if we treat it as a Rust api. fn update_progress(&mut self, local_size: &str, fraction: f64) { self.local_size.set_text(local_size); self.bar.set_fraction(fraction); } fn cancel_connect_clicked(&self, f: F) -> glib::SignalHandlerId { self.cancel.connect_clicked(f) } } 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