Skip to content

Commit

Permalink
Allow RenderObject.getTransformTo to take an arbitrary RenderObject…
Browse files Browse the repository at this point in the history
… in the same tree (flutter#148897)

This is flutter#130192 but without the additional parameter.

Fixes flutter#146764, flutter#148410
  • Loading branch information
LongCatIsLooong authored and victorsanni committed May 31, 2024
1 parent ade497e commit 13fe899
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 22 deletions.
80 changes: 60 additions & 20 deletions packages/flutter/lib/src/rendering/object.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3317,12 +3317,21 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
}

/// {@template flutter.rendering.RenderObject.getTransformTo}
/// Applies the paint transform up the tree to `ancestor`.
/// Applies the paint transform from this [RenderObject] to the `target`
/// [RenderObject].
///
/// Returns a matrix that maps the local paint coordinate system to the
/// coordinate system of `ancestor`.
/// coordinate system of `target`, or a [Matrix4.zero] if the paint transform
/// can not be computed.
///
/// If `ancestor` is null, this method returns a matrix that maps from the
/// This method throws an exception when the `target` is not in the same render
/// tree as this [RenderObject], as the behavior is undefined.
///
/// This method ignores [RenderObject.paintsChild]. This means it will still
/// try to compute the paint transform even if [this] or `target` is currently
/// not visible.
///
/// If `target` is null, this method returns a matrix that maps from the
/// local paint coordinate system to the coordinate system of the
/// [PipelineOwner.rootNode].
/// {@endtemplate}
Expand All @@ -3332,31 +3341,62 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
/// the global coordinate system in logical pixels. To get physical pixels,
/// use [applyPaintTransform] from the [RenderView] to further transform the
/// coordinate.
Matrix4 getTransformTo(RenderObject? ancestor) {
final bool ancestorSpecified = ancestor != null;
Matrix4 getTransformTo(RenderObject? target) {
assert(attached);
if (ancestor == null) {
final RenderObject? rootNode = owner!.rootNode;
if (rootNode is RenderObject) {
ancestor = rootNode;
// The paths from to fromRenderObject and toRenderObject's common ancestor.
// Each list's length is greater than 1 if not null.
//
// [this, ...., commonAncestorRenderObject], or null if `this` is the common
// ancestor.
List<RenderObject>? fromPath;
// [target, ...., commonAncestorRenderObject], or null if `target` is the
// common ancestor.
List<RenderObject>? toPath;

RenderObject from = this;
RenderObject to = target ?? owner!.rootNode!;

while (!identical(from, to)) {
final int fromDepth = from.depth;
final int toDepth = to.depth;

if (fromDepth >= toDepth) {
final RenderObject fromParent = from.parent ?? (throw FlutterError('$target and $this are not in the same render tree.'));
(fromPath ??= <RenderObject>[this]).add(fromParent);
from = fromParent;
}
if (fromDepth <= toDepth) {
final RenderObject toParent = to.parent ?? (throw FlutterError('$target and $this are not in the same render tree.'));
assert(target != null, '$this has a depth that is less than or equal to ${owner?.rootNode}');
(toPath ??= <RenderObject>[target!]).add(toParent);
to = toParent;
}
}

Matrix4? fromTransform;
if (fromPath != null) {
assert(fromPath.length > 1);
fromTransform = Matrix4.identity();
final int lastIndex = target == null ? fromPath.length - 2 : fromPath.length - 1;
for (int index = lastIndex; index > 0; index -= 1) {
fromPath[index].applyPaintTransform(fromPath[index - 1], fromTransform);
}
}
final List<RenderObject> renderers = <RenderObject>[];
for (RenderObject renderer = this; renderer != ancestor; renderer = renderer.parent!) {
renderers.add(renderer);
assert(renderer.parent != null); // Failed to find ancestor in parent chain.
if (toPath == null) {
return fromTransform ?? Matrix4.identity();
}
if (ancestorSpecified) {
renderers.add(ancestor!);

assert(toPath.length > 1);
final Matrix4 toTransform = Matrix4.identity();
for (int index = toPath.length - 1; index > 0; index -= 1) {
toPath[index].applyPaintTransform(toPath[index - 1], toTransform);
}
final Matrix4 transform = Matrix4.identity();
for (int index = renderers.length - 1; index > 0; index -= 1) {
renderers[index].applyPaintTransform(renderers[index - 1], transform);
if (toTransform.invert() == 0) { // If the matrix is singular then `invert()` doesn't do anything.
return Matrix4.zero();
}
return transform;
return (fromTransform?..multiply(toTransform)) ?? toTransform;
}


/// Returns a rect in this object's coordinate system that describes
/// the approximate bounding box of the clip rect that would be
/// applied to the given child during the paint phase, if any.
Expand Down
77 changes: 75 additions & 2 deletions packages/flutter/test/rendering/object_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ void main() {
expect(() => data3.detach(), throwsAssertionError);
});

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

test('RenderObject.getTransformTo works for siblings and descendants', () {
final PipelineOwner owner = PipelineOwner();
final TestRenderObject renderObject1 = TestRenderObject()..attach(owner);
final TestRenderObject renderObject11 = TestRenderObject();
final TestRenderObject renderObject12 = TestRenderObject();

renderObject1
..add(renderObject11)
..add(renderObject12);
expect(renderObject11.getTransformTo(renderObject12), equals(Matrix4.identity()));
expect(renderObject1.getTransformTo(renderObject11), equals(Matrix4.identity()));
expect(renderObject1.getTransformTo(renderObject12), equals(Matrix4.identity()));
expect(renderObject11.getTransformTo(renderObject1), equals(Matrix4.identity()));
expect(renderObject12.getTransformTo(renderObject1), equals(Matrix4.identity()));

expect(renderObject1.getTransformTo(renderObject1), equals(Matrix4.identity()));
expect(renderObject11.getTransformTo(renderObject11), equals(Matrix4.identity()));
expect(renderObject12.getTransformTo(renderObject12), equals(Matrix4.identity()));
});

test('RenderObject.getTransformTo gets the correct paint transform', () {
final PipelineOwner owner = PipelineOwner();
final TestRenderObject renderObject0 = TestRenderObject()
..attach(owner);
final TestRenderObject renderObject1 = TestRenderObject();
final TestRenderObject renderObject2 = TestRenderObject();
renderObject0
..add(renderObject1)
..add(renderObject2)
..paintTransform = Matrix4.diagonal3Values(9, 4, 1);

final TestRenderObject renderObject11 = TestRenderObject();
final TestRenderObject renderObject21 = TestRenderObject();
renderObject1
..add(renderObject11)
..paintTransform = Matrix4.translationValues(8, 16, 32);
renderObject2
..add(renderObject21)
..paintTransform = Matrix4.translationValues(32, 64, 128);

expect(
renderObject11.getTransformTo(renderObject21),
equals(Matrix4.translationValues((8 - 32) * 9, (16 - 64) * 4, 32 - 128)),
);
// Turn one of the paint transforms into a singular matrix and getTransformTo
// should return Matrix4.zero().
renderObject0.paintTransform = Matrix4(
1, 1, 1 ,1,
2, 2, 2, 2,
3, 3, 3, 3,
4, 4, 4, 4,
); // Not a full rank matrix, so it has to be singular.
expect(
renderObject11.getTransformTo(renderObject21),
equals(Matrix4.zero()),
);
});

test('PaintingContext.pushClipRect reuses the layer', () {
_testPaintingContextLayerReuse<ClipRectLayer>((PaintingContextCallback painter, PaintingContext context, Offset offset, Layer? oldLayer) {
return context.pushClipRect(true, offset, Rect.zero, painter, oldLayer: oldLayer as ClipRectLayer?);
Expand Down Expand Up @@ -376,7 +434,8 @@ class _TestCustomLayerBox extends RenderBox {

class TestParentData extends ParentData with ContainerParentDataMixin<RenderBox> { }

class TestRenderObject extends RenderObject {
class TestRenderObjectParentData extends ParentData with ContainerParentDataMixin<TestRenderObject> { }
class TestRenderObject extends RenderObject with ContainerRenderObjectMixin<TestRenderObject, TestRenderObjectParentData> {
TestRenderObject({this.allowPaintBounds = false});

final bool allowPaintBounds;
Expand All @@ -393,6 +452,20 @@ class TestRenderObject extends RenderObject {
return Rect.zero;
}

Matrix4 paintTransform = Matrix4.identity();
@override
void applyPaintTransform(covariant RenderObject child, Matrix4 transform) {
super.applyPaintTransform(child, transform);
transform.multiply(paintTransform);
}

@override
void setupParentData(RenderObject child) {
if (child.parentData is! TestRenderObjectParentData) {
child.parentData = TestRenderObjectParentData();
}
}

@override
void performLayout() { }

Expand Down

0 comments on commit 13fe899

Please sign in to comment.