This PR adds the framework support for a new iOS-style blur. The new
style, which I call "bounded blur", works by adding parameters to the
blur filter that specify the bounds for the region that the filter
sources pixels from.
As discussed in design doc
[flutter.dev/go/ios-style-blur-support](http://flutter.dev/go/ios-style-blur-support),
it's impossible to pass layout information to filters with the current
`ImageFilter` design. Therefore this PR creates a new class
`ImageFilterConfig`.
This PR also applies bounded blur to `CupertinoPopupSurface`. The
following images show the different looks of a dialog in front of
background with abrupt color changes just outside of the border. Notice
how the abrupt color changes no longer bleed in.
<img width="639" height="411" alt="image"
src="https://github.com/user-attachments/assets/4ceb9620-1056-45c3-b5fa-2ed16d90aace"
/>
<img width="639" height="411" alt="image"
src="https://github.com/user-attachments/assets/abe564f7-ea60-4d07-ad58-063c0e3794a5"
/>
This feature continues to matter for iOS 26, since the liquid glass
design also heavily features blurring.
### API changes
* `BackdropFilter`: Add `filterConfig`
* `RenderBackdropFilter`: Add `filterConfig`. Deprecate `filter`.
* `ImageFilter`: Add `debugShortDescription` (previously private
property `_shortDescription`)
### Demo
The following video compares the effect of a bounded blur and an
unbounded blur.
https://github.com/user-attachments/assets/f715db44-c0a0-4ac8-a163-6b859665b032
<details>
<summary>
Demo source
</summary>
```
// Add to pubspec.yaml:
//
// assets:
// - assets/kalimba.jpg
//
// and download the image from
// ec6f550237/engine/src/flutter/impeller/fixtures/kalimba.jpg
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: BlurEditorApp()));
class ControlPoint extends StatefulWidget {
const ControlPoint({
super.key,
required this.position,
required this.onPanUpdate,
this.radius = 20.0,
});
final Offset position;
final GestureDragUpdateCallback onPanUpdate;
final double radius;
@override
ControlPointState createState() => ControlPointState();
}
class ControlPointState extends State<ControlPoint> {
bool isHovering = false;
@override
Widget build(BuildContext context) {
return Positioned(
left: widget.position.dx - widget.radius,
top: widget.position.dy - widget.radius,
child: MouseRegion(
onEnter: (_) { setState((){ isHovering = true; }); },
onExit: (_) { setState((){ isHovering = false; }); },
cursor: SystemMouseCursors.move,
child: GestureDetector(
onPanUpdate: widget.onPanUpdate,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: widget.radius * 2,
height: widget.radius * 2,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isHovering
? Colors.white.withValues(alpha: 0.8)
: Colors.white.withValues(alpha: 0.4),
border: Border.all(color: Colors.white, width: 2),
boxShadow: [
if (isHovering)
BoxShadow(
color: Colors.white.withValues(alpha: 0.5),
blurRadius: 10,
spreadRadius: 2,
)
],
),
child: const Icon(Icons.drag_indicator, size: 16, color: Colors.black54),
),
),
),
);
}
}
class BlurPanel extends StatelessWidget {
const BlurPanel({super.key, required this.blurRect, required this.bounded});
final Rect blurRect;
final bool bounded;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: Stack(
children: [
Positioned.fill(
child: Image.asset(
'assets/kalimba.jpg',
fit: BoxFit.cover,
),
),
Positioned.fromRect(
rect: blurRect,
child: ClipRect(
child: BackdropFilter(
filterConfig: ImageFilterConfig.blur(
sigmaX: 10, sigmaY: 10, bounded: bounded),
child: Container(),
)),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
bounded ? 'Bounded Blur' : 'Unbounded Blur',
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
],
),
);
}
}
class BlurEditorApp extends StatefulWidget {
const BlurEditorApp({super.key});
@override
State<BlurEditorApp> createState() => _BlurEditorAppState();
}
class _BlurEditorAppState extends State<BlurEditorApp> {
Offset p1 = const Offset(100, 100);
Offset p2 = const Offset(300, 300);
@override
Widget build(BuildContext context) {
final blurRect = Rect.fromPoints(p1, p2);
return Scaffold(
body: Stack(
children: [
Positioned.fill(
child: Row(
children: [
Expanded(
child: BlurPanel(blurRect: blurRect, bounded: true),
),
Expanded(
child: BlurPanel(blurRect: blurRect, bounded: false),
),
],
),
),
ControlPoint(position: p1, onPanUpdate: (details) { setState(() => p1 = details.globalPosition); }),
ControlPoint(position: p2, onPanUpdate: (details) { setState(() => p2 = details.globalPosition); }),
],
),
);
}
}
```
</details>
## Pre-launch Checklist
- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.
If you need help, consider asking for advice on the #hackers-new channel
on [Discord].
**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.
<!-- 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
This auto-formats all *.dart files in the repository outside of the
`engine` subdirectory and enforces that these files stay formatted with
a presubmit check.
**Reviewers:** Please carefully review all the commits except for the
one titled "formatted". The "formatted" commit was auto-generated by
running `dev/tools/format.sh -a -f`. The other commits were hand-crafted
to prepare the repo for the formatting change. I recommend reviewing the
commits one-by-one via the "Commits" tab and avoiding Github's "Files
changed" tab as it will likely slow down your browser because of the
size of this PR.
---------
Co-authored-by: Kate Lovett <katelovett@google.com>
Co-authored-by: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com>
This pull request fixes#143803 by taking advantage of Dart's null-aware operators.
And unlike `switch` expressions ([9 PRs](https://github.com/flutter/flutter/pull/143634) and counting), the Flutter codebase is already fantastic when it comes to null-aware coding. After refactoring the entire repo, all the changes involving `?.` and `??` can fit into a single pull request.
Some render box subclasses have a specific layout contract that is tightly coupled with other render box subclasses (e.g. two private classes in a local project file). In these cases, it is also possible that they use a constraints object that is a subclass of `BoxConstraints`. To allow for this, this change makes the `constraints` argument to `RenderBox.computeDryLayout()` a covariant argument.
For completeness' sake, this updates the other render objects in the rendering package to also use the covariant keyword for this argument.
Fixes https://github.com/flutter/flutter/issues/59413
This relocates `mock_canvas.dart` and `recording_canvas.dart` from `flutter/test/rendering` to `flutter_test`.
The testing functionality afforded by mock_canvas should be available to everyone, not just the framework. :)
mock_canvas.dart needed a bit of cleanup - things like formatting and super parameters.
* Add test for RenderProxyBoxMixin; clarify doc, resolve TODO
The TODO comment suggested this mixin would no longer be needed once
a Dart issue on inherited constructors was fixed:
https://github.com/dart-lang/sdk/issues/31543
That issue is now long since fixed, so I went to go carry out the TODO.
But in doing so, I realized that the mixin's documentation was more
right than the TODO comment: even with that issue fixed, there is a
legitimate use case for this mixin, namely to reuse the implementation
of RenderProxyBox in a class that also inherits from some other base
class. Moreover, searching GitHub I found an example of a library
that makes real use of that capability.
So I think the right resolution is to accept that this separation
is useful and delete the TODO.
Then, add a test with an extremely simplified sketch of that
real-world example. In case someone in the future attempts to
simplify this mixin away, the test will point us at the use case
that would be broken by such a change.
Also remove the only in-tree use of the mixin, which was redundant;
and expand the mixin's documentation to advise about that case.
* Tweak formatting
Co-authored-by: Michael Goderbauer <goderbauer@google.com>
* Cut comments
---------
Co-authored-by: Michael Goderbauer <goderbauer@google.com>
* Redesigns the interface between MouseTracker and RendererBinding&RenderView.
* Simplifies the structure of RenderMouseRegion.
* Extracts the common utility code between mouse_tracker_test and mouse_tracker_cursor_test.