0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-22 06:46:40 -05:00

Merge pull request #5919 from penpot/alotor-constraints-2

🐛 Fix problems with constraints resizing
This commit is contained in:
Aitor Moreno 2025-02-20 16:26:46 +01:00 committed by GitHub
commit 3a764a9da6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 266 additions and 116 deletions

View file

@ -122,65 +122,145 @@ impl Bounds {
pub fn left(&self, p: Point) -> f32 {
let hr = Ray::new(p, self.horizontal_vec());
let vr = Ray::new(self.nw, self.vertical_vec());
if let Some(project_point) = intersect_rays(&hr, &vr) {
if vr.is_positive_side(&p) {
-Point::distance(project_point, p)
} else {
Point::distance(project_point, p)
}
let mut result = if let Some(project_point) = intersect_rays(&hr, &vr) {
Point::distance(project_point, p)
} else {
// This should not happen. All points should have a proyection so the
// intersection ray should always exist
0.0
};
if vr.is_positive_side(&p) {
result = -result;
}
if self.flip_y() {
result = -result;
}
if self.flip_x() {
result = -result;
}
result
}
pub fn right(&self, p: Point) -> f32 {
let hr = Ray::new(p, self.horizontal_vec());
let vr = Ray::new(self.ne, self.vertical_vec());
if let Some(project_point) = intersect_rays(&hr, &vr) {
if vr.is_positive_side(&p) {
Point::distance(project_point, p)
} else {
-Point::distance(project_point, p)
}
let mut result = if let Some(project_point) = intersect_rays(&hr, &vr) {
Point::distance(project_point, p)
} else {
// This should not happen. All points should have a proyection so the
// intersection ray should always exist
0.0
};
if !vr.is_positive_side(&p) {
result = -result;
}
if self.flip_y() {
result = -result;
}
if self.flip_x() {
result = -result;
}
result
}
pub fn top(&self, p: Point) -> f32 {
let vr = Ray::new(p, self.vertical_vec());
let hr = Ray::new(self.nw, self.horizontal_vec());
if let Some(project_point) = intersect_rays(&vr, &hr) {
if hr.is_positive_side(&p) {
Point::distance(project_point, p)
} else {
-Point::distance(project_point, p)
}
let mut result = if let Some(project_point) = intersect_rays(&vr, &hr) {
Point::distance(project_point, p)
} else {
// This should not happen. All points should have a proyection so the
// intersection ray should always exist
0.0
};
if !hr.is_positive_side(&p) {
result = -result;
}
if self.flip_y() {
result = -result;
}
if self.flip_x() {
result = -result;
}
result
}
pub fn bottom(&self, p: Point) -> f32 {
let vr = Ray::new(p, self.vertical_vec());
let hr = Ray::new(self.sw, self.horizontal_vec());
if let Some(project_point) = intersect_rays(&vr, &hr) {
if hr.is_positive_side(&p) {
-Point::distance(project_point, p)
} else {
Point::distance(project_point, p)
}
let mut result = if let Some(project_point) = intersect_rays(&vr, &hr) {
Point::distance(project_point, p)
} else {
// This should not happen. All points should have a proyection so the
// intersection ray should always exist
0.0
};
if hr.is_positive_side(&p) {
result = -result;
}
if self.flip_y() {
result = -result;
}
if self.flip_x() {
result = -result;
}
result
}
pub fn transform_matrix(&self) -> Option<Matrix> {
let w2 = self.width() / 2.0;
let h2 = self.height() / 2.0;
let s1x = -w2;
let s1y = -h2;
let s2x = w2;
let s2y = -h2;
let s4x = -w2;
let s4y = h2;
let d1x = self.nw.x;
let d1y = self.nw.y;
let d2x = self.ne.x;
let d2y = self.ne.y;
let d4x = self.sw.x;
let d4y = self.sw.y;
// TODO: Check how fast is to calculate here the invert matrix
let mut target_points_matrix = Matrix::new_all(d1x, d2x, d4x, d1y, d2y, d4y, 1.0, 1.0, 1.0);
let source_points_matrix = Matrix::new_all(s1x, s2x, s4x, s1y, s2y, s4y, 1.0, 1.0, 1.0);
let source_points_matrix_inv = source_points_matrix.invert()?;
target_points_matrix.pre_concat(&source_points_matrix_inv);
// Ignore translations
target_points_matrix.set_translate_x(0.0);
target_points_matrix.set_translate_y(0.0);
Some(target_points_matrix)
}
// TODO: Probably we can improve performance here removing the access
pub fn flip_x(&self) -> bool {
let m = self.transform_matrix().unwrap_or(Matrix::default());
m.scale_x() < 0.0
}
// TODO: Probably we can improve performance here removing the access
pub fn flip_y(&self) -> bool {
let m = self.transform_matrix().unwrap_or(Matrix::default());
m.scale_y() < 0.0
}
}
@ -317,14 +397,46 @@ mod tests {
#[test]
fn test_bounds_distances() {
let b1 = Bounds::new(
Point::new(1.0, 10.0),
Point::new(8.0, 10.0),
Point::new(8.0, 1.0),
Point::new(1.0, 1.0),
Point::new(8.0, 1.0),
Point::new(8.0, 10.0),
Point::new(1.0, 10.0),
);
assert_eq!(b1.left(Point::new(4.0, 8.0)), -3.0);
assert_eq!(b1.top(Point::new(4.0, 8.0)), -2.0);
assert_eq!(b1.right(Point::new(7.0, 6.0),), -1.0);
assert_eq!(b1.bottom(Point::new(7.0, 6.0),), -5.0);
assert_eq!(b1.left(Point::new(4.0, 8.0)), 3.0);
assert_eq!(b1.top(Point::new(4.0, 8.0)), 7.0);
assert_eq!(b1.right(Point::new(7.0, 6.0),), 1.0);
assert_eq!(b1.bottom(Point::new(7.0, 6.0),), 4.0);
}
#[test]
fn test_transform_matrix() {
let b = Bounds::new(
Point::new(0.0, 0.0),
Point::new(50.0, 0.0),
Point::new(50.0, 50.0),
Point::new(0.0, 50.0),
);
assert_eq!(b.width(), 50.0);
assert_eq!(b.height(), 50.0);
assert_eq!(b.transform_matrix().unwrap(), Matrix::default());
let b = Bounds::new(
Point::new(-25.0, 1.0),
Point::new(1.0, -34.5),
Point::new(27.0, 1.0),
Point::new(1.0, 36.5),
);
assert!((b.width() - 44.0).abs() <= 0.1);
assert!((b.height() - 44.0).abs() <= 0.1);
let m = b.transform_matrix().unwrap();
assert!((m.scale_x() - 0.59).abs() <= 0.1);
assert!((m.skew_y() - -0.81).abs() <= 0.1);
assert!((m.skew_x() - 0.59).abs() <= 0.1);
assert!((m.scale_y() - 0.81).abs() <= 0.1);
assert!((m.translate_x() - 0.0).abs() <= 0.1);
assert!((m.translate_y() - 0.0).abs() <= 0.1);
}
}

View file

@ -72,7 +72,7 @@ pub enum Kind {
Group(Group),
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum ConstraintH {
Left,
Right,
@ -94,7 +94,7 @@ impl ConstraintH {
}
}
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum ConstraintV {
Top,
Bottom,

View file

@ -10,79 +10,100 @@ use crate::math::Bounds;
use crate::shapes::{ConstraintH, ConstraintV, Shape, TransformEntry};
use crate::state::State;
fn calculate_new_bounds(
fn calculate_resize(
constraint_h: ConstraintH,
constraint_v: ConstraintV,
parent_before: &Bounds,
parent_after: &Bounds,
child_bounds: &Bounds,
) -> (f32, f32, f32, f32) {
let (delta_left, scale_width) = match constraint_h {
ConstraintH::Scale => {
let width_scale = parent_after.width() / parent_before.width();
let target_left = parent_before.left(child_bounds.nw) * width_scale;
let current_left = parent_after.left(child_bounds.nw);
(target_left - current_left, width_scale)
}
ConstraintH::Left => {
let target_left = parent_before.left(child_bounds.nw);
let current_left = parent_after.left(child_bounds.nw);
(target_left - current_left, 1.0)
}
ConstraintH::Right => {
let target_right = parent_before.right(child_bounds.ne);
let current_right = parent_after.right(child_bounds.ne);
(current_right - target_right, 1.0)
child_before: &Bounds,
child_after: &Bounds,
) -> Option<(f32, f32)> {
let scale_width = match constraint_h {
ConstraintH::Left | ConstraintH::Right | ConstraintH::Center => {
parent_before.width() / parent_after.width()
}
ConstraintH::LeftRight => {
let target_left = parent_before.left(child_bounds.nw);
let target_right = parent_before.right(child_bounds.ne);
let current_left = parent_after.left(child_bounds.nw);
let new_width = parent_after.width() - target_left - target_right;
let width_scale = new_width / child_bounds.width();
(target_left - current_left, width_scale)
let left = parent_before.left(child_before.nw);
let right = parent_before.right(child_before.ne);
let target_width = parent_after.width() - left - right;
target_width / child_after.width()
}
_ => 1.0,
};
let scale_height = match constraint_v {
ConstraintV::Top | ConstraintV::Bottom | ConstraintV::Center => {
parent_before.height() / parent_after.height()
}
ConstraintV::TopBottom => {
let top = parent_before.top(child_before.nw);
let bottom = parent_before.bottom(child_before.sw);
let target_height = parent_after.height() - top - bottom;
target_height / child_after.height()
}
_ => 1.0,
};
if (scale_width - 1.0).abs() < f32::EPSILON && (scale_height - 1.0).abs() < f32::EPSILON {
None
} else {
Some((scale_width, scale_height))
}
}
fn calculate_displacement(
constraint_h: ConstraintH,
constraint_v: ConstraintV,
parent_before: &Bounds,
parent_after: &Bounds,
child_before: &Bounds,
child_after: &Bounds,
) -> Option<(f32, f32)> {
let delta_x = match constraint_h {
ConstraintH::Left | ConstraintH::LeftRight => {
let target_left = parent_before.left(child_before.nw);
let current_left = parent_after.left(child_after.nw);
target_left - current_left
}
ConstraintH::Right => {
let target_right = parent_before.right(child_before.ne);
let current_right = parent_after.right(child_after.ne);
current_right - target_right
}
ConstraintH::Center => {
let delta_width = parent_after.width() - parent_before.width();
let delta_left = delta_width / 2.0;
(delta_left, 1.0)
let target_left = parent_before.left(child_before.nw);
let current_left = parent_after.left(child_after.nw);
target_left - current_left + delta_width / 2.0
}
_ => 0.0,
};
let (delta_top, scale_height) = match constraint_v {
ConstraintV::Scale => {
let height_scale = parent_after.height() / parent_before.height();
let target_top = parent_before.top(child_bounds.nw) * height_scale;
let current_top = parent_after.top(child_bounds.nw);
(target_top - current_top, height_scale)
}
ConstraintV::Top => {
let height_scale = 1.0;
let target_top = parent_before.top(child_bounds.nw);
let current_top = parent_after.top(child_bounds.nw);
(target_top - current_top, height_scale)
let delta_y = match constraint_v {
ConstraintV::Top | ConstraintV::TopBottom => {
let target_top = parent_before.top(child_before.nw);
let current_top = parent_after.top(child_after.nw);
target_top - current_top
}
ConstraintV::Bottom => {
let target_bottom = parent_before.bottom(child_bounds.sw);
let current_bottom = parent_after.bottom(child_bounds.sw);
(current_bottom - target_bottom, 1.0)
}
ConstraintV::TopBottom => {
let target_top = parent_before.top(child_bounds.nw);
let target_bottom = parent_before.bottom(child_bounds.sw);
let current_top = parent_after.top(child_bounds.nw);
let new_height = parent_after.height() - target_top - target_bottom;
let height_scale = new_height / child_bounds.height();
(target_top - current_top, height_scale)
let target_bottom = parent_before.bottom(child_before.ne);
let current_bottom = parent_after.bottom(child_after.ne);
current_bottom - target_bottom
}
ConstraintV::Center => {
let delta_height = parent_after.height() - parent_before.height();
let delta_top = delta_height / 2.0;
(delta_top, 1.0)
let target_top = parent_before.top(child_before.nw);
let current_top = parent_after.top(child_after.nw);
target_top - current_top + delta_height / 2.0
}
_ => 0.0,
};
(delta_left, delta_top, scale_width, scale_height)
if delta_x.abs() < f32::EPSILON && delta_y.abs() < f32::EPSILON {
None
} else {
Some((delta_x, delta_y))
}
}
fn propagate_shape(
@ -121,36 +142,53 @@ fn propagate_shape(
}
if let Some(child_bounds_before) = parent_bounds_before.box_bounds(&child.bounds()) {
let (delta_left, delta_top, scale_width, scale_height) = calculate_new_bounds(
let mut transform = transform;
let mut child_bounds_after = child_bounds_before.transform(&transform);
// Scale shape
if let Some((scale_width, scale_height)) = calculate_resize(
constraint_h,
constraint_v,
&parent_bounds_before,
&parent_bounds_after,
&child_bounds_before,
);
&child_bounds_after,
) {
let center = child.center();
let mut parent_transform = parent_bounds_after
.transform_matrix()
.unwrap_or(Matrix::default());
parent_transform.post_translate(center);
parent_transform.pre_translate(-center);
let parent_transform_inv = &parent_transform.invert().unwrap();
let origin = parent_transform_inv.map_point(child_bounds_after.nw);
let mut scale = Matrix::scale((scale_width, scale_height));
scale.post_translate(origin);
scale.post_concat(&parent_transform);
scale.pre_translate(-origin);
scale.pre_concat(&parent_transform_inv);
child_bounds_after.transform_mut(&scale);
transform.post_concat(&scale);
}
// Translate position
let th = parent_bounds_after.hv(delta_left);
let tv = parent_bounds_after.vv(delta_top);
let mut transform = Matrix::translate(th + tv);
let child_bounds = child_bounds_before.transform(&transform);
if let Some((delta_x, delta_y)) = calculate_displacement(
constraint_h,
constraint_v,
&parent_bounds_before,
&parent_bounds_after,
&child_bounds_before,
&child_bounds_after,
) {
let th = parent_bounds_after.hv(delta_x);
let tv = parent_bounds_after.vv(delta_y);
transform.post_concat(&Matrix::translate(th + tv));
}
// Scale shape
let center = child.center();
let mut parent_transform = shape.transform;
parent_transform.post_translate(center);
parent_transform.pre_translate(-center);
let parent_transform_inv = &parent_transform.invert().unwrap();
let origin = parent_transform_inv.map_point(child_bounds.nw);
let mut scale = Matrix::scale((scale_width, scale_height));
scale.post_translate(origin);
scale.post_concat(&parent_transform);
scale.pre_translate(-origin);
scale.pre_concat(&parent_transform_inv);
transform.post_concat(&scale);
result.push(TransformEntry::new(child_id.clone(), transform));
}
}