window: First work for drag overlay visuals

Issue #59

Part-of: <https://gitlab.gnome.org/GNOME/loupe/-/merge_requests/258>
This commit is contained in:
qwel 2023-06-10 23:43:09 +02:00 • committed by Sophie Herold
parent 597f581980
commit 3111efdd94
3 changed files with 114 additions and 35 deletions

View file

@ -168,6 +168,16 @@
<property name="locked">True</property> <property name="locked">True</property>
<property name="reveal-flap" bind-source="properties_button" bind-property="active" bind-flags="bidirectional|sync-create"/> <property name="reveal-flap" bind-source="properties_button" bind-property="active" bind-flags="bidirectional|sync-create"/>
<property name="content"> <property name="content">
<object class="GtkOverlay" id="drop_area_overlay">
<style>
<class name="custom-drag-n-drop-highlight"/>
</style>
<child>
<object class="GtkDropTarget" id="drop_target">
<property name="actions">copy</property>
</object>
</child>
<child>
<object class="GtkStack" id="stack"> <object class="GtkStack" id="stack">
<property name="visible-child">status_page</property> <property name="visible-child">status_page</property>
<property name="vexpand">True</property> <property name="vexpand">True</property>
@ -203,9 +213,6 @@
</child> </child>
</object> </object>
</child> </child>
<child>
<object class="GtkDropTarget" id="drop_target">
<property name="actions">copy</property>
</object> </object>
</child> </child>
</object> </object>

View file

@ -29,6 +29,15 @@ row.property label.subtitle {
box-shadow: 0 0 10px rgba(0, 0, 0, 200); 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 { lpprintpreview {
background-color: rgb(240, 240, 240); background-color: rgb(240, 240, 240);
border-radius: 4px; border-radius: 4px;

View file

@ -88,8 +88,12 @@ mod imp {
pub(super) image_view: TemplateChild<LpImageView>, pub(super) image_view: TemplateChild<LpImageView>,
#[template_child] #[template_child]
pub(super) properties_view: TemplateChild<LpPropertiesView>, pub(super) properties_view: TemplateChild<LpPropertiesView>,
#[template_child]
pub(super) drop_area_overlay: TemplateChild<gtk::Overlay>,
#[template_child] #[template_child]
pub(super) drop_target: TemplateChild<gtk::DropTarget>, pub(super) drop_target: TemplateChild<gtk::DropTarget>,
pub(super) drop_area_highlight_widget: OnceCell<gtk::Widget>,
#[template_child] #[template_child]
pub(super) forward_click_gesture: TemplateChild<gtk::GestureClick>, pub(super) forward_click_gesture: TemplateChild<gtk::GestureClick>,
@ -374,12 +378,41 @@ mod imp {
self.drop_target.set_types(&[gdk::FileList::static_type()]); 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 // For callbacks, you will want to reference the GTK docs on
// the relevant signal to see which parameters you need. // the relevant signal to see which parameters you need.
// In this case, we need only need the GValue, // In this case, we need only need the GValue,
// so we name it `value` then use `_` for the other spots. // so we name it `value` then use `_` for the other spots.
self.drop_target.connect_drop( self.drop_target.connect_drop(
clone!(@weak obj => @default-return false, move |_, value, _, _| { 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, // 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 // 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. // 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()) && (!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::<gtk::Overlay>().unwrap();
overlay.remove_overlay(&widget);
});
};
animation.play();
}
#[template_callback] #[template_callback]
fn on_fullscreened(&self) { fn on_fullscreened(&self) {
if !self.is_fullscreened() { if !self.is_fullscreened() {