window: Use custom overlay visual for drop target
Closes #59 Part-of: <https://gitlab.gnome.org/GNOME/loupe/-/merge_requests/258>
This commit is contained in:
parent
3111efdd94
commit
78b16518e5
6 changed files with 171 additions and 74 deletions
|
|
@ -97,7 +97,7 @@
|
||||||
<child type="top">
|
<child type="top">
|
||||||
<object class="AdwHeaderBar" id="headerbar">
|
<object class="AdwHeaderBar" id="headerbar">
|
||||||
<property name="show-end-title-buttons" bind-source="LpWindow" bind-property="fullscreened" bind-flags="sync-create|invert-boolean"/>
|
<property name="show-end-title-buttons" bind-source="LpWindow" bind-property="fullscreened" bind-flags="sync-create|invert-boolean"/>
|
||||||
<property name="show-start-title-buttons" bind-source="LpWindow" bind-property="fullscreened" bind-flags="sync-create|invert-boolean"/>"
|
<property name="show-start-title-buttons" bind-source="LpWindow" bind-property="fullscreened" bind-flags="sync-create|invert-boolean"/>
|
||||||
<style>
|
<style>
|
||||||
<class name="fullscreen-osd"/>
|
<class name="fullscreen-osd"/>
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -168,19 +168,28 @@
|
||||||
<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">
|
<object class="LpDragOverlay" id="drag_overlay">
|
||||||
<style>
|
<property name="overlayed">
|
||||||
<class name="custom-drag-n-drop-highlight"/>
|
<object class="AdwBin">
|
||||||
</style>
|
<property name="margin-top">24</property>
|
||||||
<child>
|
<property name="margin-bottom">24</property>
|
||||||
<object class="GtkDropTarget" id="drop_target">
|
<property name="margin-start">24</property>
|
||||||
<property name="actions">copy</property>
|
<property name="margin-end">24</property>
|
||||||
|
<property name="can-target">false</property>
|
||||||
|
<style>
|
||||||
|
<class name="lp-dragging-area-highlight"/>
|
||||||
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</property>
|
||||||
<child>
|
<property name="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>
|
||||||
|
<child>
|
||||||
|
<object class="GtkDropTarget" id="drop_target">
|
||||||
|
<property name="actions">copy</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwStatusPage" id="status_page">
|
<object class="AdwStatusPage" id="status_page">
|
||||||
<property name="title" translatable="yes">View Images</property>
|
<property name="title" translatable="yes">View Images</property>
|
||||||
|
|
@ -214,7 +223,7 @@
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</property>
|
||||||
</object>
|
</object>
|
||||||
</property>
|
</property>
|
||||||
<property name="separator">
|
<property name="separator">
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@ button.osd.circular {
|
||||||
background: rgba(0, 0, 0, 0.65);
|
background: rgba(0, 0, 0, 0.65);
|
||||||
}
|
}
|
||||||
|
|
||||||
shadow, border, outline {
|
shadow,
|
||||||
|
border,
|
||||||
|
outline {
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,14 +34,14 @@ row.property label.subtitle {
|
||||||
.custom-drag-n-drop-highlight {
|
.custom-drag-n-drop-highlight {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lp-dragging-area-highlight {
|
.lp-dragging-area-highlight {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: alpha(@accent_color, 0.35);
|
background-color: alpha(@blue_2, 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;
|
||||||
box-shadow: 0 0 14px 1px black;
|
box-shadow: 0 0 14px 1px black;
|
||||||
}
|
}
|
||||||
|
|
@ -20,6 +20,7 @@ src/image_metadata/obj.rs
|
||||||
src/main.rs
|
src/main.rs
|
||||||
src/util/gettext.rs
|
src/util/gettext.rs
|
||||||
src/util/mod.rs
|
src/util/mod.rs
|
||||||
|
src/widgets/drag_overlay.rs
|
||||||
src/widgets/image.rs
|
src/widgets/image.rs
|
||||||
src/widgets/image_page.rs
|
src/widgets/image_page.rs
|
||||||
src/widgets/image_view.rs
|
src/widgets/image_view.rs
|
||||||
|
|
|
||||||
121
src/widgets/drag_overlay.rs
Normal file
121
src/widgets/drag_overlay.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
// Copyright (c) 2022 Maximiliano Sandoval R <msandova@gnome.org>
|
||||||
|
// 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 <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
// 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<gtk::Widget>,
|
||||||
|
/// Widget overplayed when dragging over child
|
||||||
|
#[property(set = Self::set_overlayed)]
|
||||||
|
pub overlayed: Option<gtk::Widget>,
|
||||||
|
pub overlay: gtk::Overlay,
|
||||||
|
pub revealer: gtk::Revealer,
|
||||||
|
#[property(set = Self::set_drop_target, get, explicit_notify)]
|
||||||
|
pub drop_target: RefCell<Option<gtk::DropTarget>>,
|
||||||
|
pub handler_id: RefCell<Option<glib::SignalHandlerId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<gtk::Widget>) {
|
||||||
|
self.overlay.set_child(child.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_overlayed(&self, overlayed: Option<gtk::Widget>) {
|
||||||
|
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<imp::LpDragOverlay>)
|
||||||
|
@extends gtk::Widget, adw::Bin;
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
mod drag_overlay;
|
||||||
mod image;
|
mod image;
|
||||||
mod image_page;
|
mod image_page;
|
||||||
mod image_view;
|
mod image_view;
|
||||||
|
|
@ -24,6 +25,7 @@ mod print_preview;
|
||||||
mod properties_view;
|
mod properties_view;
|
||||||
mod sliding_view;
|
mod sliding_view;
|
||||||
|
|
||||||
|
pub use drag_overlay::LpDragOverlay;
|
||||||
pub use image::LpImage;
|
pub use image::LpImage;
|
||||||
pub use image_page::LpImagePage;
|
pub use image_page::LpImagePage;
|
||||||
pub use image_view::LpImageView;
|
pub use image_view::LpImageView;
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::util::{self, Direction, Position};
|
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
|
/// Show window after X milliseconds even if image dimensions are not known yet
|
||||||
const SHOW_WINDOW_AFTER: u64 = 2000;
|
const SHOW_WINDOW_AFTER: u64 = 2000;
|
||||||
|
|
@ -90,10 +90,9 @@ mod imp {
|
||||||
pub(super) properties_view: TemplateChild<LpPropertiesView>,
|
pub(super) properties_view: TemplateChild<LpPropertiesView>,
|
||||||
|
|
||||||
#[template_child]
|
#[template_child]
|
||||||
pub(super) drop_area_overlay: TemplateChild<gtk::Overlay>,
|
pub(super) drag_overlay: TemplateChild<LpDragOverlay>,
|
||||||
#[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>,
|
||||||
|
|
@ -378,40 +377,32 @@ mod imp {
|
||||||
|
|
||||||
self.drop_target.set_types(&[gdk::FileList::static_type()]);
|
self.drop_target.set_types(&[gdk::FileList::static_type()]);
|
||||||
|
|
||||||
// # Drag and drop stuff ---
|
//only accept drops from out side the app
|
||||||
|
//https://gitlab.gnome.org/GNOME/Incubator/loupe/-/merge_requests/244#note_1788260
|
||||||
// - Define widget used as a visual cue for users trying to drag-n-drop
|
self.drop_target.connect_accept(
|
||||||
self.drop_area_highlight_widget.set({
|
clone!(@weak obj => @default-return false, move |_drop_target, drop| {
|
||||||
const MARGIN: i32 = 24;
|
//drag is between Loupe windows
|
||||||
gtk::Box::builder()
|
if let Some(drop) = drop.drag() {
|
||||||
.margin_top(MARGIN).margin_bottom(MARGIN).margin_start(MARGIN).margin_end(MARGIN)
|
let file = drop
|
||||||
.css_classes(["lp-dragging-area-highlight"])
|
.content()
|
||||||
.can_target(false)
|
.value(gdk::FileList::static_type())
|
||||||
.build()
|
.ok()
|
||||||
.upcast() // Upcasting to fit into the required `gtk::Widget` type
|
.and_then(|value| value.get::<gdk::FileList>().ok())
|
||||||
}).expect("Drop area highlight widget was defined twice");
|
.and_then(|files| files.files().first().and_then(|file| file.path()));
|
||||||
|
let current_image = obj.image_view().current_file().and_then(|file| file.path());
|
||||||
// - Adds the widget on drag enter
|
return file.ne(¤t_image);
|
||||||
self.drop_target.connect_enter(
|
}
|
||||||
clone!(@weak obj => @default-return gdk::DragAction::COPY, move |_drop_target, _x, _y| {
|
//drag from another window, should always be accepted
|
||||||
obj.toggle_drop_area_highlight(true);
|
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
|
||||||
|
|
@ -441,6 +432,7 @@ mod imp {
|
||||||
true
|
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())
|
&& (!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() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue