Merge branch 'episode-wiget-refactor' into 'master'
Episode wiget refactor See merge request World/hammond!38
This commit is contained in:
commit
4371512ba2
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -741,7 +741,6 @@ dependencies = [
|
||||
"reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"send-cell 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@ -1799,11 +1798,6 @@ name = "take"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "take_mut"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "tempdir"
|
||||
version = "0.3.7"
|
||||
@ -2403,7 +2397,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
|
||||
"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd"
|
||||
"checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5"
|
||||
"checksum take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
|
||||
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
||||
"checksum tendril 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9de21546595a0873061940d994bbbc5c35f024ae4fd61ec5c5b159115684f508"
|
||||
"checksum term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5e6b677dd1e8214ea1ef4297f85dbcbed8e8cdddb561040cc998ca2551c37561"
|
||||
|
||||
@ -21,7 +21,6 @@ send-cell = "0.1.3"
|
||||
url = "1.7.0"
|
||||
failure = "0.1.1"
|
||||
failure_derive = "0.1.1"
|
||||
take_mut = "0.2.2"
|
||||
regex = "1.0.0"
|
||||
reqwest = "0.8.5"
|
||||
serde_json = "1.0.17"
|
||||
|
||||
@ -41,37 +41,26 @@ Tobias Bernard
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<object class="GtkBox" id="info_container">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<object class="GtkLabel" id="title_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="title_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Episode Title</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="width_chars">55</property>
|
||||
<property name="single_line_mode">True</property>
|
||||
<property name="track_visited_links">False</property>
|
||||
<property name="lines">1</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<property name="label" translatable="yes">Episode Title</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="width_chars">55</property>
|
||||
<property name="single_line_mode">True</property>
|
||||
<property name="track_visited_links">False</property>
|
||||
<property name="lines">1</property>
|
||||
<property name="xalign">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
@ -213,16 +202,16 @@ Tobias Bernard
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<object class="GtkButtonBox" id="button_box">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="layout_style">center</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="cancel_button">
|
||||
<property name="label" translatable="yes">Cancel</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="no_show_all">True</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
<packing>
|
||||
@ -237,8 +226,8 @@ Tobias Bernard
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="no_show_all">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Download this episode</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<child>
|
||||
@ -252,8 +241,8 @@ Tobias Bernard
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">2</property>
|
||||
<property name="position">1</property>
|
||||
<property name="non_homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
@ -262,6 +251,7 @@ Tobias Bernard
|
||||
<property name="receives_default">True</property>
|
||||
<property name="no_show_all">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Play this episode</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
@ -274,15 +264,14 @@ Tobias Bernard
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">3</property>
|
||||
<property name="position">2</property>
|
||||
<property name="non_homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="padding">6</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
|
||||
@ -38,7 +38,6 @@ extern crate regex;
|
||||
extern crate reqwest;
|
||||
extern crate send_cell;
|
||||
extern crate serde_json;
|
||||
extern crate take_mut;
|
||||
extern crate url;
|
||||
|
||||
use log::Level;
|
||||
|
||||
@ -2,11 +2,12 @@ use glib;
|
||||
use gtk;
|
||||
use gtk::prelude::*;
|
||||
|
||||
use chrono;
|
||||
use chrono::prelude::*;
|
||||
use crossbeam_channel::Sender;
|
||||
use failure::Error;
|
||||
use humansize::FileSize;
|
||||
use humansize::{file_size_opts as size_opts, FileSize};
|
||||
use open;
|
||||
use take_mut;
|
||||
|
||||
use hammond_data::dbqueries;
|
||||
use hammond_data::utils::get_download_folder;
|
||||
@ -14,223 +15,417 @@ use hammond_data::EpisodeWidgetQuery;
|
||||
|
||||
use app::Action;
|
||||
use manager;
|
||||
use widgets::episode_states::*;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::ops::DerefMut;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Debug)]
|
||||
lazy_static! {
|
||||
static ref SIZE_OPTS: Arc<size_opts::FileSizeOpts> = {
|
||||
// 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,
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EpisodeWidget {
|
||||
pub container: gtk::Box,
|
||||
date: DateMachine,
|
||||
duration: DurationMachine,
|
||||
title: Rc<RefCell<TitleMachine>>,
|
||||
media: Rc<RefCell<MediaMachine>>,
|
||||
info: InfoLabels,
|
||||
buttons: Buttons,
|
||||
progressbar: gtk::ProgressBar,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct InfoLabels {
|
||||
container: gtk::Box,
|
||||
title: gtk::Label,
|
||||
date: gtk::Label,
|
||||
separator1: gtk::Label,
|
||||
duration: gtk::Label,
|
||||
separator2: gtk::Label,
|
||||
local_size: gtk::Label,
|
||||
size_separator: gtk::Label,
|
||||
total_size: gtk::Label,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Buttons {
|
||||
container: gtk::ButtonBox,
|
||||
play: gtk::Button,
|
||||
download: gtk::Button,
|
||||
cancel: gtk::Button,
|
||||
}
|
||||
|
||||
impl InfoLabels {
|
||||
fn init(&self, episode: &EpisodeWidgetQuery) {
|
||||
// Set the title label state.
|
||||
self.set_title(episode);
|
||||
|
||||
// Set the date label.
|
||||
self.set_date(episode.epoch());
|
||||
|
||||
// Set the duaration label.
|
||||
self.set_duration(episode.duration());
|
||||
|
||||
// Set the total_size label.
|
||||
self.set_size(episode.length())
|
||||
}
|
||||
|
||||
fn set_title(&self, episode: &EpisodeWidgetQuery) {
|
||||
self.title.set_text(episode.title());
|
||||
|
||||
if episode.played().is_some() {
|
||||
self.title
|
||||
.get_style_context()
|
||||
.map(|c| c.add_class("dim-label"));
|
||||
} else {
|
||||
self.title
|
||||
.get_style_context()
|
||||
.map(|c| c.remove_class("dim-label"));
|
||||
}
|
||||
}
|
||||
|
||||
// Set the date label of the episode widget.
|
||||
fn set_date(&self, epoch: i32) {
|
||||
lazy_static! {
|
||||
static ref NOW: DateTime<Utc> = Utc::now();
|
||||
};
|
||||
|
||||
let ts = Utc.timestamp(i64::from(epoch), 0);
|
||||
|
||||
// If the episode is from a different year, print year as well
|
||||
if NOW.year() != ts.year() {
|
||||
self.date.set_text(ts.format("%e %b %Y").to_string().trim());
|
||||
// Else omit the year from the label
|
||||
} else {
|
||||
self.date.set_text(ts.format("%e %b").to_string().trim());
|
||||
}
|
||||
}
|
||||
|
||||
// Set the duration label of the episode widget.
|
||||
fn set_duration(&self, seconds: Option<i32>) {
|
||||
// If lenght is provided
|
||||
if let Some(s) = seconds {
|
||||
// Convert seconds to minutes
|
||||
let minutes = chrono::Duration::seconds(s.into()).num_minutes();
|
||||
// If the lenght is 1 or more minutes
|
||||
if minutes != 0 {
|
||||
// Set the label and show them.
|
||||
self.duration.set_text(&format!("{} min", minutes));
|
||||
self.duration.show();
|
||||
self.separator1.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Else hide the labels
|
||||
self.separator1.hide();
|
||||
self.duration.hide();
|
||||
}
|
||||
|
||||
// Set the size label of the episode widget.
|
||||
fn set_size(&self, bytes: Option<i32>) {
|
||||
// Convert the bytes to a String label
|
||||
let size = || -> Option<String> {
|
||||
let s = bytes?;
|
||||
if s == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
s.file_size(SIZE_OPTS.clone()).ok()
|
||||
};
|
||||
|
||||
if let Some(s) = size() {
|
||||
self.total_size.set_text(&s);
|
||||
self.total_size.show();
|
||||
self.separator2.show();
|
||||
} else {
|
||||
self.total_size.hide();
|
||||
self.separator2.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EpisodeWidget {
|
||||
fn default() -> Self {
|
||||
let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/episode_widget.ui");
|
||||
|
||||
let container: gtk::Box = builder.get_object("episode_container").unwrap();
|
||||
let progress: gtk::ProgressBar = builder.get_object("progress_bar").unwrap();
|
||||
let container = builder.get_object("episode_container").unwrap();
|
||||
let progressbar = builder.get_object("progress_bar").unwrap();
|
||||
|
||||
let download: gtk::Button = builder.get_object("download_button").unwrap();
|
||||
let play: gtk::Button = builder.get_object("play_button").unwrap();
|
||||
let cancel: gtk::Button = builder.get_object("cancel_button").unwrap();
|
||||
let buttons_container = builder.get_object("button_box").unwrap();
|
||||
let download = builder.get_object("download_button").unwrap();
|
||||
let play = builder.get_object("play_button").unwrap();
|
||||
let cancel = builder.get_object("cancel_button").unwrap();
|
||||
|
||||
let title: gtk::Label = builder.get_object("title_label").unwrap();
|
||||
let date: gtk::Label = builder.get_object("date_label").unwrap();
|
||||
let duration: gtk::Label = builder.get_object("duration_label").unwrap();
|
||||
let local_size: gtk::Label = builder.get_object("local_size").unwrap();
|
||||
let total_size: gtk::Label = builder.get_object("total_size").unwrap();
|
||||
let info_container = builder.get_object("info_container").unwrap();
|
||||
let title = builder.get_object("title_label").unwrap();
|
||||
let date = builder.get_object("date_label").unwrap();
|
||||
let duration = builder.get_object("duration_label").unwrap();
|
||||
let local_size = builder.get_object("local_size").unwrap();
|
||||
let total_size = builder.get_object("total_size").unwrap();
|
||||
|
||||
let separator1: gtk::Label = builder.get_object("separator1").unwrap();
|
||||
let separator2: gtk::Label = builder.get_object("separator2").unwrap();
|
||||
let prog_separator: gtk::Label = builder.get_object("prog_separator").unwrap();
|
||||
let separator1 = builder.get_object("separator1").unwrap();
|
||||
let separator2 = builder.get_object("separator2").unwrap();
|
||||
|
||||
let date_machine = DateMachine::new(date, 0);
|
||||
let dur_machine = DurationMachine::new(duration, separator1, None);
|
||||
let title_machine = Rc::new(RefCell::new(TitleMachine::new(title, false)));
|
||||
let media = MediaMachine::new(
|
||||
play,
|
||||
download,
|
||||
progress,
|
||||
cancel,
|
||||
total_size,
|
||||
local_size,
|
||||
separator2,
|
||||
prog_separator,
|
||||
);
|
||||
let media_machine = Rc::new(RefCell::new(media));
|
||||
let size_separator = builder.get_object("prog_separator").unwrap();
|
||||
|
||||
EpisodeWidget {
|
||||
info: InfoLabels {
|
||||
container: info_container,
|
||||
title,
|
||||
date,
|
||||
separator1,
|
||||
duration,
|
||||
separator2,
|
||||
local_size,
|
||||
total_size,
|
||||
size_separator,
|
||||
},
|
||||
buttons: Buttons {
|
||||
container: buttons_container,
|
||||
play,
|
||||
download,
|
||||
cancel,
|
||||
},
|
||||
progressbar,
|
||||
container,
|
||||
title: title_machine,
|
||||
duration: dur_machine,
|
||||
date: date_machine,
|
||||
media: media_machine,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EpisodeWidget {
|
||||
pub fn new(episode: EpisodeWidgetQuery, sender: &Sender<Action>) -> EpisodeWidget {
|
||||
let mut widget = EpisodeWidget::default();
|
||||
widget.init(episode, sender);
|
||||
pub fn new(episode: &EpisodeWidgetQuery, sender: &Sender<Action>) -> Rc<Self> {
|
||||
let widget = Rc::new(Self::default());
|
||||
widget.info.init(episode);
|
||||
Self::determine_buttons_state(&widget, episode, sender)
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.ok();
|
||||
widget
|
||||
}
|
||||
|
||||
fn init(&mut self, episode: EpisodeWidgetQuery, sender: &Sender<Action>) {
|
||||
// Set the date label.
|
||||
self.set_date(episode.epoch());
|
||||
// fn init(widget: Rc<Self>, sender: &Sender<Action>) {}
|
||||
|
||||
// Set the title label state.
|
||||
self.set_title(&episode);
|
||||
// InProgress State:
|
||||
// * Show ProgressBar and Cancel Button.
|
||||
// * Show `total_size`, `local_size` labels and `size_separator`.
|
||||
// * Hide Download and Play Buttons
|
||||
fn state_prog(&self) {
|
||||
self.progressbar.show();
|
||||
self.buttons.cancel.show();
|
||||
|
||||
// Set the duaration label.
|
||||
self.set_duration(episode.duration());
|
||||
self.info.total_size.show();
|
||||
self.info.local_size.show();
|
||||
self.info.size_separator.show();
|
||||
|
||||
// Determine what the state of the media widgets should be.
|
||||
determine_media_state(&self.media, &episode)
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.map_err(|_| error!("Could not determine Media State"))
|
||||
.ok();
|
||||
|
||||
let episode = Arc::new(Mutex::new(episode));
|
||||
self.connect_buttons(&episode, sender);
|
||||
self.buttons.play.hide();
|
||||
self.buttons.download.hide();
|
||||
}
|
||||
|
||||
fn connect_buttons(&self, episode: &Arc<Mutex<EpisodeWidgetQuery>>, sender: &Sender<Action>) {
|
||||
let title = self.title.clone();
|
||||
if let Ok(media) = self.media.try_borrow_mut() {
|
||||
media.play_connect_clicked(clone!(episode, sender => move |_| {
|
||||
if let Ok(mut ep) = episode.lock() {
|
||||
on_play_bttn_clicked(&mut ep, &title, &sender)
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.ok();
|
||||
// Playable State:
|
||||
// * Hide ProgressBar and Cancel, Download Buttons.
|
||||
// * Hide `local_size` labels and `size_separator`.
|
||||
// * Show Play Button and `total_size` label
|
||||
fn state_playable(&self) {
|
||||
self.progressbar.hide();
|
||||
self.buttons.cancel.hide();
|
||||
self.buttons.download.hide();
|
||||
self.info.local_size.hide();
|
||||
self.info.size_separator.hide();
|
||||
|
||||
self.info.total_size.show();
|
||||
self.buttons.play.show();
|
||||
}
|
||||
|
||||
// ToDownload State:
|
||||
// * Hide ProgressBar and Cancel, Play Buttons.
|
||||
// * Hide `local_size` labels and `size_separator`.
|
||||
// * Show Download Button
|
||||
// * Determine `total_size` label state (Comes from `episode.lenght`).
|
||||
fn state_download(&self) {
|
||||
self.progressbar.hide();
|
||||
self.buttons.cancel.hide();
|
||||
self.buttons.play.hide();
|
||||
|
||||
self.info.local_size.hide();
|
||||
self.info.size_separator.hide();
|
||||
|
||||
self.buttons.download.show();
|
||||
}
|
||||
|
||||
fn update_progress(&self, local_size: &str, fraction: f64) {
|
||||
self.info.local_size.set_text(local_size);
|
||||
self.progressbar.set_fraction(fraction);
|
||||
}
|
||||
|
||||
/// Change the state of the `EpisodeWidget`.
|
||||
///
|
||||
/// Function Flowchart:
|
||||
///
|
||||
/// ------------------- --------------
|
||||
/// | Is the Episode | YES | State: |
|
||||
/// | currently being | ----> | InProgress |
|
||||
/// | downloaded? | | |
|
||||
/// ------------------- --------------
|
||||
/// |
|
||||
/// | NO
|
||||
/// |
|
||||
/// \_/
|
||||
/// ------------------- --------------
|
||||
/// | is the episode | YES | State: |
|
||||
/// | downloaded | ----> | Playable |
|
||||
/// | already? | | |
|
||||
/// ------------------- --------------
|
||||
/// |
|
||||
/// | NO
|
||||
/// |
|
||||
/// \_/
|
||||
/// -------------------
|
||||
/// | State: |
|
||||
/// | ToDownload |
|
||||
/// -------------------
|
||||
fn determine_buttons_state(
|
||||
widget: &Rc<Self>,
|
||||
episode: &EpisodeWidgetQuery,
|
||||
sender: &Sender<Action>,
|
||||
) -> Result<(), Error> {
|
||||
// Reset the buttons state no matter the glade file.
|
||||
// This is just to make it easier to port to relm in the future.
|
||||
widget.buttons.cancel.hide();
|
||||
widget.buttons.play.hide();
|
||||
widget.buttons.download.hide();
|
||||
|
||||
// Check if the episode is being downloaded
|
||||
let id = episode.rowid();
|
||||
let active_dl = move || -> Result<Option<_>, Error> {
|
||||
let m = manager::ACTIVE_DOWNLOADS
|
||||
.read()
|
||||
.map_err(|_| format_err!("Failed to get a lock on the mutex."))?;
|
||||
|
||||
Ok(m.get(&id).cloned())
|
||||
};
|
||||
|
||||
// State: InProggress
|
||||
if let Some(prog) = active_dl()? {
|
||||
// set a callback that will update the state when the download finishes
|
||||
let callback = clone!(widget, sender => move || {
|
||||
if let Ok(guard) = manager::ACTIVE_DOWNLOADS.read() {
|
||||
if !guard.contains_key(&id) {
|
||||
if let Ok(ep) = dbqueries::get_episode_widget_from_rowid(id) {
|
||||
Self::determine_buttons_state(&widget, &ep, &sender)
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.ok();
|
||||
|
||||
return glib::Continue(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::Continue(true)
|
||||
});
|
||||
gtk::timeout_add(250, callback);
|
||||
|
||||
// Wire the cancel button
|
||||
widget
|
||||
.buttons
|
||||
.cancel
|
||||
.connect_clicked(clone!(prog, widget, sender => move |_| {
|
||||
// Cancel the download
|
||||
if let Ok(mut m) = prog.lock() {
|
||||
m.cancel();
|
||||
}
|
||||
|
||||
// Cancel is not instant so we have to wait a bit
|
||||
timeout_add(50, clone!(widget, sender => move || {
|
||||
if let Ok(thing) = active_dl() {
|
||||
if thing.is_none() {
|
||||
// Recalculate the widget state
|
||||
dbqueries::get_episode_widget_from_rowid(id)
|
||||
.map_err(From::from)
|
||||
.and_then(|ep| Self::determine_buttons_state(&widget, &ep, &sender))
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.ok();
|
||||
|
||||
return glib::Continue(false)
|
||||
}
|
||||
}
|
||||
|
||||
glib::Continue(true)
|
||||
}));
|
||||
}));
|
||||
|
||||
let media_machine = self.media.clone();
|
||||
media.download_connect_clicked(clone!(media_machine, episode, sender => move |dl| {
|
||||
// Setup a callback that will update the total_size label
|
||||
// with the http ContentLength header number rather than
|
||||
// relying to the RSS feed.
|
||||
update_total_size_callback(&widget, &prog);
|
||||
|
||||
// Setup a callback that will update the progress bar.
|
||||
update_progressbar_callback(&widget, &prog, id);
|
||||
|
||||
// Change the widget layout/state
|
||||
widget.state_prog();
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// State: Playable
|
||||
if episode.local_uri().is_some() {
|
||||
// Change the widget layout/state
|
||||
widget.state_playable();
|
||||
|
||||
// Wire the play button
|
||||
widget
|
||||
.buttons
|
||||
.play
|
||||
.connect_clicked(clone!(widget, sender => move |_| {
|
||||
if let Ok(mut ep) = dbqueries::get_episode_widget_from_rowid(id) {
|
||||
on_play_bttn_clicked(&widget, &mut ep, &sender)
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.ok();
|
||||
}
|
||||
}));
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// State: ToDownload
|
||||
// Wire the download button
|
||||
widget
|
||||
.buttons
|
||||
.download
|
||||
.connect_clicked(clone!(widget, sender => move |dl| {
|
||||
// Make the button insensitive so it won't be pressed twice
|
||||
dl.set_sensitive(false);
|
||||
if let Ok(ep) = episode.lock() {
|
||||
if let Ok(ep) = dbqueries::get_episode_widget_from_rowid(id) {
|
||||
on_download_clicked(&ep, &sender)
|
||||
.and_then(|_| {
|
||||
info!("Donwload started succesfully.");
|
||||
determine_media_state(&media_machine, &ep)
|
||||
})
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.map_err(|_| error!("Could not determine Media State"))
|
||||
.ok();
|
||||
Self::determine_buttons_state(&widget, &ep, &sender)
|
||||
})
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.ok();
|
||||
}
|
||||
|
||||
// Restore sensitivity after operations above complete
|
||||
dl.set_sensitive(true);
|
||||
}));
|
||||
}
|
||||
|
||||
// Change the widget state into `ToDownload`
|
||||
widget.state_download();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Determine the title state.
|
||||
fn set_title(&mut self, episode: &EpisodeWidgetQuery) {
|
||||
let mut machine = self.title.borrow_mut();
|
||||
machine.set_title(episode.title());
|
||||
take_mut::take(machine.deref_mut(), |title| {
|
||||
title.determine_state(episode.played().is_some())
|
||||
});
|
||||
}
|
||||
|
||||
/// Set the date label depending on the current time.
|
||||
fn set_date(&mut self, epoch: i32) {
|
||||
let machine = &mut self.date;
|
||||
take_mut::take(machine, |date| date.determine_state(i64::from(epoch)));
|
||||
}
|
||||
|
||||
/// Set the duration label.
|
||||
fn set_duration(&mut self, seconds: Option<i32>) {
|
||||
let machine = &mut self.duration;
|
||||
take_mut::take(machine, |duration| duration.determine_state(seconds));
|
||||
}
|
||||
}
|
||||
|
||||
fn determine_media_state(
|
||||
media_machine: &Rc<RefCell<MediaMachine>>,
|
||||
episode: &EpisodeWidgetQuery,
|
||||
) -> Result<(), Error> {
|
||||
let id = episode.rowid();
|
||||
let active_dl = || -> Result<Option<_>, Error> {
|
||||
let m = manager::ACTIVE_DOWNLOADS
|
||||
.read()
|
||||
.map_err(|_| format_err!("Failed to get a lock on the mutex."))?;
|
||||
|
||||
Ok(m.get(&id).cloned())
|
||||
}()?;
|
||||
|
||||
let mut lock = media_machine.try_borrow_mut()?;
|
||||
take_mut::take(lock.deref_mut(), |media| {
|
||||
media.determine_state(
|
||||
episode.length(),
|
||||
active_dl.is_some(),
|
||||
episode.local_uri().is_some(),
|
||||
)
|
||||
});
|
||||
|
||||
// Show or hide the play/delete/download buttons upon widget initialization.
|
||||
if let Some(prog) = active_dl {
|
||||
// set a callback that will update the state when the download finishes
|
||||
let id = episode.rowid();
|
||||
let callback = clone!(media_machine => move || {
|
||||
if let Ok(guard) = manager::ACTIVE_DOWNLOADS.read() {
|
||||
if !guard.contains_key(&id) {
|
||||
if let Ok(ep) = dbqueries::get_episode_widget_from_rowid(id) {
|
||||
determine_media_state(&media_machine, &ep)
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.map_err(|_| error!("Could not determine Media State"))
|
||||
.ok();
|
||||
|
||||
return glib::Continue(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::Continue(true)
|
||||
});
|
||||
gtk::timeout_add(250, callback);
|
||||
|
||||
lock.cancel_connect_clicked(clone!(prog, media_machine => move |_| {
|
||||
if let Ok(mut m) = prog.lock() {
|
||||
m.cancel();
|
||||
}
|
||||
|
||||
if let Ok(mut lock) = media_machine.try_borrow_mut() {
|
||||
if let Ok(episode) = dbqueries::get_episode_widget_from_rowid(id) {
|
||||
take_mut::take(lock.deref_mut(), |media| {
|
||||
media.determine_state(
|
||||
episode.length(),
|
||||
false,
|
||||
episode.local_uri().is_some(),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}));
|
||||
drop(lock);
|
||||
|
||||
// Setup a callback that will update the progress bar.
|
||||
update_progressbar_callback(&prog, &media_machine, id);
|
||||
|
||||
// Setup a callback that will update the total_size label
|
||||
// with the http ContentLength header number rather than
|
||||
// relying to the RSS feed.
|
||||
update_total_size_callback(&prog, &media_machine);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_download_clicked(ep: &EpisodeWidgetQuery, sender: &Sender<Action>) -> Result<(), Error> {
|
||||
@ -241,26 +436,23 @@ fn on_download_clicked(ep: &EpisodeWidgetQuery, sender: &Sender<Action>) -> Resu
|
||||
manager::add(ep.rowid(), download_fold)?;
|
||||
|
||||
// Update Views
|
||||
sender.send(Action::RefreshEpisodesViewBGR)?;
|
||||
|
||||
Ok(())
|
||||
sender
|
||||
.send(Action::RefreshEpisodesViewBGR)
|
||||
.map_err(From::from)
|
||||
}
|
||||
|
||||
fn on_play_bttn_clicked(
|
||||
widget: &Rc<EpisodeWidget>,
|
||||
episode: &mut EpisodeWidgetQuery,
|
||||
title: &Rc<RefCell<TitleMachine>>,
|
||||
sender: &Sender<Action>,
|
||||
) -> Result<(), Error> {
|
||||
open_uri(episode.rowid())?;
|
||||
episode.set_played_now()?;
|
||||
|
||||
let mut machine = title.try_borrow_mut()?;
|
||||
take_mut::take(machine.deref_mut(), |title| {
|
||||
title.determine_state(episode.played().is_some())
|
||||
});
|
||||
|
||||
sender.send(Action::RefreshEpisodesViewBGR)?;
|
||||
Ok(())
|
||||
widget.info.set_title(&episode);
|
||||
sender
|
||||
.send(Action::RefreshEpisodesViewBGR)
|
||||
.map_err(From::from)
|
||||
}
|
||||
|
||||
fn open_uri(rowid: i32) -> Result<(), Error> {
|
||||
@ -281,21 +473,21 @@ fn open_uri(rowid: i32) -> Result<(), Error> {
|
||||
#[inline]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(if_same_then_else))]
|
||||
fn update_progressbar_callback(
|
||||
widget: &Rc<EpisodeWidget>,
|
||||
prog: &Arc<Mutex<manager::Progress>>,
|
||||
media: &Rc<RefCell<MediaMachine>>,
|
||||
episode_rowid: i32,
|
||||
) {
|
||||
let callback = clone!(prog, media => move || {
|
||||
progress_bar_helper(&prog, &media, episode_rowid)
|
||||
let callback = clone!(widget, prog => move || {
|
||||
progress_bar_helper(&widget, &prog, episode_rowid)
|
||||
.unwrap_or(glib::Continue(false))
|
||||
});
|
||||
timeout_add(300, callback);
|
||||
timeout_add(150, callback);
|
||||
}
|
||||
|
||||
#[allow(if_same_then_else)]
|
||||
fn progress_bar_helper(
|
||||
widget: &Rc<EpisodeWidget>,
|
||||
prog: &Arc<Mutex<manager::Progress>>,
|
||||
media: &Rc<RefCell<MediaMachine>>,
|
||||
episode_rowid: i32,
|
||||
) -> Result<glib::Continue, Error> {
|
||||
let (fraction, downloaded) = {
|
||||
@ -313,9 +505,7 @@ fn progress_bar_helper(
|
||||
.file_size(SIZE_OPTS.clone())
|
||||
.map_err(|err| format_err!("{}", err))?;
|
||||
|
||||
if let Ok(mut m) = media.try_borrow_mut() {
|
||||
m.update_progress(&size, fraction);
|
||||
}
|
||||
widget.update_progress(&size, fraction);
|
||||
}
|
||||
|
||||
// info!("Fraction: {}", progress_bar.get_fraction());
|
||||
@ -342,19 +532,16 @@ fn progress_bar_helper(
|
||||
// with the http ContentLength header number rather than
|
||||
// relying to the RSS feed.
|
||||
#[inline]
|
||||
fn update_total_size_callback(
|
||||
prog: &Arc<Mutex<manager::Progress>>,
|
||||
media: &Rc<RefCell<MediaMachine>>,
|
||||
) {
|
||||
let callback = clone!(prog, media => move || {
|
||||
total_size_helper(&prog, &media).unwrap_or(glib::Continue(true))
|
||||
fn update_total_size_callback(widget: &Rc<EpisodeWidget>, prog: &Arc<Mutex<manager::Progress>>) {
|
||||
let callback = clone!(prog, widget => move || {
|
||||
total_size_helper(&widget, &prog).unwrap_or(glib::Continue(true))
|
||||
});
|
||||
timeout_add(500, callback);
|
||||
}
|
||||
|
||||
fn total_size_helper(
|
||||
widget: &Rc<EpisodeWidget>,
|
||||
prog: &Arc<Mutex<manager::Progress>>,
|
||||
media: &Rc<RefCell<MediaMachine>>,
|
||||
) -> Result<glib::Continue, Error> {
|
||||
// Get the total_bytes.
|
||||
let total_bytes = {
|
||||
@ -367,11 +554,7 @@ fn total_size_helper(
|
||||
debug!("Total Size: {}", total_bytes);
|
||||
if total_bytes != 0 {
|
||||
// Update the total_size label
|
||||
if let Ok(mut m) = media.try_borrow_mut() {
|
||||
take_mut::take(m.deref_mut(), |machine| {
|
||||
machine.set_size(Some(total_bytes as i32))
|
||||
});
|
||||
}
|
||||
widget.info.set_size(Some(total_bytes as i32));
|
||||
|
||||
// Do not call again the callback
|
||||
Ok(glib::Continue(false))
|
||||
|
||||
@ -1,879 +0,0 @@
|
||||
// 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<size_opts::FileSizeOpts> = {
|
||||
// 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> = 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<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 hurt 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: GreyedOut {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: Normal {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 Usual;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct YearShown;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Date<S> {
|
||||
date: gtk::Label,
|
||||
epoch: i64,
|
||||
state: S,
|
||||
}
|
||||
|
||||
impl<S> Date<S> {
|
||||
fn into_usual(self, epoch: i64) -> Date<Usual> {
|
||||
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<YearShown> {
|
||||
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<UnInitialized> {
|
||||
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<UnInitialized>),
|
||||
Usual(Date<Usual>),
|
||||
WithYear(Date<YearShown>),
|
||||
}
|
||||
|
||||
impl DateMachine {
|
||||
pub fn new(label: gtk::Label, epoch: i64) -> Self {
|
||||
let m = DateMachine::UnInitialized(Date::<UnInitialized>::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<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: Shown {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: Hidden {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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) {
|
||||
(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<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.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<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 download_connect_clicked<F: Fn(>k::Button) + 'static>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> glib::SignalHandlerId {
|
||||
self.download.connect_clicked(f)
|
||||
}
|
||||
|
||||
fn play_connect_clicked<F: Fn(>k::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 {},
|
||||
}
|
||||
}
|
||||
|
||||
#[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<F: Fn(>k::Button) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.cancel.connect_clicked(f)
|
||||
}
|
||||
}
|
||||
|
||||
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> = Media<Download, Y, Hidden>;
|
||||
type Playable<Y> = Media<Play, Y, Hidden>;
|
||||
type InProgress = Media<Hidden, Shown, Shown>;
|
||||
|
||||
impl<X, Y, Z> Media<X, Y, Z> {
|
||||
fn set_size(self, s: &str) -> Media<X, Shown, Z> {
|
||||
Media {
|
||||
dl: self.dl,
|
||||
size: self.size.set_size(s),
|
||||
progress: self.progress,
|
||||
}
|
||||
}
|
||||
|
||||
fn hide_size(self) -> Media<X, Hidden, Z> {
|
||||
Media {
|
||||
dl: self.dl,
|
||||
size: self.size.into_hidden(),
|
||||
progress: self.progress,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_new(self, size: &str) -> New<Shown> {
|
||||
Media {
|
||||
dl: self.dl.into_fetchable(),
|
||||
size: self.size.set_size(size),
|
||||
progress: self.progress.into_hidden(),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_new_without(self) -> New<Hidden> {
|
||||
Media {
|
||||
dl: self.dl.into_fetchable(),
|
||||
size: self.size.into_hidden(),
|
||||
progress: self.progress.into_hidden(),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_playable(self, size: &str) -> Playable<Shown> {
|
||||
Media {
|
||||
dl: self.dl.into_playable(),
|
||||
size: self.size.set_size(size),
|
||||
progress: self.progress.into_hidden(),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_playable_without(self) -> Playable<Hidden> {
|
||||
Media {
|
||||
dl: self.dl.into_playable(),
|
||||
size: self.size.into_hidden(),
|
||||
progress: self.progress.into_hidden(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<X, Z> Media<X, Shown, Z> {
|
||||
fn into_progress(self) -> InProgress {
|
||||
Media {
|
||||
dl: self.dl.into_hidden(),
|
||||
size: self.size.into_shown(),
|
||||
progress: self.progress.into_shown(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<X, Z> Media<X, Hidden, Z> {
|
||||
fn into_progress(self) -> InProgress {
|
||||
Media {
|
||||
dl: self.dl.into_hidden(),
|
||||
size: self.size.set_size("Unkown"),
|
||||
progress: self.progress.into_shown(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<X, Z> Media<X, UnInitialized, Z> {
|
||||
fn into_progress(self, size: Option<String>) -> InProgress {
|
||||
if let Some(s) = size {
|
||||
Media {
|
||||
dl: self.dl.into_hidden(),
|
||||
size: self.size.set_size(&s),
|
||||
progress: self.progress.into_shown(),
|
||||
}
|
||||
} else {
|
||||
Media {
|
||||
dl: self.dl.into_hidden(),
|
||||
size: self.size.set_size("Unkown"),
|
||||
progress: self.progress.into_shown(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InProgress {
|
||||
#[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.progress.update_progress(local_size, fraction)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ButtonsState {
|
||||
New(Media<Download, Shown, Hidden>),
|
||||
NewWithoutSize(Media<Download, Hidden, Hidden>),
|
||||
Playable(Media<Play, Shown, Hidden>),
|
||||
PlayableWithoutSize(Media<Play, Hidden, Hidden>),
|
||||
}
|
||||
|
||||
impl ButtonsState {
|
||||
pub fn determine_state(self, size: Option<String>, is_downloaded: bool) -> Self {
|
||||
use self::ButtonsState::*;
|
||||
|
||||
match (self, size, is_downloaded) {
|
||||
// From whatever to New
|
||||
(New(m), Some(s), false) => New(m.into_new(&s)),
|
||||
(Playable(m), Some(s), false) => New(m.into_new(&s)),
|
||||
|
||||
(NewWithoutSize(m), Some(s), false) => New(m.into_new(&s)),
|
||||
(PlayableWithoutSize(m), Some(s), false) => New(m.into_new(&s)),
|
||||
|
||||
// From whatever to Playable
|
||||
(New(m), Some(s), true) => Playable(m.into_playable(&s)),
|
||||
(Playable(m), Some(s), true) => Playable(m.into_playable(&s)),
|
||||
|
||||
(NewWithoutSize(m), Some(s), true) => Playable(m.into_playable(&s)),
|
||||
(PlayableWithoutSize(m), Some(s), true) => Playable(m.into_playable(&s)),
|
||||
|
||||
// From whatever to NewWithoutSize
|
||||
(New(m), None, false) => NewWithoutSize(m.hide_size()),
|
||||
(Playable(m), None, false) => NewWithoutSize(m.into_new_without()),
|
||||
|
||||
(b @ NewWithoutSize(_), None, false) => b,
|
||||
(PlayableWithoutSize(m), None, false) => NewWithoutSize(m.into_new_without()),
|
||||
|
||||
// From whatever to PlayableWithoutSize
|
||||
(New(m), None, true) => PlayableWithoutSize(m.into_playable_without()),
|
||||
(Playable(m), None, true) => PlayableWithoutSize(m.hide_size()),
|
||||
|
||||
(NewWithoutSize(val), None, true) => PlayableWithoutSize(val.into_playable_without()),
|
||||
(b @ PlayableWithoutSize(_), None, true) => b,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_progress(self) -> InProgress {
|
||||
use self::ButtonsState::*;
|
||||
|
||||
match self {
|
||||
New(m) => m.into_progress(),
|
||||
Playable(m) => m.into_progress(),
|
||||
NewWithoutSize(m) => m.into_progress(),
|
||||
PlayableWithoutSize(m) => m.into_progress(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_size(self, size: Option<String>) -> Self {
|
||||
use self::ButtonsState::*;
|
||||
|
||||
match (self, size) {
|
||||
(New(m), Some(s)) => New(m.set_size(&s)),
|
||||
(New(m), None) => NewWithoutSize(m.hide_size()),
|
||||
(Playable(m), Some(s)) => Playable(m.set_size(&s)),
|
||||
(Playable(m), None) => PlayableWithoutSize(m.hide_size()),
|
||||
(bttn @ NewWithoutSize(_), None) => bttn,
|
||||
(bttn @ PlayableWithoutSize(_), None) => bttn,
|
||||
(NewWithoutSize(m), Some(s)) => New(m.into_new(&s)),
|
||||
(PlayableWithoutSize(m), Some(s)) => Playable(m.into_playable(&s)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn download_connect_clicked<F: Fn(>k::Button) + 'static>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> glib::SignalHandlerId {
|
||||
use self::ButtonsState::*;
|
||||
|
||||
match *self {
|
||||
New(ref val) => val.dl.download_connect_clicked(f),
|
||||
NewWithoutSize(ref val) => val.dl.download_connect_clicked(f),
|
||||
Playable(ref val) => val.dl.download_connect_clicked(f),
|
||||
PlayableWithoutSize(ref val) => val.dl.download_connect_clicked(f),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_connect_clicked<F: Fn(>k::Button) + 'static>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> glib::SignalHandlerId {
|
||||
use self::ButtonsState::*;
|
||||
|
||||
match *self {
|
||||
New(ref val) => val.dl.play_connect_clicked(f),
|
||||
NewWithoutSize(ref val) => val.dl.play_connect_clicked(f),
|
||||
Playable(ref val) => val.dl.play_connect_clicked(f),
|
||||
PlayableWithoutSize(ref val) => val.dl.play_connect_clicked(f),
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_connect_clicked<F: Fn(>k::Button) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
use self::ButtonsState::*;
|
||||
|
||||
match *self {
|
||||
New(ref val) => val.progress.cancel_connect_clicked(f),
|
||||
NewWithoutSize(ref val) => val.progress.cancel_connect_clicked(f),
|
||||
Playable(ref val) => val.progress.cancel_connect_clicked(f),
|
||||
PlayableWithoutSize(ref val) => val.progress.cancel_connect_clicked(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MediaMachine {
|
||||
UnInitialized(Media<UnInitialized, UnInitialized, UnInitialized>),
|
||||
Initialized(ButtonsState),
|
||||
InProgress(Media<Hidden, Shown, Shown>),
|
||||
}
|
||||
|
||||
impl MediaMachine {
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))]
|
||||
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 download_connect_clicked<F: Fn(>k::Button) + 'static>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> glib::SignalHandlerId {
|
||||
use self::MediaMachine::*;
|
||||
|
||||
match *self {
|
||||
UnInitialized(ref val) => val.dl.download_connect_clicked(f),
|
||||
Initialized(ref val) => val.download_connect_clicked(f),
|
||||
InProgress(ref val) => val.dl.download_connect_clicked(f),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_connect_clicked<F: Fn(>k::Button) + 'static>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> glib::SignalHandlerId {
|
||||
use self::MediaMachine::*;
|
||||
|
||||
match *self {
|
||||
UnInitialized(ref val) => val.dl.play_connect_clicked(f),
|
||||
Initialized(ref val) => val.play_connect_clicked(f),
|
||||
InProgress(ref val) => val.dl.play_connect_clicked(f),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cancel_connect_clicked<F: Fn(>k::Button) + 'static>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> glib::SignalHandlerId {
|
||||
use self::MediaMachine::*;
|
||||
|
||||
match *self {
|
||||
UnInitialized(ref val) => val.progress.cancel_connect_clicked(f),
|
||||
Initialized(ref val) => val.cancel_connect_clicked(f),
|
||||
InProgress(ref val) => val.progress.cancel_connect_clicked(f),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn determine_state(self, bytes: Option<i32>, is_active: bool, is_downloaded: bool) -> Self {
|
||||
use self::ButtonsState::*;
|
||||
use self::MediaMachine::*;
|
||||
|
||||
match (self, size_helper(bytes), is_downloaded, is_active) {
|
||||
(UnInitialized(m), s, _, true) => InProgress(m.into_progress(s)),
|
||||
|
||||
// Into New
|
||||
(UnInitialized(m), Some(s), false, false) => Initialized(New(m.into_new(&s))),
|
||||
(UnInitialized(m), None, false, false) => {
|
||||
Initialized(NewWithoutSize(m.into_new_without()))
|
||||
}
|
||||
|
||||
// Into Playable
|
||||
(UnInitialized(m), Some(s), true, false) => Initialized(Playable(m.into_playable(&s))),
|
||||
(UnInitialized(m), None, true, false) => {
|
||||
Initialized(PlayableWithoutSize(m.into_playable_without()))
|
||||
}
|
||||
|
||||
(Initialized(bttn), s, dl, false) => Initialized(bttn.determine_state(s, dl)),
|
||||
(Initialized(bttn), _, _, true) => InProgress(bttn.into_progress()),
|
||||
|
||||
// Into New
|
||||
(InProgress(m), Some(s), false, false) => Initialized(New(m.into_new(&s))),
|
||||
(InProgress(m), None, false, false) => {
|
||||
Initialized(NewWithoutSize(m.into_new_without()))
|
||||
}
|
||||
|
||||
// Into Playable
|
||||
(InProgress(m), Some(s), true, false) => Initialized(Playable(m.into_playable(&s))),
|
||||
(InProgress(m), None, true, false) => {
|
||||
Initialized(PlayableWithoutSize(m.into_playable_without()))
|
||||
}
|
||||
|
||||
(i @ InProgress(_), _, _, _) => i,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_size(self, bytes: Option<i32>) -> Self {
|
||||
use self::MediaMachine::*;
|
||||
let size = size_helper(bytes);
|
||||
|
||||
match (self, size) {
|
||||
(Initialized(bttn), s) => Initialized(bttn.set_size(s)),
|
||||
(InProgress(val), Some(s)) => InProgress(val.set_size(&s)),
|
||||
(n @ InProgress(_), None) => n,
|
||||
(n @ UnInitialized(_), _) => n,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_progress(&mut self, local_size: &str, fraction: f64) {
|
||||
use self::MediaMachine::*;
|
||||
|
||||
match *self {
|
||||
Initialized(_) => (),
|
||||
UnInitialized(_) => (),
|
||||
InProgress(ref mut val) => val.update_progress(local_size, fraction),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn size_helper(bytes: Option<i32>) -> Option<String> {
|
||||
let s = bytes?;
|
||||
if s == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
s.file_size(SIZE_OPTS.clone()).ok()
|
||||
}
|
||||
@ -95,7 +95,7 @@ impl HomeView {
|
||||
let view_ = view.clone();
|
||||
let func = move |ep: EpisodeWidgetQuery| {
|
||||
let epoch = ep.epoch();
|
||||
let widget = HomeEpisode::new(ep, &sender);
|
||||
let widget = HomeEpisode::new(&ep, &sender);
|
||||
|
||||
match split(&now_utc, i64::from(epoch)) {
|
||||
Today => add_to_box(&widget, &view_.today_list, &view_.today_box),
|
||||
@ -175,6 +175,7 @@ fn split(now: &DateTime<Utc>, epoch: i64) -> ListSplit {
|
||||
struct HomeEpisode {
|
||||
container: gtk::Box,
|
||||
image: gtk::Image,
|
||||
// FIXME: Change it to `EpisodeWidget` instead of a `Box`?
|
||||
episode: gtk::Box,
|
||||
}
|
||||
|
||||
@ -196,7 +197,7 @@ impl Default for HomeEpisode {
|
||||
}
|
||||
|
||||
impl HomeEpisode {
|
||||
fn new(episode: EpisodeWidgetQuery, sender: &Sender<Action>) -> HomeEpisode {
|
||||
fn new(episode: &EpisodeWidgetQuery, sender: &Sender<Action>) -> HomeEpisode {
|
||||
let builder =
|
||||
gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/episodes_view_widget.ui");
|
||||
let container: gtk::Box = builder.get_object("container").unwrap();
|
||||
@ -207,7 +208,7 @@ impl HomeEpisode {
|
||||
let view = HomeEpisode {
|
||||
container,
|
||||
image,
|
||||
episode: ep.container,
|
||||
episode: ep.container.clone(),
|
||||
};
|
||||
|
||||
view.init(pid);
|
||||
|
||||
@ -2,7 +2,6 @@ mod aboutdialog;
|
||||
pub mod appnotif;
|
||||
mod empty;
|
||||
mod episode;
|
||||
mod episode_states;
|
||||
mod home_view;
|
||||
mod show;
|
||||
mod shows_view;
|
||||
|
||||
@ -207,7 +207,7 @@ fn populate_listbox(
|
||||
let list = show_.episodes.clone();
|
||||
|
||||
let constructor = clone!(sender => move |ep| {
|
||||
EpisodeWidget::new(ep, &sender).container
|
||||
EpisodeWidget::new(&ep, &sender).container.clone()
|
||||
});
|
||||
|
||||
let callback = clone!(pd, show_ => move || {
|
||||
@ -339,8 +339,7 @@ fn dim_titles(episodes: >k::ListBox) -> Option<()> {
|
||||
.downcast::<gtk::Box>()
|
||||
.ok()?;
|
||||
let bar = foo.get_children().remove(0).downcast::<gtk::Box>().ok()?;
|
||||
let baz = bar.get_children().remove(0).downcast::<gtk::Box>().ok()?;
|
||||
let title = baz.get_children().remove(0).downcast::<gtk::Label>().ok()?;
|
||||
let title = bar.get_children().remove(0).downcast::<gtk::Label>().ok()?;
|
||||
|
||||
title.get_style_context().map(|c| c.add_class("dim-label"));
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user