image: Add rotation and auto rotate based on exif
This commit is contained in:
parent
3d6564377a
commit
a2e6767584
10 changed files with 257 additions and 94 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
|
@ -940,6 +940,15 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kamadak-exif"
|
||||||
|
version = "0.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077"
|
||||||
|
dependencies = [
|
||||||
|
"mutate_once",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kv-log-macro"
|
name = "kv-log-macro"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
|
@ -1041,6 +1050,7 @@ dependencies = [
|
||||||
"gio",
|
"gio",
|
||||||
"gtk-macros",
|
"gtk-macros",
|
||||||
"gtk4",
|
"gtk4",
|
||||||
|
"kamadak-exif",
|
||||||
"libadwaita",
|
"libadwaita",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
@ -1074,6 +1084,12 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mutate_once"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.24.2"
|
version = "0.24.2"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.66"
|
anyhow = "1.0.66"
|
||||||
gtk-macros = "0.3.0"
|
gtk-macros = "0.3.0"
|
||||||
|
kamadak-exif = "0.5.5"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
once_cell = "1.16.0"
|
once_cell = "1.16.0"
|
||||||
pretty_env_logger = "0.4.0"
|
pretty_env_logger = "0.4.0"
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,6 @@
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="LpImage" id="image">
|
<object class="LpImage" id="image">
|
||||||
<property name="halign">center</property>
|
|
||||||
<property name="valign">center</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkGestureClick" id="click_gesture">
|
<object class="GtkGestureClick" id="click_gesture">
|
||||||
<property name="button">3</property>
|
<property name="button">3</property>
|
||||||
|
|
@ -63,10 +61,10 @@
|
||||||
<attribute name="label" translatable="yes">_Copy</attribute>
|
<attribute name="label" translatable="yes">_Copy</attribute>
|
||||||
<attribute name="action">win.copy</attribute>
|
<attribute name="action">win.copy</attribute>
|
||||||
</item>
|
</item>
|
||||||
<!--<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes">_Rotate</attribute>
|
<attribute name="label" translatable="yes">_Rotate</attribute>
|
||||||
<attribute name="action">win.rotate-image</attribute>
|
<attribute name="action">win.rotate-image</attribute>
|
||||||
</item>-->
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes">_Set as Background…</attribute>
|
<attribute name="label" translatable="yes">_Set as Background…</attribute>
|
||||||
<attribute name="action">win.set-background</attribute>
|
<attribute name="action">win.set-background</attribute>
|
||||||
|
|
|
||||||
|
|
@ -129,10 +129,10 @@
|
||||||
<attribute name="label" translatable="yes">_Print…</attribute>
|
<attribute name="label" translatable="yes">_Print…</attribute>
|
||||||
<attribute name="action">win.print</attribute>
|
<attribute name="action">win.print</attribute>
|
||||||
</item>
|
</item>
|
||||||
<!--<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes">_Rotate</attribute>
|
<attribute name="label" translatable="yes">_Rotate</attribute>
|
||||||
<attribute name="action">win.rotate-image</attribute>
|
<attribute name="action">win.rotate-image</attribute>
|
||||||
</item>-->
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes">_Set as Background…</attribute>
|
<attribute name="label" translatable="yes">_Set as Background…</attribute>
|
||||||
<attribute name="action">win.set-background</attribute>
|
<attribute name="action">win.set-background</attribute>
|
||||||
|
|
|
||||||
88
src/image_metadata.rs
Normal file
88
src/image_metadata.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
use gio::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub enum ImageMetadata {
|
||||||
|
Exif(exif::Exif),
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageMetadata {
|
||||||
|
// TODO: error handling
|
||||||
|
pub fn load(file: &gio::File) -> Self {
|
||||||
|
let file = std::fs::File::open(file.path().unwrap()).unwrap();
|
||||||
|
let mut bufreader = std::io::BufReader::new(&file);
|
||||||
|
let exifreader = exif::Reader::new();
|
||||||
|
|
||||||
|
if let Ok(exif) = exifreader.read_from_container(&mut bufreader) {
|
||||||
|
Self::Exif(exif)
|
||||||
|
} else {
|
||||||
|
Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn orientation(&self) -> Orientation {
|
||||||
|
match self {
|
||||||
|
Self::Exif(exif) => {
|
||||||
|
if let Some(orientation) = exif
|
||||||
|
.get_field(exif::Tag::Orientation, exif::In::PRIMARY)
|
||||||
|
.and_then(|x| x.value.get_uint(0))
|
||||||
|
{
|
||||||
|
Orientation::from(orientation)
|
||||||
|
} else {
|
||||||
|
Orientation::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::None => Orientation::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement something (that does not fill the complete screen with binary data)
|
||||||
|
impl std::fmt::Debug for ImageMetadata {
|
||||||
|
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Copy)]
|
||||||
|
pub struct Orientation {
|
||||||
|
pub rotation: f64,
|
||||||
|
pub mirrored: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for Orientation {
|
||||||
|
fn from(number: u32) -> Self {
|
||||||
|
match number {
|
||||||
|
8 => Self {
|
||||||
|
rotation: 90.,
|
||||||
|
mirrored: false,
|
||||||
|
},
|
||||||
|
3 => Self {
|
||||||
|
rotation: 180.,
|
||||||
|
mirrored: false,
|
||||||
|
},
|
||||||
|
6 => Self {
|
||||||
|
rotation: 270.,
|
||||||
|
mirrored: false,
|
||||||
|
},
|
||||||
|
2 => Self {
|
||||||
|
rotation: 0.,
|
||||||
|
mirrored: true,
|
||||||
|
},
|
||||||
|
5 => Self {
|
||||||
|
rotation: 90.,
|
||||||
|
mirrored: true,
|
||||||
|
},
|
||||||
|
4 => Self {
|
||||||
|
rotation: 180.,
|
||||||
|
mirrored: true,
|
||||||
|
},
|
||||||
|
7 => Self {
|
||||||
|
rotation: 270.,
|
||||||
|
mirrored: true,
|
||||||
|
},
|
||||||
|
_ => Self::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@ mod application;
|
||||||
mod config;
|
mod config;
|
||||||
mod file_model;
|
mod file_model;
|
||||||
mod i18n;
|
mod i18n;
|
||||||
|
mod image_metadata;
|
||||||
mod thumbnail;
|
mod thumbnail;
|
||||||
mod util;
|
mod util;
|
||||||
mod widgets;
|
mod widgets;
|
||||||
|
|
|
||||||
|
|
@ -19,21 +19,31 @@
|
||||||
|
|
||||||
use crate::deps::*;
|
use crate::deps::*;
|
||||||
|
|
||||||
use gtk::{prelude::*, subclass::prelude::*};
|
use adw::{prelude::*, subclass::prelude::*};
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
|
|
||||||
|
use crate::image_metadata::ImageMetadata;
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct LpImage {
|
pub struct LpImage {
|
||||||
pub file: RefCell<Option<gio::File>>,
|
pub file: RefCell<Option<gio::File>>,
|
||||||
pub image_width: Cell<i32>,
|
|
||||||
pub image_height: Cell<i32>,
|
|
||||||
pub texture: RefCell<Option<gdk::Texture>>,
|
pub texture: RefCell<Option<gdk::Texture>>,
|
||||||
|
/// Rotation final value (can differ from `rotation` during animation)
|
||||||
|
pub rotation_target: Cell<f64>,
|
||||||
|
/// Rotated presentation of original image in degrees clockwise
|
||||||
|
pub rotation: Cell<f64>,
|
||||||
|
// Animates the `rotation` property
|
||||||
|
pub rotation_animation: RefCell<Option<adw::TimedAnimation>>,
|
||||||
|
/// Mirrored presentation of original image
|
||||||
|
pub mirrored: Cell<bool>,
|
||||||
|
/// Currently EXIF data
|
||||||
|
pub image_metadata: RefCell<ImageMetadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
|
@ -46,13 +56,13 @@ mod imp {
|
||||||
impl ObjectImpl for LpImage {
|
impl ObjectImpl for LpImage {
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
vec![glib::ParamSpecObject::new(
|
vec![
|
||||||
"file",
|
glib::ParamSpecObject::builder::<gio::File>("file")
|
||||||
"File",
|
.read_only()
|
||||||
"The current file",
|
.build(),
|
||||||
gio::File::static_type(),
|
glib::ParamSpecDouble::builder("rotation").build(),
|
||||||
glib::ParamFlags::READWRITE,
|
glib::ParamSpecBoolean::builder("mirrored").build(),
|
||||||
)]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
PROPERTIES.as_ref()
|
PROPERTIES.as_ref()
|
||||||
|
|
@ -62,6 +72,8 @@ mod imp {
|
||||||
let obj = self.instance();
|
let obj = self.instance();
|
||||||
match pspec.name() {
|
match pspec.name() {
|
||||||
"file" => obj.file().to_value(),
|
"file" => obj.file().to_value(),
|
||||||
|
"rotation" => obj.rotation().to_value(),
|
||||||
|
"mirrored" => obj.mirrored().to_value(),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +81,8 @@ mod imp {
|
||||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||||
let obj = self.instance();
|
let obj = self.instance();
|
||||||
match pspec.name() {
|
match pspec.name() {
|
||||||
"file" => obj.set_file(&value.get().unwrap()),
|
"rotation" => obj.set_rotation(value.get().unwrap()),
|
||||||
|
"mirrored" => obj.set_mirrored(value.get().unwrap()),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -78,6 +91,13 @@ mod imp {
|
||||||
let obj = self.instance();
|
let obj = self.instance();
|
||||||
obj.set_hexpand(true);
|
obj.set_hexpand(true);
|
||||||
obj.set_vexpand(true);
|
obj.set_vexpand(true);
|
||||||
|
|
||||||
|
let rotation_animation = adw::TimedAnimation::builder()
|
||||||
|
.duration(200)
|
||||||
|
.widget(&*obj)
|
||||||
|
.target(&adw::PropertyAnimationTarget::new(&*obj, "rotation"))
|
||||||
|
.build();
|
||||||
|
self.rotation_animation.replace(Some(rotation_animation));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispose(&self) {
|
fn dispose(&self) {
|
||||||
|
|
@ -92,69 +112,78 @@ mod imp {
|
||||||
fn snapshot(&self, snapshot: >k::Snapshot) {
|
fn snapshot(&self, snapshot: >k::Snapshot) {
|
||||||
if let Some(texture) = self.texture.borrow().as_ref() {
|
if let Some(texture) = self.texture.borrow().as_ref() {
|
||||||
let widget = self.instance();
|
let widget = self.instance();
|
||||||
let width = widget.width() as f64;
|
let widget_width = widget.width() as f64;
|
||||||
let height = widget.height() as f64;
|
let widget_height = widget.height() as f64;
|
||||||
let texture_ratio = texture.intrinsic_aspect_ratio();
|
let texture_ratio = texture.intrinsic_aspect_ratio();
|
||||||
|
let rotated = widget.rotation().to_radians().sin().abs();
|
||||||
|
|
||||||
if texture_ratio == 0.0 {
|
if texture_ratio == 0.0 {
|
||||||
texture.snapshot(snapshot, width, height);
|
// TODO: We probably need rotation etc on this?
|
||||||
|
// Maybe just set ratio to 1. or widget_width/widget_height and run code below?
|
||||||
|
texture.snapshot(snapshot, widget_width, widget_height);
|
||||||
} else {
|
} else {
|
||||||
let widget_ratio = width / height;
|
let widget_ratio = widget_width / widget_height;
|
||||||
|
|
||||||
let (snapshot_width, snapshot_height) = if texture_ratio > widget_ratio {
|
// image dimension for default orientation
|
||||||
(width, width / texture_ratio)
|
let (snapshot_width_default, snapshot_height_default) =
|
||||||
} else {
|
if texture_ratio > widget_ratio {
|
||||||
(height * texture_ratio, height)
|
(widget_width, widget_width / texture_ratio)
|
||||||
};
|
} else {
|
||||||
|
(widget_height * texture_ratio, widget_height)
|
||||||
|
};
|
||||||
|
|
||||||
let x = (width - snapshot_width.ceil()) / 2.0;
|
// image dimensions for 90deg rotated
|
||||||
let y = (height - snapshot_height.ceil()).floor() / 2.0;
|
let (snapshot_width_rotated, snapshot_height_rotated) =
|
||||||
|
if (1.0 / texture_ratio) > widget_ratio {
|
||||||
|
(widget_width * texture_ratio, widget_width)
|
||||||
|
} else {
|
||||||
|
(widget_height, widget_height / texture_ratio)
|
||||||
|
};
|
||||||
|
|
||||||
|
// interpolate between size computation during rotation
|
||||||
|
let mut snapshot_height = rotated * snapshot_height_rotated
|
||||||
|
+ (1. - rotated) * snapshot_height_default;
|
||||||
|
let mut snapshot_width =
|
||||||
|
rotated * snapshot_width_rotated + (1. - rotated) * snapshot_width_default;
|
||||||
|
|
||||||
|
// Don't make the image larger then default size
|
||||||
|
if snapshot_height > texture.height() as f64
|
||||||
|
|| snapshot_width > texture.width() as f64
|
||||||
|
{
|
||||||
|
snapshot_height = texture.height() as f64;
|
||||||
|
snapshot_width = texture.width() as f64;
|
||||||
|
}
|
||||||
|
|
||||||
snapshot.save();
|
snapshot.save();
|
||||||
|
|
||||||
|
// center image in widget
|
||||||
|
let x = (widget_width - snapshot_width) / 2.0;
|
||||||
|
let y = (widget_height - snapshot_height) / 2.0;
|
||||||
snapshot.translate(&graphene::Point::new(x as f32, y as f32));
|
snapshot.translate(&graphene::Point::new(x as f32, y as f32));
|
||||||
|
|
||||||
|
// center image in coordinates to rotate origin
|
||||||
|
snapshot.translate(&graphene::Point::new(
|
||||||
|
(snapshot_width / 2.) as f32,
|
||||||
|
(snapshot_height / 2.) as f32,
|
||||||
|
));
|
||||||
|
|
||||||
|
// apply the transformations from properties
|
||||||
|
snapshot.rotate(widget.rotation() as f32);
|
||||||
|
if widget.mirrored() {
|
||||||
|
snapshot.scale(-1., 1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
// move back to original position
|
||||||
|
snapshot.translate(&graphene::Point::new(
|
||||||
|
(-snapshot_width / 2.) as f32,
|
||||||
|
(-snapshot_height / 2.) as f32,
|
||||||
|
));
|
||||||
|
|
||||||
texture.snapshot(snapshot, snapshot_width, snapshot_height);
|
texture.snapshot(snapshot, snapshot_width, snapshot_height);
|
||||||
snapshot.restore();
|
snapshot.restore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_mode(&self) -> gtk::SizeRequestMode {
|
|
||||||
gtk::SizeRequestMode::HeightForWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
fn measure(&self, orienation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) {
|
|
||||||
if let Some(texture) = self.texture.borrow().as_ref() {
|
|
||||||
let default_size = 16.0; // Fall back to default icon size; Just a guess since the API GtkPicture uses is private
|
|
||||||
let measurement = if for_size < 0 { 0 } else { for_size };
|
|
||||||
let (min_width, min_height): (f64, f64) = (0.0, 0.0);
|
|
||||||
|
|
||||||
let (min, nat) = if orienation == gtk::Orientation::Horizontal {
|
|
||||||
let size = texture.compute_concrete_size(
|
|
||||||
0.0,
|
|
||||||
measurement.into(),
|
|
||||||
default_size,
|
|
||||||
default_size,
|
|
||||||
);
|
|
||||||
let nat_width = size.0;
|
|
||||||
|
|
||||||
(min_width.ceil(), nat_width.ceil())
|
|
||||||
} else {
|
|
||||||
let size = texture.compute_concrete_size(
|
|
||||||
measurement.into(),
|
|
||||||
0.0,
|
|
||||||
default_size,
|
|
||||||
default_size,
|
|
||||||
);
|
|
||||||
let nat_height = size.1;
|
|
||||||
|
|
||||||
(min_height.ceil(), nat_height.ceil())
|
|
||||||
};
|
|
||||||
|
|
||||||
(min as i32, nat as i32, -1, -1)
|
|
||||||
} else {
|
|
||||||
(0, 0, -1, -1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,40 +198,22 @@ impl LpImage {
|
||||||
imp.file.borrow().clone()
|
imp.file.borrow().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_file(&self, file: &gio::File) {
|
|
||||||
let imp = self.imp();
|
|
||||||
|
|
||||||
match gdk::Texture::from_file(file) {
|
|
||||||
Ok(texture) => {
|
|
||||||
imp.image_width.set(texture.width());
|
|
||||||
imp.image_height.set(texture.height());
|
|
||||||
imp.file.replace(Some(file.clone()));
|
|
||||||
imp.texture.replace(Some(texture));
|
|
||||||
}
|
|
||||||
Err(e) => log::error!("Could not load a valid image from this file: {}", e),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.queue_draw();
|
|
||||||
self.queue_resize();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn image_width(&self) -> i32 {
|
|
||||||
let imp = self.imp();
|
|
||||||
imp.image_width.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn image_height(&self) -> i32 {
|
|
||||||
let imp = self.imp();
|
|
||||||
imp.image_height.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_texture_with_file(&self, texture: gdk::Texture, source_file: &gio::File) {
|
pub fn set_texture_with_file(&self, texture: gdk::Texture, source_file: &gio::File) {
|
||||||
let imp = self.imp();
|
let imp = self.imp();
|
||||||
|
|
||||||
imp.texture.replace(Some(texture));
|
imp.texture.replace(Some(texture));
|
||||||
imp.file.replace(Some(source_file.clone()));
|
imp.file.replace(Some(source_file.clone()));
|
||||||
|
|
||||||
|
let metadata = ImageMetadata::load(source_file);
|
||||||
|
let orientation = metadata.orientation();
|
||||||
|
|
||||||
|
imp.image_metadata.replace(metadata);
|
||||||
|
|
||||||
|
imp.rotation_target.set(-orientation.rotation);
|
||||||
|
imp.rotation.set(-orientation.rotation);
|
||||||
|
imp.mirrored.set(orientation.mirrored);
|
||||||
|
|
||||||
self.queue_draw();
|
self.queue_draw();
|
||||||
self.queue_resize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn texture(&self) -> Option<gdk::Texture> {
|
pub fn texture(&self) -> Option<gdk::Texture> {
|
||||||
|
|
@ -210,6 +221,24 @@ impl LpImage {
|
||||||
imp.texture.borrow().clone()
|
imp.texture.borrow().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rotation(&self) -> f64 {
|
||||||
|
self.imp().rotation.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_rotation(&self, rotation: f64) {
|
||||||
|
self.imp().rotation.set(rotation);
|
||||||
|
self.queue_draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mirrored(&self) -> bool {
|
||||||
|
self.imp().mirrored.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_mirrored(&self, mirrored: bool) {
|
||||||
|
self.imp().mirrored.set(mirrored);
|
||||||
|
self.queue_draw();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn content_provider(&self) -> gdk::ContentProvider {
|
pub fn content_provider(&self) -> gdk::ContentProvider {
|
||||||
let imp = self.imp();
|
let imp = self.imp();
|
||||||
let mut contents = vec![];
|
let mut contents = vec![];
|
||||||
|
|
@ -227,4 +256,16 @@ impl LpImage {
|
||||||
|
|
||||||
gdk::ContentProvider::new_union(contents.as_slice())
|
gdk::ContentProvider::new_union(contents.as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rotate(&self) {
|
||||||
|
let target = &self.imp().rotation_target;
|
||||||
|
target.set(target.get() + 90.);
|
||||||
|
|
||||||
|
let borrow = self.imp().rotation_animation.borrow();
|
||||||
|
let animation = borrow.as_ref().unwrap();
|
||||||
|
|
||||||
|
animation.set_value_from(self.rotation());
|
||||||
|
animation.set_value_to(target.get());
|
||||||
|
animation.play();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,10 @@ impl LpImagePage {
|
||||||
self.imp().file.get().cloned()
|
self.imp().file.get().cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn image(&self) -> LpImage {
|
||||||
|
self.imp().image.get()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn texture(&self) -> Option<gdk::Texture> {
|
pub fn texture(&self) -> Option<gdk::Texture> {
|
||||||
self.imp().image.texture()
|
self.imp().image.texture()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -387,6 +387,12 @@ impl LpImageView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rotate_image(&self) {
|
||||||
|
if let Some(current_page) = self.current_page() {
|
||||||
|
current_page.image().rotate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_background(&self) -> anyhow::Result<()> {
|
pub fn set_background(&self) -> anyhow::Result<()> {
|
||||||
let background = self.uri().context("No URI for current file")?;
|
let background = self.uri().context("No URI for current file")?;
|
||||||
spawn!(clone!(@weak self as view =>
|
spawn!(clone!(@weak self as view =>
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,10 @@ mod imp {
|
||||||
win.open_with();
|
win.open_with();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
klass.install_action("win.rotate-image", None, move |win, _, _| {
|
||||||
|
win.rotate_image();
|
||||||
|
});
|
||||||
|
|
||||||
klass.install_action("win.set-background", None, move |win, _, _| {
|
klass.install_action("win.set-background", None, move |win, _, _| {
|
||||||
win.set_background();
|
win.set_background();
|
||||||
});
|
});
|
||||||
|
|
@ -280,6 +284,10 @@ impl LpWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rotate_image(&self) {
|
||||||
|
self.imp().image_view.rotate_image()
|
||||||
|
}
|
||||||
|
|
||||||
fn set_background(&self) {
|
fn set_background(&self) {
|
||||||
let imp = self.imp();
|
let imp = self.imp();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue