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
// it under the terms of the GNU General Public License as published by
@ -17,7 +17,7 @@
use gtk::prelude::*;
use crate::widgets::LpWindow;
use crate::widgets::{LpEditWindow, LpWindow};
pub trait ParentWindow: WidgetExt {
fn window(&self) -> LpWindow {
@ -29,6 +29,15 @@ pub trait ParentWindow: WidgetExt {
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 {}

View file

@ -18,6 +18,7 @@
//! Shows an resizable cropping selection
use std::cell::{Cell, OnceCell};
use std::marker::PhantomData;
use adw::prelude::*;
use adw::subclass::prelude::*;
@ -29,7 +30,7 @@ use crate::util::gettext::*;
use crate::util::root::ParentWindow;
use crate::util::ErrorType;
use crate::widgets::edit::LpEditCropSelection;
use crate::widgets::{LpEditWindow, LpImage};
use crate::widgets::LpImage;
/// Aspect ratio modes that can be selected
#[derive(Debug, Clone, Copy, Default, glib::Enum, glib::Variant)]
@ -59,6 +60,7 @@ pub enum LpOrientation {
}
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
@ -79,8 +81,8 @@ mod imp {
#[property(get, construct_only)]
original_image: OnceCell<LpImage>,
#[property(get, construct_only)]
edit_window: OnceCell<LpEditWindow>,
#[property(get=Self::cropped)]
cropped: PhantomData<bool>,
#[property(get, set)]
child: OnceCell<gtk::Widget>,
@ -173,8 +175,6 @@ mod imp {
obj.imp().selection_changed();
}
));
let _ = self.apply_reset();
}
fn dispose(&self) {
@ -183,6 +183,12 @@ mod imp {
}
impl WidgetImpl for LpEditCrop {
fn root(&self) {
self.parent_root();
let _ = self.apply_reset();
}
fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
let obj = self.obj();
@ -223,12 +229,14 @@ mod imp {
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.obj().notify_cropped();
}
fn crop_area_image_coord(&self) -> Option<(u32, u32, u32, u32)> {
if !self.selection.is_cropped() {
if !self.cropped() {
return None;
}
@ -265,6 +273,10 @@ mod imp {
(x.round(), y.round(), w.round(), h.round())
}
fn cropped(&self) -> bool {
self.selection.is_cropped()
}
fn reset_selection(&self) {
let image = &self.image;
@ -277,7 +289,7 @@ mod imp {
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() {
self.add_operation(Operation::Clip(crop));
@ -372,11 +384,12 @@ glib::wrapper! {
}
impl LpEditCrop {
pub fn new(edit_window: LpEditWindow) -> Self {
glib::Object::builder()
.property("original_image", edit_window.original_image())
.property("edit_window", edit_window)
.build()
pub fn new(original_image: LpImage) -> Self {
let obj: Self = glib::Object::builder()
.property("original_image", original_image)
.build();
obj
}
pub fn selection(&self) -> LpEditCropSelection {
@ -385,11 +398,15 @@ impl LpEditCrop {
fn handle_error(&self, res: Result<(), EditingError>) {
if let Err(err) = res {
self.edit_window().window().show_error(
self.window().show_error(
&gettext("Failed to Edit Image"),
&format!("Failed to edit image: {err}"),
ErrorType::General,
);
}
}
pub fn apply_crop(&self) {
self.imp().apply_crop();
}
}

View file

@ -45,6 +45,8 @@ mod imp {
#[property(get, construct_only)]
original_image: OnceCell<LpImage>,
#[property(get, construct_only)]
pub edit_crop: OnceCell<LpEditCrop>,
pub(super) operations: RefCell<Option<Arc<Operations>>>,
}
@ -87,9 +89,6 @@ mod imp {
let obj = self.obj();
self.toolbar_view
.set_content(Some(&LpEditCrop::new(obj.to_owned())));
self.cancel.connect_clicked(glib::clone!(
#[weak]
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 LpEditWindow {
@ -194,6 +205,9 @@ mod imp {
/// Returns `true` if editing and saving was successful
async fn save(&self, current_file: gio::File, new_file: gio::File) -> bool {
let obj = self.obj();
obj.edit_crop().apply_crop();
let editor = glycin::Editor::new(current_file);
if let Some(operations) = obj.operations() {
log::debug!("Computing edited image.");
@ -260,7 +274,8 @@ impl LpEditWindow {
pub fn new(image: LpImage) -> Self {
glib::Object::builder()
.property("original_image", image)
.property("original_image", image.clone())
.property("edit_crop", LpEditCrop::new(image))
.build()
}
@ -270,13 +285,17 @@ impl LpEditWindow {
pub fn set_operations(&self, operations: Option<Arc<glycin::Operations>>) {
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.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) {

View file

@ -4,6 +4,7 @@
<property name="child">
<object class="AdwToolbarView" id="toolbar_view">
<property name="top-bar-style">raised</property>
<property name="content" bind-source="LpEditWindow" bind-property="edit_crop" />
<child type="top">
<object class="AdwHeaderBar">
<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 {
log::warn!("Could not navigate to file {}", new_file.uri());
log::warn!(
"Could not navigate to file '{}': Not in model",
new_file.uri()
);
return;
};

View file

@ -248,6 +248,7 @@ impl LpWindow {
}
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.show_image();
}