From 1f250b64655b9032aeecb82435f9bf5c11d339b7 Mon Sep 17 00:00:00 2001 From: Sophie Herold Date: Sat, 25 Jan 2025 17:33:21 +0100 Subject: [PATCH] edit/crop: Use manual allocation Don't use a grid for the position of grayed out area and crop selection. Fixes #454 --- src/widgets/edit/crop.rs | 14 ++- src/widgets/edit/crop_selection.rs | 184 ++++++++++++++++++++--------- src/widgets/edit/crop_selection.ui | 82 +------------ 3 files changed, 146 insertions(+), 134 deletions(-) diff --git a/src/widgets/edit/crop.rs b/src/widgets/edit/crop.rs index 5ff88e1..5a32eae 100644 --- a/src/widgets/edit/crop.rs +++ b/src/widgets/edit/crop.rs @@ -187,16 +187,18 @@ mod imp { let _ = self.apply_reset(); } - fn size_allocate(&self, width: i32, height: i32, baseline: i32) { + fn size_allocate(&self, total_width: i32, total_height: i32, baseline: i32) { let obj = self.obj(); let image = &self.image; - obj.child().allocate(width, height, baseline, None); + obj.child() + .allocate(total_width, total_height, baseline, None); // Adjust position and size for crop selection after widget resize - if self.last_allocation.get() != (width, height, baseline) { - self.last_allocation.replace((width, height, baseline)); + if self.last_allocation.get() != (total_width, total_height, baseline) { + self.last_allocation + .replace((total_width, total_height, baseline)); let (x, y, width, height) = ( image.image_rendering_x(), @@ -210,6 +212,10 @@ mod imp { let (x, y, width, height) = self.crop_area_widget_coord(); self.selection.set_crop_size(x, y, width, height); + + // Allocate just the crop overlay again after it knows its dimensions based on + // the image's dimensions and position + self.selection.allocate(0, 0, -1, None); } } diff --git a/src/widgets/edit/crop_selection.rs b/src/widgets/edit/crop_selection.rs index 19ff802..6eb2ea7 100644 --- a/src/widgets/edit/crop_selection.rs +++ b/src/widgets/edit/crop_selection.rs @@ -17,7 +17,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later use std::cell::{Cell, OnceCell}; -use std::marker::PhantomData; use adw::gtk::graphene; use adw::prelude::*; @@ -25,6 +24,7 @@ use adw::subclass::prelude::*; use adw::{glib, gtk}; use super::crop::{LpAspectRatio, LpEditCrop, LpOrientation}; +use crate::deps::*; const MIN_SELECTION_SIZE: f32 = 80.; @@ -158,7 +158,13 @@ mod imp { #[template(file = "crop_selection.ui")] pub struct LpEditCropSelection { #[template_child] - pub(super) space_top_left: TemplateChild, + pub(super) space_top: TemplateChild, + #[template_child] + pub(super) space_right: TemplateChild, + #[template_child] + pub(super) space_bottom: TemplateChild, + #[template_child] + pub(super) space_left: TemplateChild, #[template_child] pub(super) selection: TemplateChild, @@ -185,6 +191,9 @@ mod imp { #[template_child] selection_move: TemplateChild, + #[template_child] + selection_overlay: TemplateChild, + /// Set while in drag gesture for changing crop area pub(super) selection_in_resize: Cell>, /// Set while in drag gesture for moving crop area @@ -198,10 +207,14 @@ mod imp { #[property(get, construct_only)] crop: OnceCell, - #[property(get=Self::total_width, set=Self::set_total_width)] - total_width: PhantomData, - #[property(get=Self::total_height, set=Self::set_total_height)] - total_height: PhantomData, + /// Image position offset + pub(super) total_x: Cell, + pub(super) total_y: Cell, + /// Image size + #[property(get, set=Self::set_total_width)] + total_width: Cell, + #[property(get, set=Self::set_total_height)] + total_height: Cell, pub(super) initialized: Cell, @@ -219,7 +232,7 @@ mod imp { impl ObjectSubclass for LpEditCropSelection { const NAME: &'static str = "LpEditCropSelection"; type Type = super::LpEditCropSelection; - type ParentType = gtk::Grid; + type ParentType = gtk::Widget; fn class_init(klass: &mut Self::Class) { Self::bind_template(klass); @@ -361,17 +374,75 @@ mod imp { } } - impl WidgetImpl for LpEditCropSelection {} + impl WidgetImpl for LpEditCropSelection { + fn size_allocate(&self, _width: i32, _height: i32, _baseline: i32) { + let obj = self.obj(); + + let total_x = self.total_x.get() as f32; + let total_y = self.total_y.get() as f32; + let total_height = obj.total_height(); + let total_width = obj.total_width(); + + let x = obj.crop_x(); + let y = obj.crop_y(); + let width = obj.crop_width(); + let height = obj.crop_height(); + + // Selection area + self.selection_overlay.allocate( + width as i32, + height as i32, + -1, + Some( + gsk::Transform::new() + .translate(&graphene::Point::new(x + total_x, y + total_y)), + ), + ); + + // Grayed out areas + self.space_top.allocate( + total_width, + y as i32, + -1, + Some(gsk::Transform::new().translate(&graphene::Point::new(total_x, total_y))), + ); + self.space_right.allocate( + total_width - x as i32 - width as i32, + height as i32, + -1, + Some( + gsk::Transform::new() + .translate(&graphene::Point::new(x + width + total_x, y + total_y)), + ), + ); + self.space_bottom.allocate( + total_width, + total_height - y as i32 - height as i32, + -1, + Some( + gsk::Transform::new() + .translate(&graphene::Point::new(total_x, y + height + total_y)), + ), + ); + self.space_left.allocate( + x as i32, + height as i32, + -1, + Some(gsk::Transform::new().translate(&graphene::Point::new(total_x, y + total_y))), + ); + } + } impl GridImpl for LpEditCropSelection {} impl LpEditCropSelection { /// Current crop area fn crop_area(&self) -> graphene::Rect { + let obj = self.obj(); graphene::Rect::new( - self.space_top_left.width_request() as f32, - self.space_top_left.height_request() as f32, - self.selection.width_request() as f32, - self.selection.height_request() as f32, + obj.crop_x(), + obj.crop_y(), + obj.crop_width(), + obj.crop_height(), ) } @@ -434,12 +505,12 @@ mod imp { y = 0.; } - if x + width > obj.width_request() as f32 { - x = obj.width_request() as f32 - width; + if x + width > obj.total_width() as f32 { + x = obj.total_width() as f32 - width; } - if y + height > obj.height_request() as f32 { - y = obj.height_request() as f32 - height; + if y + height > obj.total_height() as f32 { + y = obj.total_height() as f32 - height; } graphene::Rect::new(x, y, width, height) @@ -483,12 +554,12 @@ mod imp { y = 0.; } - if x + width > obj.width_request() as f32 { - width = obj.width_request() as f32 - x; + if x + width > obj.total_width() as f32 { + width = obj.total_width() as f32 - x; } - if y + height > obj.height_request() as f32 { - height = obj.height_request() as f32 - y; + if y + height > obj.total_height() as f32 { + height = obj.total_height() as f32 - y; } graphene::Rect::new(x, y, width, height) @@ -572,14 +643,16 @@ mod imp { /// Make sure Rect is inside image area by moving it if necessary fn rect_make_contained(&self, rect: &mut graphene::Rect) { + let obj = self.obj(); + let (mut x, mut y, width, height) = rect.coordinates(); - if x + width > self.total_width() as f32 { - x = self.total_width() as f32 - width; + if x + width > obj.total_width() as f32 { + x = obj.total_width() as f32 - width; } - if y + height > self.total_height() as f32 { - y = self.total_height() as f32 - height; + if y + height > obj.total_height() as f32 { + y = obj.total_height() as f32 - height; } *rect = graphene::Rect::new(x, y, width, height); @@ -640,8 +713,8 @@ mod imp { let obj = self.obj(); let (mut x, y, mut width, mut height) = rect.coordinates(); - if y + height > obj.height_request() as f32 { - let overshoot = y + height - obj.height_request() as f32; + if y + height > obj.total_height() as f32 { + let overshoot = y + height - obj.total_height() as f32; height -= overshoot; let old_width = width; @@ -664,8 +737,8 @@ mod imp { let obj = self.obj(); let (x, mut y, mut width, mut height) = rect.coordinates(); - if x + width > obj.width_request() as f32 { - let overshoot = x + width - obj.width_request() as f32; + if x + width > obj.total_width() as f32 { + let overshoot = x + width - obj.total_width() as f32; width -= overshoot; let old_height = height; @@ -742,10 +815,12 @@ mod imp { return; } + let obj = self.obj(); + self.crop_x.set(x); - self.space_top_left.set_width_request(x.round() as i32); - self.obj().notify_crop_x(); + obj.notify_crop_x(); self.update_apply_crop_visibility(); + self.obj().queue_allocate(); } /// Set y coordinate of crop selection rectangle origin @@ -755,10 +830,12 @@ mod imp { return; } + let obj = self.obj(); + self.crop_y.set(y); - self.space_top_left.set_height_request(y.round() as i32); - self.obj().notify_crop_y(); + obj.notify_crop_y(); self.update_apply_crop_visibility(); + self.obj().queue_allocate(); } /// Set width of crop selection rectangle @@ -769,9 +846,9 @@ mod imp { } self.crop_width.set(width); - self.selection.set_width_request(width.round() as i32); self.obj().notify_crop_width(); self.update_apply_crop_visibility(); + self.obj().queue_allocate(); } /// Set height of crop selection rectangle @@ -782,28 +859,18 @@ mod imp { } self.crop_height.set(height); - self.selection.set_height_request(height.round() as i32); self.obj().notify_crop_height(); self.update_apply_crop_visibility(); - } - - /// Width of area in which the crop selection can exist - fn total_width(&self) -> i32 { - self.obj().width_request() + self.obj().queue_allocate(); } fn set_total_width(&self, width: i32) { - self.obj().set_width_request(width); + self.total_width.replace(width); self.update_apply_crop_visibility(); } - /// Height of area in which the crop selection can exist - fn total_height(&self) -> i32 { - self.obj().height_request() - } - fn set_total_height(&self, width: i32) { - self.obj().set_height_request(width); + self.total_height.replace(width); self.update_apply_crop_visibility(); } @@ -831,11 +898,20 @@ mod imp { } fn update_apply_crop_visibility(&self) { - self.apply_button.set_visible( - self.obj().is_cropped() - && self.selection_in_resize.get().is_none() - && self.selection_in_move.get().is_none(), - ); + let visibile = self.obj().is_cropped() + && self.selection_in_resize.get().is_none() + && self.selection_in_move.get().is_none(); + + // Don't use `set_visble` since it queues a resize + if visibile { + self.apply_button.set_opacity(1.); + self.apply_button.set_can_target(true); + self.apply_button.set_can_focus(true); + } else { + self.apply_button.set_opacity(0.); + self.apply_button.set_can_target(false); + self.apply_button.set_can_focus(false); + } } } } @@ -873,8 +949,8 @@ impl LpEditCropSelection { pub fn set_image_area(&self, x: f64, y: f64, width: f64, height: f64) { log::trace!("Setting image area to ({x}, {y}, {width}, {height})"); - self.set_margin_start(x as i32); - self.set_margin_top(y as i32); + self.imp().total_x.replace(x as i32); + self.imp().total_y.replace(y as i32); self.set_total_width(width as i32); self.set_total_height(height as i32); } @@ -888,7 +964,7 @@ impl LpEditCropSelection { } pub fn is_cropped(&self) -> bool { - let untouched = self.crop_x() == 0. + let untouched: bool = self.crop_x() == 0. && self.crop_y() == 0. && self.crop_width() as i32 == self.total_width() && self.crop_height() as i32 == self.total_height(); diff --git a/src/widgets/edit/crop_selection.ui b/src/widgets/edit/crop_selection.ui index ec7abf7..e645cf5 100644 --- a/src/widgets/edit/crop_selection.ui +++ b/src/widgets/edit/crop_selection.ui @@ -1,110 +1,40 @@ -