Translation-only fast path in Shape::transform_selrect and Path::transform

This commit is contained in:
Elena Torro 2026-05-04 17:48:53 +02:00
parent 9fd26d7473
commit a5c4b83611
2 changed files with 75 additions and 0 deletions

View File

@ -1363,6 +1363,41 @@ impl Shape {
}
fn transform_selrect(&mut self, transform: &Matrix) {
// Translation-only fast path. The general code rebuilds bounds
// by transforming all four corners (`bounds.transform`), then
// re-derives self.transform from those bounds. For pure
// translation, selrect just shifts and self.transform doesn't
// change shape (only its translation component). Doing this
// cheaply per-shape during a layout drag matters because
// `Shape::apply_transform` is called once per modified shape
// per pointer-move (N+1 times for layouts with N children).
let eps = 1e-4_f32;
let is_translation_only = (transform.scale_x() - 1.0).abs() < eps
&& (transform.scale_y() - 1.0).abs() < eps
&& transform.skew_x().abs() < eps
&& transform.skew_y().abs() < eps
&& transform.persp_x().abs() < eps
&& transform.persp_y().abs() < eps;
if is_translation_only {
let tx = transform.translate_x();
let ty = transform.translate_y();
// For pure translation, only `selrect` shifts — `self.transform`
// represents the shape's rotation/scale *around its center*,
// which translation leaves untouched. The general path
// confirms this: it derives `self.transform` from
// `bounds.transform_matrix()` after a translate, which still
// yields the original rotation matrix (translation lives in
// bounds position, not in `transform_matrix`).
self.selrect = math::Rect::from_xywh(
self.selrect.left + tx,
self.selrect.top + ty,
self.selrect.width(),
self.selrect.height(),
);
return;
}
let mut center = self.selrect.center();
center = transform.map_point(center);

View File

@ -236,6 +236,46 @@ impl Path {
}
pub fn transform(&mut self, mtx: &Matrix) {
// Translation-only fast path. During interactive translation
// drags the layout's children all receive the same pure-
// translation modifier, and `Path::transform` is called once
// per child per pointer-move. The general path walks every
// segment doing `mtx.map_point(...)` (a 2x3 matrix multiply
// per control point) plus a Skia `make_transform` (full path
// walk in C++). For pure translation that work reduces to
// simple x+=tx, y+=ty additions plus Skia's much cheaper
// `with_offset` op. Saves a meaningful slice of per-rAF cost
// on path-heavy layouts (icons, vector illustrations, etc).
let eps = 1e-4_f32;
let is_translation_only = (mtx.scale_x() - 1.0).abs() < eps
&& (mtx.scale_y() - 1.0).abs() < eps
&& mtx.skew_x().abs() < eps
&& mtx.skew_y().abs() < eps
&& mtx.persp_x().abs() < eps
&& mtx.persp_y().abs() < eps;
if is_translation_only {
let tx = mtx.translate_x();
let ty = mtx.translate_y();
self.segments.iter_mut().for_each(|s| match s {
Segment::MoveTo(p) | Segment::LineTo(p) => {
p.0 += tx;
p.1 += ty;
}
Segment::CurveTo((c1, c2, p)) => {
c1.0 += tx;
c1.1 += ty;
c2.0 += tx;
c2.1 += ty;
p.0 += tx;
p.1 += ty;
}
_ => {}
});
self.skia_path = self.skia_path.with_offset((tx, ty));
return;
}
self.segments.iter_mut().for_each(|s| match s {
Segment::MoveTo(p) => {
let np = mtx.map_point(skia::Point::new(p.0, p.1));