edit/crop: Apply crop on save

This commit is contained in:
Sophie Herold 2025-01-22 15:41:56 +01:00
parent 9cb08d288d
commit 729f5249ed
6 changed files with 78 additions and 28 deletions

View file

@ -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 {}

View file

@ -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();
}
} }

View file

@ -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) {

View file

@ -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>

View file

@ -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;
}; };

View file

@ -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();
} }