Merge branch 'lazy_drawing' into 'master'

Lazy drawing

See merge request World/hammond!43
This commit is contained in:
Jordan Petridis 2018-07-18 13:14:44 +00:00
commit 5e38f41530
6 changed files with 111 additions and 45 deletions

View File

@ -156,14 +156,8 @@ impl App {
let sender = self.sender.clone();
if self.settings.get_boolean("refresh-on-startup") {
info!("Refresh on startup.");
// The ui loads async, after initialization
// so we need to delay this a bit so it won't block
// requests that will come from loading the gui on startup.
gtk::timeout_add(1500, move || {
let s: Option<Vec<_>> = None;
utils::refresh(s, sender.clone());
glib::Continue(false)
});
let s: Option<Vec<_>> = None;
utils::refresh(s, sender.clone());
}
}

View File

@ -17,7 +17,8 @@ use hammond_data::EpisodeWidgetModel;
use app::Action;
use manager;
use std::rc::Rc;
use std::cell::RefCell;
use std::rc::{Rc, Weak};
use std::sync::{Arc, Mutex, TryLockError};
lazy_static! {
@ -206,12 +207,37 @@ impl Default for EpisodeWidget {
}
impl EpisodeWidget {
pub fn new(episode: &EpisodeWidgetModel, sender: &Sender<Action>) -> Rc<Self> {
pub fn new(episode: EpisodeWidgetModel, 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();
let episode = RefCell::new(Some(episode));
let weak = Rc::downgrade(&widget);
widget.container.connect_draw(clone!(sender => move |_, _| {
episode.borrow_mut().take().map(|ep| {
weak.upgrade().map(|w| w.info.init(&ep));
Self::determine_buttons_state(&weak, &ep, &sender)
.map_err(|err| error!("Error: {}", err))
.ok();
});
gtk::Inhibit(false)
}));
// When the widget is attached to a parent,
// since it's a rust struct and not a widget the
// compiler drops the refference to it at the end of
// scope. That's cause we only attach the `self.container`
// to the parent.
//
// So this callback keeps a refference to the Rust Struct
// so the compiler won't drop it.
//
// When the widget is detached from it's parent view this
// callback runs freeing the last refference we were holding.
let foo = RefCell::new(Some(widget.clone()));
widget.container.connect_remove(move |_, _| {
foo.borrow_mut().take();
});
widget
}
@ -296,10 +322,13 @@ impl EpisodeWidget {
/// | ToDownload |
/// -------------------
fn determine_buttons_state(
widget: &Rc<Self>,
weak: &Weak<Self>,
episode: &EpisodeWidgetModel,
sender: &Sender<Action>,
) -> Result<(), Error> {
let widget = weak
.upgrade()
.ok_or_else(|| format_err!("Widget is already dropped"))?;
// 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();
@ -319,11 +348,11 @@ impl EpisodeWidget {
// 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 || {
let callback = clone!(weak, 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)
Self::determine_buttons_state(&weak, &ep, &sender)
.map_err(|err| error!("Error: {}", err))
.ok();
@ -340,20 +369,20 @@ impl EpisodeWidget {
widget
.buttons
.cancel
.connect_clicked(clone!(prog, widget, sender => move |_| {
.connect_clicked(clone!(prog, weak, 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 || {
timeout_add(50, clone!(weak, 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))
.and_then(|ep| Self::determine_buttons_state(&weak, &ep, &sender))
.map_err(|err| error!("Error: {}", err))
.ok();
@ -368,10 +397,10 @@ impl EpisodeWidget {
// 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);
update_total_size_callback(&weak, &prog);
// Setup a callback that will update the progress bar.
update_progressbar_callback(&widget, &prog, id);
update_progressbar_callback(&weak, &prog, id);
// Change the widget layout/state
widget.state_prog();
@ -388,9 +417,9 @@ impl EpisodeWidget {
widget
.buttons
.play
.connect_clicked(clone!(widget, sender => move |_| {
.connect_clicked(clone!(weak, sender => move |_| {
if let Ok(mut ep) = dbqueries::get_episode_widget_from_rowid(id) {
on_play_bttn_clicked(&widget, &mut ep, &sender)
on_play_bttn_clicked(&weak, &mut ep, &sender)
.map_err(|err| error!("Error: {}", err))
.ok();
}
@ -404,14 +433,14 @@ impl EpisodeWidget {
widget
.buttons
.download
.connect_clicked(clone!(widget, sender => move |dl| {
.connect_clicked(clone!(weak, sender => move |dl| {
// Make the button insensitive so it won't be pressed twice
dl.set_sensitive(false);
if let Ok(ep) = dbqueries::get_episode_widget_from_rowid(id) {
on_download_clicked(&ep, &sender)
.and_then(|_| {
info!("Donwload started succesfully.");
Self::determine_buttons_state(&widget, &ep, &sender)
Self::determine_buttons_state(&weak, &ep, &sender)
})
.map_err(|err| error!("Error: {}", err))
.ok();
@ -441,10 +470,14 @@ fn on_download_clicked(ep: &EpisodeWidgetModel, sender: &Sender<Action>) -> Resu
}
fn on_play_bttn_clicked(
widget: &Rc<EpisodeWidget>,
widget: &Weak<EpisodeWidget>,
episode: &mut EpisodeWidgetModel,
sender: &Sender<Action>,
) -> Result<(), Error> {
let widget = widget
.upgrade()
.ok_or_else(|| format_err!("Widget is already dropped"))?;
// Mark played
episode.set_played_now()?;
// Grey out the title
@ -461,7 +494,7 @@ fn on_play_bttn_clicked(
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(if_same_then_else))]
fn update_progressbar_callback(
widget: &Rc<EpisodeWidget>,
widget: &Weak<EpisodeWidget>,
prog: &Arc<Mutex<manager::Progress>>,
episode_rowid: i32,
) {
@ -474,10 +507,15 @@ fn update_progressbar_callback(
#[allow(if_same_then_else)]
fn progress_bar_helper(
widget: &Rc<EpisodeWidget>,
widget: &Weak<EpisodeWidget>,
prog: &Arc<Mutex<manager::Progress>>,
episode_rowid: i32,
) -> Result<glib::Continue, Error> {
let widget = match widget.upgrade() {
Some(w) => w,
None => return Ok(glib::Continue(false)),
};
let (fraction, downloaded) = match prog.try_lock() {
Ok(guard) => (guard.get_fraction(), guard.get_downloaded()),
Err(TryLockError::WouldBlock) => return Ok(glib::Continue(true)),
@ -517,7 +555,7 @@ fn progress_bar_helper(
// with the http ContentLength header number rather than
// relying to the RSS feed.
#[inline]
fn update_total_size_callback(widget: &Rc<EpisodeWidget>, prog: &Arc<Mutex<manager::Progress>>) {
fn update_total_size_callback(widget: &Weak<EpisodeWidget>, prog: &Arc<Mutex<manager::Progress>>) {
let callback = clone!(prog, widget => move || {
total_size_helper(&widget, &prog).unwrap_or(glib::Continue(true))
});
@ -525,9 +563,14 @@ fn update_total_size_callback(widget: &Rc<EpisodeWidget>, prog: &Arc<Mutex<manag
}
fn total_size_helper(
widget: &Rc<EpisodeWidget>,
widget: &Weak<EpisodeWidget>,
prog: &Arc<Mutex<manager::Progress>>,
) -> Result<glib::Continue, Error> {
let widget = match widget.upgrade() {
Some(w) => w,
None => return Ok(glib::Continue(false)),
};
// Get the total_bytes.
let total_bytes = match prog.try_lock() {
Ok(guard) => guard.get_total_size(),

View File

@ -13,6 +13,7 @@ use app::Action;
use utils::{self, lazy_load_full};
use widgets::EpisodeWidget;
use std::cell::Cell;
use std::rc::Rc;
use std::sync::Mutex;
@ -95,7 +96,7 @@ impl HomeView {
let view_ = view.clone();
let func = move |ep: EpisodeWidgetModel| {
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),
@ -197,7 +198,7 @@ impl Default for HomeEpisode {
}
impl HomeEpisode {
fn new(episode: &EpisodeWidgetModel, sender: &Sender<Action>) -> HomeEpisode {
fn new(episode: EpisodeWidgetModel, 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();
@ -216,14 +217,27 @@ impl HomeEpisode {
}
fn init(&self, show_id: i32) {
self.set_cover(show_id)
.map_err(|err| error!("Failed to set a cover: {}", err))
.ok();
self.set_cover(show_id);
self.container.pack_start(&self.episode, true, true, 6);
}
fn set_cover(&self, show_id: i32) -> Result<(), Error> {
utils::set_image_from_path(&self.image, show_id, 64)
fn set_cover(&self, show_id: i32) {
// The closure above is a regular `Fn` closure.
// which means we can't mutate stuff inside it easily,
// so Cell is used.
//
// `Option<T>` along with the `.take()` method ensure
// that the function will only be run once, during the first execution.
let show_id = Cell::new(Some(show_id));
self.image.connect_draw(move |image, _| {
show_id.take().map(|id| {
utils::set_image_from_path(image, id, 64)
.map_err(|err| error!("Failed to set a cover: {}", err))
.ok();
});
gtk::Inhibit(false)
});
}
}

View File

@ -206,7 +206,7 @@ fn populate_listbox(
let list = show_.episodes.clone();
let constructor = clone!(sender => move |ep| {
EpisodeWidget::new(&ep, &sender).container.clone()
EpisodeWidget::new(ep, &sender).container.clone()
});
let callback = clone!(pd, show_ => move || {

View File

@ -11,6 +11,7 @@ use hammond_data::Show;
use app::Action;
use utils::{self, get_ignored_shows, lazy_load, set_image_from_path};
use std::cell::Cell;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::Mutex;
@ -158,11 +159,25 @@ impl ShowsChild {
WidgetExt::set_name(&self.child, &pd.id().to_string());
self.set_cover(pd.id())
.map_err(|err| error!("Failed to set a cover: {}", err))
.ok();
}
fn set_cover(&self, show_id: i32) -> Result<(), Error> {
set_image_from_path(&self.cover, show_id, 256)
fn set_cover(&self, show_id: i32) {
// The closure above is a regular `Fn` closure.
// which means we can't mutate stuff inside it easily,
// so Cell is used.
//
// `Option<T>` along with the `.take()` method ensure
// that the function will only be run once, during the first execution.
let show_id = Cell::new(Some(show_id));
self.cover.connect_draw(move |cover, _| {
show_id.take().map(|id| {
set_image_from_path(cover, id, 256)
.map_err(|err| error!("Failed to set a cover: {}", err))
.ok();
});
gtk::Inhibit(false)
});
}
}

View File

@ -1,4 +1,4 @@
#!/usr/bin/sh
#! /usr/bin/sh
file="/.flatpak-info"
if [ -f "$file" ]