podcasts/hammond-gtk/src/widgets/episode_states.rs

655 lines
17 KiB
Rust

// 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 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<Hidden> for Shown {
fn from(_: Hidden) -> Self {
Shown {}
}
}
impl From<Shown> for Hidden {
fn from(_: Shown) -> Self {
Hidden {}
}
}
impl Into<Hidden> for UnInitialized {
fn into(self) -> Hidden {
Hidden {}
}
}
impl Into<Shown> for UnInitialized {
fn into(self) -> Shown {
Shown {}
}
}
#[derive(Debug, Clone)]
pub struct Normal;
#[derive(Debug, Clone)]
pub struct GreyedOut;
impl From<Normal> for GreyedOut {
fn from(_: Normal) -> Self {
GreyedOut {}
}
}
impl From<GreyedOut> for Normal {
fn from(_: GreyedOut) -> Self {
Normal {}
}
}
#[derive(Debug, Clone)]
pub struct Title<S> {
title: gtk::Label,
state: S,
}
impl<S> Title<S> {
#[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<Normal> {
fn new(title: gtk::Label) -> Self {
Title {
title,
state: Normal {},
}
}
}
impl From<Title<Normal>> for Title<GreyedOut> {
fn from(f: Title<Normal>) -> Self {
f.title
.get_style_context()
.map(|c| c.add_class("dim-label"));
Title {
title: f.title,
state: f.state.into(),
}
}
}
impl From<Title<GreyedOut>> for Title<Normal> {
fn from(f: Title<GreyedOut>) -> 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<Normal>),
GreyedOut(Title<GreyedOut>),
}
impl TitleMachine {
pub fn new(label: gtk::Label, is_played: bool) -> Self {
let m = TitleMachine::Normal(Title::<Normal>::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<S: Visibility> {
// TODO: make duration and separator diff types
duration: gtk::Label,
separator: gtk::Label,
state: S,
}
impl<S: Visibility> Duration<S> {
// This needs a better name.
// TODO: make me mut
fn set_duration(&self, minutes: i64) {
self.duration.set_text(&format!("{} min", minutes));
}
}
impl Duration<Hidden> {
fn new(duration: gtk::Label, separator: gtk::Label) -> Self {
duration.hide();
separator.hide();
Duration {
duration,
separator,
state: Hidden {},
}
}
}
impl From<Duration<Hidden>> for Duration<Shown> {
fn from(f: Duration<Hidden>) -> Self {
f.duration.show();
f.separator.show();
Duration {
duration: f.duration,
separator: f.separator,
state: f.state.into(),
}
}
}
impl From<Duration<Shown>> for Duration<Hidden> {
fn from(f: Duration<Shown>) -> 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<Hidden>),
Shown(Duration<Shown>),
}
impl DurationMachine {
pub fn new(duration: gtk::Label, separator: gtk::Label, seconds: Option<i32>) -> Self {
let m = DurationMachine::Hidden(Duration::<Hidden>::new(duration, separator));
m.determine_state(seconds)
}
pub fn determine_state(self, seconds: Option<i32>) -> 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<S> {
size: gtk::Label,
separator: gtk::Label,
state: S,
}
impl<S> Size<S> {
fn set_size(self, s: &str) -> Size<Shown> {
self.size.set_text(s);
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<Shown> {
self.size.show();
self.separator.show();
Size {
size: self.size,
separator: self.separator,
state: Shown {},
}
}
fn into_hidden(self) -> Size<Hidden> {
self.size.hide();
self.separator.hide();
Size {
size: self.size,
separator: self.separator,
state: Hidden {},
}
}
}
impl Size<UnInitialized> {
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<S> {
play: gtk::Button,
download: gtk::Button,
state: S,
}
impl<S> DownloadPlay<S> {
// 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<Play> {
self.play.show();
self.download.hide();
DownloadPlay {
play: self.play,
download: self.download,
state: Play {},
}
}
fn into_fetchable(self) -> DownloadPlay<Download> {
self.play.hide();
self.download.show();
DownloadPlay {
play: self.play,
download: self.download,
state: Download {},
}
}
fn into_hidden(self) -> DownloadPlay<Hidden> {
self.play.hide();
self.download.hide();
DownloadPlay {
play: self.play,
download: self.download,
state: Hidden {},
}
}
fn connect_download<F: Fn(&gtk::Button) + 'static>(&self, f: F) -> glib::SignalHandlerId {
self.download.connect_clicked(f)
}
fn connect_play_button<F: Fn(&gtk::Button) + 'static>(&self, f: F) -> glib::SignalHandlerId {
self.play.connect_clicked(f)
}
}
impl DownloadPlay<UnInitialized> {
fn new(play: gtk::Button, download: gtk::Button) -> Self {
play.hide();
download.hide();
DownloadPlay {
play,
download,
state: UnInitialized {},
}
}
}
#[derive(Debug, Clone)]
pub struct Progress<S> {
bar: gtk::ProgressBar,
cancel: gtk::Button,
local_size: gtk::Label,
prog_separator: gtk::Label,
state: S,
}
impl<S> Progress<S> {
fn into_shown(self) -> Progress<Shown> {
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<Hidden> {
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 {},
}
}
}
impl Progress<UnInitialized> {
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<X, Y, Z> {
dl: DownloadPlay<X>,
size: Size<Y>,
progress: Progress<Z>,
}
type New<Y: Visibility> = Media<Download, Y, Hidden>;
type Playable<Y: Visibility> = Media<Play, Y, Hidden>;
type InProgress = Media<Hidden, Shown, Shown>;
type MediaUnInitialized = Media<UnInitialized, UnInitialized, UnInitialized>;
impl From<New<Shown>> for InProgress {
fn from(f: New<Shown>) -> Self {
Media {
dl: f.dl.into_hidden(),
size: f.size.into_shown(),
progress: f.progress.into_shown(),
}
}
}
impl From<New<Hidden>> for InProgress {
fn from(f: New<Hidden>) -> Self {
Media {
dl: f.dl.into_hidden(),
size: f.size.set_size("Unkown"),
progress: f.progress.into_shown(),
}
}
}
impl From<Playable<Shown>> for InProgress {
fn from(f: Playable<Shown>) -> Self {
Media {
dl: f.dl.into_hidden(),
size: f.size.into_shown(),
progress: f.progress.into_shown(),
}
}
}
impl From<Playable<Hidden>> for InProgress {
fn from(f: Playable<Hidden>) -> Self {
Media {
dl: f.dl.into_hidden(),
size: f.size.set_size("Unkown"),
progress: f.progress.into_shown(),
}
}
}
impl From<MediaUnInitialized> for InProgress {
fn from(f: MediaUnInitialized) -> Self {
Media {
dl: f.dl.into_hidden(),
size: f.size.into_shown(),
progress: f.progress.into_shown(),
}
}
}
impl<Y: Visibility> From<Playable<Y>> for New<Y> {
fn from(f: Playable<Y>) -> Self {
Media {
dl: f.dl.into_fetchable(),
size: f.size,
progress: f.progress,
}
}
}
impl<Y: Visibility> From<New<Y>> for Playable<Y> {
fn from(f: New<Y>) -> Self {
Media {
dl: f.dl.into_playable(),
size: f.size,
progress: f.progress,
}
}
}
impl From<MediaUnInitialized> for New<Hidden> {
fn from(f: MediaUnInitialized) -> Self {
Media {
dl: f.dl.into_fetchable(),
size: f.size.into_hidden(),
progress: f.progress.into_hidden(),
}
}
}
impl From<MediaUnInitialized> for Playable<Hidden> {
fn from(f: MediaUnInitialized) -> Self {
Media {
dl: f.dl.into_playable(),
size: f.size.into_hidden(),
progress: f.progress.into_hidden(),
}
}
}
// impl<X, Y, Z> Media<X, Y, Z> {}
#[derive(Debug, Clone)]
pub enum MediaMachine {
UnInitialized(Media<UnInitialized, UnInitialized, UnInitialized>),
New(Media<Download, Shown, Hidden>),
NewWithoutSize(Media<Download, Hidden, Hidden>),
Playable(Media<Play, Shown, Hidden>),
PlayableWithoutSize(Media<Play, Hidden, Hidden>),
InProgress(Media<Hidden, Shown, Shown>),
}
impl MediaMachine {
pub fn new(
play: gtk::Button,
download: gtk::Button,
bar: gtk::ProgressBar,
cancel: gtk::Button,
total_size: gtk::Label,
local_size: gtk::Label,
separator: gtk::Label,
prog_separator: gtk::Label,
) -> Self {
let dl = DownloadPlay::<UnInitialized>::new(play, download);
let progress = Progress::<UnInitialized>::new(bar, cancel, local_size, prog_separator);
let size = Size::<UnInitialized>::new(total_size, separator);
MediaMachine::UnInitialized(Media { dl, progress, size })
}
pub fn determine_state(self, is_downloaded: bool, is_active: bool) -> Self {
use self::MediaMachine::*;
match (self, is_downloaded, is_active) {
(UnInitialized(val), _, true) => InProgress(val.into()),
(UnInitialized(val), false, false) => NewWithoutSize(val.into()),
(UnInitialized(val), true, false) => PlayableWithoutSize(val.into()),
(Playable(val), false, false) => New(val.into()),
(New(val), true, false) => Playable(val.into()),
(NewWithoutSize(val), true, false) => PlayableWithoutSize(val.into()),
(PlayableWithoutSize(val), false, false) => NewWithoutSize(val.into()),
(New(val), _, true) => InProgress(val.into()),
(Playable(val), _, true) => InProgress(val.into()),
(PlayableWithoutSize(val), _, true) => InProgress(val.into()),
(NewWithoutSize(val), _, true) => InProgress(val.into()),
(n @ New(_), false, false) => n,
(n @ NewWithoutSize(_), false, false) => n,
(n @ Playable(_), true, false) => n,
(n @ PlayableWithoutSize(_), true, false) => n,
(n @ InProgress(_), _, _) => n,
// _ => unimplemented!(),
}
}
pub fn set_size(self, bytes: Option<i32>) -> Self {
// use self::MediaMachine::*;
// use humansize::{file_size_opts as size_opts, FileSize};
// let size_helper = || -> Option<String> {
// let s = bytes?;
// if s == 0 {
// return None;
// }
// s.file_size(SIZE_OPTS.clone()).ok()
// };
// match (self, size_helper()) {
// (New(val), Some(s)) => unimplemented!(),
// _ => unimplemented!(),
// }
unimplemented!()
}
pub fn download_connect_clicked<F: Fn(&gtk::Button) + 'static>(
&self,
f: F,
) -> glib::SignalHandlerId {
use self::MediaMachine::*;
match *self {
New(ref val) => val.dl.connect_download(f),
NewWithoutSize(ref val) => val.dl.connect_download(f),
Playable(ref val) => val.dl.connect_download(f),
PlayableWithoutSize(ref val) => val.dl.connect_download(f),
InProgress(ref val) => val.dl.connect_download(f),
UnInitialized(ref val) => val.dl.connect_download(f),
}
}
pub fn play_connect_clicked<F: Fn(&gtk::Button) + 'static>(
&self,
f: F,
) -> glib::SignalHandlerId {
use self::MediaMachine::*;
match *self {
New(ref val) => val.dl.connect_play_button(f),
NewWithoutSize(ref val) => val.dl.connect_play_button(f),
Playable(ref val) => val.dl.connect_play_button(f),
PlayableWithoutSize(ref val) => val.dl.connect_play_button(f),
InProgress(ref val) => val.dl.connect_play_button(f),
UnInitialized(ref val) => val.dl.connect_play_button(f),
}
}
}