From 9205c93faea975f065affdd7d80c4868015358ac Mon Sep 17 00:00:00 2001 From: Sophie Herold Date: Sat, 14 Sep 2024 00:48:46 +0200 Subject: [PATCH] Separate window show ui and code to enable editing --- src/widgets.rs | 2 + src/widgets/window_content.ui | 2 + src/widgets/window_image.rs | 232 +++++---------- src/widgets/window_image.ui | 406 +++++++++++++-------------- src/widgets/window_image/actions.rs | 28 +- src/widgets/window_image/controls.rs | 8 +- src/window.rs | 199 +++++++++++++ src/window.ui | 32 +++ 8 files changed, 527 insertions(+), 382 deletions(-) create mode 100644 src/window.rs create mode 100644 src/window.ui diff --git a/src/widgets.rs b/src/widgets.rs index 1212481..2add57a 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -28,6 +28,7 @@ mod properties_view; mod shy_bin; mod sliding_view; mod window_content; +mod window_image; pub use drag_overlay::LpDragOverlay; pub use error_details::LpErrorDetails; @@ -40,3 +41,4 @@ pub use properties_view::LpPropertiesView; pub use shy_bin::LpShyBin; pub use sliding_view::LpSlidingView; pub use window_content::LpWindowContent; +pub use window_image::LpWindowImage; diff --git a/src/widgets/window_content.ui b/src/widgets/window_content.ui index 98870c3..25b14ac 100644 --- a/src/widgets/window_content.ui +++ b/src/widgets/window_content.ui @@ -6,11 +6,13 @@ LpWindowContent + diff --git a/src/widgets/window_image.rs b/src/widgets/window_image.rs index 5c104a5..d6a39b5 100644 --- a/src/widgets/window_image.rs +++ b/src/widgets/window_image.rs @@ -35,8 +35,8 @@ use std::path::{Path, PathBuf}; use actions::*; use adw::prelude::*; use adw::subclass::prelude::*; -use glib::{clone, Properties}; -use gtk::{CompositeTemplate, Widget}; +use adw::{glib, gtk}; +use gtk::Widget; use crate::application::LpApplication; use crate::config; @@ -44,9 +44,7 @@ use crate::deps::*; use crate::util::gettext::*; use crate::util::Direction; use crate::widgets::{LpDragOverlay, LpImage, LpImageView, LpPropertiesView, LpWindowContent}; - -/// Show window after X milliseconds even if image dimensions are not known yet -const SHOW_WINDOW_AFTER: u64 = 2000; +use crate::window::LpWindow; /// Animation duration for showing overlay buttons in milliseconds const SHOW_CONTROLS_ANIMATION_DURATION: u32 = 200; @@ -57,43 +55,30 @@ const HIDE_CONTROLS_IDLE_TIMEOUT: u64 = 3000; mod imp { - use gio::glib::VariantTy; - use super::*; + #[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)] + #[properties(wrapper_type = super::LpWindowImage)] + #[template(file = "window_image.ui")] + pub struct LpWindowImage { + #[property(get, construct_only)] + window: OnceCell, - // To use composite templates, you need - // to use derive macro. Derive macros generate - // code to e.g. implement a trait on something. - // In this case, code is generated for Debug output - // and to handle binding the template children. - // - // For this derive macro, you need to have - // `use gtk::CompositeTemplate` in your code. - // - // Because all of our member fields implement the - // `Default` trait, we can use `#[derive(Default)]`. - // If some member fields did not implement default, - // we'd need to have a `new()` function in the - // `impl ObjectSubclass for $TYPE` section. - #[derive(Default, Debug, Properties, CompositeTemplate)] - #[properties(wrapper_type = super::LpWindow)] - #[template(file = "window.ui")] - pub struct LpWindow { #[template_child] pub(super) window_content: TemplateChild, + #[template_child] pub(super) headerbar: TemplateChild, #[template_child] pub(super) headerbar_events: TemplateChild, - #[template_child] - pub(super) properties_button: TemplateChild, - #[template_child] - pub(super) primary_menu: TemplateChild, + #[template_child] pub(super) fullscreen_button: TemplateChild, #[template_child] - pub(super) toast_overlay: TemplateChild, + pub(super) properties_button: TemplateChild, + #[template_child] + pub(super) primary_menu: TemplateChild, + #[template_child] pub(super) stack: TemplateChild, #[template_child] @@ -129,10 +114,14 @@ mod imp { } #[glib::object_subclass] - impl ObjectSubclass for LpWindow { - const NAME: &'static str = "LpWindow"; - type Type = super::LpWindow; - type ParentType = adw::ApplicationWindow; + impl ObjectSubclass for LpWindowImage { + const NAME: &'static str = "LpWindowImage"; + type Type = super::LpWindowImage; + type ParentType = adw::Bin; + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } fn class_init(klass: &mut Self::Class) { // bind_template() is a function generated by the @@ -159,45 +148,31 @@ mod imp { ActionPartGlobal::init_actions_and_bindings(klass); Action::init_actions_and_bindings(klass); - - klass.install_action( - "win.show-toast", - Some(VariantTy::TUPLE), - move |win, _, var| { - if let Some((ref toast, i)) = var.and_then(|v| v.get::<(String, i32)>()) { - win.show_toast(toast, adw::ToastPriority::__Unknown(i)); - } - }, - ); - } - - fn instance_init(obj: &glib::subclass::InitializingObject) { - obj.init_template(); } } #[glib::derived_properties] - impl ObjectImpl for LpWindow { + impl ObjectImpl for LpWindowImage { fn constructed(&self) { self.parent_constructed(); let obj = self.obj(); - self.forward_click_gesture.connect_pressed(clone!( + self.forward_click_gesture.connect_pressed(glib::clone!( #[weak] obj, move |_, _, _, _| { obj.image_view().navigate(Direction::Forward, false); } )); - self.backward_click_gesture.connect_pressed(clone!( + self.backward_click_gesture.connect_pressed(glib::clone!( #[weak] obj, move |_, _, _, _| { obj.image_view().navigate(Direction::Back, false); } )); - self.properties_button.connect_toggled(clone!( + self.properties_button.connect_toggled(glib::clone!( #[weak] obj, move |_| { @@ -205,25 +180,6 @@ mod imp { } )); - if config::APP_ID.ends_with(".Devel") { - obj.add_css_class("devel"); - } - - // Limit effect of modal dialogs to this window - // and keeps the others usable - gtk::WindowGroup::new().add_window(&*obj); - - glib::timeout_add_local_once( - std::time::Duration::from_millis(SHOW_WINDOW_AFTER), - glib::clone!( - #[weak] - obj, - move || if !obj.is_visible() { - obj.present() - } - ), - ); - obj.on_current_page_changed(); obj.on_fullscreen_changed(); @@ -233,16 +189,12 @@ mod imp { move |_| obj.on_current_page_changed() )); - obj.connect_fullscreened_notify(glib::clone!( + obj.window().connect_fullscreened_notify(glib::clone!( #[weak] obj, move |_| obj.on_fullscreen_changed() )); - obj.connect_map(|win| { - win.resize_default(); - }); - let gesture_click = gtk::GestureClick::new(); gesture_click.connect_pressed(glib::clone!( #[weak] @@ -392,15 +344,23 @@ mod imp { )); // Activate global shortcuts only if no dialog is open - obj.connect_visible_dialog_notify(|obj| obj.update_accel_status()); - obj.connect_is_active_notify(|obj| obj.update_accel_status()); + obj.window().connect_visible_dialog_notify(glib::clone!( + #[weak] + obj, + move |_| obj.update_accel_status() + )); + obj.window().connect_is_active_notify(glib::clone!( + #[weak] + obj, + move |_| obj.update_accel_status() + )); self.status_page .set_icon_name(Some(&format!("{}-symbolic", config::APP_ID))); self.drop_target.set_types(&[gdk::FileList::static_type()]); - self.drop_target.connect_accept(clone!( + self.drop_target.connect_accept(glib::clone!( #[weak] obj, #[upgrade_or] @@ -420,7 +380,7 @@ mod imp { // the relevant signal to see which parameters you need. // In this case, we need only need the GValue, // so we name it `value` then use `_` for the other spots. - self.drop_target.connect_drop(clone!( + self.drop_target.connect_drop(glib::clone!( #[weak] obj, #[upgrade_or] @@ -451,7 +411,7 @@ mod imp { } // Maybe one day this will actually work - obj.present(); + obj.window().present(); true } @@ -459,12 +419,10 @@ mod imp { } } - impl WidgetImpl for LpWindow {} - impl WindowImpl for LpWindow {} - impl ApplicationWindowImpl for LpWindow {} - impl AdwApplicationWindowImpl for LpWindow {} + impl WidgetImpl for LpWindowImage {} + impl BinImpl for LpWindowImage {} - impl LpWindow { + impl LpWindowImage { fn is_showing_image(&self) -> bool { self.image_view.try_get().map_or(false, |image_view| { image_view @@ -476,20 +434,11 @@ mod imp { } glib::wrapper! { - pub struct LpWindow(ObjectSubclass) - @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow, - @implements gio::ActionMap, gio::ActionGroup, gtk::Native; + pub struct LpWindowImage(ObjectSubclass) + @extends gtk::Widget, adw::Bin; } -impl LpWindow { - pub fn new>(app: &A) -> Self { - glib::Object::builder().property("application", app).build() - } - - fn toggle_fullscreen(&self, fullscreen: bool) { - self.set_fullscreened(fullscreen); - } - +impl LpWindowImage { fn zoom_out_cursor(&self) { if let Some(image) = self.imp().image_view.current_image() { image.zoom_out_cursor(); @@ -563,7 +512,7 @@ impl LpWindow { .as_ref(), ); - if let Ok(selected) = chooser.open_multiple_future(Some(self)).await { + if let Ok(selected) = chooser.open_multiple_future(Some(&self.window())).await { let images: Vec<_> = selected .into_iter() .filter_map(|files| { @@ -584,7 +533,7 @@ impl LpWindow { if let Some(ref file) = imp.image_view.current_file() { let launcher = gtk::FileLauncher::new(Some(file)); launcher.set_always_ask(true); - if let Err(e) = launcher.launch_future(Some(self)).await { + if let Err(e) = launcher.launch_future(Some(&self.window())).await { if !e.matches(gtk::DialogError::Dismissed) { log::error!("Could not open image in external program: {}", e); } @@ -616,7 +565,7 @@ impl LpWindow { /// Returns true if some text in metadata is currently selected fn has_metadata_selected(&self) -> bool { - if let Some(focus_widget) = self.focus() { + if let Some(focus_widget) = self.window().focus() { if focus_widget.is_ancestor(&*self.imp().properties_view) { if let Ok(label) = focus_widget.downcast::() { return label.selection_bounds().is_some(); @@ -634,7 +583,7 @@ impl LpWindow { if let Err(e) = imp.image_view.copy() { log::error!("Failed to copy to clipboard: {}", e); } else { - self.show_toast( + self.window().show_toast( gettext("Image copied to clipboard"), adw::ToastPriority::High, ); @@ -661,17 +610,17 @@ impl LpWindow { .priority(adw::ToastPriority::High) .build(); toast.connect_button_clicked(glib::clone!( - #[weak(rename_to = win)] + #[weak(rename_to = obj)] self, move |_| { let path = path.clone(); glib::spawn_future_local(async move { - win.image_view() + obj.image_view() .set_trash_restore(Some(gio::File::for_path(&path))); let result = crate::util::untrash(&path).await; if let Err(err) = result { log::error!("Failed to untrash {path:?}: {err}"); - win.show_toast( + obj.window().show_toast( gettext("Failed to restore image from trash"), adw::ToastPriority::High, ); @@ -679,14 +628,15 @@ impl LpWindow { }); } )); - self.imp().toast_overlay.add_toast(toast); + todo!(); + //self.imp().toast_overlay.add_toast(toast); } Err(err) => { if Some(gio::IOErrorEnum::NotSupported) == err.kind::() { self.delete_future(&path).await; } else { log::error!("Failed to delete file {path:?}: {err}"); - self.show_toast( + self.window().show_toast( gettext("Failed to move image to trash"), adw::ToastPriority::Normal, ); @@ -732,7 +682,7 @@ impl LpWindow { if let Err(err) = result { log::error!("Failed to delete file {path:?}: {err}"); - self.show_toast( + self.window().show_toast( gettext("Failed to delete image"), adw::ToastPriority::Normal, ); @@ -748,15 +698,6 @@ impl LpWindow { self.imp().headerbar.clone() } - fn show_toast(&self, text: impl AsRef, priority: adw::ToastPriority) { - let imp = self.imp(); - - let toast = adw::Toast::new(text.as_ref()); - toast.set_priority(priority); - - imp.toast_overlay.add_toast(toast); - } - /// Set status for actions that are related to the active image fn update_image_actions_status(&self) { // Actions that are enabled if the image is shown @@ -866,7 +807,7 @@ impl LpWindow { imp.stack.set_visible_child(&*imp.status_page); imp.status_page.grab_focus(); // Leave fullscreen since status page has no controls to leave it - self.set_fullscreened(false); + self.window().set_fullscreened(false); } imp.properties_button.set_sensitive(has_image); @@ -896,7 +837,7 @@ impl LpWindow { .and_then(|x| x.metadata().file_name()) .unwrap_or_else(|| gettext("Image Viewer")); - self.set_title(Some(&title)); + self.window().set_title(Some(&title)); } pub fn image_size_available(&self) { @@ -913,41 +854,7 @@ impl LpWindow { if image.is_some_and(|img| img.image_size_available()) { log::debug!("Showing window because image size is ready"); - self.present(); - } - } - - pub fn resize_default(&self) { - let imp = self.imp(); - - if self - .image_view() - .current_image() - .is_some_and(|img| img.image_size_available()) - { - let shows_properties = imp.properties_button.is_active(); - - let (_, window_natural_width, _, _) = self.measure(gtk::Orientation::Horizontal, -1); - let (_, window_natural_height, _, _) = self.measure(gtk::Orientation::Vertical, -1); - - // These have to be in sync with the "conditions" for the "overlay-properties" - // breakpoint - let min_width_for_overlay = adw::LengthUnit::Sp.to_px(590., None).ceil() as i32; - let min_height_for_overlay = adw::LengthUnit::Sp.to_px(550., None).ceil() as i32; - - let (width, height) = if shows_properties - && window_natural_width < min_width_for_overlay - && window_natural_height < min_height_for_overlay - { - // Avoid overlaying bottom sheet being triggered for the image properties by - // using a window wide enough to allow for a sidebar - (min_width_for_overlay.saturating_add(1), -1) - } else { - // this lets the window determine the default size from LpImage's natural size - (-1, -1) - }; - - self.set_default_size(width, height); + self.window().present(); } } @@ -962,19 +869,19 @@ impl LpWindow { if current_page.is_some_and(|page| page.image().error().is_some()) { log::debug!("Showing window because loading image failed"); - self.present(); + self.window().present(); } } fn on_fullscreen_changed(&self) { - let icon = if self.is_fullscreen() { + let icon = if self.window().is_fullscreen() { "view-restore-symbolic" } else { "view-fullscreen-symbolic" }; self.imp().fullscreen_button.set_icon_name(icon); - if !self.is_fullscreen() { + if !self.window().is_fullscreen() { self.set_cursor(None); self.show_controls(); } @@ -990,14 +897,14 @@ impl LpWindow { } fn update_accel_status(&self) { - let Some(application) = self.application() else { + let Some(application) = self.window().application() else { log::error!("No application for window found"); return; }; // Only change status if active window - if self.is_active() { - if self.visible_dialog().is_some() { + if self.window().is_active() { + if self.window().visible_dialog().is_some() { // If AdwDialog is visible, remove global accels that are for the main window ActionPartGlobal::remove_accels(&application); } else { @@ -1007,8 +914,7 @@ impl LpWindow { } } - async fn show_about(&self) { - let about = crate::about::dialog().await; - about.present(Some(self)); + pub fn properties_button(&self) -> gtk::ToggleButton { + self.imp().properties_button.clone() } } diff --git a/src/widgets/window_image.ui b/src/widgets/window_image.ui index eb1c917..fc85f94 100644 --- a/src/widgets/window_image.ui +++ b/src/widgets/window_image.ui @@ -56,62 +56,88 @@ -