edit/crop: Apply crop on save
This commit is contained in:
parent
9cb08d288d
commit
729f5249ed
6 changed files with 78 additions and 28 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2024 Sophie Herold
|
// Copyright (c) 2024-2025 Sophie Herold
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
|
||||||
use crate::widgets::LpWindow;
|
use crate::widgets::{LpEditWindow, LpWindow};
|
||||||
|
|
||||||
pub trait ParentWindow: WidgetExt {
|
pub trait ParentWindow: WidgetExt {
|
||||||
fn window(&self) -> LpWindow {
|
fn window(&self) -> LpWindow {
|
||||||
|
|
@ -29,6 +29,15 @@ pub trait ParentWindow: WidgetExt {
|
||||||
|
|
||||||
result.unwrap()
|
result.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn edit_window(&self) -> LpEditWindow {
|
||||||
|
let parent = self.parent().unwrap();
|
||||||
|
|
||||||
|
parent
|
||||||
|
.clone()
|
||||||
|
.downcast()
|
||||||
|
.unwrap_or_else(|_| parent.edit_window())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: WidgetExt> ParentWindow for T {}
|
impl<T: WidgetExt> ParentWindow for T {}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
//! Shows an resizable cropping selection
|
//! Shows an resizable cropping selection
|
||||||
|
|
||||||
use std::cell::{Cell, OnceCell};
|
use std::cell::{Cell, OnceCell};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use adw::subclass::prelude::*;
|
use adw::subclass::prelude::*;
|
||||||
|
|
@ -29,7 +30,7 @@ use crate::util::gettext::*;
|
||||||
use crate::util::root::ParentWindow;
|
use crate::util::root::ParentWindow;
|
||||||
use crate::util::ErrorType;
|
use crate::util::ErrorType;
|
||||||
use crate::widgets::edit::LpEditCropSelection;
|
use crate::widgets::edit::LpEditCropSelection;
|
||||||
use crate::widgets::{LpEditWindow, LpImage};
|
use crate::widgets::LpImage;
|
||||||
|
|
||||||
/// Aspect ratio modes that can be selected
|
/// Aspect ratio modes that can be selected
|
||||||
#[derive(Debug, Clone, Copy, Default, glib::Enum, glib::Variant)]
|
#[derive(Debug, Clone, Copy, Default, glib::Enum, glib::Variant)]
|
||||||
|
|
@ -59,6 +60,7 @@ pub enum LpOrientation {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
|
#[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
|
||||||
|
|
@ -79,8 +81,8 @@ mod imp {
|
||||||
|
|
||||||
#[property(get, construct_only)]
|
#[property(get, construct_only)]
|
||||||
original_image: OnceCell<LpImage>,
|
original_image: OnceCell<LpImage>,
|
||||||
#[property(get, construct_only)]
|
#[property(get=Self::cropped)]
|
||||||
edit_window: OnceCell<LpEditWindow>,
|
cropped: PhantomData<bool>,
|
||||||
|
|
||||||
#[property(get, set)]
|
#[property(get, set)]
|
||||||
child: OnceCell<gtk::Widget>,
|
child: OnceCell<gtk::Widget>,
|
||||||
|
|
@ -173,8 +175,6 @@ mod imp {
|
||||||
obj.imp().selection_changed();
|
obj.imp().selection_changed();
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
let _ = self.apply_reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispose(&self) {
|
fn dispose(&self) {
|
||||||
|
|
@ -183,6 +183,12 @@ mod imp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WidgetImpl for LpEditCrop {
|
impl WidgetImpl for LpEditCrop {
|
||||||
|
fn root(&self) {
|
||||||
|
self.parent_root();
|
||||||
|
|
||||||
|
let _ = self.apply_reset();
|
||||||
|
}
|
||||||
|
|
||||||
fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
|
fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
|
||||||
let obj = self.obj();
|
let obj = self.obj();
|
||||||
|
|
||||||
|
|
@ -223,12 +229,14 @@ mod imp {
|
||||||
self.crop_area_image_coord.replace(crop_area_image_coord);
|
self.crop_area_image_coord.replace(crop_area_image_coord);
|
||||||
}
|
}
|
||||||
|
|
||||||
let apply_sensitive = self.selection.is_cropped();
|
let apply_sensitive = self.cropped();
|
||||||
self.apply_crop.set_visible(apply_sensitive);
|
self.apply_crop.set_visible(apply_sensitive);
|
||||||
|
|
||||||
|
self.obj().notify_cropped();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn crop_area_image_coord(&self) -> Option<(u32, u32, u32, u32)> {
|
fn crop_area_image_coord(&self) -> Option<(u32, u32, u32, u32)> {
|
||||||
if !self.selection.is_cropped() {
|
if !self.cropped() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -265,6 +273,10 @@ mod imp {
|
||||||
(x.round(), y.round(), w.round(), h.round())
|
(x.round(), y.round(), w.round(), h.round())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cropped(&self) -> bool {
|
||||||
|
self.selection.is_cropped()
|
||||||
|
}
|
||||||
|
|
||||||
fn reset_selection(&self) {
|
fn reset_selection(&self) {
|
||||||
let image = &self.image;
|
let image = &self.image;
|
||||||
|
|
||||||
|
|
@ -277,7 +289,7 @@ mod imp {
|
||||||
self.selection.reset(x, y, width, height);
|
self.selection.reset(x, y, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_crop(&self) {
|
pub(super) fn apply_crop(&self) {
|
||||||
if let Some(crop) = self.crop_area_image_coord.get() {
|
if let Some(crop) = self.crop_area_image_coord.get() {
|
||||||
self.add_operation(Operation::Clip(crop));
|
self.add_operation(Operation::Clip(crop));
|
||||||
|
|
||||||
|
|
@ -372,11 +384,12 @@ glib::wrapper! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LpEditCrop {
|
impl LpEditCrop {
|
||||||
pub fn new(edit_window: LpEditWindow) -> Self {
|
pub fn new(original_image: LpImage) -> Self {
|
||||||
glib::Object::builder()
|
let obj: Self = glib::Object::builder()
|
||||||
.property("original_image", edit_window.original_image())
|
.property("original_image", original_image)
|
||||||
.property("edit_window", edit_window)
|
.build();
|
||||||
.build()
|
|
||||||
|
obj
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selection(&self) -> LpEditCropSelection {
|
pub fn selection(&self) -> LpEditCropSelection {
|
||||||
|
|
@ -385,11 +398,15 @@ impl LpEditCrop {
|
||||||
|
|
||||||
fn handle_error(&self, res: Result<(), EditingError>) {
|
fn handle_error(&self, res: Result<(), EditingError>) {
|
||||||
if let Err(err) = res {
|
if let Err(err) = res {
|
||||||
self.edit_window().window().show_error(
|
self.window().show_error(
|
||||||
&gettext("Failed to Edit Image"),
|
&gettext("Failed to Edit Image"),
|
||||||
&format!("Failed to edit image: {err}"),
|
&format!("Failed to edit image: {err}"),
|
||||||
ErrorType::General,
|
ErrorType::General,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_crop(&self) {
|
||||||
|
self.imp().apply_crop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ mod imp {
|
||||||
|
|
||||||
#[property(get, construct_only)]
|
#[property(get, construct_only)]
|
||||||
original_image: OnceCell<LpImage>,
|
original_image: OnceCell<LpImage>,
|
||||||
|
#[property(get, construct_only)]
|
||||||
|
pub edit_crop: OnceCell<LpEditCrop>,
|
||||||
|
|
||||||
pub(super) operations: RefCell<Option<Arc<Operations>>>,
|
pub(super) operations: RefCell<Option<Arc<Operations>>>,
|
||||||
}
|
}
|
||||||
|
|
@ -87,9 +89,6 @@ mod imp {
|
||||||
|
|
||||||
let obj = self.obj();
|
let obj = self.obj();
|
||||||
|
|
||||||
self.toolbar_view
|
|
||||||
.set_content(Some(&LpEditCrop::new(obj.to_owned())));
|
|
||||||
|
|
||||||
self.cancel.connect_clicked(glib::clone!(
|
self.cancel.connect_clicked(glib::clone!(
|
||||||
#[weak]
|
#[weak]
|
||||||
obj,
|
obj,
|
||||||
|
|
@ -109,7 +108,19 @@ mod imp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WidgetImpl for LpEditWindow {}
|
impl WidgetImpl for LpEditWindow {
|
||||||
|
fn root(&self) {
|
||||||
|
self.parent_root();
|
||||||
|
|
||||||
|
let obj = self.obj();
|
||||||
|
|
||||||
|
obj.edit_crop().connect_cropped_notify(glib::clone!(
|
||||||
|
#[weak]
|
||||||
|
obj,
|
||||||
|
move |_| obj.imp().done.set_sensitive(obj.is_done_sensitive())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
impl BinImpl for LpEditWindow {}
|
impl BinImpl for LpEditWindow {}
|
||||||
|
|
||||||
impl LpEditWindow {
|
impl LpEditWindow {
|
||||||
|
|
@ -194,6 +205,9 @@ mod imp {
|
||||||
/// Returns `true` if editing and saving was successful
|
/// Returns `true` if editing and saving was successful
|
||||||
async fn save(&self, current_file: gio::File, new_file: gio::File) -> bool {
|
async fn save(&self, current_file: gio::File, new_file: gio::File) -> bool {
|
||||||
let obj = self.obj();
|
let obj = self.obj();
|
||||||
|
|
||||||
|
obj.edit_crop().apply_crop();
|
||||||
|
|
||||||
let editor = glycin::Editor::new(current_file);
|
let editor = glycin::Editor::new(current_file);
|
||||||
if let Some(operations) = obj.operations() {
|
if let Some(operations) = obj.operations() {
|
||||||
log::debug!("Computing edited image.");
|
log::debug!("Computing edited image.");
|
||||||
|
|
@ -260,7 +274,8 @@ impl LpEditWindow {
|
||||||
|
|
||||||
pub fn new(image: LpImage) -> Self {
|
pub fn new(image: LpImage) -> Self {
|
||||||
glib::Object::builder()
|
glib::Object::builder()
|
||||||
.property("original_image", image)
|
.property("original_image", image.clone())
|
||||||
|
.property("edit_crop", LpEditCrop::new(image))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,13 +285,17 @@ impl LpEditWindow {
|
||||||
|
|
||||||
pub fn set_operations(&self, operations: Option<Arc<glycin::Operations>>) {
|
pub fn set_operations(&self, operations: Option<Arc<glycin::Operations>>) {
|
||||||
let imp = self.imp();
|
let imp = self.imp();
|
||||||
|
|
||||||
let done_sensitive = operations
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|x| !x.operations().is_empty());
|
|
||||||
imp.done.set_sensitive(done_sensitive);
|
|
||||||
|
|
||||||
imp.operations.replace(operations);
|
imp.operations.replace(operations);
|
||||||
|
imp.done.set_sensitive(self.is_done_sensitive());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_done_sensitive(&self) -> bool {
|
||||||
|
self.imp()
|
||||||
|
.operations
|
||||||
|
.borrow()
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|x| !x.operations().is_empty())
|
||||||
|
|| self.edit_crop().cropped()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_operation(&self, operation: glycin::Operation) {
|
pub fn add_operation(&self, operation: glycin::Operation) {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
<property name="child">
|
<property name="child">
|
||||||
<object class="AdwToolbarView" id="toolbar_view">
|
<object class="AdwToolbarView" id="toolbar_view">
|
||||||
<property name="top-bar-style">raised</property>
|
<property name="top-bar-style">raised</property>
|
||||||
|
<property name="content" bind-source="LpEditWindow" bind-property="edit_crop" />
|
||||||
<child type="top">
|
<child type="top">
|
||||||
<object class="AdwHeaderBar">
|
<object class="AdwHeaderBar">
|
||||||
<property name="show-start-title-buttons">false</property>
|
<property name="show-start-title-buttons">false</property>
|
||||||
|
|
|
||||||
|
|
@ -529,7 +529,10 @@ impl LpImageView {
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(new_index) = self.model().index_of(new_file) else {
|
let Some(new_index) = self.model().index_of(new_file) else {
|
||||||
log::warn!("Could not navigate to file {}", new_file.uri());
|
log::warn!(
|
||||||
|
"Could not navigate to file '{}': Not in model",
|
||||||
|
new_file.uri()
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,7 @@ impl LpWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_specific_image(&self, file: gio::File) {
|
pub fn show_specific_image(&self, file: gio::File) {
|
||||||
|
log::debug!("Showting specific image: {}", file.uri());
|
||||||
self.image_view().set_images_from_files(vec![file]);
|
self.image_view().set_images_from_files(vec![file]);
|
||||||
self.show_image();
|
self.show_image();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue