Allow RenderObject.getTransformTo to take an arbitrary RenderObject in the same tree (#148897)

This is https://github.com/flutter/flutter/pull/130192 but without the additional parameter.

Fixes https://github.com/flutter/flutter/issues/146764, https://github.com/flutter/flutter/issues/148410
This commit is contained in:
LongCatIsLooong 2024-05-22 16:32:24 -07:00 committed by GitHub
parent bb5b7d2608
commit a766945bdd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 138 additions and 25 deletions

View File

@ -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}
@ -3332,30 +3341,61 @@ 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;
}
}
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 (ancestorSpecified) {
renderers.add(ancestor!);
}
final Matrix4 transform = Matrix4.identity();
for (int index = renderers.length - 1; index > 0; index -= 1) {
renderers[index].applyPaintTransform(renderers[index - 1], transform);
}
return transform;
}
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);
}
}
if (toPath == null) {
return fromTransform ?? Matrix4.identity();
}
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);
}
if (toTransform.invert() == 0) { // If the matrix is singular then `invert()` doesn't do anything.
return Matrix4.zero();
}
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

View File

@ -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);
@ -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?);
@ -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;
@ -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() { }