image: Add newly designed thumbnail for DnD
The old method stopped working with the new decoder setup. The new design features a frame for contrast and rotates images based on EXIF information. Closes #89
This commit is contained in:
parent
88efb9e5fd
commit
05ef429b86
6 changed files with 87 additions and 122 deletions
|
|
@ -47,3 +47,10 @@ row.property label.subtitle {
|
|||
opacity: 1.0;
|
||||
font-size: 11pt;
|
||||
}
|
||||
|
||||
.drag-icon {
|
||||
margin: 16px;
|
||||
border-radius: 5px;
|
||||
outline: 2px solid white;
|
||||
box-shadow: 0 0 10px rgba(0.4, 0.4, 0.4, 0.8);
|
||||
}
|
||||
|
|
@ -26,7 +26,6 @@ mod decoder;
|
|||
mod file_model;
|
||||
mod i18n;
|
||||
mod image_metadata;
|
||||
mod thumbnail;
|
||||
mod util;
|
||||
mod widgets;
|
||||
mod window;
|
||||
|
|
|
|||
|
|
@ -1,84 +0,0 @@
|
|||
use gtk::prelude::*;
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::{gdk, glib};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
const SIZE: i32 = 128;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Thumbnail {
|
||||
pub image: OnceCell<gdk::Paintable>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for Thumbnail {
|
||||
const NAME: &'static str = "Thumbnail";
|
||||
type Type = super::Thumbnail;
|
||||
type Interfaces = (gdk::Paintable,);
|
||||
}
|
||||
|
||||
impl ObjectImpl for Thumbnail {}
|
||||
impl PaintableImpl for Thumbnail {
|
||||
fn intrinsic_height(&self) -> i32 {
|
||||
let image = self.image.get().unwrap();
|
||||
let width = image.intrinsic_width();
|
||||
let height = image.intrinsic_height();
|
||||
let aspect_ratio = width as f64 / (height as f64).max(std::f64::EPSILON);
|
||||
|
||||
if width >= height {
|
||||
(SIZE as f64 / aspect_ratio) as i32
|
||||
} else {
|
||||
SIZE
|
||||
}
|
||||
}
|
||||
|
||||
fn intrinsic_width(&self) -> i32 {
|
||||
let image = self.image.get().unwrap();
|
||||
let width = image.intrinsic_width();
|
||||
let height = image.intrinsic_height();
|
||||
let aspect_ratio = width as f64 / (height as f64).max(std::f64::EPSILON);
|
||||
|
||||
if width >= height {
|
||||
SIZE
|
||||
} else {
|
||||
(SIZE as f64 * aspect_ratio) as i32
|
||||
}
|
||||
}
|
||||
|
||||
fn intrinsic_aspect_ratio(&self) -> f64 {
|
||||
self.image.get().unwrap().intrinsic_aspect_ratio()
|
||||
}
|
||||
|
||||
fn snapshot(&self, snapshot: &gdk::Snapshot, _width: f64, _height: f64) {
|
||||
let paintable = self.obj();
|
||||
let width = paintable.intrinsic_width() as f64;
|
||||
let height = paintable.intrinsic_height() as f64;
|
||||
|
||||
self.image.get().unwrap().snapshot(snapshot, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
/// Thumbnail Paintable
|
||||
///
|
||||
/// Creates a thumbnail from another paintable. The thumbnail will be drawn
|
||||
/// at a maximum size of 128 × 128 taking into account the original
|
||||
/// paintable aspect ratio.
|
||||
pub struct Thumbnail(ObjectSubclass<imp::Thumbnail>) @implements gdk::Paintable;
|
||||
}
|
||||
|
||||
impl Thumbnail {
|
||||
pub fn new(paintable: &impl glib::IsA<gdk::Paintable>) -> Self {
|
||||
let object = glib::Object::new::<Self>();
|
||||
let imp = object.imp();
|
||||
|
||||
imp.image.set(paintable.clone().upcast()).unwrap();
|
||||
|
||||
object
|
||||
}
|
||||
}
|
||||
|
|
@ -70,6 +70,11 @@ const RUBBERBANDING_EXPONENT: f64 = 0.4;
|
|||
/// Max zoom level 2000%
|
||||
const MAX_ZOOM_LEVEL: f64 = 20.0;
|
||||
|
||||
/// Thumbnail size in application pixels
|
||||
///
|
||||
/// The thumbnail is currently used for drag and drop.
|
||||
const THUMBNAIL_SIZE: f32 = 128.;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
|
|
@ -488,7 +493,6 @@ mod imp {
|
|||
let widget = self.obj();
|
||||
let widget_width = widget.width() as f64;
|
||||
let widget_height = widget.height() as f64;
|
||||
let (original_width, original_height) = widget.original_dimensions();
|
||||
let display_width = widget.image_displayed_width();
|
||||
let display_height = widget.image_displayed_height();
|
||||
|
||||
|
|
@ -535,31 +539,8 @@ mod imp {
|
|||
// Apply zoom
|
||||
snapshot.scale(applicable_zoom as f32, applicable_zoom as f32);
|
||||
|
||||
// Put image origin at (0, 0) again with rotation
|
||||
snapshot.translate(&graphene::Point::new(
|
||||
-(original_width as f32 - display_width as f32 / applicable_zoom as f32) / 2.,
|
||||
-(original_height as f32 - display_height as f32 / applicable_zoom as f32) / 2.,
|
||||
));
|
||||
|
||||
// Undo centering in coordinates
|
||||
snapshot.translate(&graphene::Point::new(
|
||||
original_width as f32 / 2.,
|
||||
original_height as f32 / 2.,
|
||||
));
|
||||
|
||||
// Apply the transformations from properties
|
||||
snapshot.rotate(widget.rotation() as f32);
|
||||
if widget.mirrored() {
|
||||
snapshot.scale(-1., 1.);
|
||||
}
|
||||
|
||||
// Center image in coordinates.
|
||||
// Needed for rotating around the center of the image, and
|
||||
// mirroring the image does not put it to a completely different position.
|
||||
snapshot.translate(&graphene::Point::new(
|
||||
-original_width as f32 / 2.,
|
||||
-original_height as f32 / 2.,
|
||||
));
|
||||
// Apply rotation and mirroring
|
||||
widget.snapshot_rotate_mirror(snapshot, widget.rotation() as f32, widget.mirrored());
|
||||
|
||||
// Add texture(s)
|
||||
self.tiles
|
||||
|
|
@ -843,9 +824,32 @@ impl LpImage {
|
|||
}
|
||||
}
|
||||
|
||||
/// Texture that contains the original image
|
||||
pub fn texture(&self) -> Option<gdk::Texture> {
|
||||
None
|
||||
/// Returns a thumbnail of the displated image
|
||||
pub fn thumbnail(&self) -> Option<gdk::Paintable> {
|
||||
let (width, height) = self.original_dimensions();
|
||||
let long_side = i32::max(width, height);
|
||||
let orientation = self.metadata().orientation();
|
||||
|
||||
let scale = f32::min(1., THUMBNAIL_SIZE / long_side as f32);
|
||||
let render_options = tiling::RenderOptions {
|
||||
scaling_filter: gsk::ScalingFilter::Trilinear,
|
||||
};
|
||||
|
||||
let snapshot = gtk::Snapshot::new();
|
||||
|
||||
snapshot.scale(scale, scale);
|
||||
self.snapshot_rotate_mirror(
|
||||
&snapshot,
|
||||
-orientation.rotation as f32,
|
||||
orientation.mirrored,
|
||||
);
|
||||
|
||||
self.imp()
|
||||
.tiles
|
||||
.load()
|
||||
.add_to_snapshot(&snapshot, scale as f64, &render_options);
|
||||
|
||||
snapshot.to_paintable(None)
|
||||
}
|
||||
|
||||
fn mirrored(&self) -> bool {
|
||||
|
|
@ -1361,6 +1365,43 @@ impl LpImage {
|
|||
self.width() as f64
|
||||
}
|
||||
|
||||
/// Mirrors and rotates snapshot according to arguments
|
||||
///
|
||||
/// After the operation the image is positioned such that it's origin
|
||||
/// is a `(0, 0)` again.
|
||||
pub fn snapshot_rotate_mirror(&self, snapshot: >k::Snapshot, rotation: f32, mirrored: bool) {
|
||||
let applicable_zoom = self.applicable_zoom();
|
||||
let (original_width, original_height) = self.original_dimensions();
|
||||
let display_width = self.image_displayed_width();
|
||||
let display_height = self.image_displayed_height();
|
||||
|
||||
// Put image origin at (0, 0) again with rotation
|
||||
snapshot.translate(&graphene::Point::new(
|
||||
-(original_width as f32 - display_width as f32 / applicable_zoom as f32) / 2.,
|
||||
-(original_height as f32 - display_height as f32 / applicable_zoom as f32) / 2.,
|
||||
));
|
||||
|
||||
// Undo centering in coordinates
|
||||
snapshot.translate(&graphene::Point::new(
|
||||
original_width as f32 / 2.,
|
||||
original_height as f32 / 2.,
|
||||
));
|
||||
|
||||
// Apply the transformations from properties
|
||||
snapshot.rotate(rotation);
|
||||
if mirrored {
|
||||
snapshot.scale(-1., 1.);
|
||||
}
|
||||
|
||||
// Center image in coordinates.
|
||||
// Needed for rotating around the center of the image, and
|
||||
// mirroring the image does not put it to a completely different position.
|
||||
snapshot.translate(&graphene::Point::new(
|
||||
-original_width as f32 / 2.,
|
||||
-original_height as f32 / 2.,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> LpImageMetadata {
|
||||
self.imp().image_metadata.borrow().clone()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,10 +173,6 @@ impl LpImagePage {
|
|||
self.imp().image.get()
|
||||
}
|
||||
|
||||
pub fn texture(&self) -> Option<gdk::Texture> {
|
||||
self.imp().image.texture()
|
||||
}
|
||||
|
||||
pub fn content_provider(&self) -> Option<gdk::ContentProvider> {
|
||||
self.imp().image.content_provider()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
use crate::deps::*;
|
||||
use crate::file_model::LpFileModel;
|
||||
use crate::i18n::*;
|
||||
use crate::thumbnail::Thumbnail;
|
||||
use crate::util::{Direction, Position};
|
||||
use crate::widgets::{LpImage, LpImagePage, LpSlidingView};
|
||||
|
||||
|
|
@ -177,9 +176,16 @@ mod imp {
|
|||
);
|
||||
|
||||
source.connect_drag_begin(glib::clone!(@weak obj => move |source, _| {
|
||||
if let Some(texture) = obj.current_page().and_then(|p| p.texture()) {
|
||||
let thumbnail = Thumbnail::new(&texture);
|
||||
source.set_icon(Some(&thumbnail), 0, 0);
|
||||
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);
|
||||
if let Some(drag) = source.drag() {
|
||||
let drag_icon = gtk::DragIcon::for_drag(&drag);
|
||||
// Rounds corners, adds outline and shadow
|
||||
drag_icon.add_css_class("drag-icon");
|
||||
// Make border-radius clip the image
|
||||
drag_icon.set_overflow(gtk::Overflow::Hidden);
|
||||
}
|
||||
};
|
||||
}));
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue