From 01310ee7fa0abec74706748ca1d8bbd1a2458fbb Mon Sep 17 00:00:00 2001 From: Jordan Petridis Date: Tue, 12 Dec 2017 16:01:19 +0200 Subject: [PATCH] Working non-state machine stack implementation. Removed the stack state-machines. It was confusing trying to both implement statemachines and re-design the stack architecture at the same time. --- hammond-gtk/src/content.rs | 343 ++++++++--------------------- hammond-gtk/src/headerbar.rs | 17 +- hammond-gtk/src/main.rs | 5 +- hammond-gtk/src/utils.rs | 24 +- hammond-gtk/src/views/podcasts.rs | 43 ++-- hammond-gtk/src/widgets/podcast.rs | 26 ++- 6 files changed, 146 insertions(+), 312 deletions(-) diff --git a/hammond-gtk/src/content.rs b/hammond-gtk/src/content.rs index 4b7d32a..8214ab9 100644 --- a/hammond-gtk/src/content.rs +++ b/hammond-gtk/src/content.rs @@ -1,318 +1,167 @@ use gtk; use gtk::prelude::*; -// use hammond_data::Podcast; +use hammond_data::Podcast; use hammond_data::dbqueries; -use widgets::podcast::PodcastWidget; use views::podcasts::PopulatedView; use views::empty::EmptyView; +use widgets::podcast::PodcastWidget; + +use std::rc::Rc; #[derive(Debug, Clone)] pub struct Content { pub stack: gtk::Stack, - shows: ShowStateWrapper, - episodes: EpisodeStateWrapper, + shows: Rc, + episodes: Rc, } impl Content { - pub fn new() -> Content { + pub fn new() -> Rc { let stack = gtk::Stack::new(); - let shows = ShowStateWrapper::new(); - let episodes = EpisodeStateWrapper::new(); + let shows = ShowStack::new(); + let episodes = EpisodeStack::new(); - let shows_stack = shows.get_stack(); - let ep_stack = episodes.get_stack(); + stack.add_titled(&episodes.stack, "episodes", "Episodes"); + stack.add_titled(&shows.stack, "shows", "Shows"); - stack.add_titled(&ep_stack, "episodes", "Episodes"); - stack.add_titled(&shows_stack, "shows", "Shows"); - - Content { + Rc::new(Content { stack, shows, episodes, - } + }) } - pub fn update(&mut self) { - self.shows = self.shows.clone().update(); - // FIXME: like above + pub fn update(&self) { + self.shows.update(); self.episodes.update(); } } -// pub fn on_podcasts_child_activate(stack: >k::Stack, pd: &Podcast) { -// update_widget(stack, pd); -// stack.set_visible_child_full("widget", gtk::StackTransitionType::SlideLeft); -// } - -// FIXME: Rename and remove aliases -type ShowsPopulated = PopulatedView; -type ShowsEmpty = EmptyView; -type EpisodesPopulated = PodcastWidget; -type EpisodesEmpty = EmptyView; - #[derive(Debug, Clone)] -struct Populated; -#[derive(Debug, Clone)] -struct Empty; -// struct Shows; -// struct Episodes; - -// Thats probably too overengineered -// struct StackStateMachine { -// shows: ShowsMachine, -// episodes: EpisodesMachine, -// stack: gtk::Stack, -// state: T, -// } - -#[derive(Debug, Clone)] -struct ShowsMachine { - populated: ShowsPopulated, - empty: ShowsEmpty, - stack: gtk::Stack, - state: S, +pub struct ShowStack { + pub stack: gtk::Stack, } -impl ShowsMachine { - fn new(state: S) -> ShowsMachine { +impl ShowStack { + fn new() -> Rc { let stack = gtk::Stack::new(); - let pop = ShowsPopulated::new_initialized(); + + let show = Rc::new(ShowStack { stack }); + + let pop = PopulatedView::new_initialized(show.clone()); + let widget = PodcastWidget::new(); let empty = EmptyView::new(); - stack.add_named(&pop.container, "populated"); - stack.add_named(&empty.container, "empty"); - ShowsMachine { - empty, - populated: pop, - stack, - state, + show.stack.add_named(&pop.container, "podcasts"); + show.stack.add_named(&widget.container, "widget"); + show.stack.add_named(&empty.container, "empty"); + + if pop.is_empty() { + show.stack.set_visible_child_name("empty") + } else { + show.stack.set_visible_child_name("podcasts") } + + show } - fn update(&mut self) { + // fn is_empty(&self) -> bool { + // self.podcasts.is_empty() + // } + + pub fn update(&self) { + self.update_podcasts(); + self.update_widget(); + } + + pub fn update_podcasts(&self) { let vis = self.stack.get_visible_child_name().unwrap(); - let old = self.stack.get_child_by_name("populated").unwrap(); + let old = self.stack.get_child_by_name("podcasts").unwrap(); + + let pop = PopulatedView::new(); + pop.init(Rc::new(self.clone())); + self.stack.remove(&old); + self.stack.add_named(&pop.container, "podcasts"); - let pop = ShowsPopulated::new_initialized(); - self.populated = pop; - self.stack.add_named(&self.populated.container, "populated"); - self.stack.set_visible_child_name(&vis); + if pop.is_empty() { + self.stack.set_visible_child_name("empty"); + } else if vis != "empty" { + self.stack.set_visible_child_name(&vis); + } else { + self.stack.set_visible_child_name("podcasts"); + } + + old.destroy(); } -} -#[derive(Debug, Clone)] -struct EpisodesMachine { - populated: EpisodesPopulated, - empty: EpisodesEmpty, - stack: gtk::Stack, - state: S, -} + pub fn replace_widget(&self, pd: &Podcast) { + let old = self.stack.get_child_by_name("widget").unwrap(); + let new = PodcastWidget::new_initialized(Rc::new(self.clone()), pd); -impl EpisodesMachine { - // FIXME: - fn update(&mut self) { + self.stack.remove(&old); + self.stack.add_named(&new.container, "widget"); + } + + pub fn update_widget(&self) { let vis = self.stack.get_visible_child_name().unwrap(); - let old = self.stack.get_child_by_name("populated").unwrap(); + let old = self.stack.get_child_by_name("widget").unwrap(); let id = WidgetExt::get_name(&old).unwrap(); if id == "GtkBox" { return; } - let pd = dbqueries::get_podcast_from_id(id.parse::().unwrap()).unwrap(); - let pdw = EpisodesPopulated::new_initialized(&self.stack, &pd); - self.populated = pdw; - self.stack.remove(&old); - self.stack.add_named(&self.populated.container, "populated"); - self.stack.set_visible_child_name(&vis); - } -} - -// impl Into> for StackStateMachine { -// fn into(self) -> StackStateMachine { -// self.stack.set_visible_child_name("shows"); - -// StackStateMachine { -// shows: self.shows, -// episodes: self.episodes, -// stack: self.stack, -// state: Shows {}, -// } -// } -// } - -// impl Into> for StackStateMachine { -// fn into(self) -> StackStateMachine { -// self.stack.set_visible_child_name("episodes"); - -// StackStateMachine { -// shows: self.shows, -// episodes: self.episodes, -// stack: self.stack, -// state: Episodes {}, -// } -// } -// } - -// TODO: Impl instead of -impl Into> for ShowsMachine { - fn into(self) -> ShowsMachine { - self.stack.set_visible_child_name("populated"); - - ShowsMachine { - populated: self.populated, - empty: self.empty, - stack: self.stack, - state: Populated {}, + let pd = dbqueries::get_podcast_from_id(id.parse::().unwrap()); + if let Ok(pd) = pd { + self.replace_widget(&pd); + self.stack.set_visible_child_name(&vis); + old.destroy(); } } -} -impl Into> for ShowsMachine { - fn into(self) -> ShowsMachine { - self.stack.set_visible_child_name("empty"); - - ShowsMachine { - populated: self.populated, - empty: self.empty, - stack: self.stack, - state: Empty {}, - } + pub fn switch_podcasts_animated(&self) { + self.stack + .set_visible_child_full("podcasts", gtk::StackTransitionType::SlideRight); } -} -impl Into> for EpisodesMachine { - fn into(self) -> EpisodesMachine { - self.stack.set_visible_child_name("populated"); - - EpisodesMachine { - populated: self.populated, - empty: self.empty, - stack: self.stack, - state: Populated {}, - } + pub fn switch_widget_animated(&self) { + self.stack + .set_visible_child_full("widget", gtk::StackTransitionType::SlideLeft) } } -impl Into> for EpisodesMachine { - fn into(self) -> EpisodesMachine { - self.stack.set_visible_child_name("empty"); - - EpisodesMachine { - populated: self.populated, - empty: self.empty, - stack: self.stack, - state: Empty {}, - } - } -} - -// enum StackStateWrapper { -// Shows(StackStateMachine), -// Episodes(StackStateMachine), -// } - -#[derive(Debug, Clone)] -enum ShowStateWrapper { - Populated(ShowsMachine), - Empty(ShowsMachine), -} - #[derive(Debug, Clone)] -enum EpisodeStateWrapper { - Populated(EpisodesMachine), - Empty(EpisodesMachine), +struct RecentEpisodes; + +#[derive(Debug, Clone)] +struct EpisodeStack { + // populated: RecentEpisodes, + // empty: EmptyView, + stack: gtk::Stack, } -// impl StackStateWrapper { -// fn switch(mut self) -> Self { -// match self { -// StackStateWrapper::Shows(val) => StackStateWrapper::Episodes(val.into()), -// StackStateWrapper::Episodes(val) => StackStateWrapper::Shows(val.into()) -// } -// } -// } - -impl ShowStateWrapper { - fn new() -> Self { - let machine = ShowsMachine::new(Populated {}); - - if machine.populated.flowbox.get_children().is_empty() { - machine.stack.set_visible_child_name("empty"); - ShowStateWrapper::Empty(machine.into()) - } else { - machine.stack.set_visible_child_name("populated"); - ShowStateWrapper::Populated(machine) - } - } - - fn update(mut self) -> Self { - match self { - ShowStateWrapper::Populated(ref mut val) => val.update(), - ShowStateWrapper::Empty(ref mut val) => val.update(), - } - - if self.is_empty() { - match self { - ShowStateWrapper::Populated(val) => ShowStateWrapper::Empty(val.into()), - _ => self, - } - } else { - match self { - ShowStateWrapper::Empty(val) => ShowStateWrapper::Populated(val.into()), - _ => self, - } - } - } - - fn get_stack(&self) -> gtk::Stack { - match *self { - ShowStateWrapper::Populated(ref val) => val.stack.clone(), - ShowStateWrapper::Empty(ref val) => val.stack.clone(), - } - } - - fn is_empty(&self) -> bool { - match *self { - ShowStateWrapper::Populated(ref val) => val.populated.flowbox.get_children().is_empty(), - ShowStateWrapper::Empty(ref val) => val.populated.flowbox.get_children().is_empty(), - } - } -} - -impl EpisodeStateWrapper { - // FIXME: - fn new() -> Self { - let pop = PodcastWidget::new(); +impl EpisodeStack { + fn new() -> Rc { + let _pop = RecentEpisodes {}; let empty = EmptyView::new(); let stack = gtk::Stack::new(); - stack.add_named(&pop.container, "populated"); + // stack.add_named(&pop.container, "populated"); stack.add_named(&empty.container, "empty"); + // FIXME: stack.set_visible_child_name("empty"); - EpisodeStateWrapper::Empty(EpisodesMachine { - empty, - populated: pop, + Rc::new(EpisodeStack { + // empty, + // populated: pop, stack, - state: Empty {}, }) } - fn update(&mut self) { - match *self { - EpisodeStateWrapper::Populated(ref mut val) => val.update(), - EpisodeStateWrapper::Empty(ref mut val) => val.update(), - } - } - - fn get_stack(&self) -> gtk::Stack { - match *self { - EpisodeStateWrapper::Populated(ref val) => val.stack.clone(), - EpisodeStateWrapper::Empty(ref val) => val.stack.clone(), - } + fn update(&self) { + // unimplemented!() } } diff --git a/hammond-gtk/src/headerbar.rs b/hammond-gtk/src/headerbar.rs index 956e040..140eac2 100644 --- a/hammond-gtk/src/headerbar.rs +++ b/hammond-gtk/src/headerbar.rs @@ -4,7 +4,7 @@ use gtk::prelude::*; use hammond_data::Source; use hammond_data::utils::url_cleaner; -use std::sync::{Arc, Mutex}; +use std::rc::Rc; use utils; use content::Content; @@ -36,23 +36,19 @@ impl Header { } } - pub fn new_initialized(content: Arc>) -> Header { + pub fn new_initialized(content: Rc) -> Header { let header = Header::new(); header.init(content); header } - fn init(&self, content: Arc>) { + fn init(&self, content: Rc) { let builder = gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/headerbar.ui"); let add_popover: gtk::Popover = builder.get_object("add-popover").unwrap(); let new_url: gtk::Entry = builder.get_object("new-url").unwrap(); let add_button: gtk::Button = builder.get_object("add-button").unwrap(); - - { - let cont = content.lock().unwrap(); - self.switch.set_stack(&cont.stack); - } + self.switch.set_stack(&content.stack); new_url.connect_changed(move |url| { println!("{:?}", url.get_text()); @@ -68,14 +64,13 @@ impl Header { self.add_toggle.set_popover(&add_popover); // FIXME: There appears to be a memmory leak here. - let cont = content.clone(); self.refresh.connect_clicked(move |_| { - utils::refresh_feed(cont.clone(), None, None); + utils::refresh_feed(content.clone(), None, None); }); } } -fn on_add_bttn_clicked(content: Arc>, entry: >k::Entry) { +fn on_add_bttn_clicked(content: Rc, entry: >k::Entry) { let url = entry.get_text().unwrap_or_default(); let url = url_cleaner(&url); let source = Source::from_url(&url); diff --git a/hammond-gtk/src/main.rs b/hammond-gtk/src/main.rs index 9f4a4b4..77bd430 100644 --- a/hammond-gtk/src/main.rs +++ b/hammond-gtk/src/main.rs @@ -1,3 +1,5 @@ +#![cfg_attr(feature = "cargo-clippy", allow(clone_on_ref_ptr))] + extern crate gdk; extern crate gdk_pixbuf; extern crate gio; @@ -22,8 +24,6 @@ use hammond_data::utils::checkup; use gtk::prelude::*; use gio::{ActionMapExt, ApplicationExt, MenuExt, SimpleActionExt}; -use std::sync::{Arc, Mutex}; - // http://gtk-rs.org/tuto/closures #[macro_export] macro_rules! clone { @@ -72,7 +72,6 @@ fn build_ui(app: >k::Application) { // let ct = content::Content::new_initialized(); let ct = content::Content::new(); let stack = ct.stack.clone(); - let ct = Arc::new(Mutex::new(ct)); window.add(&stack); window.connect_delete_event(|w, _| { diff --git a/hammond-gtk/src/utils.rs b/hammond-gtk/src/utils.rs index 0a6a5c2..7327854 100644 --- a/hammond-gtk/src/utils.rs +++ b/hammond-gtk/src/utils.rs @@ -1,5 +1,4 @@ use glib; -use gtk; use gdk_pixbuf::Pixbuf; use hammond_data::feed; @@ -9,14 +8,12 @@ use hammond_downloader::downloader; use std::{thread, time}; use std::cell::RefCell; use std::sync::mpsc::{channel, Receiver}; -use std::borrow::Cow; use content::Content; -use regex::Regex; -use std::sync::{Arc, Mutex}; +use std::rc::Rc; -type Foo = RefCell>, Receiver)>>; +type Foo = RefCell, Receiver)>>; // Create a thread local storage that will store the arguments to be transfered. thread_local!(static GLOBAL: Foo = RefCell::new(None)); @@ -25,13 +22,13 @@ thread_local!(static GLOBAL: Foo = RefCell::new(None)); /// If `source` is None, Fetches all the `Source` entries in the database and updates them. /// `delay` represents the desired time in seconds for the thread to sleep before executing. /// When It's done,it queues up a `podcast_view` refresh. -pub fn refresh_feed(content: Arc>, source: Option>, delay: Option) { +pub fn refresh_feed(content: Rc, source: Option>, delay: Option) { // Create a async channel. let (sender, receiver) = channel(); // Pass the desired arguments into the Local Thread Storage. GLOBAL.with(clone!(content => move |global| { - *global.borrow_mut() = Some((content, receiver)); + *global.borrow_mut() = Some((content.clone(), receiver)); })); thread::spawn(move || { @@ -61,7 +58,6 @@ fn refresh_podcasts_view() -> glib::Continue { GLOBAL.with(|global| { if let Some((ref content, ref reciever)) = *global.borrow() { if reciever.try_recv().is_ok() { - let mut content = content.lock().unwrap(); content.update(); } } @@ -74,18 +70,6 @@ pub fn get_pixbuf_from_path(pd: &Podcast) -> Option { Pixbuf::new_from_file_at_scale(&img_path, 256, 256, true).ok() } -#[allow(dead_code)] -// WIP: parse html to markup -pub fn html_to_markup(s: &mut str) -> Cow { - s.trim(); - s.replace('&', "&"); - s.replace('<', "<"); - s.replace('>', ">"); - - let re = Regex::new("(?Phttps?://[^\\s&,)(\"]+(&\\w=[\\w._-]?)*(#[\\w._-]+)?)").unwrap(); - re.replace_all(s, "$url") -} - #[cfg(test)] mod tests { use hammond_data::Source; diff --git a/hammond-gtk/src/views/podcasts.rs b/hammond-gtk/src/views/podcasts.rs index 0767d72..58c5f14 100644 --- a/hammond-gtk/src/views/podcasts.rs +++ b/hammond-gtk/src/views/podcasts.rs @@ -7,8 +7,9 @@ use hammond_data::dbqueries; use hammond_data::Podcast; use utils::get_pixbuf_from_path; +use content::ShowStack; -// use content; +use std::rc::Rc; #[derive(Debug, Clone)] pub struct PopulatedView { @@ -42,25 +43,29 @@ impl PopulatedView { } #[allow(dead_code)] - pub fn new_initialized() -> PopulatedView { + pub fn new_initialized(show: Rc) -> PopulatedView { let pop = PopulatedView::new(); - pop.init(); + pop.init(show); pop } - pub fn init(&self) { - // pub fn init(&self, stack: >k::Stack) { - // use gtk::WidgetExt; + pub fn init(&self, show: Rc) { + use gtk::WidgetExt; - // // TODO: handle unwraps. - // self.flowbox - // .connect_child_activated(clone!(stack => move |_, child| { - // // This is such an ugly hack... - // // let id = child.get_name().unwrap().parse::().unwrap(); - // let id = WidgetExt::get_name(child).unwrap().parse::().unwrap(); - // let parent = dbqueries::get_podcast_from_id(id).unwrap(); - // on_flowbox_child_activate(&stack, &parent); - // })); + // TODO: handle unwraps. + // Note: flowbox_activation always adds "widnget" into the stack and switch to it, + // TODO: implement back button. + // so back button should always remove "widget" and destroy it. + let show = show.clone(); + self.flowbox + .connect_child_activated(clone!(show => move |_, child| { + // This is such an ugly hack... + let id = WidgetExt::get_name(child).unwrap().parse::().unwrap(); + let pd = dbqueries::get_podcast_from_id(id).unwrap(); + + show.replace_widget(&pd); + show.switch_widget_animated(); + })); // Populate the flowbox with the Podcasts. self.populate_flowbox(); } @@ -76,6 +81,10 @@ impl PopulatedView { self.flowbox.show_all(); } } + + pub fn is_empty(&self) -> bool { + self.flowbox.get_children().is_empty() + } } impl PodcastChild { @@ -139,7 +148,3 @@ impl PodcastChild { } } } - -// fn on_flowbox_child_activate(stack: >k::Stack, parent: &Podcast) { -// content::on_podcasts_child_activate(stack, parent) -// } diff --git a/hammond-gtk/src/widgets/podcast.rs b/hammond-gtk/src/widgets/podcast.rs index c0d4c34..1d7f91b 100644 --- a/hammond-gtk/src/widgets/podcast.rs +++ b/hammond-gtk/src/widgets/podcast.rs @@ -10,7 +10,8 @@ use hammond_downloader::downloader; use widgets::episode::episodes_listbox; use utils::get_pixbuf_from_path; -// use content; +use content::ShowStack; +use std::rc::Rc; #[derive(Debug, Clone)] pub struct PodcastWidget { @@ -47,18 +48,18 @@ impl PodcastWidget { } } - pub fn new_initialized(stack: >k::Stack, pd: &Podcast) -> PodcastWidget { + pub fn new_initialized(shows: Rc, pd: &Podcast) -> PodcastWidget { let pdw = PodcastWidget::new(); - pdw.init(stack, pd); + pdw.init(shows, pd); pdw } - pub fn init(&self, stack: >k::Stack, pd: &Podcast) { + pub fn init(&self, shows: Rc, pd: &Podcast) { WidgetExt::set_name(&self.container, &pd.id().to_string()); // TODO: should spawn a thread to avoid locking the UI probably. - self.unsub.connect_clicked(clone!(stack, pd => move |bttn| { - on_unsub_button_clicked(&stack, &pd, bttn); + self.unsub.connect_clicked(clone!(shows, pd => move |bttn| { + on_unsub_button_clicked(shows.clone(), &pd, bttn); })); self.title.set_text(pd.title()); @@ -77,8 +78,8 @@ impl PodcastWidget { self.cover.set_from_pixbuf(&i); } - self.played.connect_clicked(clone!(stack, pd => move |_| { - on_played_button_clicked(&stack, &pd); + self.played.connect_clicked(clone!(shows, pd => move |_| { + on_played_button_clicked(shows.clone(), &pd); })); self.show_played_button(pd); @@ -95,7 +96,7 @@ impl PodcastWidget { } } -fn on_unsub_button_clicked(stack: >k::Stack, pd: &Podcast, unsub_button: >k::Button) { +fn on_unsub_button_clicked(shows: Rc, pd: &Podcast, unsub_button: >k::Button) { let res = dbqueries::remove_feed(pd); if res.is_ok() { info!("{} was removed succesfully.", pd.title()); @@ -111,11 +112,12 @@ fn on_unsub_button_clicked(stack: >k::Stack, pd: &Podcast, unsub_button: >k: } }; } - // content::update_podcasts(stack); + shows.switch_podcasts_animated(); + shows.update_podcasts(); } -fn on_played_button_clicked(stack: >k::Stack, pd: &Podcast) { +fn on_played_button_clicked(shows: Rc, pd: &Podcast) { let _ = dbqueries::update_none_to_played_now(pd); - // content::update_widget_preserve_vis(stack, pd); + shows.update_widget(); }