diff --git a/data/gtk/window.ui b/data/gtk/window.ui index 8d4944f..4665cee 100644 --- a/data/gtk/window.ui +++ b/data/gtk/window.ui @@ -168,46 +168,53 @@ True - - status_page - True - - - View Images - Drag and drop images here - - - center - _Open Files… - True - win.open - - - - - - - - - - 9 - - - - - 8 - - - - + + copy + + + status_page + True + + + View Images + Drag and drop images here + + + center + _Open Files… + True + win.open + + + + + + + + + + 9 + + + + + 8 + + + + + + diff --git a/data/resources/style.css b/data/resources/style.css index 6a6a0db..96412e3 100644 --- a/data/resources/style.css +++ b/data/resources/style.css @@ -29,6 +29,15 @@ row.property label.subtitle { box-shadow: 0 0 10px rgba(0, 0, 0, 200); } +.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; +} + lpprintpreview { background-color: rgb(240, 240, 240); border-radius: 4px; diff --git a/src/window.rs b/src/window.rs index 3f37b29..2d43d06 100644 --- a/src/window.rs +++ b/src/window.rs @@ -88,8 +88,12 @@ mod imp { pub(super) image_view: TemplateChild, #[template_child] pub(super) properties_view: TemplateChild, + + #[template_child] + pub(super) drop_area_overlay: TemplateChild, #[template_child] pub(super) drop_target: TemplateChild, + pub(super) drop_area_highlight_widget: OnceCell, #[template_child] pub(super) forward_click_gesture: TemplateChild, @@ -374,12 +378,41 @@ 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 + }) + ); + + // - 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 // from the GValue, we need to use the `get()` method. @@ -916,6 +949,36 @@ 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() {