From 8081990895d0180de7831b45f0afe912c7a4ed06 Mon Sep 17 00:00:00 2001 From: James Westman Date: Tue, 29 Oct 2019 19:33:41 -0500 Subject: [PATCH] Improve show description UI Instead of being a scroll window inside a scroll window, the show description now shows just the first paragraph by default, then displays a "Read More" button if there is more to the description. Clicking the button reveals the rest. Currently, to keep the button from glitching when updating it from the size-allocate signal, a GtkRevealer with a transition-duration of 1 millisecond is used. It's a hacky workaround but I'm not quite sure how to do it better. Fixes #81 --- Cargo.lock | 1 + podcasts-gtk/Cargo.toml | 1 + podcasts-gtk/resources/gtk/show_widget.ui | 68 ++++++++++++++++++----- podcasts-gtk/src/widgets/show.rs | 50 ++++++++++++++++- 4 files changed, 104 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 460f6d0..e993247 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1624,6 +1624,7 @@ dependencies = [ "loggerv 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "mpris-player 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "open 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "pango 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "podcasts-data 0.1.0", "podcasts-downloader 0.1.0", "pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/podcasts-gtk/Cargo.toml b/podcasts-gtk/Cargo.toml index 866caeb..18cf4d1 100644 --- a/podcasts-gtk/Cargo.toml +++ b/podcasts-gtk/Cargo.toml @@ -29,6 +29,7 @@ serde_json = "1.0.41" # html2text = "0.1.8" html2text = { git = "https://github.com/jugglerchris/rust-html2text" } mpris-player = "0.4.0" +pango = "0.7.0" [dependencies.gettext-rs] git = "https://github.com/danigm/gettext-rs" diff --git a/podcasts-gtk/resources/gtk/show_widget.ui b/podcasts-gtk/resources/gtk/show_widget.ui index e66edd4..3f86abf 100644 --- a/podcasts-gtk/resources/gtk/show_widget.ui +++ b/podcasts-gtk/resources/gtk/show_widget.ui @@ -58,38 +58,78 @@ Tobias Bernard - + True - True - never - 80 + 600 - + True - False - none + True + False + none + True + 12 + + + True + False + fill + vertical + + + True + False + center + center + True + center + True + word-char + end + 4 + + + + + True + False + 1 + + + Read More + True + False + False + center + 12 + + + + + + + short + + True False center center - This is embarrasing! -Sorry, we could not find a description for this Show. True center True word-char + + full + 1 + - - False - False - 1 - diff --git a/podcasts-gtk/src/widgets/show.rs b/podcasts-gtk/src/widgets/show.rs index 0c67bfc..95f1f72 100644 --- a/podcasts-gtk/src/widgets/show.rs +++ b/podcasts-gtk/src/widgets/show.rs @@ -43,6 +43,10 @@ pub(crate) struct ShowWidget { pub(crate) view: BaseView, cover: gtk::Image, description: gtk::Label, + description_short: gtk::Label, + description_stack: gtk::Stack, + description_button: gtk::Button, + description_button_revealer: gtk::Revealer, episodes: gtk::ListBox, show_id: Option, } @@ -53,6 +57,11 @@ impl Default for ShowWidget { let sub_cont: gtk::Box = builder.get_object("sub_container").unwrap(); let cover: gtk::Image = builder.get_object("cover").unwrap(); let description: gtk::Label = builder.get_object("description").unwrap(); + let description_short: gtk::Label = builder.get_object("description_short").unwrap(); + let description_stack: gtk::Stack = builder.get_object("description_stack").unwrap(); + let description_button: gtk::Button = builder.get_object("description_button").unwrap(); + let description_button_revealer = + builder.get_object("description_button_revealer").unwrap(); let episodes = builder.get_object("episodes").unwrap(); let view = BaseView::default(); @@ -71,6 +80,10 @@ impl Default for ShowWidget { view, cover, description, + description_short, + description_stack, + description_button, + description_button_revealer, episodes, show_id: None, } @@ -95,6 +108,18 @@ impl ShowWidget { let res = populate_listbox(&pdw, pd.clone(), sender, vadj); debug_assert!(res.is_ok()); + let weak = Rc::downgrade(&pdw); + pdw.description_short + .connect_size_allocate(clone!(weak => move |_, _2| { + weak.upgrade().map(|w| w.update_read_more()); + })); + + pdw.description_button + .connect_clicked(clone!(weak => move |_| { + weak.upgrade() + .map(|w| w.description_stack.set_visible_child_name("full")); + })); + pdw } @@ -111,10 +136,31 @@ impl ShowWidget { utils::set_image_from_path(&self.cover, pd.id(), 256) } + fn update_read_more(&self) { + if let Some(layout) = self.description_short.get_layout() { + let more = layout.is_ellipsized() + || self.description.get_label() != self.description_short.get_label(); + self.description_button_revealer.set_reveal_child(more); + } + } + /// Set the description text. fn set_description(&self, text: &str) { - self.description - .set_markup(html2text::from_read(text.as_bytes(), 80).trim()); + let markup = html2text::from_read(text.as_bytes(), text.as_bytes().len()); + let markup = markup.trim(); + let lines: Vec<&str> = markup.lines().collect(); + + if markup.is_empty() { + self.description_stack.set_visible(false); + } else { + self.description_stack.set_visible(true); + + self.description.set_markup(markup); + debug_assert!(lines.len() > 0); + if lines.len() > 0 { + self.description_short.set_markup(lines[0]); + } + } } pub(crate) fn show_id(&self) -> Option {