diff --git a/data/gtk/window.ui b/data/gtk/window.ui index 8d4944fcec3c73a1418ce5bcb1558d1858f0d492..b987339c06a28bc6a46ec52c43397b6f390d2360 100644 --- a/data/gtk/window.ui +++ b/data/gtk/window.ui @@ -97,7 +97,7 @@ - " + @@ -168,46 +168,62 @@ True - - status_page - True - - - View Images - Drag and drop images here + + drop_target + + + false + + + + + + status_page + True + - - center - _Open Files… - True - win.open - + + copy - - - - - - 9 + + View Images + Drag and drop images here + + + center + _Open Files… + True + win.open + + + - - 8 + + + + 9 + + + + + 8 + + - - - - copy - - + diff --git a/data/resources/style.css b/data/resources/style.css index 6a6a0db597a6b248c51338127386ffad49ae21bc..7833aa6212000d1958569c1f39b35085df353be7 100644 --- a/data/resources/style.css +++ b/data/resources/style.css @@ -8,7 +8,9 @@ button.osd.circular { background: rgba(0, 0, 0, 0.65); } -shadow, border, outline { +shadow, +border, +outline { background: none; } @@ -29,6 +31,19 @@ row.property label.subtitle { box-shadow: 0 0 10px rgba(0, 0, 0, 200); } +/* Remove default adwaita style */ +.drop-widget:drop(active):focus, +.drop-widget:drop(active) { + box-shadow: none; +} + +/* Drag overlay */ +.lp-dragging-area-highlight { + border-radius: 8px; + background-color: alpha(@blue_2, 0.35); + margin: 24px; +} + lpprintpreview { background-color: rgb(240, 240, 240); border-radius: 4px; diff --git a/po/POTFILES.in b/po/POTFILES.in index 82000d20ce6dfaffa1370d2bc770a0156e8c5083..57f53c3a4069462fcde4276560edfbe1ebfb92ce 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -20,6 +20,7 @@ src/image_metadata/obj.rs src/main.rs src/util/gettext.rs src/util/mod.rs +src/widgets/drag_overlay.rs src/widgets/image.rs src/widgets/image_page.rs src/widgets/image_view.rs diff --git a/src/about.rs b/src/about.rs index 61f39d802cc14085cdb4451f64ec4763541b2429..f35ef4897b3a66c516990711fe18d30596f1edf0 100644 --- a/src/about.rs +++ b/src/about.rs @@ -1,3 +1,4 @@ +// Copyright (c) 2023 Christopher Davis // Copyright (c) 2023 Sophie Herold // // This program is free software: you can redistribute it and/or modify diff --git a/src/decoder/tiling.rs b/src/decoder/tiling.rs index 330888c9a62606ade42791b3c054d4fbdf626518..1d263c0683aa1e10f059d1bbe3fb064da9bc18e6 100644 --- a/src/decoder/tiling.rs +++ b/src/decoder/tiling.rs @@ -1,4 +1,5 @@ // Copyright (c) 2023 Sophie Herold +// Copyright (c) 2023 FineFindus // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/src/file_model.rs b/src/file_model.rs index ab444c8e05af57114260fac469899570ffecafaf..1f3ec83a2dac1cd76647253a9e8d3c567f0c0c4b 100644 --- a/src/file_model.rs +++ b/src/file_model.rs @@ -1,5 +1,5 @@ // Copyright (c) 2022-2023 Sophie Herold -// Copyright (c) 2022 Christopher Davis +// Copyright (c) 2022-2023 Christopher Davis // Copyright (c) 2023 Gage Berz // // This program is free software: you can redistribute it and/or modify diff --git a/src/image_metadata/gps.rs b/src/image_metadata/gps.rs index c0e95ec66a04e9541b7efaca569a441bbd87d38f..f93b826f0e483bb389eedae094c1ad3bc7e93361 100644 --- a/src/image_metadata/gps.rs +++ b/src/image_metadata/gps.rs @@ -1,4 +1,5 @@ // Copyright (c) 2022-2023 Sophie Herold +// Copyright (c) 2023 FineFindus // Copyright (c) 2023 Lubosz Sarnecki // // This program is free software: you can redistribute it and/or modify diff --git a/src/image_metadata/mod.rs b/src/image_metadata/mod.rs index d25978960204347642d29af56930716a5cff31fa..a96076ed4be2babbe0cf35697cdb0eff83a3fa15 100644 --- a/src/image_metadata/mod.rs +++ b/src/image_metadata/mod.rs @@ -1,4 +1,5 @@ // Copyright (c) 2022-2023 Sophie Herold +// Copyright (c) 2023 FineFindus // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/src/widgets/drag_overlay.rs b/src/widgets/drag_overlay.rs new file mode 100644 index 0000000000000000000000000000000000000000..e8df24c5c8799e75eebaaa7d7cf860f1dd07e6c0 --- /dev/null +++ b/src/widgets/drag_overlay.rs @@ -0,0 +1,117 @@ +// Copyright (c) 2023 Sophie Herold +// Copyright (c) 2023 FineFindus +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: GPL-3.0-or-later + +//! A widget that shows an overlay when dragging an image over the window +//! +//! This implementation is inspired by [Amberol](https://gitlab.gnome.org/World/amberol) + +use crate::deps::*; +use adw::prelude::*; + +mod imp { + use once_cell::sync::OnceCell; + + use adw::subclass::prelude::*; + use glib::{ParamSpec, Properties, Value}; + + use super::*; + + #[derive(Debug, Default, Properties)] + #[properties(wrapper_type = super::LpDragOverlay)] + pub struct LpDragOverlay { + /// Usual content + #[property(set = Self::set_child)] + pub child: Option, + /// Widget overplayed when dragging over child + #[property(set = Self::set_overlayed)] + pub overlayed: Option, + pub overlay: gtk::Overlay, + pub revealer: gtk::Revealer, + #[property(set = Self::set_drop_target, get, explicit_notify, construct_only)] + pub drop_target: OnceCell, + } + + #[glib::object_subclass] + impl ObjectSubclass for LpDragOverlay { + const NAME: &'static str = "LpDragOverlay"; + type Type = super::LpDragOverlay; + type ParentType = adw::Bin; + + fn class_init(klass: &mut Self::Class) { + klass.set_css_name("lpdragoverlay"); + } + } + + impl ObjectImpl for LpDragOverlay { + fn properties() -> &'static [ParamSpec] { + Self::derived_properties() + } + + fn set_property(&self, id: usize, value: &Value, pspec: &ParamSpec) { + self.derived_set_property(id, value, pspec) + } + + fn property(&self, id: usize, pspec: &ParamSpec) -> Value { + self.derived_property(id, pspec) + } + + fn constructed(&self) { + self.overlay.set_parent(&*self.obj()); + self.overlay.add_overlay(&self.revealer); + + self.revealer.set_can_target(false); + self.revealer + .set_transition_type(gtk::RevealerTransitionType::Crossfade); + self.revealer.set_reveal_child(false); + } + + fn dispose(&self) { + self.overlay.unparent(); + } + } + impl WidgetImpl for LpDragOverlay {} + impl BinImpl for LpDragOverlay {} + + impl LpDragOverlay { + pub fn set_child(&self, child: Option) { + self.overlay.set_child(child.as_ref()); + } + + pub fn set_overlayed(&self, overlayed: Option) { + self.revealer.set_child(overlayed.as_ref()); + } + + pub fn set_drop_target(&self, drop_target: gtk::DropTarget) { + drop_target.connect_current_drop_notify( + glib::clone!(@weak self.revealer as revealer => move |target| { + let reveal = target.current_drop().is_some(); + revealer.set_reveal_child(reveal); + }), + ); + + self.drop_target.set(drop_target).unwrap(); + + self.obj().notify("drop-target"); + } + } +} + +glib::wrapper! { + pub struct LpDragOverlay(ObjectSubclass) + @extends gtk::Widget, adw::Bin; +} diff --git a/src/widgets/image.rs b/src/widgets/image.rs index 29f3e1270462e220792113097017ec64b02bfcb9..f95cdddc793e2054e918f79207987b8790aaf73d 100644 --- a/src/widgets/image.rs +++ b/src/widgets/image.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2021-2022 Christopher Davis +// Copyright (c) 2021-2023 Christopher Davis // Copyright (c) 2022-2023 Sophie Herold // Copyright (c) 2022 Maximiliano Sandoval R // Copyright (c) 2023 Lubosz Sarnecki diff --git a/src/widgets/image_page.rs b/src/widgets/image_page.rs index 59abaea866871818dd0c4da6440165267a888f21..a52f041c438193a7f066f577bb9c8e31b4e494cd 100644 --- a/src/widgets/image_page.rs +++ b/src/widgets/image_page.rs @@ -1,6 +1,5 @@ // Copyright (c) 2022-2023 Sophie Herold // Copyright (c) 2022 Christopher Davis -// Copyright (c) 2023 Huan Nguyen // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/src/widgets/image_view.rs b/src/widgets/image_view.rs index c96b87b2eaa92f0318c8697545c63703db130c4c..4d004782a02d2e68ebb454620262eef146a9af1f 100644 --- a/src/widgets/image_view.rs +++ b/src/widgets/image_view.rs @@ -2,8 +2,8 @@ // Copyright (c) 2022-2023 Sophie Herold // Copyright (c) 2022 Elton A Rodrigues // Copyright (c) 2022 Maximiliano Sandoval R -// Copyright (c) 2023 Huan Nguyen // Copyright (c) 2023 FineFindus +// Copyright (c) 2023 Huan Nguyen // Copyright (c) 2023 Philipp Kiemle // // This program is free software: you can redistribute it and/or modify @@ -78,6 +78,8 @@ mod imp { #[template_child] pub sliding_view: TemplateChild, + pub drag_source: gtk::DragSource, + pub(super) model: RefCell, pub(super) preserve_content: Cell, @@ -164,10 +166,9 @@ mod imp { self.current_image_signals.set(signal_group).unwrap(); - let source = gtk::DragSource::new(); - source.set_exclusive(true); + self.drag_source.set_exclusive(true); - source.connect_prepare( + self.drag_source.connect_prepare( glib::clone!(@weak obj => @default-return None, move |gesture, _, _| { let is_scrollable = obj .current_page() @@ -184,7 +185,7 @@ mod imp { }), ); - source.connect_drag_begin(glib::clone!(@weak obj => move |source, _| { + self.drag_source.connect_drag_begin(glib::clone!(@weak obj => move |source, _| { if let Some(paintable) = obj.current_image().and_then(|p| p.thumbnail()) { // -6 for cursor width, +16 for margin in .drag-icon source.set_icon(Some(&paintable), paintable.intrinsic_width() / 2 - 6 + 16, -12); @@ -198,7 +199,7 @@ mod imp { }; })); - obj.add_controller(source); + obj.add_controller(self.drag_source.clone()); } } @@ -545,6 +546,10 @@ impl LpImageView { .and_then(|x| x.image().file()) } + pub fn drag_source(&self) -> gtk::DragSource { + self.imp().drag_source.clone() + } + /// Returns `true` if there is an image before the current one pub fn is_previous_available(&self) -> bool { if let Some(file) = self.current_file() { diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 389d059d04ae089931b6f966845809bc2f0ba303..2b93712022b7dc6c5be977b627cf757952622ddd 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,4 +1,5 @@ // Copyright (c) 2020-2022 Christopher Davis +// Copyright (c) 2023 FineFindus // Copyright (c) 2023 Sophie Herold // // This program is free software: you can redistribute it and/or modify @@ -16,6 +17,7 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +mod drag_overlay; mod image; mod image_page; mod image_view; @@ -24,6 +26,7 @@ mod print_preview; mod properties_view; mod sliding_view; +pub use drag_overlay::LpDragOverlay; pub use image::LpImage; pub use image_page::LpImagePage; pub use image_view::LpImageView; diff --git a/src/widgets/sliding_view.rs b/src/widgets/sliding_view.rs index 0ac7c866e6e23e9f31d39a6f010012a439d1fafb..e7649eba0aa2655c88f54795b3b65987c659162b 100644 --- a/src/widgets/sliding_view.rs +++ b/src/widgets/sliding_view.rs @@ -1,5 +1,5 @@ -// Copyright (c) 2023 Sophie Herold // Copyright (c) 2023 Christopher Davis +// Copyright (c) 2023 Sophie Herold // Copyright (c) 2023 Lubosz Sarnecki // // This program is free software: you can redistribute it and/or modify diff --git a/src/window.rs b/src/window.rs index 3f37b29edba8f17774cf5333d85bd46f333a6269..0106260585a1cbdd5a5752e31a41ff47e401ab6b 100644 --- a/src/window.rs +++ b/src/window.rs @@ -2,7 +2,9 @@ // Copyright (c) 2022-2023 Sophie Herold // Copyright (c) 2022 Elton A Rodrigues // Copyright (c) 2022 Maximiliano Sandoval R -// Copyright (c) 2023 Huan Nguyen +// Copyright (c) 2023 FineFindus +// Copyright (c) 2023 qwel +// Copyright (c) 2023 Huan Thieu Nguyen // Copyright (c) 2023 Sabri Ünal // Copyright (c) 2023 Lubosz Sarnecki // @@ -35,7 +37,7 @@ use std::path::{Path, PathBuf}; use crate::config; use crate::util::{self, Direction, Position}; -use crate::widgets::{LpImage, LpImageView, LpPropertiesView}; +use crate::widgets::{LpDragOverlay, LpImage, LpImageView, LpPropertiesView}; /// Show window after X milliseconds even if image dimensions are not known yet const SHOW_WINDOW_AFTER: u64 = 2000; @@ -88,6 +90,9 @@ mod imp { pub(super) image_view: TemplateChild, #[template_child] pub(super) properties_view: TemplateChild, + + #[template_child] + pub(super) drag_overlay: TemplateChild, #[template_child] pub(super) drop_target: TemplateChild, @@ -374,12 +379,20 @@ mod imp { self.drop_target.set_types(&[gdk::FileList::static_type()]); + // Only accept drops from external sources or different windows + self.drop_target.connect_accept( + clone!(@weak obj => @default-return false, move |_drop_target, drop| { + drop.drag().is_none() || drop.drag() != obj.image_view().drag_source().drag() + }), + ); + // For callbacks, you will want to reference the GTK docs on // 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!(@weak obj => @default-return false, move |_, value, _, _| { + // Here we use a GValue, which is a dynamic object that can hold different types, // e.g. strings, numbers, or in this case objects. In order to get the GdkFileList // from the GValue, we need to use the `get()` method.