2025-01-13 23:01:53 +01:00
|
|
|
// Copyright (c) 2024-2025 Sophie Herold
|
2025-01-22 20:39:31 +01:00
|
|
|
// Copyright (c) 2025 Hubert Figuière
|
2024-09-17 20:13:39 +02:00
|
|
|
//
|
|
|
|
|
// 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
|
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
//
|
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
|
//
|
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
//
|
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
|
2024-09-15 21:06:23 +02:00
|
|
|
use std::cell::{Cell, OnceCell};
|
|
|
|
|
|
|
|
|
|
use adw::gtk::graphene;
|
|
|
|
|
use adw::prelude::*;
|
|
|
|
|
use adw::subclass::prelude::*;
|
|
|
|
|
use adw::{glib, gtk};
|
|
|
|
|
|
|
|
|
|
use super::crop::{LpAspectRatio, LpEditCrop, LpOrientation};
|
2025-01-25 17:33:21 +01:00
|
|
|
use crate::deps::*;
|
2024-09-15 21:06:23 +02:00
|
|
|
|
2025-01-22 22:19:10 +01:00
|
|
|
const MIN_SELECTION_SIZE: f32 = 80.;
|
2024-09-15 21:06:23 +02:00
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
struct InResize {
|
|
|
|
|
corner: Corner,
|
|
|
|
|
initial_selection: graphene::Rect,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
struct InMove {
|
|
|
|
|
initial_selection: graphene::Rect,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
enum Corner {
|
|
|
|
|
TopLeft,
|
2025-01-23 14:54:06 +01:00
|
|
|
Top,
|
2024-09-15 21:06:23 +02:00
|
|
|
TopRight,
|
2025-01-23 14:54:06 +01:00
|
|
|
Right,
|
2024-09-15 21:06:23 +02:00
|
|
|
BottomRight,
|
2025-01-23 14:54:06 +01:00
|
|
|
Bottom,
|
|
|
|
|
BottomLeft,
|
|
|
|
|
Left,
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
enum HEdge {
|
|
|
|
|
Left,
|
|
|
|
|
Right,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
enum VEdge {
|
|
|
|
|
Top,
|
|
|
|
|
Bottom,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Corner {
|
|
|
|
|
fn cursor_name(&self) -> &str {
|
|
|
|
|
match self {
|
|
|
|
|
Self::TopLeft => "nw-resize",
|
2025-01-23 14:54:06 +01:00
|
|
|
Self::Top => "n-resize",
|
2024-09-15 21:06:23 +02:00
|
|
|
Self::TopRight => "ne-resize",
|
2025-01-23 14:54:06 +01:00
|
|
|
Self::Right => "e-resize",
|
2024-09-15 21:06:23 +02:00
|
|
|
Self::BottomRight => "se-resize",
|
2025-01-23 14:54:06 +01:00
|
|
|
Self::Bottom => "s-resize",
|
|
|
|
|
Self::BottomLeft => "sw-resize",
|
|
|
|
|
Self::Left => "w-resize",
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-23 14:54:06 +01:00
|
|
|
fn h_edge(&self) -> Option<HEdge> {
|
2024-09-15 21:06:23 +02:00
|
|
|
match self {
|
2025-01-23 14:54:06 +01:00
|
|
|
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),
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-23 14:54:06 +01:00
|
|
|
fn v_edge(&self) -> Option<VEdge> {
|
2024-09-15 21:06:23 +02:00
|
|
|
match self {
|
2025-01-23 14:54:06 +01:00
|
|
|
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,
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LpAspectRatio {
|
|
|
|
|
fn frac(&self) -> Option<(u32, u32)> {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Free => None,
|
|
|
|
|
Self::Original => None,
|
|
|
|
|
Self::Square => Some((1, 1)),
|
|
|
|
|
Self::R5to4 => Some((5, 4)),
|
|
|
|
|
Self::R4to3 => Some((4, 3)),
|
|
|
|
|
Self::R3to2 => Some((3, 2)),
|
|
|
|
|
Self::R16to9 => Some((16, 9)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn num(&self, crop: &LpEditCrop) -> Option<f32> {
|
|
|
|
|
let (x, y) = if matches!(self, Self::Original) {
|
|
|
|
|
let width = crop.selection().total_width() as u32;
|
|
|
|
|
let height = crop.selection().total_height() as u32;
|
|
|
|
|
|
|
|
|
|
if width > height {
|
|
|
|
|
(width, height)
|
|
|
|
|
} else {
|
|
|
|
|
(height, width)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.frac()?
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let result = match crop.orientation() {
|
|
|
|
|
LpOrientation::Landscape => x as f32 / y as f32,
|
|
|
|
|
LpOrientation::Portrait => y as f32 / x as f32,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Some(result)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trait RectExt: Into<graphene::Rect> {
|
2025-01-18 23:39:00 -05:00
|
|
|
fn coordinates(self) -> (f32, f32, f32, f32) {
|
2024-09-15 21:06:23 +02:00
|
|
|
let rect: graphene::Rect = self.into();
|
|
|
|
|
|
|
|
|
|
(rect.x(), rect.y(), rect.width(), rect.height())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl RectExt for graphene::Rect {}
|
|
|
|
|
|
|
|
|
|
mod imp {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
|
|
|
|
|
#[properties(wrapper_type = super::LpEditCropSelection)]
|
|
|
|
|
#[template(file = "crop_selection.ui")]
|
|
|
|
|
pub struct LpEditCropSelection {
|
|
|
|
|
#[template_child]
|
2025-01-25 17:33:21 +01:00
|
|
|
pub(super) space_top: TemplateChild<adw::Bin>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub(super) space_right: TemplateChild<adw::Bin>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub(super) space_bottom: TemplateChild<adw::Bin>,
|
|
|
|
|
#[template_child]
|
|
|
|
|
pub(super) space_left: TemplateChild<adw::Bin>,
|
2024-09-15 21:06:23 +02:00
|
|
|
#[template_child]
|
|
|
|
|
pub(super) selection: TemplateChild<gtk::Grid>,
|
|
|
|
|
|
|
|
|
|
#[template_child]
|
|
|
|
|
handle_top_left: TemplateChild<adw::Bin>,
|
|
|
|
|
#[template_child]
|
2025-01-23 14:54:06 +01:00
|
|
|
handle_top: TemplateChild<adw::Bin>,
|
|
|
|
|
#[template_child]
|
2024-09-15 21:06:23 +02:00
|
|
|
handle_top_right: TemplateChild<adw::Bin>,
|
|
|
|
|
#[template_child]
|
2025-01-23 14:54:06 +01:00
|
|
|
handle_right: TemplateChild<adw::Bin>,
|
|
|
|
|
#[template_child]
|
2024-09-15 21:06:23 +02:00
|
|
|
handle_bottom_right: TemplateChild<adw::Bin>,
|
|
|
|
|
#[template_child]
|
2025-01-23 14:54:06 +01:00
|
|
|
handle_bottom: TemplateChild<adw::Bin>,
|
|
|
|
|
#[template_child]
|
2024-09-15 21:06:23 +02:00
|
|
|
handle_bottom_left: TemplateChild<adw::Bin>,
|
2025-01-23 14:54:06 +01:00
|
|
|
#[template_child]
|
|
|
|
|
handle_left: TemplateChild<adw::Bin>,
|
2024-09-15 21:06:23 +02:00
|
|
|
|
2025-01-22 22:19:10 +01:00
|
|
|
#[template_child]
|
|
|
|
|
apply_button: TemplateChild<gtk::Button>,
|
|
|
|
|
|
2024-09-15 21:06:23 +02:00
|
|
|
#[template_child]
|
2024-12-21 22:51:03 +01:00
|
|
|
selection_move: TemplateChild<gtk::GestureDrag>,
|
2024-09-15 21:06:23 +02:00
|
|
|
|
2025-01-25 17:33:21 +01:00
|
|
|
#[template_child]
|
|
|
|
|
selection_overlay: TemplateChild<gtk::Overlay>,
|
|
|
|
|
|
2024-09-15 21:06:23 +02:00
|
|
|
/// Set while in drag gesture for changing crop area
|
2024-12-21 22:51:03 +01:00
|
|
|
pub(super) selection_in_resize: Cell<Option<InResize>>,
|
2024-09-15 21:06:23 +02:00
|
|
|
/// Set while in drag gesture for moving crop area
|
2024-12-21 22:51:03 +01:00
|
|
|
pub(super) selection_in_move: Cell<Option<InMove>>,
|
2025-01-17 23:23:59 +01:00
|
|
|
/// Set while resetting the crop area
|
|
|
|
|
pub(super) selection_in_reset: Cell<bool>,
|
2024-09-15 21:06:23 +02:00
|
|
|
|
|
|
|
|
// Animates changes between different fixed aspect ratios
|
|
|
|
|
aspect_ratio_animation: OnceCell<adw::TimedAnimation>,
|
|
|
|
|
|
|
|
|
|
#[property(get, construct_only)]
|
|
|
|
|
crop: OnceCell<LpEditCrop>,
|
|
|
|
|
|
2025-01-25 17:33:21 +01:00
|
|
|
/// Image position offset
|
|
|
|
|
pub(super) total_x: Cell<i32>,
|
|
|
|
|
pub(super) total_y: Cell<i32>,
|
|
|
|
|
/// Image size
|
|
|
|
|
#[property(get, set=Self::set_total_width)]
|
|
|
|
|
total_width: Cell<i32>,
|
|
|
|
|
#[property(get, set=Self::set_total_height)]
|
|
|
|
|
total_height: Cell<i32>,
|
2024-09-17 20:13:39 +02:00
|
|
|
|
2024-09-17 21:02:01 +02:00
|
|
|
pub(super) initialized: Cell<bool>,
|
|
|
|
|
|
2024-12-21 22:51:03 +01:00
|
|
|
#[property(get, set=Self::set_crop_x, explicit_notify)]
|
2024-09-17 20:13:39 +02:00
|
|
|
crop_x: Cell<f32>,
|
2024-12-21 22:51:03 +01:00
|
|
|
#[property(get, set=Self::set_crop_y, explicit_notify)]
|
2024-09-17 20:13:39 +02:00
|
|
|
crop_y: Cell<f32>,
|
2024-12-21 22:51:03 +01:00
|
|
|
#[property(get, set=Self::set_crop_width, explicit_notify)]
|
2024-09-17 20:13:39 +02:00
|
|
|
crop_width: Cell<f32>,
|
2024-12-21 22:51:03 +01:00
|
|
|
#[property(get, set=Self::set_crop_height, explicit_notify)]
|
2024-09-17 20:13:39 +02:00
|
|
|
crop_height: Cell<f32>,
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[glib::object_subclass]
|
|
|
|
|
impl ObjectSubclass for LpEditCropSelection {
|
|
|
|
|
const NAME: &'static str = "LpEditCropSelection";
|
|
|
|
|
type Type = super::LpEditCropSelection;
|
2025-01-25 17:33:21 +01:00
|
|
|
type ParentType = gtk::Widget;
|
2024-09-15 21:06:23 +02:00
|
|
|
|
|
|
|
|
fn class_init(klass: &mut Self::Class) {
|
|
|
|
|
Self::bind_template(klass);
|
|
|
|
|
klass.set_css_name("lpcrop");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
|
|
|
|
obj.init_template();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[glib::derived_properties]
|
|
|
|
|
impl ObjectImpl for LpEditCropSelection {
|
|
|
|
|
fn constructed(&self) {
|
|
|
|
|
self.parent_constructed();
|
|
|
|
|
let obj = self.obj();
|
|
|
|
|
|
|
|
|
|
obj.set_direction(gtk::TextDirection::Ltr);
|
2025-01-23 14:06:30 +01:00
|
|
|
self.selection.set_direction(gtk::TextDirection::Ltr);
|
|
|
|
|
self.handle_bottom_left
|
|
|
|
|
.set_direction(gtk::TextDirection::Ltr);
|
|
|
|
|
self.handle_bottom_right
|
|
|
|
|
.set_direction(gtk::TextDirection::Ltr);
|
|
|
|
|
self.handle_top_left.set_direction(gtk::TextDirection::Ltr);
|
|
|
|
|
self.handle_top_right.set_direction(gtk::TextDirection::Ltr);
|
2024-09-15 21:06:23 +02:00
|
|
|
|
|
|
|
|
// List of widgets that are drag handles
|
|
|
|
|
let drag_corners = std::collections::BTreeMap::from([
|
|
|
|
|
(self.handle_top_left.clone(), Corner::TopLeft),
|
2025-01-23 14:54:06 +01:00
|
|
|
(self.handle_top.clone(), Corner::Top),
|
2024-09-15 21:06:23 +02:00
|
|
|
(self.handle_top_right.clone(), Corner::TopRight),
|
2025-01-23 14:54:06 +01:00
|
|
|
(self.handle_right.clone(), Corner::Right),
|
2024-09-15 21:06:23 +02:00
|
|
|
(self.handle_bottom_right.clone(), Corner::BottomRight),
|
2025-01-23 14:54:06 +01:00
|
|
|
(self.handle_bottom.clone(), Corner::Bottom),
|
|
|
|
|
(self.handle_bottom_left.clone(), Corner::BottomLeft),
|
|
|
|
|
(self.handle_left.clone(), Corner::Left),
|
2024-09-15 21:06:23 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Set cursor style for each corner
|
|
|
|
|
for (handle_widget, corner) in drag_corners.iter() {
|
|
|
|
|
handle_widget.set_cursor_from_name(Some(corner.cursor_name()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.selection.set_cursor_from_name(Some("move"));
|
2025-01-22 22:19:10 +01:00
|
|
|
self.apply_button.set_cursor_from_name(Some("default"));
|
2024-09-15 21:06:23 +02:00
|
|
|
|
|
|
|
|
obj.crop()
|
|
|
|
|
.connect_aspect_ratio_notify(|x| x.selection().imp().on_aspect_ratio_changed());
|
|
|
|
|
|
|
|
|
|
obj.crop()
|
|
|
|
|
.connect_orientation_notify(|x| x.selection().imp().on_aspect_ratio_changed());
|
|
|
|
|
|
|
|
|
|
// Drag begin
|
2024-12-21 22:51:03 +01:00
|
|
|
self.selection_move.connect_drag_begin(glib::clone!(
|
2024-09-15 21:06:23 +02:00
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
move |gesture, _, _| {
|
|
|
|
|
let imp = obj.imp();
|
|
|
|
|
|
|
|
|
|
imp.aspect_ratio_animation().pause();
|
|
|
|
|
|
|
|
|
|
let hovered_widget = gesture
|
|
|
|
|
.start_point()
|
|
|
|
|
.and_then(|(x, y)| obj.pick(x, y, gtk::PickFlags::DEFAULT));
|
|
|
|
|
|
|
|
|
|
// Lookup if the start-point of the gesture is above a drag handle widget and
|
|
|
|
|
// what corner it is
|
|
|
|
|
if let Some(corner) = hovered_widget
|
|
|
|
|
.as_ref()
|
|
|
|
|
.and_then(|x| drag_corners.get(x).copied())
|
|
|
|
|
{
|
|
|
|
|
gesture.set_state(gtk::EventSequenceState::Claimed);
|
|
|
|
|
|
|
|
|
|
let crop_area = imp.crop_area();
|
|
|
|
|
|
2025-01-22 22:19:10 +01:00
|
|
|
imp.set_selection_in_resize(Some(InResize {
|
2024-09-15 21:06:23 +02:00
|
|
|
corner,
|
|
|
|
|
initial_selection: crop_area,
|
|
|
|
|
}));
|
2025-01-13 15:02:01 +01:00
|
|
|
} else if hovered_widget.is_some_and(|x| x == *imp.selection) {
|
2024-09-15 21:06:23 +02:00
|
|
|
gesture.set_state(gtk::EventSequenceState::Claimed);
|
|
|
|
|
|
|
|
|
|
let crop_area = imp.crop_area();
|
2025-01-22 22:19:10 +01:00
|
|
|
imp.set_selection_in_move(Some(InMove {
|
2024-09-15 21:06:23 +02:00
|
|
|
initial_selection: crop_area,
|
|
|
|
|
}));
|
|
|
|
|
} else {
|
|
|
|
|
gesture.set_state(gtk::EventSequenceState::Denied);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
// Drag moved
|
2024-12-21 22:51:03 +01:00
|
|
|
self.selection_move.connect_drag_update(glib::clone!(
|
2024-09-15 21:06:23 +02:00
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
move |_, x, y| {
|
|
|
|
|
let imp = obj.imp();
|
|
|
|
|
|
|
|
|
|
imp.aspect_ratio_animation().pause();
|
|
|
|
|
|
|
|
|
|
if let Some(in_move) = imp.selection_in_move.get() {
|
|
|
|
|
let moved_area = imp.moved_crop_area(&in_move, (x, y));
|
|
|
|
|
|
|
|
|
|
imp.set_crop(moved_area);
|
|
|
|
|
} else if let Some(drag) = imp.selection_in_resize.get() {
|
|
|
|
|
let coord = (x, y);
|
|
|
|
|
|
|
|
|
|
let new_area = if let Some(aspect_ratio) = imp.aspect_ratio() {
|
|
|
|
|
let u =
|
|
|
|
|
imp.new_crop_area_aspect_ratio(&drag, true, coord, aspect_ratio);
|
|
|
|
|
let v =
|
|
|
|
|
imp.new_crop_area_aspect_ratio(&drag, false, coord, aspect_ratio);
|
|
|
|
|
|
|
|
|
|
if u.area() > v.area() {
|
|
|
|
|
u
|
|
|
|
|
} else {
|
|
|
|
|
v
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
imp.new_crop_area_aspect_free(&drag, coord)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
imp.set_crop(new_area);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
// Drag finished
|
2024-12-21 22:51:03 +01:00
|
|
|
self.selection_move.connect_drag_end(glib::clone!(
|
2024-09-15 21:06:23 +02:00
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
move |_, _, _| {
|
2025-01-22 22:19:10 +01:00
|
|
|
obj.imp().set_selection_in_resize(None);
|
|
|
|
|
obj.imp().set_selection_in_move(None);
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-25 17:33:21 +01:00
|
|
|
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))),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-09-15 21:06:23 +02:00
|
|
|
impl GridImpl for LpEditCropSelection {}
|
|
|
|
|
|
|
|
|
|
impl LpEditCropSelection {
|
|
|
|
|
/// Current crop area
|
|
|
|
|
fn crop_area(&self) -> graphene::Rect {
|
2025-01-25 17:33:21 +01:00
|
|
|
let obj = self.obj();
|
2024-09-15 21:06:23 +02:00
|
|
|
graphene::Rect::new(
|
2025-01-25 17:33:21 +01:00
|
|
|
obj.crop_x(),
|
|
|
|
|
obj.crop_y(),
|
|
|
|
|
obj.crop_width(),
|
|
|
|
|
obj.crop_height(),
|
2024-09-15 21:06:23 +02:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2025-01-23 14:54:06 +01:00
|
|
|
Some(HEdge::Left) => (x, -x),
|
2024-09-15 21:06:23 +02:00
|
|
|
// Just change the crop width
|
2025-01-23 14:54:06 +01:00
|
|
|
Some(HEdge::Right) => (0., x),
|
|
|
|
|
// Don't change
|
|
|
|
|
None => (0., 0.),
|
2024-09-15 21:06:23 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let (y_offset, height_offset) = match resize.corner.v_edge() {
|
2025-01-23 14:54:06 +01:00
|
|
|
Some(VEdge::Top) => (y, -y),
|
|
|
|
|
Some(VEdge::Bottom) => (0., y),
|
|
|
|
|
None => (0., 0.),
|
2024-09-15 21:06:23 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut x = resize.initial_selection.x() + x_offset as f32;
|
|
|
|
|
let mut y = resize.initial_selection.y() + y_offset as f32;
|
|
|
|
|
|
|
|
|
|
let mut width = resize.initial_selection.width() + width_offset as f32;
|
|
|
|
|
let mut height = resize.initial_selection.height() + height_offset as f32;
|
|
|
|
|
|
|
|
|
|
if width < 0. {
|
2025-01-23 14:54:06 +01:00
|
|
|
if matches!(resize.corner.h_edge(), Some(HEdge::Left)) {
|
2024-09-15 21:06:23 +02:00
|
|
|
x += width;
|
|
|
|
|
}
|
|
|
|
|
width = 0.;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if height < 0. {
|
2025-01-23 14:54:06 +01:00
|
|
|
if matches!(resize.corner.v_edge(), Some(VEdge::Top)) {
|
2024-09-15 21:06:23 +02:00
|
|
|
y += height;
|
|
|
|
|
}
|
|
|
|
|
height = 0.;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
graphene::Rect::new(x, y, width, height)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn moved_crop_area(
|
|
|
|
|
&self,
|
|
|
|
|
shift: &InMove,
|
|
|
|
|
(x_shift, y_shift): (f64, f64),
|
|
|
|
|
) -> graphene::Rect {
|
|
|
|
|
let obj = self.obj();
|
|
|
|
|
|
2025-01-18 23:39:00 -05:00
|
|
|
let (mut x, mut y, width, height) = shift.initial_selection.coordinates();
|
2024-09-15 21:06:23 +02:00
|
|
|
|
|
|
|
|
x += x_shift as f32;
|
|
|
|
|
y += y_shift as f32;
|
|
|
|
|
|
|
|
|
|
if x < 0. {
|
|
|
|
|
x = 0.;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if y < 0. {
|
|
|
|
|
y = 0.;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-25 17:33:21 +01:00
|
|
|
if x + width > obj.total_width() as f32 {
|
|
|
|
|
x = obj.total_width() as f32 - width;
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-25 17:33:21 +01:00
|
|
|
if y + height > obj.total_height() as f32 {
|
|
|
|
|
y = obj.total_height() as f32 - height;
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
graphene::Rect::new(x, y, width, height)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn new_crop_area_aspect_free(
|
|
|
|
|
&self,
|
|
|
|
|
resize: &InResize,
|
|
|
|
|
(x, y): (f64, f64),
|
|
|
|
|
) -> graphene::Rect {
|
|
|
|
|
let obj = self.obj();
|
|
|
|
|
|
|
|
|
|
let (mut x, mut y, mut width, mut height) =
|
2025-01-18 23:39:00 -05:00
|
|
|
self.new_crop_area(resize, (x, y)).coordinates();
|
2024-09-15 21:06:23 +02:00
|
|
|
|
|
|
|
|
if width < MIN_SELECTION_SIZE {
|
|
|
|
|
let x_diff = width - MIN_SELECTION_SIZE;
|
|
|
|
|
width = MIN_SELECTION_SIZE;
|
|
|
|
|
|
2025-01-23 14:54:06 +01:00
|
|
|
if matches!(resize.corner.h_edge(), Some(HEdge::Left)) {
|
2024-09-15 21:06:23 +02:00
|
|
|
x += x_diff;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if height < MIN_SELECTION_SIZE {
|
|
|
|
|
let y_diff = height - MIN_SELECTION_SIZE;
|
|
|
|
|
height = MIN_SELECTION_SIZE;
|
|
|
|
|
|
2025-01-23 14:54:06 +01:00
|
|
|
if matches!(resize.corner.v_edge(), Some(VEdge::Top)) {
|
2024-09-15 21:06:23 +02:00
|
|
|
y += y_diff;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if x < 0. {
|
|
|
|
|
width += x;
|
|
|
|
|
x = 0.;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if y < 0. {
|
|
|
|
|
height += y;
|
|
|
|
|
y = 0.;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-25 17:33:21 +01:00
|
|
|
if x + width > obj.total_width() as f32 {
|
|
|
|
|
width = obj.total_width() as f32 - x;
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-25 17:33:21 +01:00
|
|
|
if y + height > obj.total_height() as f32 {
|
|
|
|
|
height = obj.total_height() as f32 - y;
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
graphene::Rect::new(x, y, width, height)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn new_crop_area_aspect_ratio(
|
|
|
|
|
&self,
|
|
|
|
|
resize: &InResize,
|
|
|
|
|
vertical: bool,
|
|
|
|
|
(x, y): (f64, f64),
|
|
|
|
|
aspect_ratio: f32,
|
|
|
|
|
) -> graphene::Rect {
|
|
|
|
|
let new_area = self.new_crop_area(resize, (x, y));
|
|
|
|
|
|
2025-01-18 23:39:00 -05:00
|
|
|
let (mut x, mut y, mut width, mut height) = new_area.coordinates();
|
2024-09-15 21:06:23 +02:00
|
|
|
|
|
|
|
|
if vertical {
|
|
|
|
|
width = new_area.height() * aspect_ratio;
|
2025-01-23 14:54:06 +01:00
|
|
|
if matches!(resize.corner.h_edge(), Some(HEdge::Left)) {
|
2024-09-15 21:06:23 +02:00
|
|
|
x += new_area.width() - width;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
height = new_area.width() / aspect_ratio;
|
2025-01-23 14:54:06 +01:00
|
|
|
if matches!(resize.corner.v_edge(), Some(VEdge::Top)) {
|
2024-09-15 21:06:23 +02:00
|
|
|
y += new_area.height() - height;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut rect = graphene::Rect::new(x, y, width, height);
|
|
|
|
|
|
|
|
|
|
self.rect_limit_top(&mut rect, aspect_ratio, resize.corner.v_edge());
|
|
|
|
|
self.rect_limit_left(&mut rect, aspect_ratio, resize.corner.h_edge());
|
|
|
|
|
self.rect_limit_bottom(&mut rect, aspect_ratio, resize.corner.h_edge());
|
|
|
|
|
self.rect_limit_right(&mut rect, aspect_ratio, resize.corner.v_edge());
|
|
|
|
|
|
|
|
|
|
self.rect_limit_minumum_size(&mut rect, aspect_ratio, resize.corner);
|
|
|
|
|
|
2025-01-18 23:39:00 -05:00
|
|
|
(x, y, width, height) = rect.coordinates();
|
2024-09-15 21:06:23 +02:00
|
|
|
graphene::Rect::new(x, y, width, height)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Ensure selection is not smaller than minimum size
|
|
|
|
|
fn rect_limit_minumum_size(
|
|
|
|
|
&self,
|
|
|
|
|
rect: &mut graphene::Rect,
|
|
|
|
|
aspect_ratio: f32,
|
|
|
|
|
corner: Corner,
|
|
|
|
|
) {
|
2025-01-18 23:39:00 -05:00
|
|
|
let (mut x, mut y, mut width, mut height) = rect.coordinates();
|
2024-09-15 21:06:23 +02:00
|
|
|
|
|
|
|
|
if width < MIN_SELECTION_SIZE {
|
|
|
|
|
let x_diff = width - MIN_SELECTION_SIZE;
|
|
|
|
|
width = MIN_SELECTION_SIZE;
|
|
|
|
|
let y_diff = height - MIN_SELECTION_SIZE / aspect_ratio;
|
|
|
|
|
height = MIN_SELECTION_SIZE / aspect_ratio;
|
|
|
|
|
|
2025-01-23 14:54:06 +01:00
|
|
|
if matches!(corner.v_edge(), Some(VEdge::Top)) {
|
2024-09-15 21:06:23 +02:00
|
|
|
y += y_diff;
|
|
|
|
|
}
|
2025-01-23 14:54:06 +01:00
|
|
|
if matches!(corner.h_edge(), Some(HEdge::Left)) {
|
2024-09-15 21:06:23 +02:00
|
|
|
x += x_diff;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if height < MIN_SELECTION_SIZE {
|
|
|
|
|
let x_diff = width - MIN_SELECTION_SIZE * aspect_ratio;
|
|
|
|
|
width = MIN_SELECTION_SIZE * aspect_ratio;
|
|
|
|
|
let y_diff = height - MIN_SELECTION_SIZE;
|
|
|
|
|
height = MIN_SELECTION_SIZE;
|
|
|
|
|
|
2025-01-23 14:54:06 +01:00
|
|
|
if matches!(corner.v_edge(), Some(VEdge::Top)) {
|
2024-09-15 21:06:23 +02:00
|
|
|
y += y_diff;
|
|
|
|
|
}
|
2025-01-23 14:54:06 +01:00
|
|
|
if matches!(corner.h_edge(), Some(HEdge::Left)) {
|
2024-09-15 21:06:23 +02:00
|
|
|
x += x_diff;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*rect = graphene::Rect::new(x, y, width, height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Make sure Rect is inside image area by moving it if necessary
|
|
|
|
|
fn rect_make_contained(&self, rect: &mut graphene::Rect) {
|
2025-01-25 17:33:21 +01:00
|
|
|
let obj = self.obj();
|
|
|
|
|
|
2025-01-18 23:39:00 -05:00
|
|
|
let (mut x, mut y, width, height) = rect.coordinates();
|
2024-09-15 21:06:23 +02:00
|
|
|
|
2025-01-25 17:33:21 +01:00
|
|
|
if x + width > obj.total_width() as f32 {
|
|
|
|
|
x = obj.total_width() as f32 - width;
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-25 17:33:21 +01:00
|
|
|
if y + height > obj.total_height() as f32 {
|
|
|
|
|
y = obj.total_height() as f32 - height;
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*rect = graphene::Rect::new(x, y, width, height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Limit Rect to not leave image area to the left
|
2025-01-23 14:54:06 +01:00
|
|
|
fn rect_limit_top(
|
|
|
|
|
&self,
|
|
|
|
|
rect: &mut graphene::Rect,
|
|
|
|
|
aspect_ratio: f32,
|
|
|
|
|
edge: Option<VEdge>,
|
|
|
|
|
) {
|
2025-01-18 23:39:00 -05:00
|
|
|
let (mut x, mut y, mut width, mut height) = rect.coordinates();
|
2024-09-15 21:06:23 +02:00
|
|
|
|
|
|
|
|
if x < 0. {
|
|
|
|
|
width += x;
|
|
|
|
|
x = 0.;
|
|
|
|
|
let old_height = height;
|
|
|
|
|
height = width / aspect_ratio;
|
2025-01-23 14:54:06 +01:00
|
|
|
if matches!(edge, Some(VEdge::Top)) {
|
2024-09-15 21:06:23 +02:00
|
|
|
y += old_height - height;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*rect = graphene::Rect::new(x, y, width, height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Limit Rect to not leave image area at to the left
|
2025-01-23 14:54:06 +01:00
|
|
|
fn rect_limit_left(
|
|
|
|
|
&self,
|
|
|
|
|
rect: &mut graphene::Rect,
|
|
|
|
|
aspect_ratio: f32,
|
|
|
|
|
edge: Option<HEdge>,
|
|
|
|
|
) {
|
2025-01-18 23:39:00 -05:00
|
|
|
let (mut x, mut y, mut width, mut height) = rect.coordinates();
|
2024-09-15 21:06:23 +02:00
|
|
|
|
|
|
|
|
if y < 0. {
|
|
|
|
|
height += y;
|
|
|
|
|
y = 0.;
|
|
|
|
|
let old_width = width;
|
|
|
|
|
width = height * aspect_ratio;
|
|
|
|
|
|
2025-01-23 14:54:06 +01:00
|
|
|
if matches!(edge, Some(HEdge::Left)) {
|
2024-09-15 21:06:23 +02:00
|
|
|
x += old_width - width;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*rect = graphene::Rect::new(x, y, width, height);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Limit Rect to not leave image area at the bottom
|
2025-01-23 14:54:06 +01:00
|
|
|
fn rect_limit_bottom(
|
|
|
|
|
&self,
|
|
|
|
|
rect: &mut graphene::Rect,
|
|
|
|
|
aspect_ratio: f32,
|
|
|
|
|
edge: Option<HEdge>,
|
|
|
|
|
) {
|
2024-09-15 21:06:23 +02:00
|
|
|
let obj = self.obj();
|
2025-01-18 23:39:00 -05:00
|
|
|
let (mut x, y, mut width, mut height) = rect.coordinates();
|
2024-09-15 21:06:23 +02:00
|
|
|
|
2025-01-25 17:33:21 +01:00
|
|
|
if y + height > obj.total_height() as f32 {
|
|
|
|
|
let overshoot = y + height - obj.total_height() as f32;
|
2024-09-15 21:06:23 +02:00
|
|
|
|
|
|
|
|
height -= overshoot;
|
|
|
|
|
let old_width = width;
|
|
|
|
|
width = height * aspect_ratio;
|
|
|
|
|
|
2025-01-23 14:54:06 +01:00
|
|
|
if matches!(edge, Some(HEdge::Left)) {
|
2024-09-15 21:06:23 +02:00
|
|
|
x += old_width - width;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*rect = graphene::Rect::new(x, y, width, height);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-23 14:54:06 +01:00
|
|
|
fn rect_limit_right(
|
|
|
|
|
&self,
|
|
|
|
|
rect: &mut graphene::Rect,
|
|
|
|
|
aspect_ratio: f32,
|
|
|
|
|
edge: Option<VEdge>,
|
|
|
|
|
) {
|
2024-09-15 21:06:23 +02:00
|
|
|
let obj = self.obj();
|
2025-01-18 23:39:00 -05:00
|
|
|
let (x, mut y, mut width, mut height) = rect.coordinates();
|
2024-09-15 21:06:23 +02:00
|
|
|
|
2025-01-25 17:33:21 +01:00
|
|
|
if x + width > obj.total_width() as f32 {
|
|
|
|
|
let overshoot = x + width - obj.total_width() as f32;
|
2024-09-15 21:06:23 +02:00
|
|
|
|
|
|
|
|
width -= overshoot;
|
|
|
|
|
let old_height = height;
|
|
|
|
|
height = width / aspect_ratio;
|
|
|
|
|
|
2025-01-23 14:54:06 +01:00
|
|
|
if matches!(edge, Some(VEdge::Top)) {
|
2024-09-15 21:06:23 +02:00
|
|
|
y += old_height - height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*rect = graphene::Rect::new(x, y, width, height);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_aspect_ratio_changed(&self) {
|
|
|
|
|
let Some(aspect_ratio) = self.aspect_ratio() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let obj = self.obj();
|
|
|
|
|
|
|
|
|
|
// Store current state as reference for animation
|
|
|
|
|
let current_area = self.crop_area();
|
2025-01-18 23:39:00 -05:00
|
|
|
let (_, y, _, height) = current_area.coordinates();
|
2024-09-15 21:06:23 +02:00
|
|
|
|
|
|
|
|
let width = current_area.height() * aspect_ratio;
|
|
|
|
|
let x = f32::max(0., current_area.x() + (current_area.width() - width) / 2.);
|
|
|
|
|
|
|
|
|
|
let mut new_area = graphene::Rect::new(x, y, width, height);
|
|
|
|
|
|
2025-01-23 14:54:06 +01:00
|
|
|
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));
|
2024-09-15 21:06:23 +02:00
|
|
|
|
|
|
|
|
self.rect_limit_minumum_size(&mut new_area, aspect_ratio, Corner::BottomLeft);
|
|
|
|
|
|
|
|
|
|
self.rect_make_contained(&mut new_area);
|
|
|
|
|
|
|
|
|
|
self.aspect_ratio_animation()
|
|
|
|
|
.set_target(&adw::CallbackAnimationTarget::new(glib::clone!(
|
|
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
move |progress| {
|
|
|
|
|
let imp = obj.imp();
|
|
|
|
|
|
|
|
|
|
// Linear interpolate between old and target value
|
|
|
|
|
imp.set_crop(current_area.interpolate(&new_area, progress));
|
|
|
|
|
}
|
|
|
|
|
)));
|
|
|
|
|
|
|
|
|
|
self.aspect_ratio_animation().play();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Fixed aspect ratio of selection if set
|
|
|
|
|
fn aspect_ratio(&self) -> Option<f32> {
|
|
|
|
|
let crop = self.obj().crop();
|
|
|
|
|
crop.aspect_ratio().num(&crop)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set all coordinates of crop selection
|
|
|
|
|
fn set_crop(&self, rect: graphene::Rect) {
|
|
|
|
|
let rect = rect.round_extents();
|
|
|
|
|
|
2024-09-17 20:13:39 +02:00
|
|
|
self.set_crop_x(rect.x());
|
|
|
|
|
self.set_crop_y(rect.y());
|
|
|
|
|
self.set_crop_width(rect.width());
|
|
|
|
|
self.set_crop_height(rect.height());
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set x coordinate of crop selection rectangle origin
|
2024-09-17 20:13:39 +02:00
|
|
|
pub(super) fn set_crop_x(&self, x: f32) {
|
|
|
|
|
if x < 0. {
|
2024-12-21 22:51:03 +01:00
|
|
|
log::error!("Tried to set x coordinate to {x}");
|
2024-09-15 21:06:23 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-25 17:33:21 +01:00
|
|
|
let obj = self.obj();
|
|
|
|
|
|
2024-09-17 20:13:39 +02:00
|
|
|
self.crop_x.set(x);
|
2025-01-25 17:33:21 +01:00
|
|
|
obj.notify_crop_x();
|
2025-01-22 22:19:10 +01:00
|
|
|
self.update_apply_crop_visibility();
|
2025-01-25 17:33:21 +01:00
|
|
|
self.obj().queue_allocate();
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set y coordinate of crop selection rectangle origin
|
2024-09-17 20:13:39 +02:00
|
|
|
pub(super) fn set_crop_y(&self, y: f32) {
|
|
|
|
|
if y < 0. {
|
2024-12-21 22:51:03 +01:00
|
|
|
log::error!("Tried to set y coordinate to {y}");
|
2024-09-15 21:06:23 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-25 17:33:21 +01:00
|
|
|
let obj = self.obj();
|
|
|
|
|
|
2024-09-17 20:13:39 +02:00
|
|
|
self.crop_y.set(y);
|
2025-01-25 17:33:21 +01:00
|
|
|
obj.notify_crop_y();
|
2025-01-22 22:19:10 +01:00
|
|
|
self.update_apply_crop_visibility();
|
2025-01-25 17:33:21 +01:00
|
|
|
self.obj().queue_allocate();
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set width of crop selection rectangle
|
2024-09-17 20:13:39 +02:00
|
|
|
pub(super) fn set_crop_width(&self, width: f32) {
|
|
|
|
|
if width < 0. {
|
2024-09-15 21:06:23 +02:00
|
|
|
eprintln!("Tried to set width to {width}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-17 20:13:39 +02:00
|
|
|
self.crop_width.set(width);
|
2024-12-21 22:51:03 +01:00
|
|
|
self.obj().notify_crop_width();
|
2025-01-22 22:19:10 +01:00
|
|
|
self.update_apply_crop_visibility();
|
2025-01-25 17:33:21 +01:00
|
|
|
self.obj().queue_allocate();
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set height of crop selection rectangle
|
2024-09-17 20:13:39 +02:00
|
|
|
pub(super) fn set_crop_height(&self, height: f32) {
|
|
|
|
|
if height < 0. {
|
2024-09-15 21:06:23 +02:00
|
|
|
eprintln!("Tried to set height to {height}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-17 20:13:39 +02:00
|
|
|
self.crop_height.set(height);
|
2024-12-21 22:51:03 +01:00
|
|
|
self.obj().notify_crop_height();
|
2025-01-22 22:19:10 +01:00
|
|
|
self.update_apply_crop_visibility();
|
2025-01-25 17:33:21 +01:00
|
|
|
self.obj().queue_allocate();
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_total_width(&self, width: i32) {
|
2025-01-25 17:33:21 +01:00
|
|
|
self.total_width.replace(width);
|
2025-01-22 22:19:10 +01:00
|
|
|
self.update_apply_crop_visibility();
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_total_height(&self, width: i32) {
|
2025-01-25 17:33:21 +01:00
|
|
|
self.total_height.replace(width);
|
2025-01-22 22:19:10 +01:00
|
|
|
self.update_apply_crop_visibility();
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-17 22:58:10 +01:00
|
|
|
pub(super) fn aspect_ratio_animation(&self) -> &adw::TimedAnimation {
|
2024-09-15 21:06:23 +02:00
|
|
|
self.aspect_ratio_animation.get_or_init(|| {
|
|
|
|
|
adw::TimedAnimation::builder()
|
|
|
|
|
.duration(200)
|
|
|
|
|
.value_from(0.)
|
|
|
|
|
.value_to(1.)
|
|
|
|
|
.easing(adw::Easing::EaseOutSine)
|
|
|
|
|
.widget(&*self.obj())
|
|
|
|
|
.target(&adw::CallbackAnimationTarget::new(|_| {}))
|
|
|
|
|
.build()
|
|
|
|
|
})
|
|
|
|
|
}
|
2025-01-22 22:19:10 +01:00
|
|
|
|
|
|
|
|
fn set_selection_in_resize(&self, in_resize: Option<InResize>) {
|
|
|
|
|
self.selection_in_resize.replace(in_resize);
|
|
|
|
|
self.update_apply_crop_visibility();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_selection_in_move(&self, in_move: Option<InMove>) {
|
|
|
|
|
self.selection_in_move.replace(in_move);
|
|
|
|
|
self.update_apply_crop_visibility();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn update_apply_crop_visibility(&self) {
|
2025-01-25 17:33:21 +01:00
|
|
|
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);
|
|
|
|
|
}
|
2025-01-22 22:19:10 +01:00
|
|
|
}
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glib::wrapper! {
|
|
|
|
|
pub struct LpEditCropSelection(ObjectSubclass<imp::LpEditCropSelection>)
|
|
|
|
|
@extends gtk::Widget;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LpEditCropSelection {
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
glib::Object::new()
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-17 21:02:01 +02:00
|
|
|
pub fn ensure_initialized(&self, x: f64, y: f64, width: f64, height: f64) {
|
2024-09-15 21:06:23 +02:00
|
|
|
let imp = self.imp();
|
|
|
|
|
|
2024-09-17 21:02:01 +02:00
|
|
|
if imp.initialized.get() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
imp.initialized.set(true);
|
|
|
|
|
|
2024-10-04 14:05:11 +02:00
|
|
|
self.reset(x, y, width, height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn reset(&self, x: f64, y: f64, width: f64, height: f64) {
|
2025-01-17 23:23:59 +01:00
|
|
|
let imp = self.imp();
|
|
|
|
|
|
|
|
|
|
imp.selection_in_reset.replace(true);
|
2024-12-21 22:51:03 +01:00
|
|
|
self.set_image_area(x, y, width, height);
|
|
|
|
|
self.set_crop_size(0., 0., width, height);
|
2025-01-17 23:23:59 +01:00
|
|
|
imp.selection_in_reset.replace(false);
|
2024-12-21 22:51:03 +01:00
|
|
|
}
|
2024-10-04 14:05:11 +02:00
|
|
|
|
2024-12-21 22:51:03 +01:00
|
|
|
pub fn set_image_area(&self, x: f64, y: f64, width: f64, height: f64) {
|
2025-01-17 23:23:59 +01:00
|
|
|
log::trace!("Setting image area to ({x}, {y}, {width}, {height})");
|
2025-01-25 17:33:21 +01:00
|
|
|
self.imp().total_x.replace(x as i32);
|
|
|
|
|
self.imp().total_y.replace(y as i32);
|
2024-09-17 21:02:01 +02:00
|
|
|
self.set_total_width(width as i32);
|
|
|
|
|
self.set_total_height(height as i32);
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-21 22:51:03 +01:00
|
|
|
pub fn set_crop_size(&self, x: f64, y: f64, width: f64, height: f64) {
|
2025-01-17 23:23:59 +01:00
|
|
|
log::trace!("Setting crop area to ({x}, {y}, {width}, {height})");
|
2024-12-21 22:51:03 +01:00
|
|
|
self.set_crop_x(x as f32);
|
|
|
|
|
self.set_crop_y(y as f32);
|
|
|
|
|
self.set_crop_width(width as f32);
|
|
|
|
|
self.set_crop_height(height as f32);
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|
2024-10-04 14:05:11 +02:00
|
|
|
|
2024-12-21 22:51:03 +01:00
|
|
|
pub fn is_cropped(&self) -> bool {
|
2025-01-25 17:33:21 +01:00
|
|
|
let untouched: bool = self.crop_x() == 0.
|
2024-10-04 14:05:11 +02:00
|
|
|
&& self.crop_y() == 0.
|
|
|
|
|
&& self.crop_width() as i32 == self.total_width()
|
|
|
|
|
&& self.crop_height() as i32 == self.total_height();
|
|
|
|
|
!untouched
|
|
|
|
|
}
|
2024-12-21 22:51:03 +01:00
|
|
|
|
|
|
|
|
pub fn is_in_user_change(&self) -> bool {
|
|
|
|
|
self.imp().selection_in_resize.get().is_some()
|
|
|
|
|
|| self.imp().selection_in_move.get().is_some()
|
2025-01-17 23:23:59 +01:00
|
|
|
|| self.imp().selection_in_reset.get()
|
2025-01-17 22:58:10 +01:00
|
|
|
|| self.imp().aspect_ratio_animation().state() == adw::AnimationState::Playing
|
2024-12-21 22:51:03 +01:00
|
|
|
}
|
2024-09-15 21:06:23 +02:00
|
|
|
}
|