perf(mornye): share chrome backdrops and scale blur to clarity

This commit is contained in:
zarzet committed 2026-10-02 01:24:26 +07:00
1 parent 718e13f24e
commit e200879d14
4 files changed
+186 -71

No files matched your search

+45 -40
View File
@@ -283,50 +283,55 @@ class _MornyeBottomBarState extends ConsumerState<MornyeBottomBar>
return Stack(
clipBehavior: Clip.none,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
if (hasPlayer || amount > 0)
Align(
alignment: Alignment.bottomCenter,
// With no track, introducing the row at full height would
// make the bar jump taller on the first animation frame.
heightFactor: hasPlayer ? 1 : amount,
child: Padding(
padding: EdgeInsets.only(
bottom: hasPlayer
? tabGap + (8 - tabGap) * amount
: 8,
// Only the non-overlapping player and tab surfaces share a
// backdrop. Moving side capsules stay outside this group: they
// cross the tab bar while folding and need separate samples.
BackdropGroup(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (hasPlayer || amount > 0)
Align(
alignment: Alignment.bottomCenter,
// With no track, introducing the row at full height would
// make the bar jump taller on the first animation frame.
heightFactor: hasPlayer ? 1 : amount,
child: Padding(
padding: EdgeInsets.only(
bottom: hasPlayer
? tabGap + (8 - tabGap) * amount
: 8,
),
child: Row(
children: [
SizedBox(
width: 60 * amount,
height: hasPlayer ? 48 + 4 * amount : 52,
),
Expanded(child: player),
SizedBox(
width: 60 * amount,
height: hasPlayer ? 48 + 4 * amount : 52,
),
],
),
),
child: Row(
children: [
SizedBox(
width: 60 * amount,
height: hasPlayer ? 48 + 4 * amount : 52,
),
Expanded(child: player),
SizedBox(
width: 60 * amount,
height: hasPlayer ? 48 + 4 * amount : 52,
),
],
),
ClipRect(
child: Align(
alignment: Alignment.bottomCenter,
heightFactor: 1 - amount,
child: IgnorePointer(
ignoring: amount > 0.5,
child: ExcludeSemantics(
excluding: amount > 0.5,
child: amount == 0 ? fullTabs : foldingTabs,
),
),
),
),
ClipRect(
child: Align(
alignment: Alignment.bottomCenter,
heightFactor: 1 - amount,
child: IgnorePointer(
ignoring: amount > 0.5,
child: ExcludeSemantics(
excluding: amount > 0.5,
child: amount == 0 ? fullTabs : foldingTabs,
),
),
),
),
],
],
),
),
movingIcon(leading: true, surface: leadingSurface),
movingIcon(leading: false, surface: searchSurface),
+53 -31
View File
@@ -357,36 +357,42 @@ class MornyeGlass extends StatelessWidget {
/// Keep the capsule legible independently of the shader renderer. The liquid
/// lens adds refraction above this frosted base, never above bare page text.
class _MornyeGlassSurface extends StatelessWidget {
static final _backdropBlur = ImageFilter.blur(sigmaX: 18, sigmaY: 18);
class _MornyeGlassSurface extends ConsumerWidget {
// Reuse a small set of filters rather than constructing a new blur for every
// slider frame. Nearly opaque glass needs a much smaller sampling radius.
static final _backdropBlurs = [
for (final sigma in [4.0, 8.0, 12.0, 18.0])
ImageFilter.blur(sigmaX: sigma, sigmaY: sigma),
];
// Reduce luminance without flattening the backdrop's color differences.
// Subtract 35% of Rec.709 luma from every channel: white is bounded at 0.65,
// while colored artwork stays visible without per-frame pixel readback.
static final _darkBackdrop = ImageFilter.compose(
outer: const ColorFilter.matrix([
0.92559,
-0.25032,
-0.02527,
0,
0,
-0.07441,
0.74968,
-0.02527,
0,
0,
-0.07441,
-0.25032,
0.97473,
0,
0,
0,
0,
0,
1,
0,
]),
inner: _backdropBlur,
);
static const _darken = ColorFilter.matrix([
0.92559,
-0.25032,
-0.02527,
0,
0,
-0.07441,
0.74968,
-0.02527,
0,
0,
-0.07441,
-0.25032,
0.97473,
0,
0,
0,
0,
0,
1,
0,
]);
static final _darkBackdrops = [
for (final blur in _backdropBlurs)
ImageFilter.compose(outer: _darken, inner: blur),
];
const _MornyeGlassSurface({
required this.child,
@@ -413,12 +419,21 @@ class _MornyeGlassSurface extends StatelessWidget {
final bool samplesBackdrop;
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final scheme = Theme.of(context).colorScheme;
final clarity = MornyeTheme.glassClarityOf(context);
final useBlur =
blurEnabled && clarity > 0 && !MediaQuery.highContrastOf(context);
final dark = scheme.brightness == Brightness.dark;
final frosted =
ref.watch(mornyeGlassLevelProvider) == MornyeGlassLevel.frosted;
final filterIndex = clarity <= 0.25
? 0
: frosted || clarity <= 0.50
? 1
: clarity <= 0.75
? 2
: 3;
final shape = BorderRadius.vertical(
top: firstInGroup ? Radius.circular(radius) : Radius.zero,
bottom: lastInGroup ? Radius.circular(radius) : Radius.zero,
@@ -434,6 +449,10 @@ class _MornyeGlassSurface extends StatelessWidget {
baseTint,
)
: scheme.surfaceContainerHigh;
// On mid-range GPUs an almost opaque tint hides the blur anyway. Keep its
// translucency and rim without paying for a backdrop pass at low clarity.
final sampleBackdrop =
useBlur && samplesBackdrop && (!frosted || tint.a < 0.95);
final surface = DecoratedBox(
decoration: BoxDecoration(color: tint, borderRadius: shape),
child: child,
@@ -463,10 +482,13 @@ class _MornyeGlassSurface extends StatelessWidget {
),
child: ClipRRect(
borderRadius: shape,
child: useBlur && samplesBackdrop
? BackdropFilter(
child: sampleBackdrop
? BackdropFilter.grouped(
filter:
backdropFilter ?? (dark ? _darkBackdrop : _backdropBlur),
backdropFilter ??
(dark
? _darkBackdrops[filterIndex]
: _backdropBlurs[filterIndex]),
child: surface,
)
: surface,