mirror of
https://github.com/penpot/penpot.git
synced 2026-05-19 14:14:05 -04:00
Merge pull request #9724 from penpot/alotor-fix-grid-position
🐛 Fix problem with grid child positions
This commit is contained in:
@@ -651,6 +651,37 @@ pub fn grid_cell_data<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
// Returns `(h_min, v_min, h_size, v_size)` — the child's bounding box expressed in the
|
||||
// layout frame's own coordinate system (projected onto its `hv`/`vv` unit vectors).
|
||||
//
|
||||
// Using the frame axes rather than screen x/y is necessary when the parent grid frame
|
||||
// is itself rotated: in that case `max_x - min_x` is the screen-AABB width, which
|
||||
// differs from the width measured along the frame's horizontal axis.
|
||||
fn child_frame_aabb(child_bounds: &Bounds, hv: Vector, vv: Vector) -> (f32, f32, f32, f32) {
|
||||
let corners = child_bounds.points();
|
||||
let mut h_min = f32::INFINITY;
|
||||
let mut h_max = f32::NEG_INFINITY;
|
||||
let mut v_min = f32::INFINITY;
|
||||
let mut v_max = f32::NEG_INFINITY;
|
||||
for p in &corners {
|
||||
let h = hv.x * p.x + hv.y * p.y;
|
||||
let v = vv.x * p.x + vv.y * p.y;
|
||||
if h < h_min {
|
||||
h_min = h;
|
||||
}
|
||||
if h > h_max {
|
||||
h_max = h;
|
||||
}
|
||||
if v < v_min {
|
||||
v_min = v;
|
||||
}
|
||||
if v > v_max {
|
||||
v_max = v;
|
||||
}
|
||||
}
|
||||
(h_min, v_min, h_max - h_min, v_max - v_min)
|
||||
}
|
||||
|
||||
fn child_position(
|
||||
child: &Shape,
|
||||
layout_bounds: &Bounds,
|
||||
@@ -667,12 +698,17 @@ fn child_position(
|
||||
let margin_right = layout_item.map(|i| i.margin_right).unwrap_or(0.0);
|
||||
let margin_bottom = layout_item.map(|i| i.margin_bottom).unwrap_or(0.0);
|
||||
|
||||
// Project corners onto the frame's own axes so that both a rotated child *and* a
|
||||
// rotated parent frame are handled correctly. For an axis-aligned frame this
|
||||
// reduces to max_x-min_x / max_y-min_y, so non-rotated layouts are unaffected.
|
||||
let (_, _, child_width, child_height) = child_frame_aabb(child_bounds, hv, vv);
|
||||
|
||||
let vpos = match (cell.align_self, layout_data.align_items) {
|
||||
(Some(AlignSelf::Start), _) => margin_top,
|
||||
(Some(AlignSelf::Center), _) => (cell.height - child_bounds.height()) / 2.0,
|
||||
(Some(AlignSelf::End), _) => margin_bottom + cell.height - child_bounds.height(),
|
||||
(_, AlignItems::Center) => (cell.height - child_bounds.height()) / 2.0,
|
||||
(_, AlignItems::End) => margin_bottom + cell.height - child_bounds.height(),
|
||||
(Some(AlignSelf::Center), _) => (cell.height - child_height) / 2.0,
|
||||
(Some(AlignSelf::End), _) => margin_bottom + cell.height - child_height,
|
||||
(_, AlignItems::Center) => (cell.height - child_height) / 2.0,
|
||||
(_, AlignItems::End) => margin_bottom + cell.height - child_height,
|
||||
_ => margin_top,
|
||||
};
|
||||
|
||||
@@ -684,10 +720,10 @@ fn child_position(
|
||||
|
||||
let hpos = match (cell.justify_self, layout_data.justify_items) {
|
||||
(Some(JustifySelf::Start), _) => margin_left,
|
||||
(Some(JustifySelf::Center), _) => (cell.width - child_bounds.width()) / 2.0,
|
||||
(Some(JustifySelf::End), _) => margin_right + cell.width - child_bounds.width(),
|
||||
(_, JustifyItems::Center) => (cell.width - child_bounds.width()) / 2.0,
|
||||
(_, JustifyItems::End) => margin_right + cell.width - child_bounds.width(),
|
||||
(Some(JustifySelf::Center), _) => (cell.width - child_width) / 2.0,
|
||||
(Some(JustifySelf::End), _) => margin_right + cell.width - child_width,
|
||||
(_, JustifyItems::Center) => (cell.width - child_width) / 2.0,
|
||||
(_, JustifyItems::End) => margin_right + cell.width - child_width,
|
||||
_ => margin_left,
|
||||
};
|
||||
|
||||
@@ -747,11 +783,27 @@ pub fn reflow_grid_layout(
|
||||
let Some(child) = cell.shape else { continue };
|
||||
let child_bounds = bounds.find(child);
|
||||
|
||||
// Compute frame-axis projections once; used for both sizing and positioning.
|
||||
let hv = layout_bounds.hv(1.0);
|
||||
let vv = layout_bounds.vv(1.0);
|
||||
let (h_min, v_min, child_frame_w, child_frame_h) = child_frame_aabb(&child_bounds, hv, vv);
|
||||
|
||||
// resize_matrix scales the child in the parent's local frame coordinate system
|
||||
// by (new_width / child_bounds.width()) in the h-axis and
|
||||
// (new_height / child_bounds.height()) in the v-axis. For a rotated child the
|
||||
// frame-projected extent differs from the intrinsic bounds dimensions, so we
|
||||
// back-calculate the intrinsic target that will produce the desired
|
||||
// frame-projected extent.
|
||||
let mut new_width = child_bounds.width();
|
||||
if child.is_layout_horizontal_fill() {
|
||||
let margin_left = child.layout_item.map(|i| i.margin_left).unwrap_or(0.0);
|
||||
let margin_right = child.layout_item.map(|i| i.margin_right).unwrap_or(0.0);
|
||||
new_width = cell.width - margin_left - margin_right;
|
||||
let target_frame_w = cell.width - margin_left - margin_right;
|
||||
new_width = if child_frame_w > MIN_SIZE {
|
||||
target_frame_w * child_bounds.width() / child_frame_w
|
||||
} else {
|
||||
target_frame_w
|
||||
};
|
||||
let min_width = child.layout_item.and_then(|i| i.min_w).unwrap_or(MIN_SIZE);
|
||||
let max_width = child.layout_item.and_then(|i| i.max_w).unwrap_or(MAX_SIZE);
|
||||
new_width = new_width.clamp(min_width, max_width);
|
||||
@@ -761,7 +813,12 @@ pub fn reflow_grid_layout(
|
||||
if child.is_layout_vertical_fill() {
|
||||
let margin_top = child.layout_item.map(|i| i.margin_top).unwrap_or(0.0);
|
||||
let margin_bottom = child.layout_item.map(|i| i.margin_bottom).unwrap_or(0.0);
|
||||
new_height = cell.height - margin_top - margin_bottom;
|
||||
let target_frame_h = cell.height - margin_top - margin_bottom;
|
||||
new_height = if child_frame_h > MIN_SIZE {
|
||||
target_frame_h * child_bounds.height() / child_frame_h
|
||||
} else {
|
||||
target_frame_h
|
||||
};
|
||||
let min_height = child.layout_item.and_then(|i| i.min_h).unwrap_or(MIN_SIZE);
|
||||
let max_height = child.layout_item.and_then(|i| i.max_h).unwrap_or(MAX_SIZE);
|
||||
new_height = new_height.clamp(min_height, max_height);
|
||||
@@ -792,7 +849,36 @@ pub fn reflow_grid_layout(
|
||||
cell,
|
||||
);
|
||||
|
||||
let delta_v = Vector::new_points(&child_bounds.nw, &position);
|
||||
// Compute the child's reference point in the frame's coordinate system.
|
||||
// For a rotated parent frame, (min_x, min_y) is wrong because it is the
|
||||
// screen-AABB corner, not the frame-axis-aligned corner.
|
||||
// child_ref = h_min * hv + v_min * vv gives the world-space point whose
|
||||
// projections onto hv/vv are the child's minima along those axes —
|
||||
// the "top-left in frame coordinates".
|
||||
//
|
||||
// For fill axes, resize_matrix scales local-x/y by (new_w / child_bounds.width())
|
||||
// anchored at nw. This shifts h_min/v_min: the post-resize minimum is
|
||||
// h_min_new = nw_h + (h_min - nw_h) * scale_w
|
||||
// We must translate FROM this post-resize minimum, not the pre-resize one.
|
||||
let nw_h = hv.x * child_bounds.nw.x + hv.y * child_bounds.nw.y;
|
||||
let nw_v = vv.x * child_bounds.nw.x + vv.y * child_bounds.nw.y;
|
||||
let h_anchor = if child.is_layout_horizontal_fill() && child_bounds.width() > MIN_SIZE {
|
||||
let scale_w = new_width / child_bounds.width();
|
||||
nw_h + (h_min - nw_h) * scale_w
|
||||
} else {
|
||||
h_min
|
||||
};
|
||||
let v_anchor = if child.is_layout_vertical_fill() && child_bounds.height() > MIN_SIZE {
|
||||
let scale_h = new_height / child_bounds.height();
|
||||
nw_v + (v_min - nw_v) * scale_h
|
||||
} else {
|
||||
v_min
|
||||
};
|
||||
let child_ref = Point::new(
|
||||
h_anchor * hv.x + v_anchor * vv.x,
|
||||
h_anchor * hv.y + v_anchor * vv.y,
|
||||
);
|
||||
let delta_v = Vector::new_points(&child_ref, &position);
|
||||
|
||||
if delta_v.x.abs() > MIN_SIZE || delta_v.y.abs() > MIN_SIZE {
|
||||
transform.post_concat(&Matrix::translate(delta_v));
|
||||
|
||||
Reference in New Issue
Block a user