From 9c5600efb7efe36870c9d546f1f5439c21882b63 Mon Sep 17 00:00:00 2001 From: Sophie Herold Date: Thu, 23 Jan 2025 14:54:06 +0100 Subject: [PATCH] edit/crop: Allow draging from edges --- data/resources/edit_crop.css | 37 ++++++--- src/widgets/edit/crop_selection.rs | 129 ++++++++++++++++++++--------- src/widgets/edit/crop_selection.ui | 59 ++++++++++--- 3 files changed, 164 insertions(+), 61 deletions(-) diff --git a/data/resources/edit_crop.css b/data/resources/edit_crop.css index 8aa7219..87f342d 100644 --- a/data/resources/edit_crop.css +++ b/data/resources/edit_crop.css @@ -4,7 +4,8 @@ lpcrop { --handle-border-color: var(--selection-border-color); --handle-size: 20px; --handle-border-width: 3px; - --handle-offset: -4px; + --handle-offset: 4px; + --n-handle-offset: calc(-1 * var(--handle-offset)); } .selection { @@ -18,29 +19,41 @@ lpcrop { .handle-top-left { border-top: var(--handle-border-width) var(--handle-border-color) solid; border-left: var(--handle-border-width) var(--handle-border-color) solid; - margin-top: var(--handle-offset); - margin-left: var(--handle-offset); + transform: translate(var(--n-handle-offset), var(--n-handle-offset)); +} + +.handle-top { + transform: translateY(var(--n-handle-offset)); } .handle-top-right { border-top: var(--handle-border-width) var(--handle-border-color) solid; border-right: var(--handle-border-width) var(--handle-border-color) solid; - margin-top: var(--handle-offset); - margin-right: var(--handle-offset); + transform: translate(var(--handle-offset), var(--n-handle-offset)); } -.handle-bottom-left { - border-left: var(--handle-border-width) var(--handle-border-color) solid; - border-bottom: var(--handle-border-width) var(--handle-border-color) solid; - margin-left: var(--handle-offset); - margin-bottom: var(--handle-offset); +.handle-right { + transform: translateX(var(--handle-offset)); } .handle-bottom-right { border-bottom: var(--handle-border-width) var(--handle-border-color) solid; border-right: var(--handle-border-width) var(--handle-border-color) solid; - margin-bottom: var(--handle-offset); - margin-right: var(--handle-offset); + transform: translate(var(--handle-offset), var(--handle-offset)); +} + +.handle-bottom { + transform: translateY(var(--handle-offset)); +} + +.handle-bottom-left { + border-left: var(--handle-border-width) var(--handle-border-color) solid; + border-bottom: var(--handle-border-width) var(--handle-border-color) solid; + transform: translate(var(--n-handle-offset), var(--handle-offset)); +} + +.handle-left { + transform: translateX(var(--n-handle-offset)); } .discarded-area { diff --git a/src/widgets/edit/crop_selection.rs b/src/widgets/edit/crop_selection.rs index d0ea260..084aed1 100644 --- a/src/widgets/edit/crop_selection.rs +++ b/src/widgets/edit/crop_selection.rs @@ -42,9 +42,13 @@ struct InMove { #[derive(Debug, Clone, Copy)] enum Corner { TopLeft, + Top, TopRight, - BottomLeft, + Right, BottomRight, + Bottom, + BottomLeft, + Left, } #[derive(Debug, Clone, Copy)] @@ -63,27 +67,39 @@ impl Corner { fn cursor_name(&self) -> &str { match self { Self::TopLeft => "nw-resize", + Self::Top => "n-resize", Self::TopRight => "ne-resize", - Self::BottomLeft => "sw-resize", + Self::Right => "e-resize", Self::BottomRight => "se-resize", + Self::Bottom => "s-resize", + Self::BottomLeft => "sw-resize", + Self::Left => "w-resize", } } - fn h_edge(&self) -> HEdge { + fn h_edge(&self) -> Option { match self { - Self::TopLeft => HEdge::Left, - Self::TopRight => HEdge::Right, - Self::BottomLeft => HEdge::Left, - Self::BottomRight => HEdge::Right, + Self::TopLeft => Some(HEdge::Left), + Self::Top => None, + Self::TopRight => Some(HEdge::Right), + Self::Right => Some(HEdge::Right), + Self::BottomRight => Some(HEdge::Right), + Self::Bottom => None, + Self::BottomLeft => Some(HEdge::Left), + Self::Left => Some(HEdge::Left), } } - fn v_edge(&self) -> VEdge { + fn v_edge(&self) -> Option { match self { - Self::TopLeft => VEdge::Top, - Self::TopRight => VEdge::Top, - Self::BottomLeft => VEdge::Bottom, - Self::BottomRight => VEdge::Bottom, + Self::TopLeft => Some(VEdge::Top), + Self::Top => Some(VEdge::Top), + Self::TopRight => Some(VEdge::Top), + Self::Right => None, + Self::BottomRight => Some(VEdge::Bottom), + Self::Bottom => Some(VEdge::Bottom), + Self::BottomLeft => Some(VEdge::Bottom), + Self::Left => None, } } } @@ -149,11 +165,19 @@ mod imp { #[template_child] handle_top_left: TemplateChild, #[template_child] + handle_top: TemplateChild, + #[template_child] handle_top_right: TemplateChild, #[template_child] + handle_right: TemplateChild, + #[template_child] handle_bottom_right: TemplateChild, #[template_child] + handle_bottom: TemplateChild, + #[template_child] handle_bottom_left: TemplateChild, + #[template_child] + handle_left: TemplateChild, #[template_child] apply_button: TemplateChild, @@ -225,9 +249,13 @@ mod imp { // List of widgets that are drag handles let drag_corners = std::collections::BTreeMap::from([ (self.handle_top_left.clone(), Corner::TopLeft), + (self.handle_top.clone(), Corner::Top), (self.handle_top_right.clone(), Corner::TopRight), - (self.handle_bottom_left.clone(), Corner::BottomLeft), + (self.handle_right.clone(), Corner::Right), (self.handle_bottom_right.clone(), Corner::BottomRight), + (self.handle_bottom.clone(), Corner::Bottom), + (self.handle_bottom_left.clone(), Corner::BottomLeft), + (self.handle_left.clone(), Corner::Left), ]); // Set cursor style for each corner @@ -350,14 +378,17 @@ mod imp { fn new_crop_area(&self, resize: &InResize, (x, y): (f64, f64)) -> graphene::Rect { let (x_offset, width_offset) = match resize.corner.h_edge() { // Change the crop x position and reduce width accordingly - HEdge::Left => (x, -x), + Some(HEdge::Left) => (x, -x), // Just change the crop width - HEdge::Right => (0., x), + Some(HEdge::Right) => (0., x), + // Don't change + None => (0., 0.), }; let (y_offset, height_offset) = match resize.corner.v_edge() { - VEdge::Top => (y, -y), - VEdge::Bottom => (0., y), + Some(VEdge::Top) => (y, -y), + Some(VEdge::Bottom) => (0., y), + None => (0., 0.), }; let mut x = resize.initial_selection.x() + x_offset as f32; @@ -367,14 +398,14 @@ mod imp { let mut height = resize.initial_selection.height() + height_offset as f32; if width < 0. { - if matches!(resize.corner.h_edge(), HEdge::Left) { + if matches!(resize.corner.h_edge(), Some(HEdge::Left)) { x += width; } width = 0.; } if height < 0. { - if matches!(resize.corner.v_edge(), VEdge::Top) { + if matches!(resize.corner.v_edge(), Some(VEdge::Top)) { y += height; } height = 0.; @@ -428,7 +459,7 @@ mod imp { let x_diff = width - MIN_SELECTION_SIZE; width = MIN_SELECTION_SIZE; - if matches!(resize.corner.h_edge(), HEdge::Left) { + if matches!(resize.corner.h_edge(), Some(HEdge::Left)) { x += x_diff; } } @@ -437,7 +468,7 @@ mod imp { let y_diff = height - MIN_SELECTION_SIZE; height = MIN_SELECTION_SIZE; - if matches!(resize.corner.v_edge(), VEdge::Top) { + if matches!(resize.corner.v_edge(), Some(VEdge::Top)) { y += y_diff; } } @@ -476,12 +507,12 @@ mod imp { if vertical { width = new_area.height() * aspect_ratio; - if matches!(resize.corner.h_edge(), HEdge::Left) { + if matches!(resize.corner.h_edge(), Some(HEdge::Left)) { x += new_area.width() - width; } } else { height = new_area.width() / aspect_ratio; - if matches!(resize.corner.v_edge(), VEdge::Top) { + if matches!(resize.corner.v_edge(), Some(VEdge::Top)) { y += new_area.height() - height; } } @@ -514,10 +545,10 @@ mod imp { let y_diff = height - MIN_SELECTION_SIZE / aspect_ratio; height = MIN_SELECTION_SIZE / aspect_ratio; - if matches!(corner.v_edge(), VEdge::Top) { + if matches!(corner.v_edge(), Some(VEdge::Top)) { y += y_diff; } - if matches!(corner.h_edge(), HEdge::Left) { + if matches!(corner.h_edge(), Some(HEdge::Left)) { x += x_diff; } } @@ -528,10 +559,10 @@ mod imp { let y_diff = height - MIN_SELECTION_SIZE; height = MIN_SELECTION_SIZE; - if matches!(corner.v_edge(), VEdge::Top) { + if matches!(corner.v_edge(), Some(VEdge::Top)) { y += y_diff; } - if matches!(corner.h_edge(), HEdge::Left) { + if matches!(corner.h_edge(), Some(HEdge::Left)) { x += x_diff; } } @@ -555,7 +586,12 @@ mod imp { } /// Limit Rect to not leave image area to the left - fn rect_limit_top(&self, rect: &mut graphene::Rect, aspect_ratio: f32, edge: VEdge) { + fn rect_limit_top( + &self, + rect: &mut graphene::Rect, + aspect_ratio: f32, + edge: Option, + ) { let (mut x, mut y, mut width, mut height) = rect.coordinates(); if x < 0. { @@ -563,7 +599,7 @@ mod imp { x = 0.; let old_height = height; height = width / aspect_ratio; - if matches!(edge, VEdge::Top) { + if matches!(edge, Some(VEdge::Top)) { y += old_height - height; } } @@ -572,7 +608,12 @@ mod imp { } /// Limit Rect to not leave image area at to the left - fn rect_limit_left(&self, rect: &mut graphene::Rect, aspect_ratio: f32, edge: HEdge) { + fn rect_limit_left( + &self, + rect: &mut graphene::Rect, + aspect_ratio: f32, + edge: Option, + ) { let (mut x, mut y, mut width, mut height) = rect.coordinates(); if y < 0. { @@ -581,7 +622,7 @@ mod imp { let old_width = width; width = height * aspect_ratio; - if matches!(edge, HEdge::Left) { + if matches!(edge, Some(HEdge::Left)) { x += old_width - width; } @@ -590,7 +631,12 @@ mod imp { } /// Limit Rect to not leave image area at the bottom - fn rect_limit_bottom(&self, rect: &mut graphene::Rect, aspect_ratio: f32, edge: HEdge) { + fn rect_limit_bottom( + &self, + rect: &mut graphene::Rect, + aspect_ratio: f32, + edge: Option, + ) { let obj = self.obj(); let (mut x, y, mut width, mut height) = rect.coordinates(); @@ -601,7 +647,7 @@ mod imp { let old_width = width; width = height * aspect_ratio; - if matches!(edge, HEdge::Left) { + if matches!(edge, Some(HEdge::Left)) { x += old_width - width; } @@ -609,7 +655,12 @@ mod imp { } } - fn rect_limit_right(&self, rect: &mut graphene::Rect, aspect_ratio: f32, edge: VEdge) { + fn rect_limit_right( + &self, + rect: &mut graphene::Rect, + aspect_ratio: f32, + edge: Option, + ) { let obj = self.obj(); let (x, mut y, mut width, mut height) = rect.coordinates(); @@ -620,7 +671,7 @@ mod imp { let old_height = height; height = width / aspect_ratio; - if matches!(edge, VEdge::Top) { + if matches!(edge, Some(VEdge::Top)) { y += old_height - height; } @@ -644,10 +695,10 @@ mod imp { let mut new_area = graphene::Rect::new(x, y, width, height); - self.rect_limit_top(&mut new_area, aspect_ratio, VEdge::Bottom); - self.rect_limit_left(&mut new_area, aspect_ratio, HEdge::Right); - self.rect_limit_bottom(&mut new_area, aspect_ratio, HEdge::Left); - self.rect_limit_right(&mut new_area, aspect_ratio, VEdge::Top); + self.rect_limit_top(&mut new_area, aspect_ratio, Some(VEdge::Bottom)); + self.rect_limit_left(&mut new_area, aspect_ratio, Some(HEdge::Right)); + self.rect_limit_bottom(&mut new_area, aspect_ratio, Some(HEdge::Left)); + self.rect_limit_right(&mut new_area, aspect_ratio, Some(VEdge::Top)); self.rect_limit_minumum_size(&mut new_area, aspect_ratio, Corner::BottomLeft); diff --git a/src/widgets/edit/crop_selection.ui b/src/widgets/edit/crop_selection.ui index bd7f3df..ec7abf7 100644 --- a/src/widgets/edit/crop_selection.ui +++ b/src/widgets/edit/crop_selection.ui @@ -130,7 +130,6 @@ - start start 0 @@ -143,13 +142,23 @@ - - end + true 0 1 + + + + + + + 0 + 2 + + + + + + true + + 1 + 2 + + + + + + + + 2 + 0 + + + + + + + 2 + 2 +