1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'dart:ui' as ui show lerpDouble;
import 'package:flutter/foundation.dart';
import 'basic_types.dart';
import 'debug.dart';
/// A shadow cast by a box.
///
/// [BoxShadow] can cast non-rectangular shadows if the box is non-rectangular
/// (e.g., has a border radius or a circular shape).
///
/// This class is similar to CSS box-shadow.
///
/// See also:
///
/// * [Canvas.drawShadow], which is a more efficient way to draw shadows.
@immutable
class BoxShadow {
/// Creates a box shadow.
///
/// By default, the shadow is solid black with zero [offset], [blurRadius],
/// and [spreadRadius].
const BoxShadow({
this.color = const Color(0xFF000000),
this.offset = Offset.zero,
this.blurRadius = 0.0,
this.spreadRadius = 0.0
});
/// The color of the shadow.
final Color color;
/// The displacement of the shadow from the box.
final Offset offset;
/// The standard deviation of the Gaussian to convolve with the box's shape.
final double blurRadius;
/// The amount the box should be inflated prior to applying the blur.
final double spreadRadius;
/// Converts a blur radius in pixels to sigmas.
///
/// See the sigma argument to [MaskFilter.blur].
//
// See SkBlurMask::ConvertRadiusToSigma().
// <https://github.com/google/skia/blob/bb5b77db51d2e149ee66db284903572a5aac09be/src/effects/SkBlurMask.cpp#L23>
static double convertRadiusToSigma(double radius) {
return radius * 0.57735 + 0.5;
}
/// The [blurRadius] in sigmas instead of logical pixels.
///
/// See the sigma argument to [MaskFilter.blur].
double get blurSigma => convertRadiusToSigma(blurRadius);
/// Create the [Paint] object that corresponds to this shadow description.
///
/// The [offset] and [spreadRadius] are not represented in the [Paint] object.
/// To honor those as well, the shape should be inflated by [spreadRadius] pixels
/// in every direction and then translated by [offset] before being filled using
/// this [Paint].
Paint toPaint() {
final Paint result = new Paint()
..color = color
..maskFilter = new MaskFilter.blur(BlurStyle.normal, blurSigma);
assert(() {
if (debugDisableShadows)
result.maskFilter = null;
return true;
}());
return result;
}
/// Returns a new box shadow with its offset, blurRadius, and spreadRadius scaled by the given factor.
BoxShadow scale(double factor) {
return new BoxShadow(
color: color,
offset: offset * factor,
blurRadius: blurRadius * factor,
spreadRadius: spreadRadius * factor
);
}
/// 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.
///
/// The `t` argument represents position on the timeline, with 0.0 meaning
/// that the interpolation has not started, returning `a` (or something
/// equivalent to `a`), 1.0 meaning that the interpolation has finished,
/// returning `b` (or something equivalent to `b`), and values in between
/// meaning that the interpolation is at the relevant point on the timeline
/// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
/// 1.0, so negative values and values greater than 1.0 are valid (and can
/// easily be generated by curves such as [Curves.elasticInOut]).
///
/// Values for `t` are usually obtained from an [Animation<double>], such as
/// an [AnimationController].
static BoxShadow lerp(BoxShadow a, BoxShadow b, double t) {
assert(t != null);
if (a == null && b == null)
return null;
if (a == null)
return b.scale(t);
if (b == null)
return a.scale(1.0 - t);
return new BoxShadow(
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),
);
}
/// Linearly interpolate between two lists of box shadows.
///
/// If the lists differ in length, excess items are lerped with null.
///
/// The `t` argument represents position on the timeline, with 0.0 meaning
/// that the interpolation has not started, returning `a` (or something
/// equivalent to `a`), 1.0 meaning that the interpolation has finished,
/// returning `b` (or something equivalent to `b`), and values in between
/// meaning that the interpolation is at the relevant point on the timeline
/// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
/// 1.0, so negative values and values greater than 1.0 are valid (and can
/// easily be generated by curves such as [Curves.elasticInOut]).
///
/// Values for `t` are usually obtained from an [Animation<double>], such as
/// an [AnimationController].
static List<BoxShadow> lerpList(List<BoxShadow> a, List<BoxShadow> b, double t) {
assert(t != null);
if (a == null && b == null)
return null;
a ??= <BoxShadow>[];
b ??= <BoxShadow>[];
final List<BoxShadow> result = <BoxShadow>[];
final int commonLength = math.min(a.length, b.length);
for (int i = 0; i < commonLength; i += 1)
result.add(BoxShadow.lerp(a[i], b[i], t));
for (int i = commonLength; i < a.length; i += 1)
result.add(a[i].scale(1.0 - t));
for (int i = commonLength; i < b.length; i += 1)
result.add(b[i].scale(t));
return result;
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (runtimeType != other.runtimeType)
return false;
final BoxShadow typedOther = other;
return color == typedOther.color &&
offset == typedOther.offset &&
blurRadius == typedOther.blurRadius &&
spreadRadius == typedOther.spreadRadius;
}
@override
int get hashCode => hashValues(color, offset, blurRadius, spreadRadius);
@override
String toString() => 'BoxShadow($color, $offset, $blurRadius, $spreadRadius)';
}