Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
5b5a5b82
Unverified
Commit
5b5a5b82
authored
Sep 04, 2018
by
Jonah Williams
Committed by
GitHub
Sep 04, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Revert "Enable taking screenshots of arbitrary RenderObjects from a running a… (#20637)" (#21395)
This reverts commit
3306fc10
.
parent
3306fc10
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
43 additions
and
1378 deletions
+43
-1378
goldens.version
bin/internal/goldens.version
+1
-1
layer.dart
packages/flutter/lib/src/rendering/layer.dart
+1
-7
object.dart
packages/flutter/lib/src/rendering/object.dart
+16
-83
widget_inspector.dart
packages/flutter/lib/src/widgets/widget_inspector.dart
+2
-663
service_extensions_test.dart
...ages/flutter/test/foundation/service_extensions_test.dart
+1
-1
proxy_box_test.dart
packages/flutter/test/rendering/proxy_box_test.dart
+4
-35
widget_inspector_test.dart
packages/flutter/test/widgets/widget_inspector_test.dart
+0
-554
matchers.dart
packages/flutter_test/lib/src/matchers.dart
+18
-34
No files found.
bin/internal/goldens.version
View file @
5b5a5b82
da615501c1032cb803b2a5623b07d7f4834d9640
1a999092d10a22bc700214b257cd4890c5800078
packages/flutter/lib/src/rendering/layer.dart
View file @
5b5a5b82
...
...
@@ -50,8 +50,6 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
Layer
_previousSibling
;
/// Removes this layer from its parent layer's child list.
///
/// This has no effect if the layer's parent is already null.
@mustCallSuper
void
remove
()
{
parent
?.
_removeChild
(
this
);
...
...
@@ -590,11 +588,7 @@ class OffsetLayer extends ContainerLayer {
assert
(
bounds
!=
null
);
assert
(
pixelRatio
!=
null
);
final
ui
.
SceneBuilder
builder
=
new
ui
.
SceneBuilder
();
final
Matrix4
transform
=
new
Matrix4
.
translationValues
(
(-
bounds
.
left
-
offset
.
dx
)
*
pixelRatio
,
(-
bounds
.
top
-
offset
.
dy
)
*
pixelRatio
,
0.0
,
);
final
Matrix4
transform
=
new
Matrix4
.
translationValues
(
bounds
.
left
-
offset
.
dx
,
bounds
.
top
-
offset
.
dy
,
0.0
);
transform
.
scale
(
pixelRatio
,
pixelRatio
);
builder
.
pushTransform
(
transform
.
storage
);
addToScene
(
builder
,
Offset
.
zero
);
...
...
packages/flutter/lib/src/rendering/object.dart
View file @
5b5a5b82
...
...
@@ -60,13 +60,7 @@ typedef void PaintingContextCallback(PaintingContext context, Offset offset);
/// New [PaintingContext] objects are created automatically when using
/// [PaintingContext.repaintCompositedChild] and [pushLayer].
class
PaintingContext
extends
ClipContext
{
/// Creates a painting context.
///
/// Typically only called by [PaintingContext.repaintCompositedChild]
/// and [pushLayer].
@protected
PaintingContext
(
this
.
_containerLayer
,
this
.
estimatedBounds
)
PaintingContext
.
_
(
this
.
_containerLayer
,
this
.
estimatedBounds
)
:
assert
(
_containerLayer
!=
null
),
assert
(
estimatedBounds
!=
null
);
...
...
@@ -92,19 +86,8 @@ class PaintingContext extends ClipContext {
/// * [RenderObject.isRepaintBoundary], which determines if a [RenderObject]
/// has a composited layer.
static
void
repaintCompositedChild
(
RenderObject
child
,
{
bool
debugAlsoPaintedParent
=
false
})
{
assert
(
child
.
_needsPaint
);
_repaintCompositedChild
(
child
,
debugAlsoPaintedParent:
debugAlsoPaintedParent
,
);
}
static
void
_repaintCompositedChild
(
RenderObject
child
,
{
bool
debugAlsoPaintedParent
=
false
,
PaintingContext
childContext
,
})
{
assert
(
child
.
isRepaintBoundary
);
assert
(
child
.
_needsPaint
);
assert
(()
{
// register the call for RepaintBoundary metrics
child
.
debugRegisterRepaintBoundaryPaint
(
...
...
@@ -124,32 +107,9 @@ class PaintingContext extends ClipContext {
child
.
_layer
.
debugCreator
=
child
.
debugCreator
??
child
.
runtimeType
;
return
true
;
}());
childContext
??=
new
PaintingContext
(
child
.
_layer
,
child
.
paintBounds
);
final
PaintingContext
childContext
=
new
PaintingContext
.
_
(
child
.
_layer
,
child
.
paintBounds
);
child
.
_paintWithContext
(
childContext
,
Offset
.
zero
);
childContext
.
stopRecordingIfNeeded
();
}
/// In debug mode, repaint the given render object using a custom painting
/// context that can record the results of the painting operation in addition
/// to performing the regular paint of the child.
///
/// See also:
///
/// * [repaintCompositedChild], for repainting a composited child without
/// instrumentation.
static
void
debugInstrumentRepaintCompositedChild
(
RenderObject
child
,
{
bool
debugAlsoPaintedParent
=
false
,
@required
PaintingContext
customContext
,
})
{
assert
(()
{
_repaintCompositedChild
(
child
,
debugAlsoPaintedParent:
debugAlsoPaintedParent
,
childContext:
customContext
,
);
return
true
;
}());
childContext
.
_stopRecordingIfNeeded
();
}
/// Paint a child [RenderObject].
...
...
@@ -165,7 +125,7 @@ class PaintingContext extends ClipContext {
}());
if
(
child
.
isRepaintBoundary
)
{
stopRecordingIfNeeded
();
_
stopRecordingIfNeeded
();
_compositeChild
(
child
,
offset
);
}
else
{
child
.
_paintWithContext
(
this
,
offset
);
...
...
@@ -199,20 +159,10 @@ class PaintingContext extends ClipContext {
}());
}
child
.
_layer
.
offset
=
offset
;
appendLayer
(
child
.
_layer
);
_
appendLayer
(
child
.
_layer
);
}
/// Adds a layer to the recording requiring that the recording is already
/// stopped.
///
/// Do not call this function directly: call [addLayer] or [pushLayer]
/// instead. This function is called internally when all layers not
/// generated from the [canvas] are added.
///
/// Subclasses that need to customize how layers are added should override
/// this method.
@protected
void
appendLayer
(
Layer
layer
)
{
void
_appendLayer
(
Layer
layer
)
{
assert
(!
_isRecording
);
layer
.
remove
();
_containerLayer
.
append
(
layer
);
...
...
@@ -260,19 +210,7 @@ class PaintingContext extends ClipContext {
_containerLayer
.
append
(
_currentLayer
);
}
/// Stop recording to a canvas if recording has started.
///
/// Do not call this function directly: functions in this class will call
/// this method as needed. This function is called internally to ensure that
/// recording is stopped before adding layers or finalizing the results of a
/// paint.
///
/// Subclasses that need to customize how recording to a canvas is performed
/// should override this method to save the results of the custom canvas
/// recordings.
@protected
@mustCallSuper
void
stopRecordingIfNeeded
()
{
void
_stopRecordingIfNeeded
()
{
if
(!
_isRecording
)
return
;
assert
(()
{
...
...
@@ -333,8 +271,8 @@ class PaintingContext extends ClipContext {
/// * [pushLayer], for adding a layer and using its canvas to paint with that
/// layer.
void
addLayer
(
Layer
layer
)
{
stopRecordingIfNeeded
();
appendLayer
(
layer
);
_
stopRecordingIfNeeded
();
_
appendLayer
(
layer
);
}
/// Appends the given layer to the recording, and calls the `painter` callback
...
...
@@ -357,21 +295,15 @@ class PaintingContext extends ClipContext {
/// See also:
///
/// * [addLayer], for pushing a leaf layer whose canvas is not used.
void
pushLayer
(
Container
Layer
childLayer
,
PaintingContextCallback
painter
,
Offset
offset
,
{
Rect
childPaintBounds
})
{
void
pushLayer
(
Layer
childLayer
,
PaintingContextCallback
painter
,
Offset
offset
,
{
Rect
childPaintBounds
})
{
assert
(!
childLayer
.
attached
);
assert
(
childLayer
.
parent
==
null
);
assert
(
painter
!=
null
);
stopRecordingIfNeeded
();
appendLayer
(
childLayer
);
final
PaintingContext
childContext
=
createChildContext
(
childLayer
,
childPaintBounds
??
estimatedBounds
);
_
stopRecordingIfNeeded
();
_
appendLayer
(
childLayer
);
final
PaintingContext
childContext
=
new
PaintingContext
.
_
(
childLayer
,
childPaintBounds
??
estimatedBounds
);
painter
(
childContext
,
offset
);
childContext
.
stopRecordingIfNeeded
();
}
/// Creates a compatible painting context to paint onto [childLayer].
@protected
PaintingContext
createChildContext
(
ContainerLayer
childLayer
,
Rect
bounds
)
{
return
new
PaintingContext
(
childLayer
,
bounds
);
childContext
.
_stopRecordingIfNeeded
();
}
/// Clip further painting using a rectangle.
...
...
@@ -2104,6 +2036,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
Rect
get
paintBounds
;
/// Override this method to paint debugging information.
@protected
void
debugPaint
(
PaintingContext
context
,
Offset
offset
)
{
}
/// Paint this render object into the given context at the given offset.
...
...
packages/flutter/lib/src/widgets/widget_inspector.dart
View file @
5b5a5b82
...
...
@@ -7,26 +7,13 @@ import 'dart:collection';
import
'dart:convert'
;
import
'dart:developer'
as
developer
;
import
'dart:math'
as
math
;
import
'dart:typed_data'
;
import
'dart:ui'
as
ui
show
window
,
ClipOp
,
Image
,
ImageByteFormat
,
Paragraph
,
Picture
,
PictureRecorder
,
PointMode
,
SceneBuilder
,
Vertices
;
import
'dart:ui'
show
Canvas
,
Offset
;
import
'dart:ui'
as
ui
show
window
,
Picture
,
SceneBuilder
,
PictureRecorder
;
import
'dart:ui'
show
Offset
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/painting.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'package:vector_math/vector_math_64.dart'
;
import
'app.dart'
;
import
'basic.dart'
;
...
...
@@ -44,556 +31,6 @@ typedef void _RegisterServiceExtensionCallback({
@required
ServiceExtensionCallback
callback
});
/// A layer that mimics the behavior of another layer.
///
/// A proxy layer is used for cases where a layer needs to be placed into
/// multiple trees of layers.
class
_ProxyLayer
extends
Layer
{
final
Layer
_layer
;
_ProxyLayer
(
this
.
_layer
);
@override
void
addToScene
(
ui
.
SceneBuilder
builder
,
Offset
layerOffset
)
{
_layer
.
addToScene
(
builder
,
layerOffset
);
}
@override
S
find
<
S
>(
Offset
regionOffset
)
=>
_layer
.
find
(
regionOffset
);
}
/// A [Canvas] that multicasts all method calls to a main canvas and a
/// secondary screenshot canvas so that a screenshot can be recorded at the same
/// time as performing a normal paint.
class
_MulticastCanvas
implements
Canvas
{
final
Canvas
_main
;
final
Canvas
_screenshot
;
_MulticastCanvas
({
@required
Canvas
main
,
@required
Canvas
screenshot
,
})
:
assert
(
main
!=
null
),
assert
(
screenshot
!=
null
),
_main
=
main
,
_screenshot
=
screenshot
;
@override
void
clipPath
(
Path
path
,
{
bool
doAntiAlias
=
true
})
{
_main
.
clipPath
(
path
,
doAntiAlias:
doAntiAlias
);
_screenshot
.
clipPath
(
path
,
doAntiAlias:
doAntiAlias
);
}
@override
void
clipRRect
(
RRect
rrect
,
{
bool
doAntiAlias
=
true
})
{
_main
.
clipRRect
(
rrect
,
doAntiAlias:
doAntiAlias
);
_screenshot
.
clipRRect
(
rrect
,
doAntiAlias:
doAntiAlias
);
}
@override
void
clipRect
(
Rect
rect
,
{
ui
.
ClipOp
clipOp
=
ui
.
ClipOp
.
intersect
,
bool
doAntiAlias
=
true
})
{
_main
.
clipRect
(
rect
,
clipOp:
clipOp
,
doAntiAlias:
doAntiAlias
);
_screenshot
.
clipRect
(
rect
,
clipOp:
clipOp
,
doAntiAlias:
doAntiAlias
);
}
@override
void
drawArc
(
Rect
rect
,
double
startAngle
,
double
sweepAngle
,
bool
useCenter
,
Paint
paint
)
{
_main
.
drawArc
(
rect
,
startAngle
,
sweepAngle
,
useCenter
,
paint
);
_screenshot
.
drawArc
(
rect
,
startAngle
,
sweepAngle
,
useCenter
,
paint
);
}
@override
void
drawAtlas
(
ui
.
Image
atlas
,
List
<
RSTransform
>
transforms
,
List
<
Rect
>
rects
,
List
<
Color
>
colors
,
BlendMode
blendMode
,
Rect
cullRect
,
Paint
paint
)
{
_main
.
drawAtlas
(
atlas
,
transforms
,
rects
,
colors
,
blendMode
,
cullRect
,
paint
);
_screenshot
.
drawAtlas
(
atlas
,
transforms
,
rects
,
colors
,
blendMode
,
cullRect
,
paint
);
}
@override
void
drawCircle
(
Offset
c
,
double
radius
,
Paint
paint
)
{
_main
.
drawCircle
(
c
,
radius
,
paint
);
_screenshot
.
drawCircle
(
c
,
radius
,
paint
);
}
@override
void
drawColor
(
Color
color
,
BlendMode
blendMode
)
{
_main
.
drawColor
(
color
,
blendMode
);
_screenshot
.
drawColor
(
color
,
blendMode
);
}
@override
void
drawDRRect
(
RRect
outer
,
RRect
inner
,
Paint
paint
)
{
_main
.
drawDRRect
(
outer
,
inner
,
paint
);
_screenshot
.
drawDRRect
(
outer
,
inner
,
paint
);
}
@override
void
drawImage
(
ui
.
Image
image
,
Offset
p
,
Paint
paint
)
{
_main
.
drawImage
(
image
,
p
,
paint
);
_screenshot
.
drawImage
(
image
,
p
,
paint
);
}
@override
void
drawImageNine
(
ui
.
Image
image
,
Rect
center
,
Rect
dst
,
Paint
paint
)
{
_main
.
drawImageNine
(
image
,
center
,
dst
,
paint
);
_screenshot
.
drawImageNine
(
image
,
center
,
dst
,
paint
);
}
@override
void
drawImageRect
(
ui
.
Image
image
,
Rect
src
,
Rect
dst
,
Paint
paint
)
{
_main
.
drawImageRect
(
image
,
src
,
dst
,
paint
);
_screenshot
.
drawImageRect
(
image
,
src
,
dst
,
paint
);
}
@override
void
drawLine
(
Offset
p1
,
Offset
p2
,
Paint
paint
)
{
_main
.
drawLine
(
p1
,
p2
,
paint
);
_screenshot
.
drawLine
(
p1
,
p2
,
paint
);
}
@override
void
drawOval
(
Rect
rect
,
Paint
paint
)
{
_main
.
drawOval
(
rect
,
paint
);
_screenshot
.
drawOval
(
rect
,
paint
);
}
@override
void
drawPaint
(
Paint
paint
)
{
_main
.
drawPaint
(
paint
);
_screenshot
.
drawPaint
(
paint
);
}
@override
void
drawParagraph
(
ui
.
Paragraph
paragraph
,
Offset
offset
)
{
_main
.
drawParagraph
(
paragraph
,
offset
);
_screenshot
.
drawParagraph
(
paragraph
,
offset
);
}
@override
void
drawPath
(
Path
path
,
Paint
paint
)
{
_main
.
drawPath
(
path
,
paint
);
_screenshot
.
drawPath
(
path
,
paint
);
}
@override
void
drawPicture
(
ui
.
Picture
picture
)
{
_main
.
drawPicture
(
picture
);
_screenshot
.
drawPicture
(
picture
);
}
@override
void
drawPoints
(
ui
.
PointMode
pointMode
,
List
<
Offset
>
points
,
Paint
paint
)
{
_main
.
drawPoints
(
pointMode
,
points
,
paint
);
_screenshot
.
drawPoints
(
pointMode
,
points
,
paint
);
}
@override
void
drawRRect
(
RRect
rrect
,
Paint
paint
)
{
_main
.
drawRRect
(
rrect
,
paint
);
_screenshot
.
drawRRect
(
rrect
,
paint
);
}
@override
void
drawRawAtlas
(
ui
.
Image
atlas
,
Float32List
rstTransforms
,
Float32List
rects
,
Int32List
colors
,
BlendMode
blendMode
,
Rect
cullRect
,
Paint
paint
)
{
_main
.
drawRawAtlas
(
atlas
,
rstTransforms
,
rects
,
colors
,
blendMode
,
cullRect
,
paint
);
_screenshot
.
drawRawAtlas
(
atlas
,
rstTransforms
,
rects
,
colors
,
blendMode
,
cullRect
,
paint
);
}
@override
void
drawRawPoints
(
ui
.
PointMode
pointMode
,
Float32List
points
,
Paint
paint
)
{
_main
.
drawRawPoints
(
pointMode
,
points
,
paint
);
_screenshot
.
drawRawPoints
(
pointMode
,
points
,
paint
);
}
@override
void
drawRect
(
Rect
rect
,
Paint
paint
)
{
_main
.
drawRect
(
rect
,
paint
);
_screenshot
.
drawRect
(
rect
,
paint
);
}
@override
void
drawShadow
(
Path
path
,
Color
color
,
double
elevation
,
bool
transparentOccluder
)
{
_main
.
drawShadow
(
path
,
color
,
elevation
,
transparentOccluder
);
_screenshot
.
drawShadow
(
path
,
color
,
elevation
,
transparentOccluder
);
}
@override
void
drawVertices
(
ui
.
Vertices
vertices
,
BlendMode
blendMode
,
Paint
paint
)
{
_main
.
drawVertices
(
vertices
,
blendMode
,
paint
);
_screenshot
.
drawVertices
(
vertices
,
blendMode
,
paint
);
}
@override
int
getSaveCount
()
{
// The main canvas is used instead of the screenshot canvas as the main
// canvas is guaranteed to be consistent with the canvas expected by the
// normal paint pipeline so any logic depending on getSaveCount() will
// behave the same as for the regular paint pipeline.
return
_main
.
getSaveCount
();
}
@override
void
restore
()
{
_main
.
restore
();
_screenshot
.
restore
();
}
@override
void
rotate
(
double
radians
)
{
_main
.
rotate
(
radians
);
_screenshot
.
rotate
(
radians
);
}
@override
void
save
()
{
_main
.
save
();
_screenshot
.
save
();
}
@override
void
saveLayer
(
Rect
bounds
,
Paint
paint
)
{
_main
.
saveLayer
(
bounds
,
paint
);
_screenshot
.
saveLayer
(
bounds
,
paint
);
}
@override
void
scale
(
double
sx
,
[
double
sy
])
{
_main
.
scale
(
sx
,
sy
);
_screenshot
.
scale
(
sx
,
sy
);
}
@override
void
skew
(
double
sx
,
double
sy
)
{
_main
.
skew
(
sx
,
sy
);
_screenshot
.
skew
(
sx
,
sy
);
}
@override
void
transform
(
Float64List
matrix4
)
{
_main
.
transform
(
matrix4
);
_screenshot
.
transform
(
matrix4
);
}
@override
void
translate
(
double
dx
,
double
dy
)
{
_main
.
translate
(
dx
,
dy
);
_screenshot
.
translate
(
dx
,
dy
);
}
}
Rect
_calculateSubtreeBoundsHelper
(
RenderObject
object
,
Matrix4
transform
)
{
Rect
bounds
=
MatrixUtils
.
transformRect
(
transform
,
object
.
semanticBounds
);
object
.
visitChildren
((
RenderObject
child
)
{
final
Matrix4
childTransform
=
transform
.
clone
();
object
.
applyPaintTransform
(
child
,
childTransform
);
Rect
childBounds
=
_calculateSubtreeBoundsHelper
(
child
,
childTransform
);
final
Rect
paintClip
=
object
.
describeApproximatePaintClip
(
child
);
if
(
paintClip
!=
null
)
{
final
Rect
transformedPaintClip
=
MatrixUtils
.
transformRect
(
transform
,
paintClip
,
);
childBounds
=
childBounds
.
intersect
(
transformedPaintClip
);
}
if
(
childBounds
.
isFinite
&&
!
childBounds
.
isEmpty
)
{
bounds
=
bounds
.
isEmpty
?
childBounds
:
bounds
.
expandToInclude
(
childBounds
);
}
});
return
bounds
;
}
/// Calculate bounds for a render object and all of its descendants.
Rect
_calculateSubtreeBounds
(
RenderObject
object
)
{
return
_calculateSubtreeBoundsHelper
(
object
,
new
Matrix4
.
identity
());
}
/// A layer that omits its own offset when adding children to the scene so that
/// screenshots render to the scene in the local coordinate system of the layer.
class
_ScreenshotContainerLayer
extends
OffsetLayer
{
@override
void
addToScene
(
ui
.
SceneBuilder
builder
,
Offset
layerOffset
)
{
addChildrenToScene
(
builder
,
layerOffset
);
}
}
/// Data shared between nested [_ScreenshotPaintingContext] objects recording
/// a screenshot.
class
_ScreenshotData
{
_ScreenshotData
({
@required
this
.
target
,
})
:
assert
(
target
!=
null
),
containerLayer
=
new
_ScreenshotContainerLayer
();
/// Target to take a screenshot of.
final
RenderObject
target
;
/// Root of the layer tree containing the screenshot.
final
OffsetLayer
containerLayer
;
/// Whether the screenshot target has already been found in the render tree.
bool
foundTarget
=
false
;
/// Whether paint operations should record to the screenshot.
///
/// At least one of [includeInScreenshot] and [includeInRegularContext] must
/// be true.
bool
includeInScreenshot
=
false
;
/// Whether paint operations should record to the regular context.
///
/// This should only be set to false before paint operations that should only
/// apply to the screenshot such rendering debug information about the
/// [target].
///
/// At least one of [includeInScreenshot] and [includeInRegularContext] must
/// be true.
bool
includeInRegularContext
=
true
;
/// Offset of the screenshot corresponding to the offset [target] was given as
/// part of the regular paint.
Offset
get
screenshotOffset
{
assert
(
foundTarget
);
return
containerLayer
.
offset
;
}
set
screenshotOffset
(
Offset
offset
)
{
containerLayer
.
offset
=
offset
;
}
}
/// A place to paint to build screenshots of [RenderObject]s.
///
/// Requires that the render objects have already painted successfully as part
/// of the regular rendering pipeline.
/// This painting context behaves the same as standard [PaintingContext] with
/// instrumentation added to compute a screenshot of a specified [RenderObject]
/// added. To correctly mimic the behavor of the regular rendering pipeline, the
/// full subtree of the first [RepaintBoundary] ancestor of the specified
/// [RenderObject] will also be rendered rather than just the subtree of the
/// render object.
class
_ScreenshotPaintingContext
extends
PaintingContext
{
_ScreenshotPaintingContext
({
@required
ContainerLayer
containerLayer
,
@required
Rect
estimatedBounds
,
@required
_ScreenshotData
screenshotData
,
})
:
_data
=
screenshotData
,
super
(
containerLayer
,
estimatedBounds
);
final
_ScreenshotData
_data
;
// Recording state
PictureLayer
_screenshotCurrentLayer
;
ui
.
PictureRecorder
_screenshotRecorder
;
Canvas
_screenshotCanvas
;
_MulticastCanvas
_multicastCanvas
;
@override
Canvas
get
canvas
{
if
(
_data
.
includeInScreenshot
)
{
if
(
_screenshotCanvas
==
null
)
{
_startRecordingScreenshot
();
}
assert
(
_screenshotCanvas
!=
null
);
return
_data
.
includeInRegularContext
?
_multicastCanvas
:
_screenshotCanvas
;
}
else
{
assert
(
_data
.
includeInRegularContext
);
return
super
.
canvas
;
}
}
bool
get
_isScreenshotRecording
{
final
bool
hasScreenshotCanvas
=
_screenshotCanvas
!=
null
;
assert
(()
{
if
(
hasScreenshotCanvas
)
{
assert
(
_screenshotCurrentLayer
!=
null
);
assert
(
_screenshotRecorder
!=
null
);
assert
(
_screenshotCanvas
!=
null
);
}
else
{
assert
(
_screenshotCurrentLayer
==
null
);
assert
(
_screenshotRecorder
==
null
);
assert
(
_screenshotCanvas
==
null
);
}
return
true
;
}());
return
hasScreenshotCanvas
;
}
void
_startRecordingScreenshot
()
{
assert
(
_data
.
includeInScreenshot
);
assert
(!
_isScreenshotRecording
);
_screenshotCurrentLayer
=
new
PictureLayer
(
estimatedBounds
);
_screenshotRecorder
=
new
ui
.
PictureRecorder
();
_screenshotCanvas
=
new
Canvas
(
_screenshotRecorder
);
_data
.
containerLayer
.
append
(
_screenshotCurrentLayer
);
if
(
_data
.
includeInRegularContext
)
{
_multicastCanvas
=
new
_MulticastCanvas
(
main:
super
.
canvas
,
screenshot:
_screenshotCanvas
,
);
}
else
{
_multicastCanvas
=
null
;
}
}
@override
void
stopRecordingIfNeeded
()
{
super
.
stopRecordingIfNeeded
();
_stopRecordingScreenshotIfNeeded
();
}
void
_stopRecordingScreenshotIfNeeded
()
{
if
(!
_isScreenshotRecording
)
return
;
// There is no need to ever draw repaint rainbows as part of the screenshot.
_screenshotCurrentLayer
.
picture
=
_screenshotRecorder
.
endRecording
();
_screenshotCurrentLayer
=
null
;
_screenshotRecorder
=
null
;
_multicastCanvas
=
null
;
_screenshotCanvas
=
null
;
}
@override
void
appendLayer
(
Layer
layer
)
{
if
(
_data
.
includeInRegularContext
)
{
super
.
appendLayer
(
layer
);
if
(
_data
.
includeInScreenshot
)
{
assert
(!
_isScreenshotRecording
);
// We must use a proxy layer here as the layer is already attached to
// the regular layer tree.
_data
.
containerLayer
.
append
(
new
_ProxyLayer
(
layer
));
}
}
else
{
// Only record to the screenshot.
assert
(!
_isScreenshotRecording
);
assert
(
_data
.
includeInScreenshot
);
layer
.
remove
();
_data
.
containerLayer
.
append
(
layer
);
return
;
}
}
@override
PaintingContext
createChildContext
(
ContainerLayer
childLayer
,
Rect
bounds
)
{
if
(
_data
.
foundTarget
)
{
// We have already found the screenshotTarget in the layer tree
// so we can optimize and use a standard PaintingContext.
return
super
.
createChildContext
(
childLayer
,
bounds
);
}
else
{
return
new
_ScreenshotPaintingContext
(
containerLayer:
childLayer
,
estimatedBounds:
bounds
,
screenshotData:
_data
,
);
}
}
@override
void
paintChild
(
RenderObject
child
,
Offset
offset
)
{
final
bool
isScreenshotTarget
=
identical
(
child
,
_data
.
target
);
if
(
isScreenshotTarget
)
{
assert
(!
_data
.
includeInScreenshot
);
assert
(!
_data
.
foundTarget
);
_data
.
foundTarget
=
true
;
_data
.
screenshotOffset
=
offset
;
_data
.
includeInScreenshot
=
true
;
}
super
.
paintChild
(
child
,
offset
);
if
(
isScreenshotTarget
)
{
_stopRecordingScreenshotIfNeeded
();
_data
.
includeInScreenshot
=
false
;
}
}
/// Captures an image of the current state of [renderObject] and its children.
///
/// The returned [ui.Image] has uncompressed raw RGBA bytes, will be offset
/// by the top-left corner of [renderBounds], and have dimensions equal to the
/// size of [renderBounds] multiplied by [pixelRatio].
///
/// To use [toImage], the render object must have gone through the paint phase
/// (i.e. [debugNeedsPaint] must be false).
///
/// The [pixelRatio] describes the scale between the logical pixels and the
/// size of the output image. It is independent of the
/// [window.devicePixelRatio] for the device, so specifying 1.0 (the default)
/// will give you a 1:1 mapping between logical pixels and the output pixels
// / in the image.
///
/// The [debugPaint] argument specifies whether the image should include the
/// output of [RenderObject.debugPaint] for [renderObject] with
/// [debugPaintSizeEnabled] set to `true`. Debug paint information is not
/// included for the children of [renderObject] so that it is clear precisely
/// which object the debug paint information references.
///
/// See also:
///
/// * [RenderRepaintBoundary.toImage] for a similar API for [RenderObject]s
/// that are repaint boundaries that can be used outside of the inspector.
/// * [OffsetLayer.toImage] for a similar API at the layer level.
/// * [dart:ui.Scene.toImage] for more information about the image returned.
static
Future
<
ui
.
Image
>
toImage
(
RenderObject
renderObject
,
Rect
renderBounds
,
{
double
pixelRatio
=
1.0
,
bool
debugPaint
=
false
,
})
{
RenderObject
repaintBoundary
=
renderObject
;
while
(
repaintBoundary
!=
null
&&
!
repaintBoundary
.
isRepaintBoundary
)
{
repaintBoundary
=
repaintBoundary
.
parent
;
}
assert
(
repaintBoundary
!=
null
);
final
_ScreenshotData
data
=
new
_ScreenshotData
(
target:
renderObject
);
final
_ScreenshotPaintingContext
context
=
new
_ScreenshotPaintingContext
(
containerLayer:
repaintBoundary
.
debugLayer
,
estimatedBounds:
repaintBoundary
.
paintBounds
,
screenshotData:
data
,
);
if
(
identical
(
renderObject
,
repaintBoundary
))
{
// Painting the existing repaint boundary to the screenshot is sufficient.
// We don't just take a direct screenshot of the repaint boundary as we
// want to capture debugPaint information as well.
data
.
containerLayer
.
append
(
new
_ProxyLayer
(
repaintBoundary
.
layer
));
data
.
foundTarget
=
true
;
data
.
screenshotOffset
=
repaintBoundary
.
layer
.
offset
;
}
else
{
// Repaint everything under the repaint boundary.
// We call debugInstrumentRepaintCompositedChild instead of paintChild as
// we need to force everything under the repaint boundary to repaint.
PaintingContext
.
debugInstrumentRepaintCompositedChild
(
repaintBoundary
,
customContext:
context
,
);
}
// The check that debugPaintSizeEnabled is false exists to ensure we only
// call debugPaint when it wasn't already called.
if
(
debugPaint
&&
!
debugPaintSizeEnabled
)
{
data
.
includeInRegularContext
=
false
;
// Existing recording may be to a canvas that draws to both the normal and
// screenshot canvases.
context
.
stopRecordingIfNeeded
();
assert
(
data
.
foundTarget
);
data
.
includeInScreenshot
=
true
;
debugPaintSizeEnabled
=
true
;
try
{
renderObject
.
debugPaint
(
context
,
data
.
screenshotOffset
);
}
finally
{
debugPaintSizeEnabled
=
false
;
context
.
stopRecordingIfNeeded
();
}
}
// We must build the regular scene before we can build the screenshot
// scene as building the screenshot scene assumes addToScene has already
// been called successfully for all layers in the regular scene.
repaintBoundary
.
layer
.
addToScene
(
new
ui
.
SceneBuilder
(),
Offset
.
zero
);
return
data
.
containerLayer
.
toImage
(
renderBounds
,
pixelRatio:
pixelRatio
);
}
}
/// A class describing a step along a path through a tree of [DiagnosticsNode]
/// objects.
///
...
...
@@ -1027,33 +464,6 @@ class WidgetInspectorService {
name:
'isWidgetCreationTracked'
,
callback:
isWidgetCreationTracked
,
);
registerServiceExtension
(
name:
'screenshot'
,
callback:
(
Map
<
String
,
String
>
parameters
)
async
{
assert
(
parameters
.
containsKey
(
'id'
));
assert
(
parameters
.
containsKey
(
'width'
));
assert
(
parameters
.
containsKey
(
'height'
));
final
ui
.
Image
image
=
await
screenshot
(
toObject
(
parameters
[
'id'
]),
width:
double
.
parse
(
parameters
[
'width'
]),
height:
double
.
parse
(
parameters
[
'height'
]),
margin:
parameters
.
containsKey
(
'margin'
)
?
double
.
parse
(
parameters
[
'margin'
])
:
0.0
,
maxPixelRatio:
parameters
.
containsKey
(
'maxPixelRatio'
)
?
double
.
parse
(
parameters
[
'maxPixelRatio'
])
:
1.0
,
debugPaint:
parameters
[
'debugPaint'
]
==
'true'
,
);
if
(
image
==
null
)
{
return
<
String
,
Object
>{
'result'
:
null
};
}
final
ByteData
byteData
=
await
image
.
toByteData
(
format:
ui
.
ImageByteFormat
.
png
);
return
<
String
,
Object
>{
'result'
:
base64
.
encoder
.
convert
(
new
Uint8List
.
view
(
byteData
.
buffer
)),
};
},
);
}
/// Clear all InspectorService object references.
...
...
@@ -1626,77 +1036,6 @@ class WidgetInspectorService {
return
_safeJsonEncode
(
_getSelectedWidget
(
previousSelectionId
,
groupName
));
}
/// Captures an image of the current state of an [object] that is a
/// [RenderObject] or [Element].
///
/// The returned [ui.Image] has uncompressed raw RGBA bytes and will be scaled
/// to be at most [width] pixels wide and [height] pixels tall. The returned
/// image will never have a scale between logical pixels and the
/// size of the output image larger than maxPixelRatio.
/// [margin] indicates the number of pixels relative to the unscaled size of
/// the [object] to include as a margin to include around the bounds of the
/// [object] in the screenshot. Including a margin can be useful to capture
/// areas that are slightly outside of the normal bounds of an object such as
/// some debug paint information.
@protected
Future
<
ui
.
Image
>
screenshot
(
Object
object
,
{
@required
double
width
,
@required
double
height
,
double
margin
=
0.0
,
double
maxPixelRatio
=
1.0
,
bool
debugPaint
=
false
,
})
async
{
if
(
object
is
!
Element
&&
object
is
!
RenderObject
)
{
return
null
;
}
final
RenderObject
renderObject
=
object
is
Element
?
object
.
renderObject
:
object
;
if
(
renderObject
==
null
||
!
renderObject
.
attached
)
{
return
null
;
}
if
(
renderObject
.
debugNeedsLayout
)
{
final
PipelineOwner
owner
=
renderObject
.
owner
;
assert
(
owner
!=
null
);
assert
(!
owner
.
debugDoingLayout
);
owner
..
flushLayout
()
..
flushCompositingBits
()
..
flushPaint
();
// If we still need layout, then that means that renderObject was skipped
// in the layout phase and therefore can't be painted. It is clearer to
// return null indicating that a screenshot is unavailable than to return
// an empty image.
if
(
renderObject
.
debugNeedsLayout
)
{
return
null
;
}
}
Rect
renderBounds
=
_calculateSubtreeBounds
(
renderObject
);
if
(
margin
!=
0.0
)
{
renderBounds
=
renderBounds
.
inflate
(
margin
);
}
if
(
renderBounds
.
isEmpty
)
{
return
null
;
}
final
double
pixelRatio
=
math
.
min
(
maxPixelRatio
,
math
.
min
(
width
/
renderBounds
.
width
,
height
/
renderBounds
.
height
,
),
);
return
_ScreenshotPaintingContext
.
toImage
(
renderObject
,
renderBounds
,
pixelRatio:
pixelRatio
,
debugPaint:
debugPaint
,
);
}
Map
<
String
,
Object
>
_getSelectedWidget
(
String
previousSelectionId
,
String
groupName
)
{
final
DiagnosticsNode
previousSelection
=
toObject
(
previousSelectionId
);
final
Element
current
=
selection
?.
currentElement
;
...
...
packages/flutter/test/foundation/service_extensions_test.dart
View file @
5b5a5b82
...
...
@@ -512,7 +512,7 @@ void main() {
// If you add a service extension... TEST IT! :-)
// ...then increment this number.
expect
(
binding
.
extensions
.
length
,
3
8
);
expect
(
binding
.
extensions
.
length
,
3
7
);
expect
(
console
,
isEmpty
);
debugPrint
=
debugPrintThrottled
;
...
...
packages/flutter/test/rendering/proxy_box_test.dart
View file @
5b5a5b82
...
...
@@ -185,43 +185,12 @@ void main() {
image
=
await
boundary
.
toImage
();
expect
(
image
.
width
,
equals
(
20
));
expect
(
image
.
height
,
equals
(
20
));
ByteData
data
=
await
image
.
toByteData
();
int
getPixel
(
int
x
,
int
y
)
=>
data
.
getUint32
((
x
+
y
*
image
.
width
)
*
4
);
final
ByteData
data
=
await
image
.
toByteData
();
expect
(
data
.
lengthInBytes
,
equals
(
20
*
20
*
4
));
expect
(
data
.
elementSizeInBytes
,
equals
(
1
));
expect
(
getPixel
(
0
,
0
),
equals
(
0x00000080
));
expect
(
getPixel
(
image
.
width
-
1
,
0
),
equals
(
0xffffffff
));
final
OffsetLayer
layer
=
boundary
.
layer
;
image
=
await
layer
.
toImage
(
Offset
.
zero
&
const
Size
(
20.0
,
20.0
));
expect
(
image
.
width
,
equals
(
20
));
expect
(
image
.
height
,
equals
(
20
));
data
=
await
image
.
toByteData
();
expect
(
getPixel
(
0
,
0
),
equals
(
0x00000080
));
expect
(
getPixel
(
image
.
width
-
1
,
0
),
equals
(
0xffffffff
));
// non-zero offsets.
image
=
await
layer
.
toImage
(
const
Offset
(-
10.0
,
-
10.0
)
&
const
Size
(
30.0
,
30.0
));
expect
(
image
.
width
,
equals
(
30
));
expect
(
image
.
height
,
equals
(
30
));
data
=
await
image
.
toByteData
();
expect
(
getPixel
(
0
,
0
),
equals
(
0x00000000
));
expect
(
getPixel
(
10
,
10
),
equals
(
0x00000080
));
expect
(
getPixel
(
image
.
width
-
1
,
0
),
equals
(
0x00000000
));
expect
(
getPixel
(
image
.
width
-
1
,
10
),
equals
(
0xffffffff
));
// offset combined with a custom pixel ratio.
image
=
await
layer
.
toImage
(
const
Offset
(-
10.0
,
-
10.0
)
&
const
Size
(
30.0
,
30.0
),
pixelRatio:
2.0
);
expect
(
image
.
width
,
equals
(
60
));
expect
(
image
.
height
,
equals
(
60
));
data
=
await
image
.
toByteData
();
expect
(
getPixel
(
0
,
0
),
equals
(
0x00000000
));
expect
(
getPixel
(
20
,
20
),
equals
(
0x00000080
));
expect
(
getPixel
(
image
.
width
-
1
,
0
),
equals
(
0x00000000
));
expect
(
getPixel
(
image
.
width
-
1
,
20
),
equals
(
0xffffffff
));
const
int
stride
=
20
*
4
;
expect
(
data
.
getUint32
(
0
),
equals
(
0x00000080
));
expect
(
data
.
getUint32
(
stride
-
4
),
equals
(
0xffffffff
));
});
test
(
'RenderOpacity does not composite if it is transparent'
,
()
{
...
...
packages/flutter/test/widgets/widget_inspector_test.dart
View file @
5b5a5b82
...
...
@@ -4,8 +4,6 @@
import
'dart:async'
;
import
'dart:convert'
;
import
'dart:io'
show
Platform
;
import
'dart:ui'
as
ui
show
PictureRecorder
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
...
...
@@ -14,80 +12,6 @@ import 'package:flutter_test/flutter_test.dart';
typedef
FutureOr
<
Map
<
String
,
Object
>>
InspectorServiceExtensionCallback
(
Map
<
String
,
String
>
parameters
);
class
RenderRepaintBoundaryWithDebugPaint
extends
RenderRepaintBoundary
{
@override
void
debugPaintSize
(
PaintingContext
context
,
Offset
offset
)
{
super
.
debugPaintSize
(
context
,
offset
);
assert
(()
{
// Draw some debug paint UI interleaving creating layers and drawing
// directly to the context's canvas.
final
Paint
paint
=
new
Paint
()
..
style
=
PaintingStyle
.
stroke
..
strokeWidth
=
1.0
..
color
=
Colors
.
red
;
{
final
PictureLayer
pictureLayer
=
new
PictureLayer
(
Offset
.
zero
&
size
);
final
ui
.
PictureRecorder
recorder
=
new
ui
.
PictureRecorder
();
final
Canvas
pictureCanvas
=
new
Canvas
(
recorder
);
pictureCanvas
.
drawCircle
(
Offset
.
zero
,
20.0
,
paint
);
pictureLayer
.
picture
=
recorder
.
endRecording
();
context
.
addLayer
(
new
OffsetLayer
()
..
offset
=
offset
..
append
(
pictureLayer
),
);
}
context
.
canvas
.
drawLine
(
offset
,
offset
.
translate
(
size
.
width
,
size
.
height
),
paint
,
);
{
final
PictureLayer
pictureLayer
=
new
PictureLayer
(
Offset
.
zero
&
size
);
final
ui
.
PictureRecorder
recorder
=
new
ui
.
PictureRecorder
();
final
Canvas
pictureCanvas
=
new
Canvas
(
recorder
);
pictureCanvas
.
drawCircle
(
const
Offset
(
20.0
,
20.0
),
20.0
,
paint
);
pictureLayer
.
picture
=
recorder
.
endRecording
();
context
.
addLayer
(
new
OffsetLayer
()
..
offset
=
offset
..
append
(
pictureLayer
),
);
}
paint
.
color
=
Colors
.
blue
;
context
.
canvas
.
drawLine
(
offset
,
offset
.
translate
(
size
.
width
*
0.5
,
size
.
height
*
0.5
),
paint
,
);
return
true
;
}());
}
}
class
RepaintBoundaryWithDebugPaint
extends
RepaintBoundary
{
/// Creates a widget that isolates repaints.
const
RepaintBoundaryWithDebugPaint
({
Key
key
,
Widget
child
,
})
:
super
(
key:
key
,
child:
child
);
@override
RenderRepaintBoundary
createRenderObject
(
BuildContext
context
)
{
return
new
RenderRepaintBoundaryWithDebugPaint
();
}
}
int
getChildLayerCount
(
OffsetLayer
layer
)
{
Layer
child
=
layer
.
firstChild
;
int
count
=
0
;
while
(
child
!=
null
)
{
count
++;
child
=
child
.
nextSibling
;
}
return
count
;
}
void
main
(
)
{
TestWidgetInspectorService
.
runTests
();
}
...
...
@@ -1315,483 +1239,5 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
expect
(
service
.
rebuildCount
,
equals
(
2
));
expect
(
WidgetsApp
.
debugShowWidgetInspectorOverride
,
isFalse
);
});
testWidgets
(
'ext.flutter.inspector.screenshot'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
outerContainerKey
=
new
GlobalKey
();
final
GlobalKey
paddingKey
=
new
GlobalKey
();
final
GlobalKey
redContainerKey
=
new
GlobalKey
();
final
GlobalKey
whiteContainerKey
=
new
GlobalKey
();
final
GlobalKey
sizedBoxKey
=
new
GlobalKey
();
// Complex widget tree intended to exercise features such as children
// with rotational transforms and clipping without introducing platform
// specific behavior as text rendering would.
await
tester
.
pumpWidget
(
new
Center
(
child:
new
RepaintBoundaryWithDebugPaint
(
child:
new
Container
(
key:
outerContainerKey
,
color:
Colors
.
white
,
child:
new
Padding
(
key:
paddingKey
,
padding:
const
EdgeInsets
.
all
(
100.0
),
child:
new
SizedBox
(
key:
sizedBoxKey
,
height:
100.0
,
width:
100.0
,
child:
new
Transform
.
rotate
(
angle:
1.0
,
// radians
child:
new
ClipRRect
(
borderRadius:
const
BorderRadius
.
only
(
topLeft:
Radius
.
elliptical
(
10.0
,
20.0
),
topRight:
Radius
.
elliptical
(
5.0
,
30.0
),
bottomLeft:
Radius
.
elliptical
(
2.5
,
12.0
),
bottomRight:
Radius
.
elliptical
(
15.0
,
6.0
),
),
child:
new
Container
(
key:
redContainerKey
,
color:
Colors
.
red
,
child:
new
Container
(
key:
whiteContainerKey
,
color:
Colors
.
white
,
child:
new
RepaintBoundary
(
child:
new
Center
(
child:
new
Container
(
color:
Colors
.
black
,
height:
10.0
,
width:
10.0
,
),
),
),
),
),
),
),
),
),
),
),
),
);
final
Element
repaintBoundary
=
find
.
byType
(
RepaintBoundaryWithDebugPaint
).
evaluate
().
single
;
final
RenderRepaintBoundary
renderObject
=
repaintBoundary
.
renderObject
;
final
OffsetLayer
layer
=
renderObject
.
debugLayer
;
final
int
expectedChildLayerCount
=
getChildLayerCount
(
layer
);
expect
(
expectedChildLayerCount
,
equals
(
2
));
await
expectLater
(
layer
.
toImage
(
renderObject
.
semanticBounds
.
inflate
(
50.0
)),
matchesGoldenFile
(
'inspector.repaint_boundary_margin.png'
),
);
// Regression test for how rendering with a pixel scale other than 1.0
// was handled.
await
expectLater
(
layer
.
toImage
(
renderObject
.
semanticBounds
.
inflate
(
50.0
),
pixelRatio:
0.5
,
),
matchesGoldenFile
(
'inspector.repaint_boundary_margin_small.png'
),
);
await
expectLater
(
layer
.
toImage
(
renderObject
.
semanticBounds
.
inflate
(
50.0
),
pixelRatio:
2.0
,
),
matchesGoldenFile
(
'inspector.repaint_boundary_margin_large.png'
),
);
final
Layer
layerParent
=
layer
.
parent
;
final
Layer
firstChild
=
layer
.
firstChild
;
expect
(
layerParent
,
isNotNull
);
expect
(
firstChild
,
isNotNull
);
await
expectLater
(
service
.
screenshot
(
repaintBoundary
,
width:
300.0
,
height:
300.0
,
),
matchesGoldenFile
(
'inspector.repaint_boundary.png'
),
);
// Verify that taking a screenshot didn't change the layers associated with
// the renderObject.
expect
(
renderObject
.
debugLayer
,
equals
(
layer
));
// Verify that taking a screenshot did not change the number of children
// of the layer.
expect
(
getChildLayerCount
(
layer
),
equals
(
expectedChildLayerCount
));
await
expectLater
(
service
.
screenshot
(
repaintBoundary
,
width:
500.0
,
height:
500.0
,
margin:
50.0
,
),
matchesGoldenFile
(
'inspector.repaint_boundary_margin.png'
),
);
// Verify that taking a screenshot didn't change the layers associated with
// the renderObject.
expect
(
renderObject
.
debugLayer
,
equals
(
layer
));
// Verify that taking a screenshot did not change the number of children
// of the layer.
expect
(
getChildLayerCount
(
layer
),
equals
(
expectedChildLayerCount
));
// Make sure taking a screenshot didn't change the parent of the layer.
expect
(
layer
.
parent
,
equals
(
layerParent
));
await
expectLater
(
service
.
screenshot
(
repaintBoundary
,
width:
300.0
,
height:
300.0
,
debugPaint:
true
,
),
matchesGoldenFile
(
'inspector.repaint_boundary_debugPaint.png'
),
);
// Verify that taking a screenshot with debug paint on did not change
// the number of children the layer has.
expect
(
getChildLayerCount
(
layer
),
equals
(
expectedChildLayerCount
));
// Ensure that creating screenshots including ones with debug paint
// hasn't changed the regular render of the widget.
await
expectLater
(
find
.
byType
(
RepaintBoundaryWithDebugPaint
),
matchesGoldenFile
(
'inspector.repaint_boundary.png'
),
);
expect
(
renderObject
.
debugLayer
,
equals
(
layer
));
expect
(
layer
.
attached
,
isTrue
);
// Full size image
await
expectLater
(
service
.
screenshot
(
find
.
byKey
(
outerContainerKey
).
evaluate
().
single
,
width:
100.0
,
height:
100.0
,
),
matchesGoldenFile
(
'inspector.container.png'
),
);
await
expectLater
(
service
.
screenshot
(
find
.
byKey
(
outerContainerKey
).
evaluate
().
single
,
width:
100.0
,
height:
100.0
,
debugPaint:
true
,
),
matchesGoldenFile
(
'inspector.container_debugPaint.png'
),
);
{
// Verify calling the screenshot method still works if the RenderObject
// needs to be laid out again.
final
RenderObject
container
=
find
.
byKey
(
outerContainerKey
).
evaluate
().
single
.
renderObject
;
container
..
markNeedsLayout
()
..
markNeedsPaint
();
expect
(
container
.
debugNeedsLayout
,
isTrue
);
await
expectLater
(
service
.
screenshot
(
find
.
byKey
(
outerContainerKey
).
evaluate
().
single
,
width:
100.0
,
height:
100.0
,
debugPaint:
true
,
),
matchesGoldenFile
(
'inspector.container_debugPaint.png'
),
);
expect
(
container
.
debugNeedsLayout
,
isFalse
);
}
// Small image
await
expectLater
(
service
.
screenshot
(
find
.
byKey
(
outerContainerKey
).
evaluate
().
single
,
width:
50.0
,
height:
100.0
,
),
matchesGoldenFile
(
'inspector.container_small.png'
),
);
await
expectLater
(
service
.
screenshot
(
find
.
byKey
(
outerContainerKey
).
evaluate
().
single
,
width:
400.0
,
height:
400.0
,
maxPixelRatio:
3.0
,
),
matchesGoldenFile
(
'inspector.container_large.png'
),
);
// This screenshot will show the clip rect debug paint but no other
// debug paint.
await
expectLater
(
service
.
screenshot
(
find
.
byType
(
ClipRRect
).
evaluate
().
single
,
width:
100.0
,
height:
100.0
,
debugPaint:
true
,
),
matchesGoldenFile
(
'inspector.clipRect_debugPaint.png'
),
);
// Add a margin so that the clip icon shows up in the screenshot.
// This golden image is platform dependent due to the clip icon.
await
expectLater
(
service
.
screenshot
(
find
.
byType
(
ClipRRect
).
evaluate
().
single
,
width:
100.0
,
height:
100.0
,
margin:
20.0
,
debugPaint:
true
,
),
matchesGoldenFile
(
'inspector.clipRect_debugPaint_margin.png'
),
skip:
!
Platform
.
isLinux
);
// Test with a very visible debug paint
await
expectLater
(
service
.
screenshot
(
find
.
byKey
(
paddingKey
).
evaluate
().
single
,
width:
300.0
,
height:
300.0
,
debugPaint:
true
,
),
matchesGoldenFile
(
'inspector.padding_debugPaint.png'
),
);
// The bounds for this box crop its rendered content.
await
expectLater
(
service
.
screenshot
(
find
.
byKey
(
sizedBoxKey
).
evaluate
().
single
,
width:
300.0
,
height:
300.0
,
debugPaint:
true
,
),
matchesGoldenFile
(
'inspector.sizedBox_debugPaint.png'
),
);
// Verify that setting a margin includes the previously cropped content.
await
expectLater
(
service
.
screenshot
(
find
.
byKey
(
sizedBoxKey
).
evaluate
().
single
,
width:
300.0
,
height:
300.0
,
margin:
50.0
,
debugPaint:
true
,
),
matchesGoldenFile
(
'inspector.sizedBox_debugPaint_margin.png'
),
);
});
testWidgets
(
'Screenshot of composited transforms - only offsets'
,
(
WidgetTester
tester
)
async
{
// Composited transforms are challenging to take screenshots of as the
// LeaderLayer and FollowerLayer classes used by CompositedTransformTarget
// and CompositedTransformFollower depend on traversing ancestors of the
// layer tree and mutating a [LayerLink] object when attaching layers to
// the tree so that the FollowerLayer knows about the LeaderLayer.
// 1. Finding the correct position for the follower layers requires
// traversing the ancestors of the follow layer to find a common ancestor
// with the leader layer.
// 2. Creating a LeaderLayer and attaching it to a layer tree has side
// effects as the leader layer will attempt to modify the mutable
// LeaderLayer object shared by the LeaderLayer and FollowerLayer.
// These tests verify that screenshots can still be taken and look correct
// when the leader and follower layer are both in the screenshots and when
// only the leader or follower layer is in the screenshot.
final
LayerLink
link
=
new
LayerLink
();
final
GlobalKey
key
=
new
GlobalKey
();
final
GlobalKey
mainStackKey
=
new
GlobalKey
();
final
GlobalKey
transformTargetParent
=
new
GlobalKey
();
final
GlobalKey
stackWithTransformFollower
=
new
GlobalKey
();
await
tester
.
pumpWidget
(
new
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
new
RepaintBoundary
(
child:
new
Stack
(
key:
mainStackKey
,
children:
<
Widget
>[
new
Stack
(
key:
transformTargetParent
,
children:
<
Widget
>[
new
Positioned
(
left:
123.0
,
top:
456.0
,
child:
new
CompositedTransformTarget
(
link:
link
,
child:
new
Container
(
height:
20.0
,
width:
20.0
,
color:
const
Color
.
fromARGB
(
128
,
255
,
0
,
0
)),
),
),
],
),
new
Positioned
(
left:
787.0
,
top:
343.0
,
child:
new
Stack
(
key:
stackWithTransformFollower
,
children:
<
Widget
>[
// Container so we can see how the follower layer was
// transformed relative to its initial location.
new
Container
(
height:
15.0
,
width:
15.0
,
color:
const
Color
.
fromARGB
(
128
,
0
,
0
,
255
)),
new
CompositedTransformFollower
(
link:
link
,
child:
new
Container
(
key:
key
,
height:
10.0
,
width:
10.0
,
color:
const
Color
.
fromARGB
(
128
,
0
,
255
,
0
)),
),
],
),
),
],
),
),
),
);
final
RenderBox
box
=
key
.
currentContext
.
findRenderObject
();
expect
(
box
.
localToGlobal
(
Offset
.
zero
),
const
Offset
(
123.0
,
456.0
));
await
expectLater
(
find
.
byKey
(
mainStackKey
),
matchesGoldenFile
(
'inspector.composited_transform.only_offsets.png'
),
);
await
expectLater
(
WidgetInspectorService
.
instance
.
screenshot
(
find
.
byKey
(
stackWithTransformFollower
).
evaluate
().
first
,
width:
5000.0
,
height:
500.0
,
),
matchesGoldenFile
(
'inspector.composited_transform.only_offsets_follower.png'
),
);
await
expectLater
(
WidgetInspectorService
.
instance
.
screenshot
(
find
.
byType
(
Stack
).
evaluate
().
first
,
width:
300.0
,
height:
300.0
),
matchesGoldenFile
(
'inspector.composited_transform.only_offsets_small.png'
),
);
await
expectLater
(
WidgetInspectorService
.
instance
.
screenshot
(
find
.
byKey
(
transformTargetParent
).
evaluate
().
first
,
width:
500.0
,
height:
500.0
,
),
matchesGoldenFile
(
'inspector.composited_transform.only_offsets_target.png'
),
);
});
testWidgets
(
'Screenshot composited transforms - with rotations'
,
(
WidgetTester
tester
)
async
{
final
LayerLink
link
=
new
LayerLink
();
final
GlobalKey
key1
=
new
GlobalKey
();
final
GlobalKey
key2
=
new
GlobalKey
();
final
GlobalKey
rotate1
=
new
GlobalKey
();
final
GlobalKey
rotate2
=
new
GlobalKey
();
final
GlobalKey
mainStackKey
=
new
GlobalKey
();
final
GlobalKey
stackWithTransformTarget
=
new
GlobalKey
();
final
GlobalKey
stackWithTransformFollower
=
new
GlobalKey
();
await
tester
.
pumpWidget
(
new
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
new
Stack
(
key:
mainStackKey
,
children:
<
Widget
>[
new
Stack
(
key:
stackWithTransformTarget
,
children:
<
Widget
>[
new
Positioned
(
top:
123.0
,
left:
456.0
,
child:
new
Transform
.
rotate
(
key:
rotate1
,
angle:
1.0
,
// radians
child:
new
CompositedTransformTarget
(
link:
link
,
child:
new
Container
(
key:
key1
,
height:
20.0
,
width:
20.0
,
color:
const
Color
.
fromARGB
(
128
,
255
,
0
,
0
)),
),
),
),
],
),
new
Positioned
(
top:
487.0
,
left:
243.0
,
child:
new
Stack
(
key:
stackWithTransformFollower
,
children:
<
Widget
>[
new
Container
(
height:
15.0
,
width:
15.0
,
color:
const
Color
.
fromARGB
(
128
,
0
,
0
,
255
)),
new
Transform
.
rotate
(
key:
rotate2
,
angle:
-
0.3
,
// radians
child:
new
CompositedTransformFollower
(
link:
link
,
child:
new
Container
(
key:
key2
,
height:
10.0
,
width:
10.0
,
color:
const
Color
.
fromARGB
(
128
,
0
,
255
,
0
)),
),
),
],
),
),
],
),
),
);
final
RenderBox
box1
=
key1
.
currentContext
.
findRenderObject
();
final
RenderBox
box2
=
key2
.
currentContext
.
findRenderObject
();
// Snapshot the positions of the two relevant boxes to ensure that taking
// screenshots doesn't impact their positions.
final
Offset
position1
=
box1
.
localToGlobal
(
Offset
.
zero
);
final
Offset
position2
=
box2
.
localToGlobal
(
Offset
.
zero
);
expect
(
position1
.
dx
,
moreOrLessEquals
(
position2
.
dx
));
expect
(
position1
.
dy
,
moreOrLessEquals
(
position2
.
dy
));
// Image of the full scene to use as reference to help validate that the
// screenshots of specific subtrees are reasonable.
await
expectLater
(
find
.
byKey
(
mainStackKey
),
matchesGoldenFile
(
'inspector.composited_transform.with_rotations.png'
),
);
await
expectLater
(
WidgetInspectorService
.
instance
.
screenshot
(
find
.
byKey
(
mainStackKey
).
evaluate
().
first
,
width:
500.0
,
height:
500.0
,
),
matchesGoldenFile
(
'inspector.composited_transform.with_rotations_small.png'
),
);
await
expectLater
(
WidgetInspectorService
.
instance
.
screenshot
(
find
.
byKey
(
stackWithTransformTarget
).
evaluate
().
first
,
width:
500.0
,
height:
500.0
,
),
matchesGoldenFile
(
'inspector.composited_transform.with_rotations_target.png'
),
);
await
expectLater
(
WidgetInspectorService
.
instance
.
screenshot
(
find
.
byKey
(
stackWithTransformFollower
).
evaluate
().
first
,
width:
500.0
,
height:
500.0
,
),
matchesGoldenFile
(
'inspector.composited_transform.with_rotations_follower.png'
),
);
// Make sure taking screenshots hasn't modified the positions of the
// TransformTarget or TransformFollower layers.
expect
(
identical
(
key1
.
currentContext
.
findRenderObject
(),
box1
),
isTrue
);
expect
(
identical
(
key2
.
currentContext
.
findRenderObject
(),
box2
),
isTrue
);
expect
(
box1
.
localToGlobal
(
Offset
.
zero
),
equals
(
position1
));
expect
(
box2
.
localToGlobal
(
Offset
.
zero
),
equals
(
position2
));
});
}
}
packages/flutter_test/lib/src/matchers.dart
View file @
5b5a5b82
...
...
@@ -251,12 +251,8 @@ Matcher isMethodCall(String name, {@required dynamic arguments}) {
Matcher
coversSameAreaAs
(
Path
expectedPath
,
{
@required
Rect
areaToCompare
,
int
sampleSize
=
20
})
=>
new
_CoversSameAreaAs
(
expectedPath
,
areaToCompare:
areaToCompare
,
sampleSize:
sampleSize
);
/// Asserts that a [Finder], [Future<ui.Image>], or [ui.Image] matches the
/// golden image file identified by [key].
///
/// For the case of a [Finder], the [Finder] must match exactly one widget and
/// the rendered image of the first [RepaintBoundary] ancestor of the widget is
/// treated as the image for the widget.
/// Asserts that a [Finder] matches exactly one widget whose rendered image
/// matches the golden image file identified by [key].
///
/// [key] may be either a [Uri] or a [String] representation of a URI.
///
...
...
@@ -268,8 +264,6 @@ Matcher coversSameAreaAs(Path expectedPath, {@required Rect areaToCompare, int s
///
/// ```dart
/// await expectLater(find.text('Save'), matchesGoldenFile('save.png'));
/// await expectLater(image, matchesGoldenFile('save.png'));
/// await expectLater(imageFuture, matchesGoldenFile('save.png'));
/// ```
///
/// See also:
...
...
@@ -1502,17 +1496,6 @@ class _CoversSameAreaAs extends Matcher {
description
.
add
(
'covers expected area and only expected area'
);
}
Future
<
ui
.
Image
>
_captureImage
(
Element
element
)
{
RenderObject
renderObject
=
element
.
renderObject
;
while
(!
renderObject
.
isRepaintBoundary
)
{
renderObject
=
renderObject
.
parent
;
assert
(
renderObject
!=
null
);
}
assert
(!
renderObject
.
debugNeedsPaint
);
final
OffsetLayer
layer
=
renderObject
.
layer
;
return
layer
.
toImage
(
renderObject
.
paintBounds
);
}
class
_MatchesGoldenFile
extends
AsyncMatcher
{
const
_MatchesGoldenFile
(
this
.
key
);
...
...
@@ -1521,22 +1504,23 @@ class _MatchesGoldenFile extends AsyncMatcher {
final
Uri
key
;
@override
Future
<
String
>
matchAsync
(
dynamic
item
)
async
{
Future
<
ui
.
Image
>
imageFuture
;
if
(
item
is
Future
<
ui
.
Image
>)
{
imageFuture
=
item
;
}
else
if
(
item
is
ui
.
Image
)
{
imageFuture
=
new
Future
<
ui
.
Image
>.
value
(
item
);
}
else
{
final
Finder
finder
=
item
;
Future
<
String
>
matchAsync
(
covariant
Finder
finder
)
async
{
final
Iterable
<
Element
>
elements
=
finder
.
evaluate
();
if
(
elements
.
isEmpty
)
{
return
'could not be rendered because no widget was found'
;
}
else
if
(
elements
.
length
>
1
)
{
return
'matched too many widgets'
;
}
imageFuture
=
_captureImage
(
elements
.
single
);
final
Element
element
=
elements
.
single
;
RenderObject
renderObject
=
element
.
renderObject
;
while
(!
renderObject
.
isRepaintBoundary
)
{
renderObject
=
renderObject
.
parent
;
assert
(
renderObject
!=
null
);
}
assert
(!
renderObject
.
debugNeedsPaint
);
final
OffsetLayer
layer
=
renderObject
.
layer
;
final
Future
<
ui
.
Image
>
imageFuture
=
layer
.
toImage
(
renderObject
.
paintBounds
);
final
TestWidgetsFlutterBinding
binding
=
TestWidgetsFlutterBinding
.
ensureInitialized
();
return
binding
.
runAsync
<
String
>(()
async
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment