-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
Is there an existing issue for this?
- I have searched the existing issues
- I have read the guide to filing a bug
Steps to reproduce
1.) Using Flutter version - Channel master, 3.13.0-2.0.pre.60, on macOS 13.4.1 22F82 darwin-arm64,
2.) Using package "flutter_inset_box_shadow" which basically just uses a custom BoxShadow and BoxDecoration class. But for simplicity, the code from the package is extracted in a single file as below.
Expected results
With this sample code, Shadows should show inset in the container i.e. showing inwards in the container ( like a pressed button in Neumorphic effect). A similar issue was filed in "Inner Shadows from well known packages not rendering at all with Impeller #124956" and was fixed in "flutter/engine#41332".
But has reappeared now. The sample code should show the following:

Actual results
But the inner shadows are completely gone in this code sample.
If I try to change the offset and blur values and colours of the shadows, the shadows are either completely invisible or garbaged/erroneous.

Code sample
Code sample
import 'package:flutter/material.dart' hide BoxDecoration, BoxShadow;
import 'dart:ui' as ui show lerpDouble;
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart' hide BoxShadow, BoxDecoration;
import 'package:flutter/painting.dart' as painting;
void main() {
runApp(const ExampleApp());
}
const primaryColor = Color(0xFFE0E0E0);
class ExampleApp extends StatelessWidget {
const ExampleApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Example',
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: primaryColor,
body: Center(
child: Container(
width: 150,
height: 150,
decoration: MyBoxDecoration(
borderRadius: BorderRadius.circular(50),
color: primaryColor,
boxShadow: const [
MyBoxShadow(
offset: Offset(-20, -20),
blurRadius: 60,
color: Colors.white,
inset: true,
),
MyBoxShadow(
offset: Offset(20, 20),
blurRadius: 60,
color: Color(0xFFBEBEBE),
inset: true,
),
],
),
),
),
),
);
}
}
class MyBoxShadow extends painting.BoxShadow {
const MyBoxShadow({
Color color = const Color(0xFF000000),
Offset offset = Offset.zero,
double blurRadius = 0.0,
double spreadRadius = 0.0,
BlurStyle blurStyle = BlurStyle.normal,
this.inset = false,
}) : super(
color: color,
offset: offset,
blurRadius: blurRadius,
spreadRadius: spreadRadius,
blurStyle: blurStyle,
);
/// Wether this shadow should be inset or not.
final bool inset;
/// Returns a new box shadow with its offset, blurRadius, and spreadRadius scaled by the given factor.
@override
MyBoxShadow scale(double factor) {
return MyBoxShadow(
color: color,
offset: offset * factor,
blurRadius: blurRadius * factor,
spreadRadius: spreadRadius * factor,
blurStyle: blurStyle,
inset: inset,
);
}
/// Linearly interpolate between two box shadows.
///
/// If either box shadow is null, this function linearly interpolates from a
/// a box shadow that matches the other box shadow in color but has a zero
/// offset and a zero blurRadius.
///
/// {@macro dart.ui.shadow.lerp}
static MyBoxShadow? lerp(MyBoxShadow? a, MyBoxShadow? b, double t) {
if (a == null && b == null) {
return null;
}
if (a == null) {
return b!.scale(t);
}
if (b == null) {
return a.scale(1.0 - t);
}
final blurStyle =
a.blurStyle == BlurStyle.normal ? b.blurStyle : a.blurStyle;
if (a.inset != b.inset) {
return MyBoxShadow(
color: lerpColorWithPivot(a.color, b.color, t),
offset: lerpOffsetWithPivot(a.offset, b.offset, t),
blurRadius: lerpDoubleWithPivot(a.blurRadius, b.blurRadius, t),
spreadRadius: lerpDoubleWithPivot(a.spreadRadius, b.spreadRadius, t),
blurStyle: blurStyle,
inset: t >= 0.5 ? b.inset : a.inset,
);
}
return MyBoxShadow(
color: Color.lerp(a.color, b.color, t)!,
offset: Offset.lerp(a.offset, b.offset, t)!,
blurRadius: ui.lerpDouble(a.blurRadius, b.blurRadius, t)!,
spreadRadius: ui.lerpDouble(a.spreadRadius, b.spreadRadius, t)!,
blurStyle: blurStyle,
inset: b.inset,
);
}
/// Linearly interpolate between two lists of box shadows.
///
/// If the lists differ in length, excess items are lerped with null.
///
/// {@macro dart.ui.shadow.lerp}
static List<MyBoxShadow>? lerpList(
List<MyBoxShadow>? a,
List<MyBoxShadow>? b,
double t,
) {
if (a == null && b == null) {
return null;
}
a ??= <MyBoxShadow>[];
b ??= <MyBoxShadow>[];
final int commonLength = math.min(a.length, b.length);
return <MyBoxShadow>[
for (int i = 0; i < commonLength; i += 1) MyBoxShadow.lerp(a[i], b[i], t)!,
for (int i = commonLength; i < a.length; i += 1) a[i].scale(1.0 - t),
for (int i = commonLength; i < b.length; i += 1) b[i].scale(t),
];
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is MyBoxShadow &&
other.color == color &&
other.offset == offset &&
other.blurRadius == blurRadius &&
other.spreadRadius == spreadRadius &&
other.blurStyle == blurStyle &&
other.inset == inset;
}
@override
int get hashCode =>
hashValues(color, offset, blurRadius, spreadRadius, blurStyle, inset);
@override
String toString() =>
'MyBoxShadow($color, $offset, ${debugFormatDouble(blurRadius)}, ${debugFormatDouble(spreadRadius)}), $blurStyle, $inset)';
}
double lerpDoubleWithPivot(num? a, num? b, double t) {
if (t < 0.5) {
return ui.lerpDouble(a, 0, t * 2)!;
}
return ui.lerpDouble(0, b, (t - 0.5) * 2)!;
}
Offset lerpOffsetWithPivot(Offset? a, Offset? b, double t) {
if (t < 0.5) {
return Offset.lerp(a, Offset.zero, t * 2)!;
}
return Offset.lerp(Offset.zero, b, (t - 0.5) * 2)!;
}
Color lerpColorWithPivot(Color? a, Color? b, double t) {
if (t < 0.5) {
return Color.lerp(a, a?.withOpacity(0), t * 2)!;
}
return Color.lerp(b?.withOpacity(0), b, (t - 0.5) * 2)!;
}
class MyBoxDecoration extends painting.BoxDecoration {
const MyBoxDecoration({
Color? color,
DecorationImage? image,
BoxBorder? border,
BorderRadiusGeometry? borderRadius,
List<MyBoxShadow>? boxShadow,
Gradient? gradient,
BlendMode? backgroundBlendMode,
BoxShape shape = BoxShape.rectangle,
}) : super(
color: color,
border: border,
borderRadius: borderRadius,
boxShadow: boxShadow,
gradient: gradient,
backgroundBlendMode: backgroundBlendMode,
shape: shape,
);
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
@override
MyBoxDecoration copyWith({
Color? color,
DecorationImage? image,
BoxBorder? border,
BorderRadiusGeometry? borderRadius,
List<painting.BoxShadow>? boxShadow,
Gradient? gradient,
BlendMode? backgroundBlendMode,
BoxShape? shape,
}) {
assert(boxShadow is List<MyBoxShadow>?);
return MyBoxDecoration(
color: color ?? this.color,
image: image ?? this.image,
border: border ?? this.border,
borderRadius: borderRadius ?? this.borderRadius,
boxShadow: (boxShadow ?? this.boxShadow) as List<MyBoxShadow>?,
gradient: gradient ?? this.gradient,
backgroundBlendMode: backgroundBlendMode ?? this.backgroundBlendMode,
shape: shape ?? this.shape,
);
}
/// Returns a new box decoration that is scaled by the given factor.
@override
MyBoxDecoration scale(double factor) {
return MyBoxDecoration(
color: Color.lerp(null, color, factor),
image: image, // TODO(ianh): fade the image from transparent
border: BoxBorder.lerp(null, border, factor),
borderRadius: BorderRadiusGeometry.lerp(null, borderRadius, factor),
boxShadow: MyBoxShadow.lerpList(null, boxShadow as List<MyBoxShadow>, factor),
gradient: gradient?.scale(factor),
shape: shape,
);
}
@override
MyBoxDecoration? lerpFrom(Decoration? a, double t) {
if (a == null) return scale(t);
if (a is MyBoxDecoration) return MyBoxDecoration.lerp(a, this, t);
return super.lerpFrom(a, t) as MyBoxDecoration?;
}
@override
MyBoxDecoration? lerpTo(Decoration? b, double t) {
if (b == null) return scale(1.0 - t);
if (b is MyBoxDecoration) return MyBoxDecoration.lerp(this, b, t);
return super.lerpTo(b, t) as MyBoxDecoration?;
}
/// Linearly interpolate between two box decorations.
///
/// Interpolates each parameter of the box decoration separately.
///
/// The [shape] is not interpolated. To interpolate the shape, consider using
/// a [ShapeDecoration] with different border shapes.
///
/// If both values are null, this returns null. Otherwise, it returns a
/// non-null value. If one of the values is null, then the result is obtained
/// by applying [scale] to the other value. If neither value is null and `t ==
/// 0.0`, then `a` is returned unmodified; if `t == 1.0` then `b` is returned
/// unmodified. Otherwise, the values are computed by interpolating the
/// properties appropriately.
///
/// {@macro dart.ui.shadow.lerp}
///
/// See also:
///
/// * [Decoration.lerp], which can interpolate between any two types of
/// [Decoration]s, not just [MyBoxDecoration]s.
/// * [lerpFrom] and [lerpTo], which are used to implement [Decoration.lerp]
/// and which use [MyBoxDecoration.lerp] when interpolating two
/// [MyBoxDecoration]s or a [MyBoxDecoration] to or from null.
static MyBoxDecoration? lerp(MyBoxDecoration? a, MyBoxDecoration? b, double t) {
if (a == null && b == null) {
return null;
}
if (a == null) {
return b!.scale(t);
}
if (b == null) {
return a.scale(1.0 - t);
}
if (t == 0.0) {
return a;
}
if (t == 1.0) {
return b;
}
return MyBoxDecoration(
color: Color.lerp(a.color, b.color, t),
image: t < 0.5 ? a.image : b.image, // TODO(ianh): cross-fade the image
border: BoxBorder.lerp(a.border, b.border, t),
borderRadius: BorderRadiusGeometry.lerp(
a.borderRadius,
b.borderRadius,
t,
),
boxShadow: MyBoxShadow.lerpList(
a.boxShadow as List<MyBoxShadow>,
b.boxShadow as List<MyBoxShadow>,
t,
),
gradient: Gradient.lerp(a.gradient, b.gradient, t),
shape: t < 0.5 ? a.shape : b.shape,
);
}
@override
BoxPainter createBoxPainter([VoidCallback? onChanged]) {
assert(onChanged != null || image == null);
return _InsetBoxDecorationPainter(this, onChanged);
}
}
/// An object that paints a [MyBoxDecoration] or an [InsetBoxDecoration] into a canvas.
class _InsetBoxDecorationPainter extends BoxPainter {
_InsetBoxDecorationPainter(
this._decoration,
VoidCallback? onChanged,
) : super(onChanged);
final MyBoxDecoration _decoration;
Paint? _cachedBackgroundPaint;
Rect? _rectForCachedBackgroundPaint;
Paint _getBackgroundPaint(Rect rect, TextDirection? textDirection) {
assert(
_decoration.gradient != null || _rectForCachedBackgroundPaint == null);
if (_cachedBackgroundPaint == null ||
(_decoration.gradient != null &&
_rectForCachedBackgroundPaint != rect)) {
final Paint paint = Paint();
if (_decoration.backgroundBlendMode != null) {
paint.blendMode = _decoration.backgroundBlendMode!;
}
if (_decoration.color != null) paint.color = _decoration.color!;
if (_decoration.gradient != null) {
paint.shader = _decoration.gradient!.createShader(
rect,
textDirection: textDirection,
);
_rectForCachedBackgroundPaint = rect;
}
_cachedBackgroundPaint = paint;
}
return _cachedBackgroundPaint!;
}
void _paintBox(
Canvas canvas, Rect rect, Paint paint, TextDirection? textDirection) {
switch (_decoration.shape) {
case BoxShape.circle:
assert(_decoration.borderRadius == null);
final Offset center = rect.center;
final double radius = rect.shortestSide / 2.0;
canvas.drawCircle(center, radius, paint);
break;
case BoxShape.rectangle:
if (_decoration.borderRadius == null) {
canvas.drawRect(rect, paint);
} else {
canvas.drawRRect(
_decoration.borderRadius!.resolve(textDirection).toRRect(rect),
paint,
);
}
break;
}
}
void _paintOuterShadows(
Canvas canvas,
Rect rect,
TextDirection? textDirection,
) {
if (_decoration.boxShadow == null) {
return;
}
for (final painting.BoxShadow boxShadow in _decoration.boxShadow!) {
if (boxShadow is MyBoxShadow) {
if (boxShadow.inset) {
continue;
}
}
final Paint paint = boxShadow.toPaint();
final Rect bounds =
rect.shift(boxShadow.offset).inflate(boxShadow.spreadRadius);
_paintBox(canvas, bounds, paint, textDirection);
}
}
void _paintBackgroundColor(
Canvas canvas,
Rect rect,
TextDirection? textDirection,
) {
if (_decoration.color != null || _decoration.gradient != null) {
_paintBox(
canvas,
rect,
_getBackgroundPaint(rect, textDirection),
textDirection,
);
}
}
DecorationImagePainter? _imagePainter;
void _paintBackgroundImage(
Canvas canvas, Rect rect, ImageConfiguration configuration) {
if (_decoration.image == null) return;
_imagePainter ??= _decoration.image!.createPainter(onChanged!);
Path? clipPath;
switch (_decoration.shape) {
case BoxShape.circle:
assert(_decoration.borderRadius == null);
final Offset center = rect.center;
final double radius = rect.shortestSide / 2.0;
final Rect square = Rect.fromCircle(center: center, radius: radius);
clipPath = Path()..addOval(square);
break;
case BoxShape.rectangle:
if (_decoration.borderRadius != null) {
clipPath = Path()
..addRRect(_decoration.borderRadius!
.resolve(configuration.textDirection)
.toRRect(rect));
}
break;
}
_imagePainter!.paint(canvas, rect, clipPath, configuration);
}
void _paintInnerShadows(
Canvas canvas,
Rect rect,
TextDirection? textDirection,
) {
if (_decoration.boxShadow == null) {
return;
}
for (final painting.BoxShadow boxShadow in _decoration.boxShadow!) {
if (boxShadow is! MyBoxShadow || !boxShadow.inset) {
continue;
}
final color = boxShadow.color;
final borderRadiusGeometry = _decoration.borderRadius ??
(_decoration.shape == BoxShape.circle
? BorderRadius.circular(rect.longestSide)
: BorderRadius.zero);
final borderRadius = borderRadiusGeometry.resolve(textDirection);
final clipRRect = borderRadius.toRRect(rect);
final innerRect = rect.deflate(boxShadow.spreadRadius);
if (innerRect.isEmpty) {
final paint = Paint()..color = color;
canvas.drawRRect(clipRRect, paint);
}
var innerRRect = borderRadius.toRRect(innerRect);
canvas.save();
canvas.clipRRect(clipRRect);
final outerRect = _areaCastingShadowInHole(rect, boxShadow);
canvas.drawDRRect(
RRect.fromRectAndRadius(outerRect, Radius.zero),
innerRRect.shift(boxShadow.offset),
Paint()
..color = color
..colorFilter = ColorFilter.mode(color, BlendMode.srcIn)
..maskFilter = MaskFilter.blur(BlurStyle.normal, boxShadow.blurSigma),
);
canvas.restore();
}
}
@override
void dispose() {
_imagePainter?.dispose();
super.dispose();
}
/// Paint the box decoration into the given location on the given canvas.
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
assert(configuration.size != null);
final Rect rect = offset & configuration.size!;
final TextDirection? textDirection = configuration.textDirection;
_paintOuterShadows(canvas, rect, textDirection);
_paintBackgroundColor(canvas, rect, textDirection);
_paintBackgroundImage(canvas, rect, configuration);
_paintInnerShadows(canvas, rect, textDirection);
_decoration.border?.paint(
canvas,
rect,
shape: _decoration.shape,
borderRadius: _decoration.borderRadius?.resolve(textDirection),
textDirection: configuration.textDirection,
);
}
@override
String toString() {
return '_InsetBoxDecorationPainter for $_decoration';
}
}
Rect _areaCastingShadowInHole(Rect holeRect, MyBoxShadow shadow) {
var bounds = holeRect;
bounds = bounds.inflate(shadow.blurRadius);
if (shadow.spreadRadius < 0) {
bounds = bounds.inflate(-shadow.spreadRadius);
}
final offsetBounds = bounds.shift(shadow.offset);
return _unionRects(bounds, offsetBounds);
}
Rect _unionRects(Rect a, Rect b) {
if (a.isEmpty) {
return b;
}
if (b.isEmpty) {
return a;
}
final left = math.min(a.left, b.left);
final top = math.min(a.top, b.top);
final right = math.max(a.right, b.right);
final bottom = math.max(a.bottom, b.bottom);
return Rect.fromLTRB(left, top, right, bottom);
}Screenshots or Video
Screenshots / Video demonstration
[Upload media here]
Logs
Logs
[Paste your logs here]Flutter Doctor output
Doctor output
[Paste your output here]