Skip to content

Commit a766945

Browse files
Allow RenderObject.getTransformTo to take an arbitrary RenderObject in the same tree (flutter#148897)
This is flutter#130192 but without the additional parameter. Fixes flutter#146764, flutter#148410
1 parent bb5b7d2 commit a766945

File tree

2 files changed

+135
-22
lines changed

2 files changed

+135
-22
lines changed

packages/flutter/lib/src/rendering/object.dart

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3317,12 +3317,21 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
33173317
}
33183318

33193319
/// {@template flutter.rendering.RenderObject.getTransformTo}
3320-
/// Applies the paint transform up the tree to `ancestor`.
3320+
/// Applies the paint transform from this [RenderObject] to the `target`
3321+
/// [RenderObject].
33213322
///
33223323
/// Returns a matrix that maps the local paint coordinate system to the
3323-
/// coordinate system of `ancestor`.
3324+
/// coordinate system of `target`, or a [Matrix4.zero] if the paint transform
3325+
/// can not be computed.
33243326
///
3325-
/// If `ancestor` is null, this method returns a matrix that maps from the
3327+
/// This method throws an exception when the `target` is not in the same render
3328+
/// tree as this [RenderObject], as the behavior is undefined.
3329+
///
3330+
/// This method ignores [RenderObject.paintsChild]. This means it will still
3331+
/// try to compute the paint transform even if [this] or `target` is currently
3332+
/// not visible.
3333+
///
3334+
/// If `target` is null, this method returns a matrix that maps from the
33263335
/// local paint coordinate system to the coordinate system of the
33273336
/// [PipelineOwner.rootNode].
33283337
/// {@endtemplate}
@@ -3332,31 +3341,62 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
33323341
/// the global coordinate system in logical pixels. To get physical pixels,
33333342
/// use [applyPaintTransform] from the [RenderView] to further transform the
33343343
/// coordinate.
3335-
Matrix4 getTransformTo(RenderObject? ancestor) {
3336-
final bool ancestorSpecified = ancestor != null;
3344+
Matrix4 getTransformTo(RenderObject? target) {
33373345
assert(attached);
3338-
if (ancestor == null) {
3339-
final RenderObject? rootNode = owner!.rootNode;
3340-
if (rootNode is RenderObject) {
3341-
ancestor = rootNode;
3346+
// The paths from to fromRenderObject and toRenderObject's common ancestor.
3347+
// Each list's length is greater than 1 if not null.
3348+
//
3349+
// [this, ...., commonAncestorRenderObject], or null if `this` is the common
3350+
// ancestor.
3351+
List<RenderObject>? fromPath;
3352+
// [target, ...., commonAncestorRenderObject], or null if `target` is the
3353+
// common ancestor.
3354+
List<RenderObject>? toPath;
3355+
3356+
RenderObject from = this;
3357+
RenderObject to = target ?? owner!.rootNode!;
3358+
3359+
while (!identical(from, to)) {
3360+
final int fromDepth = from.depth;
3361+
final int toDepth = to.depth;
3362+
3363+
if (fromDepth >= toDepth) {
3364+
final RenderObject fromParent = from.parent ?? (throw FlutterError('$target and $this are not in the same render tree.'));
3365+
(fromPath ??= <RenderObject>[this]).add(fromParent);
3366+
from = fromParent;
3367+
}
3368+
if (fromDepth <= toDepth) {
3369+
final RenderObject toParent = to.parent ?? (throw FlutterError('$target and $this are not in the same render tree.'));
3370+
assert(target != null, '$this has a depth that is less than or equal to ${owner?.rootNode}');
3371+
(toPath ??= <RenderObject>[target!]).add(toParent);
3372+
to = toParent;
3373+
}
3374+
}
3375+
3376+
Matrix4? fromTransform;
3377+
if (fromPath != null) {
3378+
assert(fromPath.length > 1);
3379+
fromTransform = Matrix4.identity();
3380+
final int lastIndex = target == null ? fromPath.length - 2 : fromPath.length - 1;
3381+
for (int index = lastIndex; index > 0; index -= 1) {
3382+
fromPath[index].applyPaintTransform(fromPath[index - 1], fromTransform);
33423383
}
33433384
}
3344-
final List<RenderObject> renderers = <RenderObject>[];
3345-
for (RenderObject renderer = this; renderer != ancestor; renderer = renderer.parent!) {
3346-
renderers.add(renderer);
3347-
assert(renderer.parent != null); // Failed to find ancestor in parent chain.
3385+
if (toPath == null) {
3386+
return fromTransform ?? Matrix4.identity();
33483387
}
3349-
if (ancestorSpecified) {
3350-
renderers.add(ancestor!);
3388+
3389+
assert(toPath.length > 1);
3390+
final Matrix4 toTransform = Matrix4.identity();
3391+
for (int index = toPath.length - 1; index > 0; index -= 1) {
3392+
toPath[index].applyPaintTransform(toPath[index - 1], toTransform);
33513393
}
3352-
final Matrix4 transform = Matrix4.identity();
3353-
for (int index = renderers.length - 1; index > 0; index -= 1) {
3354-
renderers[index].applyPaintTransform(renderers[index - 1], transform);
3394+
if (toTransform.invert() == 0) { // If the matrix is singular then `invert()` doesn't do anything.
3395+
return Matrix4.zero();
33553396
}
3356-
return transform;
3397+
return (fromTransform?..multiply(toTransform)) ?? toTransform;
33573398
}
33583399

3359-
33603400
/// Returns a rect in this object's coordinate system that describes
33613401
/// the approximate bounding box of the clip rect that would be
33623402
/// applied to the given child during the paint phase, if any.

packages/flutter/test/rendering/object_test.dart

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ void main() {
156156
expect(() => data3.detach(), throwsAssertionError);
157157
});
158158

159-
test('RenderObject.getTransformTo asserts is argument is not descendant', () {
159+
test('RenderObject.getTransformTo asserts if target not in the same render tree', () {
160160
final PipelineOwner owner = PipelineOwner();
161161
final TestRenderObject renderObject1 = TestRenderObject();
162162
renderObject1.attach(owner);
@@ -165,6 +165,64 @@ void main() {
165165
expect(() => renderObject1.getTransformTo(renderObject2), throwsAssertionError);
166166
});
167167

168+
test('RenderObject.getTransformTo works for siblings and descendants', () {
169+
final PipelineOwner owner = PipelineOwner();
170+
final TestRenderObject renderObject1 = TestRenderObject()..attach(owner);
171+
final TestRenderObject renderObject11 = TestRenderObject();
172+
final TestRenderObject renderObject12 = TestRenderObject();
173+
174+
renderObject1
175+
..add(renderObject11)
176+
..add(renderObject12);
177+
expect(renderObject11.getTransformTo(renderObject12), equals(Matrix4.identity()));
178+
expect(renderObject1.getTransformTo(renderObject11), equals(Matrix4.identity()));
179+
expect(renderObject1.getTransformTo(renderObject12), equals(Matrix4.identity()));
180+
expect(renderObject11.getTransformTo(renderObject1), equals(Matrix4.identity()));
181+
expect(renderObject12.getTransformTo(renderObject1), equals(Matrix4.identity()));
182+
183+
expect(renderObject1.getTransformTo(renderObject1), equals(Matrix4.identity()));
184+
expect(renderObject11.getTransformTo(renderObject11), equals(Matrix4.identity()));
185+
expect(renderObject12.getTransformTo(renderObject12), equals(Matrix4.identity()));
186+
});
187+
188+
test('RenderObject.getTransformTo gets the correct paint transform', () {
189+
final PipelineOwner owner = PipelineOwner();
190+
final TestRenderObject renderObject0 = TestRenderObject()
191+
..attach(owner);
192+
final TestRenderObject renderObject1 = TestRenderObject();
193+
final TestRenderObject renderObject2 = TestRenderObject();
194+
renderObject0
195+
..add(renderObject1)
196+
..add(renderObject2)
197+
..paintTransform = Matrix4.diagonal3Values(9, 4, 1);
198+
199+
final TestRenderObject renderObject11 = TestRenderObject();
200+
final TestRenderObject renderObject21 = TestRenderObject();
201+
renderObject1
202+
..add(renderObject11)
203+
..paintTransform = Matrix4.translationValues(8, 16, 32);
204+
renderObject2
205+
..add(renderObject21)
206+
..paintTransform = Matrix4.translationValues(32, 64, 128);
207+
208+
expect(
209+
renderObject11.getTransformTo(renderObject21),
210+
equals(Matrix4.translationValues((8 - 32) * 9, (16 - 64) * 4, 32 - 128)),
211+
);
212+
// Turn one of the paint transforms into a singular matrix and getTransformTo
213+
// should return Matrix4.zero().
214+
renderObject0.paintTransform = Matrix4(
215+
1, 1, 1 ,1,
216+
2, 2, 2, 2,
217+
3, 3, 3, 3,
218+
4, 4, 4, 4,
219+
); // Not a full rank matrix, so it has to be singular.
220+
expect(
221+
renderObject11.getTransformTo(renderObject21),
222+
equals(Matrix4.zero()),
223+
);
224+
});
225+
168226
test('PaintingContext.pushClipRect reuses the layer', () {
169227
_testPaintingContextLayerReuse<ClipRectLayer>((PaintingContextCallback painter, PaintingContext context, Offset offset, Layer? oldLayer) {
170228
return context.pushClipRect(true, offset, Rect.zero, painter, oldLayer: oldLayer as ClipRectLayer?);
@@ -376,7 +434,8 @@ class _TestCustomLayerBox extends RenderBox {
376434

377435
class TestParentData extends ParentData with ContainerParentDataMixin<RenderBox> { }
378436

379-
class TestRenderObject extends RenderObject {
437+
class TestRenderObjectParentData extends ParentData with ContainerParentDataMixin<TestRenderObject> { }
438+
class TestRenderObject extends RenderObject with ContainerRenderObjectMixin<TestRenderObject, TestRenderObjectParentData> {
380439
TestRenderObject({this.allowPaintBounds = false});
381440

382441
final bool allowPaintBounds;
@@ -393,6 +452,20 @@ class TestRenderObject extends RenderObject {
393452
return Rect.zero;
394453
}
395454

455+
Matrix4 paintTransform = Matrix4.identity();
456+
@override
457+
void applyPaintTransform(covariant RenderObject child, Matrix4 transform) {
458+
super.applyPaintTransform(child, transform);
459+
transform.multiply(paintTransform);
460+
}
461+
462+
@override
463+
void setupParentData(RenderObject child) {
464+
if (child.parentData is! TestRenderObjectParentData) {
465+
child.parentData = TestRenderObjectParentData();
466+
}
467+
}
468+
396469
@override
397470
void performLayout() { }
398471

0 commit comments

Comments
 (0)