From 78b16518e5a7b63dcc1877f52589641cde0b234d Mon Sep 17 00:00:00 2001 From: FineFindus Date: Sun, 25 Jun 2023 21:13:56 +0200 Subject: [PATCH] window: Use custom overlay visual for drop target Closes #59 Part-of: --- data/gtk/window.ui | 31 +++++---- data/resources/style.css | 10 +-- po/POTFILES.in | 1 + src/widgets/drag_overlay.rs | 121 ++++++++++++++++++++++++++++++++++++ src/widgets/mod.rs | 2 + src/window.rs | 80 +++++++----------------- 6 files changed, 171 insertions(+), 74 deletions(-) create mode 100644 src/widgets/drag_overlay.rs diff --git a/data/gtk/window.ui b/data/gtk/window.ui index 4665cee..51db83d 100644 --- a/data/gtk/window.ui +++ b/data/gtk/window.ui @@ -97,7 +97,7 @@ - " + @@ -168,19 +168,28 @@ True - - - - - copy + + + + 24 + 24 + 24 + 24 + false + - - + + status_page True + + + copy + + View Images @@ -214,7 +223,7 @@ - + diff --git a/data/resources/style.css b/data/resources/style.css index 96412e3..4a339da 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; } @@ -32,14 +34,14 @@ row.property label.subtitle { .custom-drag-n-drop-highlight { box-shadow: none; } + .lp-dragging-area-highlight { border-radius: 8px; - background-color: alpha(@accent_color, 0.35); - border: 2px @accent_color solid; + background-color: alpha(@blue_2, 0.35); } lpprintpreview { background-color: rgb(240, 240, 240); border-radius: 4px; box-shadow: 0 0 14px 1px black; -} +} \ No newline at end of file diff --git a/po/POTFILES.in b/po/POTFILES.in index 82000d2..57f53c3 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/widgets/drag_overlay.rs b/src/widgets/drag_overlay.rs new file mode 100644 index 0000000..cd622f8 --- /dev/null +++ b/src/widgets/drag_overlay.rs @@ -0,0 +1,121 @@ +// Copyright (c) 2022 Maximiliano Sandoval R +// 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 + +use gtk::{glib, prelude::*}; + +mod imp { + use std::cell::RefCell; + + 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)] + pub drop_target: RefCell>, + pub handler_id: RefCell>, + } + + #[glib::object_subclass] + impl ObjectSubclass for LpDragOverlay { + const NAME: &'static str = "LpDragOverlay"; + type Type = super::LpDragOverlay; + type ParentType = adw::Bin; + } + + 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: >k::DropTarget) { + if let Some(target) = self.drop_target.borrow_mut().take() { + self.obj().remove_controller(&target); + + if let Some(handler_id) = self.handler_id.borrow_mut().take() { + target.disconnect(handler_id); + } + } + + let handler_id = 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.handler_id.replace(Some(handler_id)); + + // avoid `gtk_event_controller_get_widget (controller) == NULL` failing + if drop_target.widget().is_visible() { + self.obj().add_controller(drop_target.clone()); + self.drop_target.replace(Some(drop_target.clone())); + self.obj().notify("drop-target"); + } + } + } +} + +glib::wrapper! { + pub struct LpDragOverlay(ObjectSubclass) + @extends gtk::Widget, adw::Bin; +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 389d059..0908786 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -16,6 +16,7 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +mod drag_overlay; mod image; mod image_page; mod image_view; @@ -24,6 +25,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/window.rs b/src/window.rs index 2d43d06..828a704 100644 --- a/src/window.rs +++ b/src/window.rs @@ -35,7 +35,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; @@ -90,10 +90,9 @@ mod imp { pub(super) properties_view: TemplateChild, #[template_child] - pub(super) drop_area_overlay: TemplateChild, + pub(super) drag_overlay: TemplateChild, #[template_child] pub(super) drop_target: TemplateChild, - pub(super) drop_area_highlight_widget: OnceCell, #[template_child] pub(super) forward_click_gesture: TemplateChild, @@ -378,40 +377,32 @@ mod imp { self.drop_target.set_types(&[gdk::FileList::static_type()]); - // # Drag and drop stuff --- - - // - Define widget used as a visual cue for users trying to drag-n-drop - self.drop_area_highlight_widget.set({ - const MARGIN: i32 = 24; - gtk::Box::builder() - .margin_top(MARGIN).margin_bottom(MARGIN).margin_start(MARGIN).margin_end(MARGIN) - .css_classes(["lp-dragging-area-highlight"]) - .can_target(false) - .build() - .upcast() // Upcasting to fit into the required `gtk::Widget` type - }).expect("Drop area highlight widget was defined twice"); - - // - Adds the widget on drag enter - self.drop_target.connect_enter( - clone!(@weak obj => @default-return gdk::DragAction::COPY, move |_drop_target, _x, _y| { - obj.toggle_drop_area_highlight(true); - gdk::DragAction::COPY - }) + //only accept drops from out side the app + //https://gitlab.gnome.org/GNOME/Incubator/loupe/-/merge_requests/244#note_1788260 + self.drop_target.connect_accept( + clone!(@weak obj => @default-return false, move |_drop_target, drop| { + //drag is between Loupe windows + if let Some(drop) = drop.drag() { + let file = drop + .content() + .value(gdk::FileList::static_type()) + .ok() + .and_then(|value| value.get::().ok()) + .and_then(|files| files.files().first().and_then(|file| file.path())); + let current_image = obj.image_view().current_file().and_then(|file| file.path()); + return file.ne(¤t_image); + } + //drag from another window, should always be accepted + true + }), ); - // - Removes the widget on drag leave - self.drop_target.connect_leave(clone!(@weak obj => move |_drop_target| { - obj.toggle_drop_area_highlight(false); - })); - // 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, _, _| { - // - Removes the widget on drag completion - obj.toggle_drop_area_highlight(false); // 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 @@ -441,6 +432,7 @@ mod imp { true }), ); + self.drag_overlay.set_drop_target(&*self.drop_target); } } @@ -949,36 +941,6 @@ impl LpWindow { && (!self.is_headerbar_flat() || !self.imp().headerbar_events.contains_pointer()) } - /// Displays visual feedback for users trying to drag and drop - fn toggle_drop_area_highlight(&self, display: bool) { - let overlay = &self.imp().drop_area_overlay; - let widget = self.imp().drop_area_highlight_widget.get() - .expect("`drop_area_highlight_widget` needs to be defined before being toggled"); - - // Create fade in animation. Will be reversed to make a fade-out. - let animation = adw::TimedAnimation::new( - widget, 0.0, 1.0, 250, adw::PropertyAnimationTarget::new(widget, "opacity") - ); - - if display && widget.parent().is_none() { - // Overlay widget - overlay.add_overlay(widget); - // Hide controls to avoid messy superpositions - self.hide_controls() - } else if !display && widget.parent().is_some() { - // Reverse the fade-in animation - animation.set_reverse(true); - // Remove overlay after animation end - animation.connect_done(|animation| { - let widget = animation.widget(); - let overlay = widget.parent().and_downcast::().unwrap(); - overlay.remove_overlay(&widget); - }); - }; - - animation.play(); - } - #[template_callback] fn on_fullscreened(&self) { if !self.is_fullscreened() {