Skip to content

Commit

Permalink
Allow taking a screenshot of a dirty tree
Browse files Browse the repository at this point in the history
  • Loading branch information
passsy committed Nov 28, 2024
1 parent 4e9de97 commit a27d356
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 133 deletions.
2 changes: 0 additions & 2 deletions lib/src/screenshot/screenshot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@ ui.Image _captureImageSync(Element element) {
// ignore: unnecessary_cast
renderObject = renderObject.parent! as RenderObject;
}
assert(!renderObject.debugNeedsPaint);

final OffsetLayer layer = renderObject.debugLayer! as OffsetLayer;
final ui.Image image = layer.toImageSync(renderObject.paintBounds);
Expand Down Expand Up @@ -338,7 +337,6 @@ Future<ui.Image> _captureImage(Element element) async {
// ignore: unnecessary_cast
renderObject = renderObject.parent! as RenderObject;
}
assert(!renderObject.debugNeedsPaint);

final OffsetLayer layer = renderObject.debugLayer! as OffsetLayer;
final ui.Image image = await layer.toImage(renderObject.paintBounds);
Expand Down
149 changes: 18 additions & 131 deletions test/screenshot/screenshot_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -267,42 +267,27 @@ void main() {
testWidgets('Take screenshot of dirty tree', (tester) async {
tester.view.physicalSize = const Size(210, 210);
tester.view.devicePixelRatio = 1.0;
const red = Color(0xffff0000);
const orange = Color(0xffff7f00);
await tester.pumpWidget(_RainbowColorBox());

await loadAppFonts(); // causes system notification that a repaint is needed
// but pump is not called, causing all RenderParagraphs to be dirty
throw "TODO implement";

final shot1 = await takeScreenshot();
expect(shot1.file.existsSync(), isTrue);
final redPixelCoverage = await percentageOfPixelsWithColor(shot1.file, red);
expect(redPixelCoverage, greaterThan(0.9));

await tester.tap(spot<_RainbowColorBox>().finder); // tap, do not pump
final state =
spot<_RainbowColorBox>().snapshotState<_RainbowColorBoxState>();
expect(state.color, orange); // changed, but not yet rendered

final shot2 =
await takeScreenshot(selector: spot<_ColoredBoxRenderObjectWidget>());
expect(shot2.file.existsSync(), isTrue);
expect(
await percentageOfPixelsWithColor(shot2.file, orange),
0.0, // not orange
);
expect(
await percentageOfPixelsWithColor(shot2.file, red),
greaterThan(0.9), // still red
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
// Banner listens to PaintingBinding.instance.systemFonts and requests repaint (markNeedsPaint)
child: Banner(
message: 'Hello',
location: BannerLocation.topEnd,
child: Container(color: Colors.white),
),
),
);

await tester.pump();
final shot3 = await takeScreenshot();
expect(
await percentageOfPixelsWithColor(shot3.file, orange),
greaterThan(0.9), // now orange
);
// FontLoader.load triggers PaintBinding.instance.systemFonts listeners
await loadAppFonts();
final renderObject =
spot<Banner>().spot<CustomPaint>().snapshotRenderObject();
expect(renderObject.debugNeedsPaint, isTrue);

// When elements are dirty, taking a screenshot should still work
await takeScreenshot();
});

group('Annotate Screenshot test', () {
Expand Down Expand Up @@ -505,101 +490,3 @@ int _currentLineNumber() {
// parts[parts.length - 1] is the column number
return parts[parts.length - 2].toInt();
}

class _RainbowColorBox extends StatefulWidget {
const _RainbowColorBox();

@override
State<_RainbowColorBox> createState() => _RainbowColorBoxState();
}

class _RainbowColorBoxState extends State<_RainbowColorBox> {
int _index = 0;

Color get color => rainbowColors[_index % rainbowColors.length];

@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onTap: () {
setState(() {
_index = _index + 1;
});
},
child: SizedBox(
height: 200,
width: 200,
child: _ColoredBoxRenderObjectWidget(
color: color,
),
),
),
);
}
}

class _ColoredBoxRenderObjectWidget extends LeafRenderObjectWidget {
const _ColoredBoxRenderObjectWidget({required this.color});

final Color color;

@override
RenderObject createRenderObject(BuildContext context) {
return _DirtyRenderObject()..color = color;
}

@override
void updateRenderObject(
BuildContext context,
_DirtyRenderObject renderObject,
) {
renderObject.color = color;
}
}

class _DirtyRenderObject extends RenderBox {
late Color _color;

Color get color => _color;

set color(Color value) {
_color = value;
// make it dirty
markNeedsPaint();
}

@override
void performLayout() {
size = constraints.biggest;
}

@override
bool hitTestSelf(Offset position) {
if (position.dx < 0 || position.dx > size.width) {
return false;
}
if (position.dy < 0 || position.dy > size.height) {
return false;
}
return true;
}

@override
void paint(PaintingContext context, Offset offset) {
context.canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
Paint()..color = color,
);
}
}

const rainbowColors = [
Color(0xffff0000),
Color(0xffff7f00),
Color(0xffffff00),
Color(0xff00ff00),
Color(0xff0000ff),
Color(0xff4b0082),
Color(0xff9400d3),
];

0 comments on commit a27d356

Please sign in to comment.