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:
parent
bb5b7d2608
commit
a766945bdd
@ -3317,12 +3317,21 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// {@template flutter.rendering.RenderObject.getTransformTo}
|
/// {@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
|
/// 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
|
/// local paint coordinate system to the coordinate system of the
|
||||||
/// [PipelineOwner.rootNode].
|
/// [PipelineOwner.rootNode].
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
@ -3332,30 +3341,61 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
|
|||||||
/// the global coordinate system in logical pixels. To get physical pixels,
|
/// the global coordinate system in logical pixels. To get physical pixels,
|
||||||
/// use [applyPaintTransform] from the [RenderView] to further transform the
|
/// use [applyPaintTransform] from the [RenderView] to further transform the
|
||||||
/// coordinate.
|
/// coordinate.
|
||||||
Matrix4 getTransformTo(RenderObject? ancestor) {
|
Matrix4 getTransformTo(RenderObject? target) {
|
||||||
final bool ancestorSpecified = ancestor != null;
|
|
||||||
assert(attached);
|
assert(attached);
|
||||||
if (ancestor == null) {
|
// The paths from to fromRenderObject and toRenderObject's common ancestor.
|
||||||
final RenderObject? rootNode = owner!.rootNode;
|
// Each list's length is greater than 1 if not null.
|
||||||
if (rootNode is RenderObject) {
|
//
|
||||||
ancestor = rootNode;
|
// [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
|
/// Returns a rect in this object's coordinate system that describes
|
||||||
/// the approximate bounding box of the clip rect that would be
|
/// the approximate bounding box of the clip rect that would be
|
||||||
|
@ -156,7 +156,7 @@ void main() {
|
|||||||
expect(() => data3.detach(), throwsAssertionError);
|
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 PipelineOwner owner = PipelineOwner();
|
||||||
final TestRenderObject renderObject1 = TestRenderObject();
|
final TestRenderObject renderObject1 = TestRenderObject();
|
||||||
renderObject1.attach(owner);
|
renderObject1.attach(owner);
|
||||||
@ -165,6 +165,64 @@ void main() {
|
|||||||
expect(() => renderObject1.getTransformTo(renderObject2), throwsAssertionError);
|
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', () {
|
test('PaintingContext.pushClipRect reuses the layer', () {
|
||||||
_testPaintingContextLayerReuse<ClipRectLayer>((PaintingContextCallback painter, PaintingContext context, Offset offset, Layer? oldLayer) {
|
_testPaintingContextLayerReuse<ClipRectLayer>((PaintingContextCallback painter, PaintingContext context, Offset offset, Layer? oldLayer) {
|
||||||
return context.pushClipRect(true, offset, Rect.zero, painter, oldLayer: oldLayer as ClipRectLayer?);
|
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 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});
|
TestRenderObject({this.allowPaintBounds = false});
|
||||||
|
|
||||||
final bool allowPaintBounds;
|
final bool allowPaintBounds;
|
||||||
@ -393,6 +452,20 @@ class TestRenderObject extends RenderObject {
|
|||||||
return Rect.zero;
|
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
|
@override
|
||||||
void performLayout() { }
|
void performLayout() { }
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user