
Previously when subclassing `MultiChildRenderObjectWidget` and `RenderObject with ContainerRenderObjectMixin`, if one forgot to set up parent data, the error message does not give hint that `setupParentData` need to be implemented by the `RenderObject`. This PR add assertion that check parent data type before type cast and give hints if it is was not properly set. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
574 lines
21 KiB
Dart
574 lines
21 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:ui' as ui;
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
|
|
|
|
import 'rendering_tester.dart';
|
|
|
|
void main() {
|
|
TestRenderingFlutterBinding.ensureInitialized();
|
|
|
|
test('PipelineOwner dispatches memory events', () async {
|
|
await expectLater(
|
|
await memoryEvents(() => PipelineOwner().dispose(), PipelineOwner),
|
|
areCreateAndDispose,
|
|
);
|
|
});
|
|
|
|
test('ensure frame is scheduled for markNeedsSemanticsUpdate', () {
|
|
// Initialize all bindings because owner.flushSemantics() requires a window
|
|
final TestRenderObject renderObject = TestRenderObject();
|
|
int onNeedVisualUpdateCallCount = 0;
|
|
final PipelineOwner owner = PipelineOwner(
|
|
onNeedVisualUpdate: () {
|
|
onNeedVisualUpdateCallCount +=1;
|
|
},
|
|
onSemanticsUpdate: (ui.SemanticsUpdate update) {}
|
|
);
|
|
owner.ensureSemantics();
|
|
renderObject.attach(owner);
|
|
renderObject.layout(const BoxConstraints.tightForFinite()); // semantics are only calculated if layout information is up to date.
|
|
owner.flushSemantics();
|
|
|
|
expect(onNeedVisualUpdateCallCount, 1);
|
|
renderObject.markNeedsSemanticsUpdate();
|
|
expect(onNeedVisualUpdateCallCount, 2);
|
|
});
|
|
|
|
test('onSemanticsUpdate is called during flushSemantics.', () {
|
|
int onSemanticsUpdateCallCount = 0;
|
|
final PipelineOwner owner = PipelineOwner(
|
|
onSemanticsUpdate: (ui.SemanticsUpdate update) {
|
|
onSemanticsUpdateCallCount += 1;
|
|
},
|
|
);
|
|
owner.ensureSemantics();
|
|
|
|
expect(onSemanticsUpdateCallCount, 0);
|
|
|
|
final TestRenderObject renderObject = TestRenderObject();
|
|
renderObject.attach(owner);
|
|
renderObject.layout(const BoxConstraints.tightForFinite());
|
|
owner.flushSemantics();
|
|
|
|
expect(onSemanticsUpdateCallCount, 1);
|
|
});
|
|
|
|
test('Enabling semantics without configuring onSemanticsUpdate is invalid.', () {
|
|
final PipelineOwner pipelineOwner = PipelineOwner();
|
|
expect(() => pipelineOwner.ensureSemantics(), throwsAssertionError);
|
|
});
|
|
|
|
|
|
test('onSemanticsUpdate during sendSemanticsUpdate.', () {
|
|
int onSemanticsUpdateCallCount = 0;
|
|
final SemanticsOwner owner = SemanticsOwner(
|
|
onSemanticsUpdate: (ui.SemanticsUpdate update) {
|
|
onSemanticsUpdateCallCount += 1;
|
|
},
|
|
);
|
|
|
|
final SemanticsNode node = SemanticsNode.root(owner: owner);
|
|
node.rect = Rect.largest;
|
|
|
|
expect(onSemanticsUpdateCallCount, 0);
|
|
|
|
owner.sendSemanticsUpdate();
|
|
|
|
expect(onSemanticsUpdateCallCount, 1);
|
|
});
|
|
|
|
test('detached RenderObject does not do semantics', () {
|
|
final TestRenderObject renderObject = TestRenderObject();
|
|
expect(renderObject.attached, isFalse);
|
|
expect(renderObject.describeSemanticsConfigurationCallCount, 0);
|
|
|
|
renderObject.markNeedsSemanticsUpdate();
|
|
expect(renderObject.describeSemanticsConfigurationCallCount, 0);
|
|
});
|
|
|
|
test('ensure errors processing render objects are well formatted', () {
|
|
late FlutterErrorDetails errorDetails;
|
|
final FlutterExceptionHandler? oldHandler = FlutterError.onError;
|
|
FlutterError.onError = (FlutterErrorDetails details) {
|
|
errorDetails = details;
|
|
};
|
|
final PipelineOwner owner = PipelineOwner();
|
|
final TestThrowingRenderObject renderObject = TestThrowingRenderObject();
|
|
try {
|
|
renderObject.attach(owner);
|
|
renderObject.layout(const BoxConstraints());
|
|
} finally {
|
|
FlutterError.onError = oldHandler;
|
|
}
|
|
|
|
expect(errorDetails, isNotNull);
|
|
expect(errorDetails.stack, isNotNull);
|
|
// Check the ErrorDetails without the stack trace
|
|
final List<String> lines = errorDetails.toString().split('\n');
|
|
// The lines in the middle of the error message contain the stack trace
|
|
// which will change depending on where the test is run.
|
|
expect(lines.length, greaterThan(8));
|
|
expect(
|
|
lines.take(4).join('\n'),
|
|
equalsIgnoringHashCodes(
|
|
'══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞══════════════════════\n'
|
|
'The following assertion was thrown during performLayout():\n'
|
|
'TestThrowingRenderObject does not support performLayout.\n',
|
|
),
|
|
);
|
|
|
|
expect(
|
|
lines.getRange(lines.length - 8, lines.length).join('\n'),
|
|
equalsIgnoringHashCodes(
|
|
'\n'
|
|
'The following RenderObject was being processed when the exception was fired:\n'
|
|
' TestThrowingRenderObject#00000 NEEDS-PAINT:\n'
|
|
' parentData: MISSING\n'
|
|
' constraints: BoxConstraints(unconstrained)\n'
|
|
'This RenderObject has no descendants.\n'
|
|
'═════════════════════════════════════════════════════════════════\n',
|
|
),
|
|
);
|
|
});
|
|
|
|
test('ContainerParentDataMixin requires nulled out pointers to siblings before detach', () {
|
|
expect(() => TestParentData().detach(), isNot(throwsAssertionError));
|
|
|
|
final TestParentData data1 = TestParentData()
|
|
..nextSibling = RenderOpacity()
|
|
..previousSibling = RenderOpacity();
|
|
expect(() => data1.detach(), throwsAssertionError);
|
|
|
|
final TestParentData data2 = TestParentData()
|
|
..previousSibling = RenderOpacity();
|
|
expect(() => data2.detach(), throwsAssertionError);
|
|
|
|
final TestParentData data3 = TestParentData()
|
|
..nextSibling = RenderOpacity();
|
|
expect(() => data3.detach(), throwsAssertionError);
|
|
});
|
|
|
|
test('RenderObject.getTransformTo asserts if target not in the same render tree', () {
|
|
final PipelineOwner owner = PipelineOwner();
|
|
final TestRenderObject renderObject1 = TestRenderObject();
|
|
renderObject1.attach(owner);
|
|
final TestRenderObject renderObject2 = TestRenderObject();
|
|
renderObject2.attach(owner);
|
|
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?);
|
|
});
|
|
});
|
|
|
|
test('PaintingContext.pushClipRRect reuses the layer', () {
|
|
_testPaintingContextLayerReuse<ClipRRectLayer>((PaintingContextCallback painter, PaintingContext context, Offset offset, Layer? oldLayer) {
|
|
return context.pushClipRRect(true, offset, Rect.zero, RRect.fromRectAndRadius(Rect.zero, const Radius.circular(1.0)), painter, oldLayer: oldLayer as ClipRRectLayer?);
|
|
});
|
|
});
|
|
|
|
test('PaintingContext.pushClipPath reuses the layer', () {
|
|
_testPaintingContextLayerReuse<ClipPathLayer>((PaintingContextCallback painter, PaintingContext context, Offset offset, Layer? oldLayer) {
|
|
return context.pushClipPath(true, offset, Rect.zero, Path(), painter, oldLayer: oldLayer as ClipPathLayer?);
|
|
});
|
|
});
|
|
|
|
test('PaintingContext.pushColorFilter reuses the layer', () {
|
|
_testPaintingContextLayerReuse<ColorFilterLayer>((PaintingContextCallback painter, PaintingContext context, Offset offset, Layer? oldLayer) {
|
|
return context.pushColorFilter(offset, const ColorFilter.mode(Color.fromRGBO(0, 0, 0, 1.0), BlendMode.clear), painter, oldLayer: oldLayer as ColorFilterLayer?);
|
|
});
|
|
});
|
|
|
|
test('PaintingContext.pushTransform reuses the layer', () {
|
|
_testPaintingContextLayerReuse<TransformLayer>((PaintingContextCallback painter, PaintingContext context, Offset offset, Layer? oldLayer) {
|
|
return context.pushTransform(true, offset, Matrix4.identity(), painter, oldLayer: oldLayer as TransformLayer?);
|
|
});
|
|
});
|
|
|
|
test('PaintingContext.pushOpacity reuses the layer', () {
|
|
_testPaintingContextLayerReuse<OpacityLayer>((PaintingContextCallback painter, PaintingContext context, Offset offset, Layer? oldLayer) {
|
|
return context.pushOpacity(offset, 100, painter, oldLayer: oldLayer as OpacityLayer?);
|
|
});
|
|
});
|
|
|
|
test('RenderObject.dispose sets debugDisposed to true', () {
|
|
final TestRenderObject renderObject = TestRenderObject();
|
|
expect(renderObject.debugDisposed, false);
|
|
renderObject.dispose();
|
|
expect(renderObject.debugDisposed, true);
|
|
expect(renderObject.toStringShort(), contains('DISPOSED'));
|
|
});
|
|
|
|
test('Leader layer can switch to a different render object within one frame', () {
|
|
List<FlutterErrorDetails?>? caughtErrors;
|
|
TestRenderingFlutterBinding.instance.onErrors = () {
|
|
caughtErrors = TestRenderingFlutterBinding.instance.takeAllFlutterErrorDetails().toList();
|
|
};
|
|
|
|
final LayerLink layerLink = LayerLink();
|
|
// renderObject1 paints the leader layer first.
|
|
final LeaderLayerRenderObject renderObject1 = LeaderLayerRenderObject();
|
|
renderObject1.layerLink = layerLink;
|
|
renderObject1.attach(TestRenderingFlutterBinding.instance.pipelineOwner);
|
|
final OffsetLayer rootLayer1 = OffsetLayer();
|
|
rootLayer1.attach(renderObject1);
|
|
renderObject1.scheduleInitialPaint(rootLayer1);
|
|
renderObject1.layout(const BoxConstraints.tightForFinite());
|
|
|
|
final LeaderLayerRenderObject renderObject2 = LeaderLayerRenderObject();
|
|
final OffsetLayer rootLayer2 = OffsetLayer();
|
|
rootLayer2.attach(renderObject2);
|
|
renderObject2.attach(TestRenderingFlutterBinding.instance.pipelineOwner);
|
|
renderObject2.scheduleInitialPaint(rootLayer2);
|
|
renderObject2.layout(const BoxConstraints.tightForFinite());
|
|
TestRenderingFlutterBinding.instance.pumpCompleteFrame();
|
|
|
|
// Swap the layer link to renderObject2 in the same frame
|
|
renderObject1.layerLink = null;
|
|
renderObject1.markNeedsPaint();
|
|
renderObject2.layerLink = layerLink;
|
|
renderObject2.markNeedsPaint();
|
|
TestRenderingFlutterBinding.instance.pumpCompleteFrame();
|
|
|
|
// Swap the layer link to renderObject1 in the same frame
|
|
renderObject1.layerLink = layerLink;
|
|
renderObject1.markNeedsPaint();
|
|
renderObject2.layerLink = null;
|
|
renderObject2.markNeedsPaint();
|
|
TestRenderingFlutterBinding.instance.pumpCompleteFrame();
|
|
|
|
TestRenderingFlutterBinding.instance.onErrors = null;
|
|
expect(caughtErrors, isNull);
|
|
});
|
|
|
|
test('Leader layer append to two render objects does crash', () {
|
|
List<FlutterErrorDetails?>? caughtErrors;
|
|
TestRenderingFlutterBinding.instance.onErrors = () {
|
|
caughtErrors = TestRenderingFlutterBinding.instance.takeAllFlutterErrorDetails().toList();
|
|
};
|
|
final LayerLink layerLink = LayerLink();
|
|
// renderObject1 paints the leader layer first.
|
|
final LeaderLayerRenderObject renderObject1 = LeaderLayerRenderObject();
|
|
renderObject1.layerLink = layerLink;
|
|
renderObject1.attach(TestRenderingFlutterBinding.instance.pipelineOwner);
|
|
final OffsetLayer rootLayer1 = OffsetLayer();
|
|
rootLayer1.attach(renderObject1);
|
|
renderObject1.scheduleInitialPaint(rootLayer1);
|
|
renderObject1.layout(const BoxConstraints.tightForFinite());
|
|
|
|
final LeaderLayerRenderObject renderObject2 = LeaderLayerRenderObject();
|
|
renderObject2.layerLink = layerLink;
|
|
final OffsetLayer rootLayer2 = OffsetLayer();
|
|
rootLayer2.attach(renderObject2);
|
|
renderObject2.attach(TestRenderingFlutterBinding.instance.pipelineOwner);
|
|
renderObject2.scheduleInitialPaint(rootLayer2);
|
|
renderObject2.layout(const BoxConstraints.tightForFinite());
|
|
TestRenderingFlutterBinding.instance.pumpCompleteFrame();
|
|
|
|
TestRenderingFlutterBinding.instance.onErrors = null;
|
|
expect(caughtErrors!.isNotEmpty, isTrue);
|
|
});
|
|
|
|
test('RenderObject.dispose null the layer on repaint boundaries', () {
|
|
final TestRenderObject renderObject = TestRenderObject(allowPaintBounds: true);
|
|
// Force a layer to get set.
|
|
renderObject.isRepaintBoundary = true;
|
|
PaintingContext.repaintCompositedChild(renderObject, debugAlsoPaintedParent: true);
|
|
expect(renderObject.debugLayer, isA<OffsetLayer>());
|
|
|
|
// Dispose with repaint boundary still being true.
|
|
renderObject.dispose();
|
|
expect(renderObject.debugLayer, null);
|
|
});
|
|
|
|
test('RenderObject.dispose nulls the layer on non-repaint boundaries', () {
|
|
final TestRenderObject renderObject = TestRenderObject(allowPaintBounds: true);
|
|
// Force a layer to get set.
|
|
renderObject.isRepaintBoundary = true;
|
|
PaintingContext.repaintCompositedChild(renderObject, debugAlsoPaintedParent: true);
|
|
|
|
// Dispose with repaint boundary being false.
|
|
renderObject.isRepaintBoundary = false;
|
|
renderObject.dispose();
|
|
expect(renderObject.debugLayer, null);
|
|
});
|
|
|
|
test('Add composition callback works', () {
|
|
final ContainerLayer root = ContainerLayer();
|
|
final PaintingContext context = PaintingContext(root, Rect.zero);
|
|
bool calledBack = false;
|
|
final TestObservingRenderObject object = TestObservingRenderObject((Layer layer) {
|
|
expect(layer, root);
|
|
calledBack = true;
|
|
});
|
|
|
|
expect(calledBack, false);
|
|
object.paint(context, Offset.zero);
|
|
expect(calledBack, false);
|
|
|
|
root.buildScene(ui.SceneBuilder()).dispose();
|
|
expect(calledBack, true);
|
|
});
|
|
|
|
test('ContainerParentDataMixin asserts parentData type', () {
|
|
final TestRenderObject renderObject = TestRenderObjectWithoutSetupParentData();
|
|
final TestRenderObject child = TestRenderObject();
|
|
expect(
|
|
() => renderObject.add(child),
|
|
throwsA(
|
|
isA<AssertionError>().having(
|
|
(AssertionError error) => error.toString(),
|
|
'description',
|
|
contains(
|
|
'A child of TestRenderObjectWithoutSetupParentData has parentData of type ParentData, '
|
|
'which does not conform to TestRenderObjectParentData. Class using ContainerRenderObjectMixin '
|
|
'should override setupParentData() to set parentData to type TestRenderObjectParentData.'
|
|
),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
|
|
class TestObservingRenderObject extends RenderBox {
|
|
TestObservingRenderObject(this.callback);
|
|
|
|
final CompositionCallback callback;
|
|
|
|
@override
|
|
bool get sizedByParent => true;
|
|
|
|
@override
|
|
void paint(PaintingContext context, Offset offset) {
|
|
context.addCompositionCallback(callback);
|
|
}
|
|
}
|
|
// Tests the create-update cycle by pumping two frames. The first frame has no
|
|
// prior layer and forces the painting context to create a new one. The second
|
|
// frame reuses the layer painted on the first frame.
|
|
void _testPaintingContextLayerReuse<L extends Layer>(_LayerTestPaintCallback painter) {
|
|
final _TestCustomLayerBox box = _TestCustomLayerBox(painter);
|
|
layout(box, phase: EnginePhase.paint);
|
|
|
|
// Force a repaint. Otherwise, pumpFrame is a noop.
|
|
box.markNeedsPaint();
|
|
pumpFrame(phase: EnginePhase.paint);
|
|
expect(box.paintedLayers, hasLength(2));
|
|
expect(box.paintedLayers[0], isA<L>());
|
|
expect(box.paintedLayers[0], same(box.paintedLayers[1]));
|
|
}
|
|
|
|
typedef _LayerTestPaintCallback = Layer? Function(PaintingContextCallback painter, PaintingContext context, Offset offset, Layer? oldLayer);
|
|
|
|
class _TestCustomLayerBox extends RenderBox {
|
|
_TestCustomLayerBox(this.painter);
|
|
|
|
final _LayerTestPaintCallback painter;
|
|
final List<Layer> paintedLayers = <Layer>[];
|
|
|
|
@override
|
|
bool get isRepaintBoundary => false;
|
|
|
|
@override
|
|
void performLayout() {
|
|
size = constraints.smallest;
|
|
}
|
|
|
|
@override
|
|
void paint(PaintingContext context, Offset offset) {
|
|
final Layer paintedLayer = painter(super.paint, context, offset, layer)!;
|
|
paintedLayers.add(paintedLayer);
|
|
layer = paintedLayer as ContainerLayer;
|
|
}
|
|
}
|
|
|
|
class TestParentData extends ParentData with ContainerParentDataMixin<RenderBox> { }
|
|
|
|
class TestRenderObjectParentData extends ParentData with ContainerParentDataMixin<TestRenderObject> { }
|
|
class TestRenderObject extends RenderObject with ContainerRenderObjectMixin<TestRenderObject, TestRenderObjectParentData> {
|
|
TestRenderObject({this.allowPaintBounds = false});
|
|
|
|
final bool allowPaintBounds;
|
|
|
|
@override
|
|
bool isRepaintBoundary = false;
|
|
|
|
@override
|
|
void debugAssertDoesMeetConstraints() { }
|
|
|
|
@override
|
|
Rect get paintBounds {
|
|
assert(allowPaintBounds); // For some tests, this should not get called.
|
|
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() { }
|
|
|
|
@override
|
|
void performResize() { }
|
|
|
|
@override
|
|
Rect get semanticBounds => const Rect.fromLTWH(0.0, 0.0, 10.0, 20.0);
|
|
|
|
int describeSemanticsConfigurationCallCount = 0;
|
|
|
|
@override
|
|
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
|
super.describeSemanticsConfiguration(config);
|
|
config.isSemanticBoundary = true;
|
|
describeSemanticsConfigurationCallCount++;
|
|
}
|
|
}
|
|
|
|
class TestRenderObjectWithoutSetupParentData extends TestRenderObject {
|
|
@override
|
|
void setupParentData(RenderObject child) {
|
|
// Use a mismatched parent data type.
|
|
if (child.parentData is! ParentData) {
|
|
child.parentData = ParentData();
|
|
}
|
|
}
|
|
}
|
|
|
|
class LeaderLayerRenderObject extends RenderObject {
|
|
LeaderLayerRenderObject();
|
|
|
|
LayerLink? layerLink;
|
|
|
|
@override
|
|
bool isRepaintBoundary = true;
|
|
|
|
@override
|
|
void debugAssertDoesMeetConstraints() { }
|
|
|
|
@override
|
|
Rect get paintBounds {
|
|
return Rect.zero;
|
|
}
|
|
|
|
@override
|
|
void paint(PaintingContext context, Offset offset) {
|
|
if (layerLink != null) {
|
|
context.pushLayer(LeaderLayer(link: layerLink!), super.paint, offset);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void performLayout() { }
|
|
|
|
@override
|
|
void performResize() { }
|
|
|
|
@override
|
|
Rect get semanticBounds => const Rect.fromLTWH(0.0, 0.0, 10.0, 20.0);
|
|
}
|
|
|
|
class TestThrowingRenderObject extends RenderObject {
|
|
@override
|
|
void performLayout() {
|
|
throw FlutterError('TestThrowingRenderObject does not support performLayout.');
|
|
}
|
|
|
|
@override
|
|
void debugAssertDoesMeetConstraints() { }
|
|
|
|
@override
|
|
Rect get paintBounds {
|
|
assert(false); // The test shouldn't call this.
|
|
return Rect.zero;
|
|
}
|
|
|
|
@override
|
|
void performResize() { }
|
|
|
|
@override
|
|
Rect get semanticBounds {
|
|
assert(false); // The test shouldn't call this.
|
|
return Rect.zero;
|
|
}
|
|
}
|