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 is a second attempt to merge #107269. Currently I've fixed two of the issues:
1. Fixed horizontal scrollview by using a switch statement to consider vertical/horizontal case.
2. Fixed issue of `paintExtent` not being the right extent for painting. Rather using a `scrollExtent` for the main axis length of the decoration box and painting it offsetted by the `scrollOffset`.
3. If the sliver child has inifinite scrollExtent, then we only draw the decoration down to the bottom of the `cacheExtent`. The developer is expected to ensure that the border does not creep up above the cache area.
This PR includes a test that checks that the correct rectangle is drawn at a certain scrollOffset for both the horizontal and vertical case which should be sufficient for checking that `SliverDecoration` works properly now.
Fixes https://github.com/flutter/flutter/issues/107498.
* Update project.pbxproj files to say Flutter rather than Chromium
Also, the templates now have an empty organization so that we don't cause people to give their apps a Flutter copyright.
* Update the copyright notice checker to require a standard notice on all files
* Update copyrights on Dart files. (This was a mechanical commit.)
* Fix weird license headers on Dart files that deviate from our conventions; relicense Shrine.
Some were already marked "The Flutter Authors", not clear why. Their
dates have been normalized. Some were missing the blank line after the
license. Some were randomly different in trivial ways for no apparent
reason (e.g. missing the trailing period).
* Clean up the copyrights in non-Dart files. (Manual edits.)
Also, make sure templates don't have copyrights.
* Fix some more ORGANIZATIONNAMEs
RenderAndroidView is responsible for sizing and displaying an embedded
Android view.
AndroidView is responsible for creating and disposing the Android view
and is using RenderAndroidView to display it.
This allows the scheduler library to depend on the services library
and the painting library to depend on the scheduler library without
the services library having to depend on the scheduler library.
While I was at it I also cleaned up some of the binding logic: the
licenses logic can now be overridden (and the test library does so),
and the image cache can now be overridden as well.
Summary:
- Add `key` field to `SemanticsNode`, while moving key into `foundation` library so it can be used by the render layer.
- Introduce `SemanticsProperties` and move many of the `Semantics` fields into it.
- Introduce `CustomPaintSemantics` - a `SemanticsNode` prototype created by `CustomPainter`.
- Introduce `semanticsBuilder` and `shouldRebuildSemantics` in `CustomerPainter`
**Breaking change**
The default `Semantics` constructor becomes non-const (due to https://github.com/dart-lang/sdk/issues/20962). However, a new `const Semantics.fromProperties` is added that still allowed creating constant `Semantics` widgets ([mailing list announcement](https://groups.google.com/forum/#!topic/flutter-dev/KQXBl2_1sws)).
Fixes https://github.com/flutter/flutter/issues/11791
Fixes https://github.com/flutter/flutter/issues/1666
UnconstrainedBox will allow its child to size itself as if it had no constraints, and then attempt to fit around that object, until its own constraints are exceeded, in which case it will clip and display an overflow warning.
I also factored out DebugOverflowIndicator, which will draw overflow indicators on containers which overflow but aren't expected to.
This will enable both to be RTL'ed.
Also, factor out common border painting code into paintBorder.
Also, make Border paint uniform non-rounded borders using drawRect.
Also, add some documentation about an issue that wasted an hour of my life.
Also, factor out all the border painting code into TableBorder.paint.
The Wrap widget is a layout that places children in a run in order along its
main axis until it runs out of space. At that point, starts placing children in
a new run that is adjacent in the cross axis.
Fixes#8831
This patch adds grid supports to slivers and introduces a ScrollGrid
convenience class for making the common types of scrollable grids.
This patch also deploys ScrollGrid in an example in the Flutter Gallery.
Add SliverList
A SliverList is a linear layout of box children in a viewport that all
have a common, fixed extent along the scroll axis. The layout is similar
to a SliverBlock but more efficient.