-
Notifications
You must be signed in to change notification settings - Fork 28.7k
[framework] simplify raster widget, rename, combine painters #109485
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change). If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like how this change simplifies usage of the widget.
|
||
/// Controls how the [SnapshotWidget] paints its child via the [SnapshotWidgetController]. | ||
enum SnapshotMode { | ||
/// the child is snapshotted, but only if all descendants can be snapshotted. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the -> The
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
/// snapshot will not be used and the child will be rendered as normal. | ||
permissive, | ||
|
||
/// the child is snapshotted, but only if all descendants can be snapshotted. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
/// the child is snapshotted, but only if all descendants can be snapshotted. | ||
/// | ||
/// If there is a platform view in the children of a raster widget, the | ||
/// snapshot will not be used and the child will be rendered as normal. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
normal as an SnapshotMode.normal
(which seems to indicate that this would throw) or is this a different normal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, no - not sure how else to phrase this - the normal way as in ... super.paint
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of calling it normal maybe say things will be rendered like they would if no SnapshotWidget were to be used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but we still use the painter
} | ||
|
||
/// Whether a snapshot of this child widget is painted in its place. | ||
bool get enabled => _enabled; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When first reading through this I got confused what the "enabled" state is. Maybe rename this to allowSnapshotting
to avoid confusion? It implies that if that to true, snapshotting may happen if other parameters line up, but it is not guaranteed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
/// be expensive or that cannot rely on raster caching. For example, scale and | ||
/// skew animations are often expensive to perform on complex children, as are | ||
/// blurs. For a short animation, a widget that contains these expensive effects | ||
/// can be replaced with a snapshot of it self and manipulated instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it self -> itself? (I think)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
// Now invoke clear and the raster is re-generated. | ||
controller.clear(); | ||
await tester.pumpAndSettle(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
await tester.pump()
should do here? This all is expected to happen in the next frame, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
} | ||
|
||
class RenderTestPlatformView extends RenderProxyBox { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove blank line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -392,10 +401,8 @@ class _ZoomExitTransition extends StatefulWidget { | |||
} | |||
|
|||
class _ZoomExitTransitionState extends State<_ZoomExitTransition> with _ZoomTransitionBase { | |||
late _ZoomExitTransitionDelegate delegate; | |||
|
|||
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this TODO still apply?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -285,10 +283,8 @@ class _ZoomEnterTransition extends StatefulWidget { | |||
} | |||
|
|||
class _ZoomEnterTransitionState extends State<_ZoomEnterTransition> with _ZoomTransitionBase { | |||
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this TODO still apply?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From benchmarking, we don't want to use snapshotting on the web due to the single frame
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add that context as a comment here to explain why we don't want this on the web?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
/// defaults to [SnapshotMode.normal] which will throw an exception if a | ||
/// platform view is encountered. | ||
/// | ||
/// * This widget is not supported on the HTML backend of Flutter for the web. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
document what "not supported" means. Is this widget always a no-op on the web? Are you never allowed to instantiate this on the web? What happens if you do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I arrived at this question because the ZoomTransition appears to unconditionally be using this widget. So, some modes of this widget must be supported on the web??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure the best way to phrase this, I tried some updates
Do you have benchmark numbers to check whether this approach does anything to the performance of the page transition? Or does this have the same performance characteristics? |
This comment was marked as outdated.
This comment was marked as outdated.
I backed out the widget changes, I couldn't claw back enough performance to justify removing the delegate |
This pull request has been changed to a draft. The currently pending flutter-gold status will not be able to resolve until a new commit is pushed or the change is marked ready for review again. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
@goderbauer I added some questions on documentation updates |
@@ -285,10 +283,8 @@ class _ZoomEnterTransition extends StatefulWidget { | |||
} | |||
|
|||
class _ZoomEnterTransitionState extends State<_ZoomEnterTransition> with _ZoomTransitionBase { | |||
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add that context as a comment here to explain why we don't want this on the web?
), | ||
), | ||
)); | ||
await tester.pump(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need this pump? The snapshot widget should be doing all its magic in one frame, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(same for the extra pumps below)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
/// the child is snapshotted, but only if all descendants can be snapshotted. | ||
/// | ||
/// If there is a platform view in the children of a raster widget, the | ||
/// snapshot will not be used and the child will be rendered as normal. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of calling it normal maybe say things will be rendered like they would if no SnapshotWidget were to be used?
enum SnapshotMode { | ||
/// The child is snapshotted, but only if all descendants can be snapshotted. | ||
/// | ||
/// If there is a platform view in the children of a raster widget, the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: its not called raster widget anymore..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
} | ||
|
||
@override | ||
// ignore: library_private_types_in_public_api |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Elsewhere, we just declare the parameter as RenderObject and cast inside the method to the private type. Any reason not to do that here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, TIL
return; | ||
} | ||
_devicePixelRatio = value; | ||
markNeedsPaint(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to throw away the rastered image to force re-rasterization with the new paint?
Also tiny optimization: do we need a paint if _childRaster is currently null and not used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
markNeedsPaint(); | ||
} | ||
|
||
/// The painter used to paint the child snapshot or child widgets. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: indentation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
if (mode == SnapshotMode.normal) { | ||
throw FlutterError('SnapshotWidget used with a child that contains a PlatformView.'); | ||
} | ||
_disableSnapshotAttempt = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There doesn't appear to be a path that ever resets this to false? Is that expected? There are no conditions where we can try snapshotting again?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps on detach we can reset the value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and also on reset
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or just on notification from the controller
/// The [offset] and [size] are the location and dimensions of the render object. | ||
void paint(PaintingContext context, Offset offset, Size size, PaintingContextCallback painter); | ||
|
||
/// Called whenever a new instance of the raster widget delegate class is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: What's a raster widget? My name is snapshot widget!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
final SnapshotPainter oldPainter = painter; | ||
oldPainter.removeListener(markNeedsPaint); | ||
_painter = value; | ||
if (painter.shouldRepaint(oldPainter)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if painter and oldPainter have different runtimetypes you probably always want to repaint without asking shouldRepaint
similarly to how CustomPaint does it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Golden file changes are available for triage from new commit, Click here to view. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
I've updated the PR based on the last round of comments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
expect(painter.count, 1); | ||
}, skip: kIsWeb); // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689 | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove one blank line?
expect(tester.layers.last, isA<PictureLayer>()); | ||
}, skip: kIsWeb); // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689 | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
} | ||
|
||
class TestPainter2 extends TestPainter { | ||
@override |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: indentation
Greatly simplifies the raster widget, renaming to snapshot widget.
While working to use the raster widget in more animated examples, I found that generally it was still difficult to switch between the custom painter like behavior and regular widgets - especially if the child may have a platform view, but also because we may need to pause the animation on a rotation or partial opacity. If an animation is paused, then we should of course render the regular widget children so they are interactable. Thus we need to add a painter and fallback for almost every usage, which doesn't feel like its working well enough.
While using regular widgets instead of the painter still turned out to be a performance regression, we can atleast simplify the delegates and rendering modes so that it is more obvious what a "normal" usage of the snapshot widget is